基于EPOLL机制的Linux网络服务器设计与实现
摘 要
笔者在开发一款Linux网络服务器时,经过对多种机制的比较,发现EPOLL机制效率最高,该机制可以满足并发处理海量网络连接的要求,非常适合高性能服务器的设计要求。因此笔者对EPOLL机制进行了深入的研究,并且设计了一款基于EPOLL机制的高效的Linux网络服务器。在实际应用中,该服务器有着稳定、高效的特点。
【关键词】EPOLL Linux服务器
1 引言
笔者在实际工作中,需要开发一款基于Linux的高性能网络服务器,该服务器与前端的多种嵌入式设备通过有线网络或者无线网络连接。在对Linux网络编程技术分析后,最终选择了EPOLL机制。在经过几个月的调试、改进之后,服务器可以准确、稳定、高效的工作。在模拟测试中,该服务器可以稳定的连接与控制前端10万个设备。取得了良好的效果。
2 Linux中I/O多路复用技术分析
Linux上的I/O多路复用技术主要有select模型、poll模型和epoll模型。
select模型最大的缺点是最大并发数限制,因为一个进程所打开的socket FD(文件描述符)是有限制的,默认是1024,因此select模型的最大并发数就被限制了。当然,我们可以通过修改系统参数增大最大并发数,然而由于select的每次调用都会线性扫描全部的socket FD集合。当socket FD增大时,会导致服务器效率线性下降。
poll模型基本上和select在效率上是一样的,select的问题在poll模型中也没有被解决。
epoll模型是目前比较优秀的I/O多路复用模型,首先,epoll没有最大并发连接的限制,上限是整个系统最大可以打开的socket FD数目,这个数远大于2048,一般这个数目和系统内存关系很大,1G内存的机器的最大socket FD数目可以达到10万左右。其次,epoll最大的优点在于它只关心活跃的连接,而跟连接总数无关。因此在实际的网络环境中,epoll的效率会远高于select和poll。
epoll相关的系统调用有:epoll_create, epoll_ctl和epoll_wait。其中epoll_create用来创建一个epoll文件描述符,epoll_ctl用来添加/修改/删除需要侦听的文件描述符及其事件,epoll_wait接收发生在被侦听的描述符上的,用户感兴趣的IO事件。epoll文件描述符用完后,直接用close关闭即可,非常方便。事实上,任何被侦听的文件符只要其被关闭,那么它也会自动从被侦听的文件描述符集合中删除,很是智能。
3 框架设计与系统实现
3.1 系统设计
整个系统分为三个部分:前端探测设备、网络服务器、上位机。
该网络服务器用于连接、监控和控制前端多种嵌入式探测设备,这些探测设备分布于广阔的空间范围,并通过有线网络或者无线网络与网络服务器相连。
网络服务器设计是本文的核心。网络服务器向下连接大量的探测设备,向上连接上位机。网络服务器的框架如图1所示。
多种前端嵌入式探测设备用于探测所在位置的温度、湿度、风速,以及其他意外突发情况,这些嵌入式设备将探测到的数据,以某种协议格式发送给网络服务器,可能是通过有线网络,也有可能通过无线网络。这些数据在网络服务器上存储并转发给上位机显示处理。
探测设备与网络服务器之间,用心跳帧维护链路。有的探测设备会主动发送心跳帧给服务器,有些探测设备则需要网络服务器发送心跳帧询问。对方收到心跳帧后,都给予应答帧。
3.2 服务器实现
(1)将服务器进程设置成实时进程
struct sched_param p;
p.sched_priority = 9;
sched_setscheduler(0,SCHED_RR,&p);
(2)设置服务器时钟,只负责计时,其他事情不做,方式是记录流逝的秒数。使用全局变量ec,必须设置成原子的。在用sigaction函数登记的信号处理函数中可以做的处理是被严格限定的,所以变量ec必须是“volatile sig_atomic_t”类型的全局变量。代码如下:
static volatile sig_atomic_t ec = 0;
static void sig1sec_handler(int sig)
{
++ec;
}
下面代码,在主程序中设置并启动定时器,设定每一秒钟触发一次信号,该定时器信号导致函数sig1sec_handler的执行,该函数使得全局变量ec自增一。
signal(SIGALRM, &sig1sec_handler);
struct itimerval t;
t.it_interval.tv_sec = 1;
t.it_interval.tv_usec = 0;
t.it_value = t.it_interval;
setitimer(ITIMER_REAL, &t, NULL);
(3)设置进程的最大打开文件限制,相当于设置进程能够管理的TCP链接数量。设置数量限制为10万。
int cn = 100000;
struct rlimit rlim, new;
new.rlim_cur = new.rlim_max = cn;
while (setrlimit(RLIMIT_NOFILE, &new)!=0) {
cn -= 1000;
new.rlim_cur = new.rlim_max = cn;
}
(4)捕获SIGPIPE信号。如果向收到RST的TCP链路写入数据,进程会收到SIGPIPE信号而终止进程,SIGPIPE信号的缺省处理方式就是终止进程,现在改成忽略处理。
signal(SIGPIPE, SIG_IGN);
(5)核心代码。
Step1:建立epoll系统。
epfd = epoll_create(10000)
Step2:处理和时钟(定时器)有关的事情。根据当前的计时,处理一切有关时间的事情。每次定时器信号都会使得进程从epoll_wait中出来,所以该函数在信号发生后有执行的机会。先把计时的秒数读取出来。如果秒数为零,则返回。对于每一个有效的TCP链路,将心跳帧放到各自的发送队列中排队。如果心跳帧发过3次,仍然没有收到应答帧,则关闭并且删除该TCP链路。
Step3:此时进程会进入睡眠。等待探测设备TCP链接的连入,或者链路有写入读取的要求,或者其他请求。epoll_wait函数也会因为信号返回,包括定时器信号,这个时候会跳到Step2。
struct epoll_event events[10000];
n = epoll_wait(epfd, events, 10000, -1);
Step4:函数返回后,events[]数组中保存n个有效的事件。这些事件有几种情况:
如果是有新链路接入,则用accept函数获取这个新的链路,并且用epoll_ctl函数把这个新链路放入epoll中进行监控,当然,这个新链路必须设置成非阻塞的。
如果是输入EPOLLIN事件,则读取相应的数据,进行处理。
如果是输出EPOLLOUT事件,说明链路的输出缓冲区变空了,下面可以再发送数据了。
如果是其他进程的通信管道信息,则与其他进行进行通信。
跳到Step2,以此构成循环。
4 测试
测试用服务器的配置:
CPU:Intel Xeon 2.4G
内存:4G
网卡:千兆
测试用客户机的配置:
CPU:Intel Core 2.66G
内存:4G
网卡:千兆
由于本方案是特定的服务器,使用了用户自定义的用户网络协议,因此业界通用的一些测试程序不能很好的完成测试。鉴于此,笔者自己采用epoll机制编写了测试程序。模拟多用户链接服务器,进而模拟海量用户连接服务器,服务器都能稳定的运行,并且符合项目各项要求。
参考文献
[1]吴旭东.高性能Linux网络服务器设计与实现[J].电脑编程技巧与维护,2011,20(2):89-90.
[2]王明路,王希敏,王哲.嵌入式系统中池式内存分配方法的分析[J].计算机与数字工程,2008,36(2):57-61.
[3]孙晓辉,王劲林,陈晓.实时系统中的动态内存分配算法[J].计算机工程,2008,34(8):80-84.
作者简介
何凯(1981-),男,河北省黄骅市人。硕士学位。现为黄骅市职业技术教育中心讲师。主要研究方向为计算机应用技术。
作者单位
黄骅市职业技术教育中心 河北省黄骅市 061100