现在的位置: 首页 > 综合 > 正文

Linux epoll浅析(收集整理版)

2019年09月25日 ⁄ 综合 ⁄ 共 18384字 ⁄ 字号 评论关闭
文章目录

Linux epoll详解

关键词: select、poll、kqueue(FreeBSD 4.1+)、epoll(Linux 2.6+)

一、什么是epoll

epoll是什么?

按照man手册的说法:

是为处理大批量句柄而作了改进的poll。当然,这不是2.6内核才有的,它是在2.5.44内核中被引进的(epoll(4) is a new API introduced in Linuxkernel 2.5.44),它几乎具备了之前所说的一切优点,被公认为Linux2.6下性能最好的多路I/O就绪通知方法。

有哪些应用使用了epoll?  java NIO, netty, mina, libevent, Nginx, ACE

 

二、epoll的相关系统调用

epoll只有epoll_create,epoll_ctl,epoll_wait 3个系统调用。

 

1. int epoll_create(int size);

功能   :生成一个epoll专用的文件描述符(或句柄)。

参数   :size: 在该epoll fd上关注的最大socket fd数。 自从linux2.6.8之后,size参数是被忽略的。

返回值:生成的文件描述符。

需要注意:当创建好epoll句柄后,它就是会占用一个fd值,在linux下如果查看/proc/进程id/fd/,是能够看到这个fd的,所以在使用完epoll后,必须调用close()关闭,否则可能导致fd被耗尽。

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

epoll的事件注册函数,它不同于select()是在监听事件时告诉内核要监听什么类型的事件,而是在这里先注册要监听的事件类型。
功能  :控制某个epoll文件描述符上的事件,可以注册事件,修改事件,删除事件。
参数  :
     参数epfd  :由epoll_create生成的epoll专用的文件描述符;
    
参数
op    :用三个宏来表示:

               EPOLL_CTL_ADD:注册新的fd到epfd中;

               EPOLL_CTL_MOD:修改已经注册的fd的监听事件;

               EPOLL_CTL_DEL:从epfd中删除一个fd;

     参数fd    :关联的文件描述符;
    
参数
event :指向epoll_event的指针;
返回值: 0:成功; 
          -1:失败;

  struct epoll_event(第四个参数event)结构如下:

//保存触发事件的某个文件描述符相关的数据(与具体使用方式有关)
typedef
union
  epoll_data {
   void     
*
ptr;
   int       fd;
   __uint32_t u32;
   __uint64_t u64;
}  epoll_data_t;

// 感兴趣的事件和被触发的事件
struct epoll_event {
    __uint32_t    events; /* Epoll events      */
    epoll_data_t  data;   /* User data variable */
};

events可以是以下几个宏的集合:

  EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);

  EPOLLOUT:表示对应的文件描述符可以写;

  EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);

  EPOLLERR:表示对应的文件描述符发生错误;

  EPOLLHUP:表示对应的文件描述符被挂断;

  EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,

          这是相对于水平触发(Level Triggered)来说的。

  EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,

          需要再次把这个socket加入到EPOLL队列里

 

3. int epoll_wait(int epfd, structepoll_event * events, int maxevents, int timeout);

功能  :轮询I/O事件的发生。

参数  :    epfd        :由 epoll_create 生成的epoll专用的文件描述符,即epoll_create的返回值;

            events      :用于回传待处理事件的数组;参数events 收集在epoll监控的事件中已经发生的事件。
                             参数events是分配好的epoll_event结构体数组,epoll将会把发生的事件赋值到events数组
                             中(events不可以是空指针,内核只负责把数据复制到这个events数组中,不会去帮助我们
                             在用户态中分配内存)。

            maxevents :每次能处理的事件数; 这个 maxevents的值不能大于创建epoll_create()时的size.

            timeout     :等待I/O事件发生的超时值;-1相当于阻塞,0相当于非阻塞;

返回值:      >=0     :返回发生事件数; 

                -1       :错误;

                     如果函数调用成功,返回对应I/O上已准备好的文件描述符数目,如返回0表示已超时。

4. int epoll_pwait(与函数epoll_wait类似)

int epoll_pwait(int epfd, struct epoll_event *events,
                   int maxevents, int timeout,
                   const sigset_t *sigmask); 

与epoll_wait的区别是可以通过最后一个参数设置阻塞过程中信号屏蔽字。

上面的函数原型等价于:

sigset_toriginmask;

sigpromask(SIG_SETMASK,&sigmask,& originmask);

ready = epoll_wait(epfd,&events,maxevents,timeout);

sigpromask(SIG_SETMASK,&  originmask ,NULL);

 

三、epoll工作原理

linuxepoll如何实现高效处理百万句柄的?

设想一个场景:

有100万用户同时与一个进程保持着TCP连接,而每一个时刻只有几十个或几百个TCP连接时活跃的(接收到TCP包),也就是说,在每一时刻,进程值需要处理这100万连接中的一小部分连接。

那么,如何才能高效地处理这种场景呢?进程是否在每次询问操作系统收集有事件发生的TCP连接时,把这100万个连接告诉操作系统,然后由操作系统找出其中有事件发生的几百个连接呢?实际上,在Linux内核2.4版本以前select或者poll事件驱动方式就是这样做的。

       这里有一个非常明显的问题,即在某一时刻,进程收集有事件的连接时,其实这100万连接中的大部分都是没有事件发生的。因此,如果每次收集事件时,都把这100万连接的套接字传给操作系统(这首先就是用户态内存到内核态内存的大量复制),而由操作系统内核寻找这些连接上有没有未处理的事件,将会是巨大的资源浪费,然而select和poll就是这样做的,因此他们最多只能处理几千个并发连接。

而epoll不这样做,他在linux内核中申请了一个简易的文件系统,把原先的一个select或者poll调用分成了3个部分:

调用epoll_create建立1个epoll对象(在epoll文件系统中给这个句柄分配资源)、

调用epoll_ctl向epoll对象中添加这100万个连接的套接字、

调用epoll_wati收集发生事件的连接。

这样,只需要在进程启动时建立1个epoll对象,并在需要的时候向它添加或删除连接就可以了,因此,在实际收集事件时,epoll_wait的效率就会非常高,因为调用epoll_wait时并没有向它传递着100万个连接,内核也不需要去遍历全部的连接。

     

介绍epoll是怎么处理这种情况的?

     当某一个进程调用epoll_create方法时,linux内核会创建一个eventpoll结构体,这个结构体中有两个成员于epoll的使用方式密切相关,如下所示

     struct   eventpoll{

     /*红黑树的跟节点,这棵树中存储着所有添加到epoll中的事件,也就是这个epoll监控的事件*/

      struct rb_root_rbr;

     //双向链表tdllist保存着将要通过epoll_wait放回给用户的、满足条件的事件

     struct  list_head_rdllist;

}

/*

*This structure is stored inside the "private_data" member of the file

*structure and represents the main data structure for the eventpoll

*interface.

*/

struct eventpoll {

       /* Protect the access to thisstructure */

       spinlock_t lock;

 

       /*

        * This mutex is used to ensurethat files are not removed

        * while epoll is using them. Thisis held during the event

        * collection loop, the filecleanup path, the epoll file  

        * exit code and the ctl operations.

        */

       struct mutex mtx;

 

       /* Wait queue used bysys_epoll_wait() */

       wait_queue_head_t wq;

 

       /* Wait queue used byfile->poll() */

       wait_queue_head_t poll_wait;

 

       /* List of ready file descriptors*/

       struct list_head rdllist;

 

       /* RB tree root used to storemonitored fd structs */

    /* 红黑树根节点,这棵树存储着所有添加到epoll中的事件,也就是这个epoll监控的事件
*/

       struct rb_root rbr; 

       /*

        * This is a single linked listthat chains all the "struct epitem" that

        * happened while transferringready events to userspace w/out

        * holding ->lock.

        */

       struct epitem*
ovflist;

 

       /* wakeup_source used whenep_scan_ready_list is running */

       struct wakeup_source*ws;

 

       /* The user that created theeventpoll descriptor */

       struct user_struct*
user;

 

       struct file*
file;

 

       /* used to optimize loopdetection check */

       int visited;

 

  /*双向链表中保存着将要通过epoll_wait返回给用户的、满足条件的事件
*/

       struct list_head visited_list_link;

};

epoll中,对于每一个事件都会建立一个epitem结构体:

/*

  * Each file descriptor added to the eventpoll interfacewill

  * have an entry of this type linked to the"rbr" RB tree.

  * Avoid increasing the size of this struct, there can bemany thousands

  * of these on a server and we do not want this to takeanother cache line.

  */

 struct epitem {

         /* RB tree node used to link this structure to theeventpoll RB tree */

         struct rb_node rbn;

 

         /* List header used to link this structure to the eventpollready list */

         struct list_head rdllink;

 

         /*

          * Workstogether "struct eventpoll"->ovflist in keeping the

          * singlelinked chain of items.

          */

         struct epitem*next;

 

         /* The file descriptor information this item refers to */

         struct epoll_filefd ffd;

 

         /* Number of active wait queue attached to poll operations*/

         int nwait;

 

         /* List containing poll wait queues */

         struct list_head pwqlist;

 

         /* The "container" of this item */

         struct eventpoll*ep;

 

         /* List header used to link this item to the "structfile" items list */

         struct list_head fllink;

 

         /* wakeup_source used when EPOLLWAKEUP is set */

         struct wakeup_source __rcu*ws;

 

         /* The structure that describe the interested events andthe source fd */

         struct epoll_event event;

 

 };

每一个epoll对象都有一个独立的eventpoll结构体,这个结构体会在内核空间中创造独立的内存,用于存储使用epoll_ctl方法向epoll对象中添加进来的事件。这些事件都会挂到rbr红黑树中,这样,重复添加的事件就可以通过红黑树而高效标示出来(epoll_ctl方法会很快)。

     所有添加到epoll中的事件都会与设备(如网卡)驱动程序建立回调关系,也就是说,相应的事件发生时会调用这里的回调方法。这个回调方法在内核中叫做ep_epoll_callback,它会把这样的事件放到上面的rdllist双向链表中。在epoll中,对于每一个事件都会建立一个epitem结构体。这里包含每一个事件对应着的信息。

      当调用epoll_wait检查是否有发生事件的连接时,只是检查eventpoll对象中的rdllist双向链表是否有epitem元素而已,如果rdllist链表不为空,则把这里的事件复制到用户态内存中,同时将准备的事件数量返回给用户,因此,epoll_wait的效率非常高,epoll_ctl在向epoll对象中添加、修改。删除事件时,从rbr红黑树中查找事件也非常快,也就是说,epoll是非常高效的,它理论上可以处理百万级的并发连接。

   epoll同样只告知那些就绪的文件描述符,而且当调用epoll_wait()获得就绪文件描述符时,返回的不是实际的描述符,而是一个代表就绪描述符数量的值,因此只需要去epoll指定的一个数组中依次取得相应数量的文件描述符即可,这里也使用了内存映射(mmap)技术,这样便彻底省掉了这些文件描述符在系统调用时复制的开销。

 

   另一个本质的改进在于epoll采用基于事件的就绪通知方式。在select/poll中,进程只有在调用一定的方法后,内核才对所有监视的文件描述符进行扫描,而epoll事先通过epoll_ctl()来注册一个文件描述符,一旦基于某个文件描述符就绪时,内核会采用类似callback的回调机制,迅速激活这个文件描述符,当进程调用epoll_wait()时便得到通知。

 

Epoll2种工作方式-水平触发(LT)和边缘触发(ET

假如有这样一个例子:

1.把一个用来从管道中读取数据的文件句柄(RFD)添加到epoll描述符

2.然后从管道的另一端被写入了2KB的数据

3.调用epoll_wait(2),并且它会返回RFD,说明它已经准备好读取操作

4.然后读取了1KB的数据

5.调用epoll_wait(2)......

 

EdgeTriggered工作模式:

   如果在第1步将RFD添加到epoll描述符的时候使用了EPOLLET标志,那么在第5步调用epoll_wait(2)之后将有可能会挂起?因为剩余的数据还存在于文件的输入缓冲区内,而且数据发出端还在等待一个针对已经发出数据的反馈信息。只有在监视的文件句柄上发生了某个事件的时候ET工作模式才会汇报事件。因此在第5步的时候,调用者可能会放弃等待仍在存在于文件输入缓冲区内的剩余数据。在上面的例子中,会有一个事件产生在RFD句柄上,因为在第2步执行了一个写操作,然后,事件将会在第3步被销毁。因为第4步的读取操作没有读空文件输入缓冲区内的数据,因此我们在第5步调用epoll_wait(2)完成后,是否挂起是不确定的。epoll工作在ET模式的时候,必须使用非阻塞套接口,以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。最好以下面的方式调用ET模式的epoll接口,在后面会介绍避免可能的缺陷。

  i  基于非阻塞文件句柄

  ii 只有当read(2)或者write(2)返回EAGAIN时才需要挂起,等待。但这并不是说每次read()时都需要循环读,直到读到产生一个EAGAIN才认为此次事件处理完成,当read()返回的读到的数据长度小于请求的数据长度时,就可以确定此时缓冲中已没有数据了,也就可以认为此事读事件已处理完成。

 

Level Triggered工作模式  ???

相反的,以LT方式调用epoll接口的时候,它就相当于一个速度比较快的poll(2),并且无论后面的数据是否被使用,都会出发。因为即使使用ET模式的epoll,在收到多个chunk的数据的时候仍然会产生多个事件。调用者可以设定EPOLLONESHOT标志,在
epoll_wait(2)
收到事件后epoll会与事件关联的文件句柄从epoll描述符中禁止掉。因此当EPOLLONESHOT设定后,使用带有EPOLL_CTL_MOD标志的epoll_ctl(2)处理文件句柄就成为调用者必须作的事情。

 

LT(level triggered)epoll缺省的工作方式,并且同时支持blockno-block
socket.
在这种做法中,内核告诉一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果不作任何操作,内核还是会继续通知的,所以这种模式编程出错误可能性要小一点。传统的select/poll都是这种模型的代表.

 

ET (edge-triggered)是高速工作方式,只支持no-block
socket
,它效率要比LT更高。ETLT的区别在于,当一个新的事件到来时,ET模式下当然可以从epoll_wait调用中获取到这个事件,可是如果这次没有把这个事件对应的套接字缓冲区处理完,在这个套接字中没有新的事件再次到来时,在ET模式下是无法再次从epoll_wait调用中获取这个事件的。而LT模式正好相反,只要一个事件对应的套接字缓冲区还有数据,就总能从epoll_wait中获取这个事件。

 

因此LT模式下开发基于epoll的应用要简单些,不太容易出错。

而在ET模式下事件发生时,如果没有彻底地将缓冲区数据处理完,则会导致缓冲区中的用户请求得不到响应。

图示说明:


注:Nginx默认采用ET模式来使用epoll

 

性能比较及优点

Select、Poll与Epoll简介

Select

select本质上是通过设置或者检查存放fd标志位的数据结构来进行下一步处理。

这样所带来的缺点是:

1 单个进程可监视的fd数量被限制

2 需要维护一个用来存放大量fd的数据结构,这样会使得用户空间和内核空间在传递该结构时复制开销大

3 对socket进行扫描时是线性扫描

Poll

poll本质上和select没有区别,它将用户传入的数组拷贝到内核空间,然后查询每个fd对应的设备状态,如果设备就绪则在设备等待队列中加入一项并继续遍历,如果遍历完所有fd后没有发现就绪设备,则挂起当前进程,直到设备就绪或者主动超时,被唤醒后它又要再次遍历fd。这个过程经历了多次无谓的遍历。

它没有最大连接数的限制,原因是它是基于链表来存储的,但是同样有一个缺点:大量的fd的数组被整体复制于用户态和内核地址空间之间,而不管这样的复制是不是有意义。

poll还有一个特点是“水平触发”,如果报告了fd后,没有被处理,那么下次poll时会再次报告该fd。

Epoll

epoll支持水平触发和边缘触发,最大的特点在于边缘触发,它只告诉进程哪些fd刚刚变为就绪态,并且只会通知一次。

在前面说到的复制问题上,epoll使用mmap减少复制开销。

还有一个特点是,epoll使用“事件”的就绪通知方式,通过epoll_ctl注册fd,一旦该fd就绪,内核就会采用类似callback的回调机制来激活该fd,epoll_wait便可以收到通知

注:

水平触发(level-triggered)  ——只要满足条件,就触发一个事件(只要有数据没有被获取,内核就不断通知你);

边缘触发(edge-triggered)  ——每当状态变化时,触发一个事件。

Select、Poll与Epoll区别

 

Select

Poll

Epoll

支持最大连接数

1024(x86) or

2048(x64)

无上限

无上限

IO效率

每次调用进行线性遍历,时间复杂度为O(N)

每次调用进行线性遍历,时间复杂度为O(N)

使用“事件”通知方式,每当fd就绪,系统注册的回调函数就会被调用,将就绪fd放到rdllist里面,这样epoll_wait返回的时候我们就拿到了就绪的fd。

时间发复杂度O(1)

fd拷贝

每次select都拷贝

每次poll都拷贝

调用epoll_ctl时拷贝进内核并由内核保存,之后每次epoll_wait不拷贝

epoll的优点:

1.支持一个进程打开大数目的socket描述符(FD)

    select最不能忍受的是一个进程所打开的FD是有一定限制的,由FD_SETSIZE设置,默认值是2048。对于那些需要支持的上万连接数目的IM服务器来说显然太少了。

这时候一是可以选择修改这个宏然后重新编译内核,不过资料也同时指出这样会带来网络效率的下降,

二是可以选择多进程的解决方案(传统的 Apache方案),不过虽然linux上面创建进程的代价比较小,但仍旧是不可忽视的,加上进程间数据同步远比不上线程间同步的高效,所以也不是一种完美的方案。

不过 epoll则没有这个限制,它所支持的FD上限是最大可以打开文件的数目,这个数字一般远大于2048,举个例子,1GB内存的机器上大约是10万左右,具体数目可以cat
/proc/sys/fs/file-max
察看,一般来说这个数目和系统内存关系很大。

 

2.IO效率不随FD数目增加而线性下降

    传统的select/poll另一个致命弱点就是当拥有一个很大的socket集合,由于网络延时,任一时间只有部分的socket"活跃"的,select/poll每次调用都会线性扫描全部的集合,导致效率呈现线性下降。

   但是epoll不存在这个问题,它只会对"活跃"socket进行操作---这是因为在内核实现中epoll是根据每个fd上面的callback函数实现的。那么,只有"活跃"socket才会主动的去调用 callback函数,其他idle状态socket则不会,在这点上,epoll实现了一个""AIO因为这时候推动力在os内核。在一些 benchmark中,如果所有的socket基本上都是活跃的---比如一个高速LAN环境,epoll并不比select/poll有什么效率,相反,如果过多使用epoll_ctl,效率相比还有稍微的下降。但是一旦使用idle
connections
模拟WAN环境,epoll的效率就远在select/poll之上了。

 

3.使用mmap加速内核与用户空间的消息传递

这点实际上涉及到epoll的具体实现了。无论是select,poll还是epoll都需要内核把FD消息通知给用户空间,如何避免不必要的内存拷贝就很重要,在这点上,epoll是通过内核与用户空间mmap共享使用同一块内存实现的。

 

4.内核微调

这一点其实不算epoll的优点了,而是整个linux平台的优点。

也许你可以怀疑linux平台,但是你无法回避linux平台赋予你微调内核的能力。比如,内核TCP/IP协议栈使用内存池管理sk_buff结构,那么可以在运行时期动态调整这个内存pool(skb_head_pool)的大小--- 通过echoXXXX>/proc/sys/net/core/hot_list_length完成。再比如listen函数的第2个参数(TCP完成3次握手的数据包队列长度),也可以根据你平台内存大小动态调整。更甚至在一个数据包面数目巨大但同时每个数据包本身大小却很小的特殊系统上尝试最新的NAPI网卡驱动架构。

 

 

五、epoll的使用方法

那么究竟如何来使用epoll呢?

通过在包含一个头文件#include <sys/epoll.h> 以及几个简单的API将可以大大的提高网络服务器的支持数量。


首先通过create_epoll(int maxfds)来创建一个epoll的句柄。这个函数会返回一个新的epoll句柄,之后的所有操作将通过这个句柄来进行操作。在用完之后,记得用close()来关闭这个创建出来的epoll句柄。

 

之后在你的网络主循环里面,每一帧的调用epoll_wait(int epfd, epoll_event events, int max events, inttimeout)来查询所有的网络接口,看哪一个可以读,哪一个可以写了。

基本的语法为:

nfds = epoll_wait(kdpfd, events, maxevents, -1);

其中kdpfd为用epoll_create创建之后的句柄,events是一个epoll_event*的指针,当epoll_wait这个函数操作成功之后,epoll_events里面将储存所有的读写事件。max_events是当前需要监听的所有socket句柄数。最后一个timeout epoll_wait的超时,为0的时候表示马上返回,为-1的时候表示一直等下去,直到有事件返回,为任意正整数的时候表示等这么长的时间,如果一直没有事件,则返回。一般如果网络主循环是单独的线程的话,可以用-1来等,这样可以保证一些效率,如果是和主逻辑在同一个线程的话,则可以用0来保证主循环的效率。

epoll_wait返回之后应该是一个循环,遍历所有的事件。

几乎所有的epoll程序都使用下面的框架:

for( ; ; ) {
        nfds = epoll_wait(epfd,events,20,500);
        for(i=0;i<nfds;++i) {
            if(events[i].data.fd==listenfd) {//有新的连接
                connfd = accept(listenfd,(sockaddr*)&clientaddr,&clilen);  //accept这个连接
                ev.data.fd=connfd;
                ev.events=EPOLLIN|EPOLLET;
                epoll_ctl(epfd, EPOLL_CTL_ADD, connfd, &ev);  //将新的fd添加到epoll的监听队列中
            } else
if
( events[i].events&EPOLLIN )  {//接收到数据,读socket
                n = read(sockfd, line, MAXLINE))<0    //读
                ev.data.ptr = md;     //md为自定义类型,添加数据
                ev.events=EPOLLOUT|EPOLLET;
                epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);//修改标识符,等待下一个循环时发送数据,异步处理的精髓
            } else
if
(events[i].events&EPOLLOUT){//有数据待发送,写socket
                struct myepoll_data* md= (myepoll_data*)events[i].data.ptr;    //取数据
                sockfd = md->fd;
                send( sockfd, md->ptr, strlen((char*)md->ptr),0
);        //发送数据
                ev.data.fd=sockfd;
                ev.events=EPOLLIN|EPOLLET;
                epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);//修改标识符,等待下一个循环时接收数据
            } else  {
                //其他的处理
            }
        }
    }

六、epoll的程序实例

 How to use epoll? A complete example in C  

#include<stdio.h>

#include<stdlib.h>

#include<unistd.h>

#include<errno.h>

#include<sys/socket.h>

#include<netdb.h>

#include<fcntl.h>

#include<sys/epoll.h>

#include<string.h>

 

#defineMAXEVENTS 64

 

//函数:

//功能:创建和绑定一个TCP
socket

//参数:端口

//返回值:创建的socket

static int create_and_bind(char*port){

 struct addrinfo hints;

 struct addrinfo
*
result,*rp;

 int s,
sfd
;

  memset(&hints,0,sizeof(struct
addrinfo
));

  hints.ai_family=AF_UNSPEC;  /*
Return IPv4 and IPv6 choices */

  hints.ai_socktype=SOCK_STREAM;/*
We want a TCP socket */

  hints.ai_flags=AI_PASSIVE;  /*
All interfaces */

  s = getaddrinfo(NULL,
port
,
&hints,&result);

 if(s!=0){

  fprintf(stderr,"getaddrinfo:
%s\n"
, gai_strerror(s));

   return
-
1;

  }

 for(rp=
result
; rp!= NULL;
rp
= rp->ai_next){

   
sfd
= socket(rp->ai_family,
rp
->ai_socktype, rp->ai_protocol);

   if(sfd==-1)

    continue;

   
s
= bind(sfd,
rp
->ai_addr, rp->ai_addrlen);

   if(s==0){

     /*
We managed to bind successfully! */

     break;

    }

   
close
(sfd);

  }

 

 if(rp==
NULL
){

   
fprintf
(stderr,"Could
not bind\n"
);

   return-1;

  }

  freeaddrinfo(result);

 return sfd;

}

 

//函数

//功能:设置socket为非阻塞的

static int make_socket_non_blocking(int sfd){

 int flags,
s
;

 //得到文件状态标志

  flags= fcntl(sfd,
F_GETFL
,0);

 if(flags==-1){

   
perror
("fcntl");

   
return
-1;

  }

 //设置文件状态标志

  flags |= O_NONBLOCK;

  s= fcntl(sfd,
F_SETFL
, flags);

 if(s==-1){

   
perror
("fcntl");

   return
-
1;

  }

 return0;

}

 

//端口由参数argv[1]指定

int main (int argc,char*argv[]){

 int sfd,
s
;

 int efd;

 struct epoll_eventevent;

 struct epoll_event*events;

 if(argc!=2){

   
fprintf
(stderr,"Usage:
%s [port]\n"
, argv[0]);

   exit(EXIT_FAILURE);

  }

 sfd = create_and_bind(argv[1]);

 if(sfd==-1){

  abort();

  }

 s= make_socket_non_blocking(sfd);

 if(s==-1)

  abort();

  s= listen(sfd,
SOMAXCONN
);

 if(s==-1){

   
perror
("listen");

   
abort
();

  }

 

 //除了参数size被忽略外,此函数和epoll_create完全相同

  efd= epoll_create1(0);

 if(efd==-1){

   
perror
("epoll_create");

   
abort
();

  }

 

 event.data.fd=
sfd
;

 event.events=
EPOLLIN
| EPOLLET;//读入,边缘触发方式

 s= epoll_ctl(efd,
EPOLL_CTL_ADD
, sfd,&event);

 if(s==-1){

   
perror
("epoll_ctl");

   
abort
();

  }

 

 /* Buffer where events are returned */

  events= calloc(MAXEVENTS,
sizeof
event);


 /* The event loop */

while(1)
{

 int n,
i
;

  n=
epoll_wait
(efd, events,MAXEVENTS,
-
1);

   for(i=0;
i
<n; i++){

     if((events[i].events
&
EPOLLERR) ||

       (events[i].events
&
EPOLLHUP) ||

       (!(events[i].events
&
EPOLLIN))){

        /*
An error has occured on this fd, or the socketis not

        readyfor
reading (why were we notified then?) */

       
fprintf
(stderr,"epoll
error\n"
);

       
close
(events[i].data.fd);

        continue;

      }
else
if(sfd==
events
[i].data.fd){

        /*
We have a notification on the listening socket,which

         meansone
or more incoming connections. */

       while(1){

         struct
sockaddr in_addr
;

        socklen_t
in_len
;

         int
infd
;

         char
hbuf
[NI_MAXHOST],
sbuf
[NI_MAXSERV];

         
in_len
=sizeof(in_addr);

         
infd
= accept(sfd,&in_addr,&in_len);

         if(infd==-1){

           if((errno==
EAGAIN
)||

             (errno==
EWOULDBLOCK
)){

             /*
We have processed all incoming

               connections.*/

             break;

            }else{

             
perror
("accept");

             break;

            }

          }

 

         //将地址转化为主机名或者服务名

         
s
= getnameinfo(&in_addr,
in_len
,

                  hbuf,sizeofhbuf,

                  sbuf,sizeofsbuf,

                  NI_NUMERICHOST|
NI_NUMERICSERV
);//flag参数:以数字名返回

                 //主机地址和服务地址

 

         if(s==0){

           
printf
("Accepted connection on descriptor %d "

               "(host=%s,port=%s)\n",
infd
, hbuf,sbuf);

          }

 

         /*
Make the incoming socket non-blocking and addit to the

           listof
fds to monitor. */

        s=
make_socket_non_blocking
(infd);

         if(s==-1)

          abort();

         event.data.fd=infd;

         event.events=
EPOLLIN
| EPOLLET;

         
s
= epoll_ctl(efd,
EPOLL_CTL_ADD
,infd,&event);

         if(s==-1){

           
perror
("epoll_ctl");

           
abort
();

          }

        }

       continue;

      }else{

       /*
We have data on the fd waiting to be read. Readand

         displayit.
We must read whatever data is available

         completely,as
we are running in edge-triggered mode

         andwon't
get a notification again for the same

         data.*/

       intdone=0;

       while(1){

        ssize_t
count
;

         char
buf
[512];

        count=
read
(events[i].data.fd,
buf
,sizeof(buf));

         if(count==-1){

           /*
If errno == EAGAIN, that means we have read all

             data.So
go back to the main loop. */

           if(errno!=
EAGAIN
){

             
perror
("read");

             done=1;

            }

           break;

          }elseif(count==0){

           /*
End of file. The remote has closed the

             connection.*/

           done=1;

           break;

          }

 

         /*
Write the buffer to standard output */

         
s
= write(1,
buf
, count);

         if(s==-1)

          {

           
perror
("write");

           
abort
();

          }

        }

 

       if(done)

        {

         
printf
("Closed connection on descriptor %d\n",

             
events
[i].data.fd);

 

         /*
Closing the descriptor will make epoll removeit

           fromthe
set of descriptors which are monitored. */

         
close
(events[i].data.fd);

        }

      }

    }

  }

  free(events);

  close(sfd);

 return EXIT_SUCCESS;

}


 

运行方式:

在一个终端运行此程序:epoll.out PORT

另一个终端:telnet 127.0.0.1
PORT

 

七 文档信息

http://blog.csdn.net/lingfengtengfei/article/details/12398299

epoll - I/O event notification facility:  http://man7.org/linux/man-pages/man7/epoll.7.html
epoll基本原理及使用框架: http://www.cnblogs.com/panfeng412/articles/2229095.html
select,poll,epoll的归纳总结区分: http://www.cnblogs.com/Huluwa-Vs-Aoteman/p/3713019.html
epoll的原理和用法: http://blog.csdn.net/yusiguyuan/article/details/24833339
I/O多路复用之epoll: http://my.oschina.net/pathenon/blog/64219

《Linux/Unix系统编程手册》章节 63.4 epoll编程接口

EPOLL 内核实现  http://blog.csdn.net/chensichensi/article/details/5338765 

抱歉!评论已关闭.