读书人

采取epoll实现多客户server端

发布时间: 2013-10-22 16:17:14 作者: rapoo

采用epoll实现多客户server端

一).Epoll 介绍

Epoll 可是当前在 Linux 下开发大规模并发网络程序的热门人选, Epoll 在 Linux2.6 内核中正式引入,和 select 相似,其实都 I/O 多路复用技术而已 ,并没有什么神秘的。其实在 Linux 下设计并发网络程序,向来不缺少方法,比如典型的 Apache 模型( Process Per Connection ,简称 PPC ), TPC ( Thread Per Connection )模型,以及 select 模型和 poll 模型,那为何还要再引入 Epoll 呢?那还是有得说说的 …

二). 常用模型的缺点

如果不摆出来其他模型的缺点,怎么能对比出 Epoll 的优点呢。

① PPC/TPC 模型

这两种模型思想类似,就是让每一个到来的连接一边自己做事去,别再来烦我 。只是 PPC 是为它开了一个进程,而 TPC 开了一个线程。可是别烦我是有代价的,它要时间和空间啊,连接多了之后,那么多的进程 / 线程切换,这开销就上来了;因此这类模型能接受的最大连接数都不会高,一般在几百个左右。

② select 模型

1. 最大并发数限制,因为一个进程所打开的 FD (文件描述符)是有限制的,由 FD_SETSIZE 设置,默认值是 1024/2048 ,因此 Select 模型的最大并发数就被相应限制了。自己改改这个 FD_SETSIZE ?想法虽好,可是先看看下面吧 …

2. 效率问题, select 每次调用都会线性扫描全部的 FD 集合,这样效率就会呈现线性下降,把 FD_SETSIZE 改大的后果就是,大家都慢慢来,什么?都超时了。

3. 内核 / 用户空间 内存拷贝问题,如何让内核把 FD 消息通知给用户空间呢?在这个问题上 select 采取了内存拷贝方法。

总结为:1.连接数受限 2.查找配对速度慢 3.数据由内核拷贝到用户态

③ poll 模型

基本上效率和 select 是相同的, select 缺点的 2 和 3 它都没有改掉。

三). Epoll 的提升

把其他模型逐个批判了一下,再来看看 Epoll 的改进之处吧,其实把 select 的缺点反过来那就是 Epoll 的优点了。

①. Epoll 没有最大并发连接的限制,上限是最大可以打开文件的数目,这个数字一般远大于 2048, 一般来说这个数目和系统内存关系很大 ,具体数目可以 cat /proc/sys/fs/file-max 察看。

②. 效率提升, Epoll 最大的优点就在于它只管你“活跃”的连接 ,而跟连接总数无关,因此在实际的网络环境中, Epoll 的效率就会远远高于 select 和 poll 。

③. 内存拷贝, Epoll 在这点上使用了“共享内存 ”,这个内存拷贝也省略了。

四). Epoll 为什么高效

Epoll 的高效和其数据结构的设计是密不可分的,这个下面就会提到。

首先回忆一下 select 模型,当有 I/O 事件到来时, select 通知应用程序有事件到了快去处理,而应用程序必须轮询所有的 FD 集合,测试每个 FD 是否有事件发生,并处理事件;代码像下面这样:

int epoll_create(int size);  

生成一个 Epoll 专用的文件描述符,其实是申请一个内核空间,用来存放你想关注的 socket fd 上是否发生以及发生了什么事件。 size 是用户期待监控的最大的文件描述符数目,系统将该值作为一个参考进行内存分配,在需要的时候,内存可以同步增大,也就是说epoll可以监控的最大文件描述符数目是可以无限大的,只要系统硬件满足。在最新版本的epoll中,epoll甚至不以size作为参考,自行动态增长,由于要兼容老版本,size参数必须提供,只需大于0即可。

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event );

控制某个 Epoll 文件描述符上的事件:注册、修改、删除。其中参数 epfd 是 epoll_create() 创建 Epoll 专用的文件描述符。相对于 select 模型中的 FD_SET 和 FD_CLR 宏。

int epoll_wait(int epfd,struct epoll_event * events,int maxevents,int timeout);

等待 I/O 事件的发生;参数说明:

epfd: 由 epoll_create() 生成的 Epoll 专用的文件描述符;

epoll_event: 用于回传代处理事件的数组;

maxevents: 每次能处理的事件数;

timeout: 等待 I/O 事件发生的超时值;

返回发生事件数。

相对于 select 模型中的 select 函数

相关参数的英文说明:

epfd为epoll_create返回的描述符。op表示对描述符fd采取的操作,取值如下:
EPOLL_CTL_ADD
Add a monitor on the file associated with the file descriptor fd to the epoll instance associated with epfd, per the events defined in event.

EPOLL_CTL_DEL
Remove a monitor on the file associated with the file descriptor fd from the epollinstance associated with epfd.
EPOLL_CTL_MOD
Modify an existing monitor of fd with the updated events specified by event.

epoll_event结构中的events字段,表示对该文件描述符所关注的事件,它的取值如下:
EPOLLET
Enables edge-triggered behavior for the monitor of the file .The default behavior is level-
triggered.//默认采用水平触发
EPOLLHUP
A hangup occurred on the file. This event is always monitored, even if it’s not specified.
EPOLLIN
The file is available to be read from without blocking.
EPOLLONESHOT
After an event is generated and read, the file is automatically no longer monitored.A new event mask must be specified via EPOLL_CTL_MOD to reenable the watch.
EPOLLOUT
The file is available to be written to without blocking.
EPOLLPRI
There is urgent out-of-band data available to read.


在Linux的帮助文档中,关于epoll帮助内容如下:

           #define MAX_EVENTS 10           struct epoll_event ev, events[MAX_EVENTS];           int listen_sock, conn_sock, nfds, epollfd;           /* Set up listening socket, 'listen_sock' (socket(),              bind(), listen()) */           epollfd = epoll_create(10);           if (epollfd == -1) {               perror("epoll_create");               exit(EXIT_FAILURE);           }           ev.events = EPOLLIN;           ev.data.fd = listen_sock;           if (epoll_ctl(epollfd, EPOLL_CTL_ADD, listen_sock, &ev) == -1)          {               perror("epoll_ctl: listen_sock");               exit(EXIT_FAILURE);           }           for (;;)              {               nfds = epoll_wait(epollfd, events, MAX_EVENTS, -1);//有多少个描述符可以处理               if (nfds == -1) {                   perror("epoll_pwait");                   exit(EXIT_FAILURE);               }               for (n = 0; n < nfds; ++n) {//可读的描述符直接位于events的前nfds个元素中,因而只需遍历前nfds个元素就可以处理                   if (events[n].data.fd == listen_sock) {                       conn_sock = accept(listen_sock,                                       (struct sockaddr *) &local, &addrlen);                       if (conn_sock == -1) {                           perror("accept");                           exit(EXIT_FAILURE);                       }                       setnonblocking(conn_sock);                       ev.events = EPOLLIN | EPOLLET;                       ev.data.fd = conn_sock;                       if (epoll_ctl(epollfd, EPOLL_CTL_ADD, conn_sock,                                   &ev) == -1) {                           perror("epoll_ctl: conn_sock");                           exit(EXIT_FAILURE);                       }                   } else {                       do_use_fd(events[n].data.fd);                   }               }           }


参考:

1. http://blog.chinaunix.net/uid-20583479-id-1920065.html

2.http://blog.csdn.net/tianmohust/article/details/6677985


读书人网 >编程

热点推荐