epoll学习

来源:互联网 发布:游龙网络靠什么赚钱的 编辑:程序博客网 时间:2024/06/05 19:14

参考文章:

http://blog.163.com/huchengsz@126/blog/static/73483745201181824629285/

百度百科


epoll是在内核2.5.44中引进的。

epoll是Linux内核为处理大批量文件描述符而作了改进的poll,是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。另一点原因就是获取事件的时候,它无须遍历整个被侦听的描述符集,只要遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合就行了。epoll除了提供select/poll那种IO事件的水平触发(Level Triggered)外,还提供了边缘触发(Edge Triggered),这就使得用户空间程序有可能缓存IO状态,减少epoll_wait/epoll_pwait的调用,提高应用程序效率。

epoll系统调用:epoll_create, epoll_ctl和poll_wait


include/linux/posix_types.h:
#define __FD_SETSIZE         1024

select只能支持1024文件描述符的检测。


epoll的工作模式
    令人高兴的是,2.6内核的epoll比其2.5开发版本的/dev/epoll简洁了许多,所以,大部分情况下,强大的东西往往是简单的。唯一有点麻烦 是epoll有2种工作方式:LT和ET。
LT(level triggered)是缺省的工作方式,并且同时支持block和no-block socket.在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你 的,所以,这种模式编程出错误可能性要小一点。传统的select/poll都是这种模型的代表.
ET (edge-triggered)是高速工作方式,只支持no-block socket。在这种模式下,当描述符从未就绪变为就绪时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述 符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了(比如,你在发送,接收或者接收请求,或者发送接收的数据少于一定量时导致 了一个EWOULDBLOCK 错误)。但是请注意,如果一直不对这个fd作IO操作(从而导致它再次变成未就绪),内核不会发送更多的通知(only once),不过在TCP协议中,ET模式的加速效用仍需要更多的benchmark确认。


Epoll模型主要负责对大量并发用户的请求进行及时处理,完成服务器与客户端的数据交互。其具体的实现步骤如下:
(a) 使用epoll_create()函数创建文件描述,设定将可管理的最大socket描述符数目。
(b) 创建与epoll关联的接收线程,应用程序可以创建多个接收线程来处理epoll上的读通知事件,线程的数量依赖于程序的具体需要。
(c) 创建一个侦听socket描述符ListenSock;将该描述符设定为非阻塞模式,调用Listen()函数在套接字上侦听有无新的连接请求,在 epoll_event结构中设置要处理的事件类型EPOLLIN,工作方式为 epoll_ET,以提高工作效率,同时使用epoll_ctl()注册事件,最后启动网络监视线程。
(d) 网络监视线程启动循环,epoll_wait()等待epoll事件发生。
(e) 如果epoll事件表明有新的连接请求,则调用accept()函数,将用户socket描述符添加到epoll_data联合体,同时设定该描述符为非 阻塞,并在epoll_event结构中设置要处理的事件类型为读和写,工作方式为epoll_ET.
(f) 如果epoll事件表明socket描述符上有数据可读,则将该socket描述符加入可读队列,通知接收线程读入数据,并将接收到的数据放入到接收数据 的链表中,经逻辑处理后,将反馈的数据包放入到发送数据链表中,等待由发送线程发送。

01 //epoll_wait范围之后应该是一个循环,遍利所有的事件:02 for (n = 0; n < nfds; ++n)03 {04         if (events[n].data.fd == listener)05         {//如果是主socket的事件的话,则表示有新连接进入了,进行新连接的处理。06                 client = accept (listener, (struct sockaddr *) &local, &addrlen);07                 if (client < 0)08                 {09                         perror ("accept");10                         continue;11                 }12                 setnonblocking (client);        // 将新连接置于非阻塞模式13                 ev.events = EPOLLIN | EPOLLET; // 并且将新连接也加入EPOLL的监听队列。14                 //注意,这里的参数EPOLLIN | EPOLLET并没有设置对写socket的监听,15                 //如果有写操作的话,这个时候epoll是不会返回事件的,16                 //如果要对写操作也监听的话,应该是EPOLLIN | EPOLLOUT | EPOLLET17                 ev.data.fd = client;18                 if (epoll_ctl (kdpfd, EPOLL_CTL_ADD, client, &ev) < 0)19                 {20                         /*21                                 设置好event之后,将这个新的event通过epoll_ctl加入到epoll的监听队列里面,22                                 这里用EPOLL_CTL_ADD来加一个新的epoll事件,通过EPOLL_CTL_DEL来减少一个epoll事件,通23                                 过EPOLL_CTL_MOD来改变一个事件的监听方式.24                         */25                         fprintf (stderr, "epoll set insertion error: fd=%d", client);26                         return -1;27                 }28         }29         else                                                            // 如果不是主socket的事件的话,则代表是一个用户socket的事件,30                 do_use_fd (events[n].data.fd); //则来处理这个用户socket的事情,比如说read(fd,xxx)之类的,或者一些其他的处理。31 }
对,epoll的操作就这么简单,总共不过4个 API:epoll_create, epoll_ctl, epoll_wait和close。
如果您对epoll的效率还不太了解,请参考我 之前关于网络游戏的网络编程等相关的文章。

    以前公司的服务器都是使用HTTP连接,但是这样的话,在手机目前的网络情况下不但显得速度较慢,而且不稳定。因此大家一致同意用 SOCKET来进行连接。虽然使用SOCKET之后,对于用户的费用可能会增加(由于是用了CMNET而非CMWAP),但是,秉着用户体验至上的原则, 相信大家还是能够接受的(希望那些玩家月末收到帐单不后能够保持克制...)。
这次的服务器设计中,最重要的一个突破,是使用了EPOLL模型, 虽然对之也是一知半解,但是既然在各大PC网游中已经经过了如此严酷的考验,相信他不会让我们失望,使用后的结果,确实也是表现相当不错。在这里,我还是 主要大致介绍一下这个模型的结构。
6、Linux下EPOll编程实例
EPOLL模型似乎只有一种格式,所以大家只要参考我下面的代码, 就能够对EPOLL有所了解了,代码的解释都已经在注释中:

while (TRUE)02 {03         int nfds = epoll_wait (m_epoll_fd, m_events, MAX_EVENTS, EPOLL_TIME_OUT);       //等待EPOLL时间的发生,相当于监听,04         //至于相关的端口,需要在初始化EPOLL的时候绑定。05         if (nfds <= 0)06                 continue;07         m_bOnTimeChecking = FALSE;08         G_CurTime = time (NULL);09         for (int i = 0; i < nfds; i++)10         {11                 try12                 {13                         if (m_events[i].data.fd == m_listen_http_fd)    //如果新监测到一个HTTP用户连接到绑定的HTTP端口,14                                 //建立新的连接。由于我们新采用了SOCKET连接,所以基本没用。15                         {16                                 OnAcceptHttpEpoll ();17                         }18                         else if (m_events[i].data.fd == m_listen_sock_fd)       //如果新监测到一个SOCKET用户连接到了绑定的SOCKET端口,19                                 //建立新的连接。20                         {21                                 OnAcceptSockEpoll ();22                         }23                         else if (m_events[i].events & EPOLLIN) //如果是已经连接的用户,并且收到数据,那么进行读入。24                         {25                                 OnReadEpoll (i);26                         }27 28                         OnWriteEpoll (i);       //查看当前的活动连接是否有需要写出的数据。29                 }30                 catch (int)31                 {32                         PRINTF ("CATCH捕获错误\n");33                         continue;34                 }35         }36         m_bOnTimeChecking = TRUE;37         OnTimer ();                                     //进行一些定时的操作,主要就是删除一些短线用户等。38 }


<3>使用mmap加速内核 与用户空间的消息传递。Linux 2.5的时候需要手动映射。


0 0
原创粉丝点击