【Linux学习】epoll详解

2023-10-26

什么是epoll

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

 

epoll的相关系统调用

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

 

1. int epoll_create(int size);

创建一个epoll的句柄。自从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_create()的返回值。

第二个参数表示动作,用三个宏来表示:

EPOLL_CTL_ADD:注册新的fdepfd中;

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

EPOLL_CTL_DEL:从epfd中删除一个fd

 

第三个参数是需要监听的fd

第四个参数是告诉内核需要监听什么事,struct epoll_event结构如下:

  1. //保存触发事件的某个文件描述符相关的数据(与具体使用方式有关)  
  2.   
  3. typedef union epoll_data {  
  4.     void *ptr;  
  5.     int fd;  
  6.     __uint32_t u32;  
  7.     __uint64_t u64;  
  8. } epoll_data_t;  
  9.  //感兴趣的事件和被触发的事件  
  10. struct epoll_event {  
  11.     __uint32_t events; /* Epoll events */  
  12.     epoll_data_t data; /* User data variable */  
  13. };  

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

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

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

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

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

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

EPOLLET EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。

EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里


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

收集在epoll监控的事件中已经发送的事件。参数events是分配好的epoll_event结构体数组,epoll将会把发生的事件赋值到events数组中(events不可以是空指针,内核只负责把数据复制到这个events数组中,不会去帮助我们在用户态中分配内存)maxevents告之内核这个events有多大,这个 maxevents的值不能大于创建epoll_create()时的size,参数timeout是超时时间(毫秒,0会立即返回,-1将不确定,也有说法说是永久阻塞)。如果函数调用成功,返回对应I/O上已准备好的文件描述符数目,如返回0表示已超时。

 

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)......


Edge Triggered 工作模式:

如果我们在第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更高。ET与LT的区别在于,当一个新的事件到来时,ET模式下当然可以从epoll_wait调用中获取到这个事件,可是如果这次没有把这个事件对应的套接字缓冲区处理完,在这个套接字中没有新的事件再次到来时,在ET模式下是无法再次从epoll_wait调用中获取这个事件的。而LT模式正好相反,只要一个事件对应的套接字缓冲区还有数据,就总能从epoll_wait中获取这个事件。

因此,LT模式下开发基于epoll的应用要简单些,不太容易出错。而在ET模式下事件发生时,如果没有彻底地将缓冲区数据处理完,则会导致缓冲区中的用户请求得不到响应。

图示说明:


Nginx默认采用ET模式来使用epoll。

 

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同一块内存实现的。而如果你想我一样从2.5内核就关注epoll的话,一定不会忘记手工 mmap这一步的。

 

4.内核微调

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

 

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

开发高性能网络程序时,windows开发者们言必称iocplinux开发者们则言必称epoll。大家都明白epoll是一种IO多路复用技术,可以非常高效的处理数以百万计的socket句柄,比起以前的selectpoll效率高大发了。我们用起epoll来都感觉挺爽,确实快,那么,它到底为什么可以高速处理这么多并发连接呢?

 

使用起来很清晰,首先要调用epoll_create建立一个epoll对象。参数size是内核保证能够正确处理的最大句柄数,多于这个最大数时内核可不保证效果。

 

epoll_ctl可以操作上面建立的epoll,例如,将刚建立的socket加入到epoll中让其监控,或者把 epoll正在监控的某个socket句柄移出epoll,不再监控它等等。

 

epoll_wait在调用时,在给定的timeout时间内,当在监控的所有句柄中有事件发生时,就返回用户态的进程。

 

从上面的调用方式就可以看到epollselect/poll的优越之处:因为后者每次调用时都要传递你所要监控的所有socketselect/poll系统调用,这意味着需要将用户态的socket列表copy到内核态,如果以万计的句柄会导致每次都要copy几十几百KB的内存到内核态,非常低效。而我们调用epoll_wait时就相当于以往调用select/poll,但是这时却不用传递socket句柄给内核,因为内核已经在epoll_ctl中拿到了要监控的句柄列表。

 

所以,实际上在你调用epoll_create后,内核就已经在内核态开始准备帮你存储要监控的句柄了,每次调用epoll_ctl只是在往内核的数据结构里塞入新的socket句柄。

 当一个进程调用epoll_creaqte方法时,Linux内核会创建一个eventpoll结构体,这个结构体中有两个成员与epoll的使用方式密切相关:

  1. /* 
  2.  
  3.  171 * This structure is stored inside the "private_data" member of the file 
  4.  
  5.  172 * structure and represents the main data structure for the eventpoll 
  6.  
  7.  173 * interface. 
  8.  
  9.  174 */  
  10.   
  11.  175struct eventpoll {  
  12.   
  13.  176        /* Protect the access to this structure */  
  14.   
  15.  177        spinlock_t lock;  
  16.   
  17.  178  
  18.   
  19.  179        /* 
  20.  
  21.  180         * This mutex is used to ensure that files are not removed 
  22.  
  23.  181         * while epoll is using them. This is held during the event 
  24.  
  25.  182         * collection loop, the file cleanup path, the epoll file exit 
  26.  
  27.  183         * code and the ctl operations. 
  28.  
  29.  184         */  
  30.   
  31.  185        struct mutex mtx;  
  32.   
  33.  186  
  34.   
  35.  187        /* Wait queue used by sys_epoll_wait() */  
  36.   
  37.  188        wait_queue_head_t wq;  
  38.   
  39.  189  
  40.   
  41.  190        /* Wait queue used by file->poll() */  
  42.   
  43.  191        wait_queue_head_t poll_wait;  
  44.   
  45.  192  
  46.   
  47.  193        /* List of ready file descriptors */  
  48.   
  49.  194        struct list_head rdllist;  
  50.   
  51.  195  
  52.   
  53.  196        /* RB tree root used to store monitored fd structs */  
  54.   
  55.  197        struct rb_root rbr;//红黑树根节点,这棵树存储着所有添加到epoll中的事件,也就是这个epoll监控的事件  
  56.  198  
  57.  199        /* 
  58.  200         * This is a single linked list that chains all the "struct epitem" that 
  59.  201         * happened while transferring ready events to userspace w/out 
  60.  202         * holding ->lock. 
  61.  203         */  
  62.  204        struct epitem *ovflist;  
  63.  205  
  64.  206        /* wakeup_source used when ep_scan_ready_list is running */  
  65.  207        struct wakeup_source *ws;  
  66.  208  
  67.  209        /* The user that created the eventpoll descriptor */  
  68.  210        struct user_struct *user;  
  69.  211  
  70.  212        struct file *file;  
  71.  213  
  72.  214        /* used to optimize loop detection check */  
  73.  215        int visited;  
  74.  216        struct list_head visited_list_link;//双向链表中保存着将要通过epoll_wait返回给用户的、满足条件的事件  
  75.  217};  

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

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

  1. /* 
  2.  130 * Each file descriptor added to the eventpoll interface will 
  3.  131 * have an entry of this type linked to the "rbr" RB tree. 
  4.  132 * Avoid increasing the size of this struct, there can be many thousands 
  5.  133 * of these on a server and we do not want this to take another cache line. 
  6.  134 */  
  7.  135struct epitem {  
  8.  136        /* RB tree node used to link this structure to the eventpoll RB tree */  
  9.  137        struct rb_node rbn;  
  10.  138  
  11.  139        /* List header used to link this structure to the eventpoll ready list */  
  12.  140        struct list_head rdllink;  
  13.  141  
  14.  142        /* 
  15.  143         * Works together "struct eventpoll"->ovflist in keeping the 
  16.  144         * single linked chain of items. 
  17.  145         */  
  18.  146        struct epitem *next;  
  19.  147  
  20.  148        /* The file descriptor information this item refers to */  
  21.  149        struct epoll_filefd ffd;  
  22.  150  
  23.  151        /* Number of active wait queue attached to poll operations */  
  24.  152        int nwait;  
  25.  153  
  26.  154        /* List containing poll wait queues */  
  27.  155        struct list_head pwqlist;  
  28.  156  
  29.  157        /* The "container" of this item */  
  30.  158        struct eventpoll *ep;  
  31.  159  
  32.  160        /* List header used to link this item to the "struct file" items list */  
  33.  161        struct list_head fllink;  
  34.  162  
  35.  163        /* wakeup_source used when EPOLLWAKEUP is set */  
  36.  164        struct wakeup_source __rcu *ws;  
  37.  165  
  38.  166        /* The structure that describe the interested events and the source fd */  
  39.  167        struct epoll_event event;  
  40.  168};  

此外,epoll还维护了一个双链表,用户存储发生的事件。epoll_wait调用时,仅仅观察这个list链表里有没有数据即eptime项即可。有数据就返回,没有数据就sleep,等到timeout时间到后即使链表没数据也返回。所以,epoll_wait非常高效。

 

而且,通常情况下即使我们要监控百万计的句柄,大多一次也只返回很少量的准备就绪句柄而已,所以,epoll_wait仅需要从内核态copy少量的句柄到用户态而已,如何能不高效?!

 

那么,这个准备就绪list链表是怎么维护的呢?当我们执行epoll_ctl时,除了把socket放到epoll文件系统里file对象对应的红黑树上之外,还会给内核中断处理程序注册一个回调函数,告诉内核,如果这个句柄的中断到了,就把它放到准备就绪list链表里。所以,当一个socket上有数据到了,内核在把网卡上的数据copy到内核中后就来把socket插入到准备就绪链表里了。

 

如此,一颗红黑树,一张准备就绪句柄链表,少量的内核cache,就帮我们解决了大并发下的socket处理问题。执行epoll_create时,创建了红黑树和就绪链表,执行epoll_ctl时,如果增加socket句柄,则检查在红黑树中是否存在,存在立即返回,不存在则添加到树干上,然后向内核注册回调函数,用于当中断事件来临时向准备就绪链表中插入数据。执行epoll_wait时立刻返回准备就绪链表里的数据即可。

 

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, int timeout)来查询所有的网络接口,看哪一个可以读,哪一个可以写了。基本的语法为:

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程序都使用下面的框架:

  1. for( ; ; )  
  2.    {  
  3.        nfds = epoll_wait(epfd,events,20,500);  
  4.        for(i=0;i<nfds;++i)  
  5.        {  
  6.            if(events[i].data.fd==listenfd) //有新的连接  
  7.            {  
  8.                connfd = accept(listenfd,(sockaddr *)&clientaddr, &clilen); //accept这个连接  
  9.                ev.data.fd=connfd;  
  10.                ev.events=EPOLLIN|EPOLLET;  
  11.                epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&ev); //将新的fd添加到epoll的监听队列中  
  12.            }  
  13.   
  14.            else if( events[i].events&EPOLLIN ) //接收到数据,读socket  
  15.            {  
  16.                n = read(sockfd, line, MAXLINE)) < 0    //读  
  17.                ev.data.ptr = md;     //md为自定义类型,添加数据  
  18.                ev.events=EPOLLOUT|EPOLLET;  
  19.                epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);//修改标识符,等待下一个循环时发送数据,异步处理的精髓  
  20.            }  
  21.            else if(events[i].events&EPOLLOUT) //有数据待发送,写socket  
  22.            {  
  23.                struct myepoll_data* md = (myepoll_data*)events[i].data.ptr;    //取数据  
  24.                sockfd = md->fd;  
  25.                send( sockfd, md->ptr, strlen((char*)md->ptr), 0 );        //发送数据  
  26.                ev.data.fd=sockfd;  
  27.                ev.events=EPOLLIN|EPOLLET;  
  28.                epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev); //修改标识符,等待下一个循环时接收数据  
  29.            }  
  30.            else  
  31.            {  
  32.                //其他的处理  
  33.            }  
  34.        }  
  35.    }  

epoll的程序实例

  1.  #include <stdio.h>  
  2. #include <stdlib.h>  
  3. #include <unistd.h>  
  4. #include <errno.h>  
  5. #include <sys/socket.h>  
  6. #include <netdb.h>  
  7. #include <fcntl.h>  
  8. #include <sys/epoll.h>  
  9. #include <string.h>  
  10.   
  11. #define MAXEVENTS 64  
  12.   
  13. //函数:  
  14. //功能:创建和绑定一个TCP socket  
  15. //参数:端口  
  16. //返回值:创建的socket  
  17. static int  
  18. create_and_bind (char *port)  
  19. {  
  20.   struct addrinfo hints;  
  21.   struct addrinfo *result, *rp;  
  22.   int s, sfd;  
  23.   
  24.   memset (&hints, 0, sizeof (struct addrinfo));  
  25.   hints.ai_family = AF_UNSPEC;     /* Return IPv4 and IPv6 choices */  
  26.   hints.ai_socktype = SOCK_STREAM; /* We want a TCP socket */  
  27.   hints.ai_flags = AI_PASSIVE;     /* All interfaces */  
  28.   
  29.   s = getaddrinfo (NULL, port, &hints, &result);  
  30.   if (s != 0)  
  31.     {  
  32.       fprintf (stderr, "getaddrinfo: %s\n", gai_strerror (s));  
  33.       return -1;  
  34.     }  
  35.   
  36.   for (rp = result; rp != NULL; rp = rp->ai_next)  
  37.     {  
  38.       sfd = socket (rp->ai_family, rp->ai_socktype, rp->ai_protocol);  
  39.       if (sfd == -1)  
  40.         continue;  
  41.   
  42.       s = bind (sfd, rp->ai_addr, rp->ai_addrlen);  
  43.       if (s == 0)  
  44.         {  
  45.           /* We managed to bind successfully! */  
  46.           break;  
  47.         }  
  48.   
  49.       close (sfd);  
  50.     }  
  51.   
  52.   if (rp == NULL)  
  53.     {  
  54.       fprintf (stderr, "Could not bind\n");  
  55.       return -1;  
  56.     }  
  57.   
  58.   freeaddrinfo (result);  
  59.   
  60.   return sfd;  
  61. }  
  62.   
  63.   
  64. //函数  
  65. //功能:设置socket为非阻塞的  
  66. static int  
  67. make_socket_non_blocking (int sfd)  
  68. {  
  69.   int flags, s;  
  70.   
  71.   //得到文件状态标志  
  72.   flags = fcntl (sfd, F_GETFL, 0);  
  73.   if (flags == -1)  
  74.     {  
  75.       perror ("fcntl");  
  76.       return -1;  
  77.     }  
  78.   
  79.   //设置文件状态标志  
  80.   flags |= O_NONBLOCK;  
  81.   s = fcntl (sfd, F_SETFL, flags);  
  82.   if (s == -1)  
  83.     {  
  84.       perror ("fcntl");  
  85.       return -1;  
  86.     }  
  87.   
  88.   return 0;  
  89. }  
  90.   
  91. //端口由参数argv[1]指定  
  92. int  
  93. main (int argc, char *argv[])  
  94. {  
  95.   int sfd, s;  
  96.   int efd;  
  97.   struct epoll_event event;  
  98.   struct epoll_event *events;  
  99.   
  100.   if (argc != 2)  
  101.     {  
  102.       fprintf (stderr, "Usage: %s [port]\n", argv[0]);  
  103.       exit (EXIT_FAILURE);  
  104.     }  
  105.   
  106.   sfd = create_and_bind (argv[1]);  
  107.   if (sfd == -1)  
  108.     abort ();  
  109.   
  110.   s = make_socket_non_blocking (sfd);  
  111.   if (s == -1)  
  112.     abort ();  
  113.   
  114.   s = listen (sfd, SOMAXCONN);  
  115.   if (s == -1)  
  116.     {  
  117.       perror ("listen");  
  118.       abort ();  
  119.     }  
  120.   
  121.   //除了参数size被忽略外,此函数和epoll_create完全相同  
  122.   efd = epoll_create1 (0);  
  123.   if (efd == -1)  
  124.     {  
  125.       perror ("epoll_create");  
  126.       abort ();  
  127.     }  
  128.   
  129.   event.data.fd = sfd;  
  130.   event.events = EPOLLIN | EPOLLET;//读入,边缘触发方式  
  131.   s = epoll_ctl (efd, EPOLL_CTL_ADD, sfd, &event);  
  132.   if (s == -1)  
  133.     {  
  134.       perror ("epoll_ctl");  
  135.       abort ();  
  136.     }  
  137.   
  138.   /* Buffer where events are returned */  
  139.   events = calloc (MAXEVENTS, sizeof event);  
  140.   
  141.   /* The event loop */  
  142.   while (1)  
  143.     {  
  144.       int n, i;  
  145.   
  146.       n = epoll_wait (efd, events, MAXEVENTS, -1);  
  147.       for (i = 0; i < n; i++)  
  148.         {  
  149.           if ((events[i].events & EPOLLERR) ||  
  150.               (events[i].events & EPOLLHUP) ||  
  151.               (!(events[i].events & EPOLLIN)))  
  152.             {  
  153.               /* An error has occured on this fd, or the socket is not 
  154.                  ready for reading (why were we notified then?) */  
  155.               fprintf (stderr, "epoll error\n");  
  156.               close (events[i].data.fd);  
  157.               continue;  
  158.             }  
  159.   
  160.           else if (sfd == events[i].data.fd)  
  161.             {  
  162.               /* We have a notification on the listening socket, which 
  163.                  means one or more incoming connections. */  
  164.               while (1)  
  165.                 {  
  166.                   struct sockaddr in_addr;  
  167.                   socklen_t in_len;  
  168.                   int infd;  
  169.                   char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV];  
  170.   
  171.                   in_len = sizeof in_addr;  
  172.                   infd = accept (sfd, &in_addr, &in_len);  
  173.                   if (infd == -1)  
  174.                     {  
  175.                       if ((errno == EAGAIN) ||  
  176.                           (errno == EWOULDBLOCK))  
  177.                         {  
  178.                           /* We have processed all incoming 
  179.                              connections. */  
  180.                           break;  
  181.                         }  
  182.                       else  
  183.                         {  
  184.                           perror ("accept");  
  185.                           break;  
  186.                         }  
  187.                     }  
  188.   
  189.                                   //将地址转化为主机名或者服务名  
  190.                   s = getnameinfo (&in_addr, in_len,  
  191.                                    hbuf, sizeof hbuf,  
  192.                                    sbuf, sizeof sbuf,  
  193.                                    NI_NUMERICHOST | NI_NUMERICSERV);//flag参数:以数字名返回  
  194.                                   //主机地址和服务地址  
  195.   
  196.                   if (s == 0)  
  197.                     {  
  198.                       printf("Accepted connection on descriptor %d "  
  199.                              "(host=%s, port=%s)\n", infd, hbuf, sbuf);  
  200.                     }  
  201.   
  202.                   /* Make the incoming socket non-blocking and add it to the 
  203.                      list of fds to monitor. */  
  204.                   s = make_socket_non_blocking (infd);  
  205.                   if (s == -1)  
  206.                     abort ();  
  207.   
  208.                   event.data.fd = infd;  
  209.                   event.events = EPOLLIN | EPOLLET;  
  210.                   s = epoll_ctl (efd, EPOLL_CTL_ADD, infd, &event);  
  211.                   if (s == -1)  
  212.                     {  
  213.                       perror ("epoll_ctl");  
  214.                       abort ();  
  215.                     }  
  216.                 }  
  217.               continue;  
  218.             }  
  219.           else  
  220.             {  
  221.               /* We have data on the fd waiting to be read. Read and 
  222.                  display it. We must read whatever data is available 
  223.                  completely, as we are running in edge-triggered mode 
  224.                  and won't get a notification again for the same 
  225.                  data. */  
  226.               int done = 0;  
  227.   
  228.               while (1)  
  229.                 {  
  230.                   ssize_t count;  
  231.                   char buf[512];  
  232.   
  233.                   count = read (events[i].data.fd, buf, sizeof(buf));  
  234.                   if (count == -1)  
  235.                     {  
  236.                       /* If errno == EAGAIN, that means we have read all 
  237.                          data. So go back to the main loop. */  
  238.                       if (errno != EAGAIN)  
  239.                         {  
  240.                           perror ("read");  
  241.                           done = 1;  
  242.                         }  
  243.                       break;  
  244.                     }  
  245.                   else if (count == 0)  
  246.                     {  
  247.                       /* End of file. The remote has closed the 
  248.                          connection. */  
  249.                       done = 1;  
  250.                       break;  
  251.                     }  
  252.   
  253.                   /* Write the buffer to standard output */  
  254.                   s = write (1, buf, count);  
  255.                   if (s == -1)  
  256.                     {  
  257.                       perror ("write");  
  258.                       abort ();  
  259.                     }  
  260.                 }  
  261.   
  262.               if (done)  
  263.                 {  
  264.                   printf ("Closed connection on descriptor %d\n",  
  265.                           events[i].data.fd);  
  266.   
  267.                   /* Closing the descriptor will make epoll remove it 
  268.                      from the set of descriptors which are monitored. */  
  269.                   close (events[i].data.fd);  
  270.                 }  
  271.             }  
  272.         }  
  273.     }  
  274.   
  275.   free (events);  
  276.   
  277.   close (sfd);  
  278.   
  279.   return EXIT_SUCCESS;  
  280. }  

运行方式:

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

另一个终端:telnet  127.0.0.1 PORT

截图:



参考资料:

http://man7.org/linux/man-pages/man2/epoll_create.2.html

https://banu.com/blog/2/how-to-use-epoll-a-complete-example-in-c/

http://blog.csdn.net/sparkliang/article/details/4770655

 《深入理解Nginx模块开发与架构解析》9.6小节

http://blog.csdn.net/eroswang/article/details/4481521

http://www.ccvita.com/515.html

http://blog.codingnow.com/2006/04/iocp_kqueue_epoll.html



FROM: http://blog.csdn.net/xiajun07061225/article/details/9250579



本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【Linux学习】epoll详解 的相关文章

  • linux网络编程实现投票功能

    投票系统 1 说明 写了一个投票系统 过程是先配置好服务器 在写一个网上投票功能 要实现网上投票功能 其实功能实现还是很简单的 麻烦一点的在于过程比较繁杂 要做的东西还是挺多的 2 过程 第一步 配置httpd服务器 先配置好httpd服务
  • ipv6 socket bind 失败 - accept_dad

    file proc sys net ipv6 conf interface accept dad variable net ipv6 conf interface accept dad Official reference Whether
  • C++在线五子棋对战(网页版)项目:websocket协议

    目标 认识理解websocket协议 websocket切换过程和websocket协议格式 认识和学会使用websocketpp库常用接口 了解websocketpp库搭建服务器流程 认识和学会使用websocketpp库bin接口 最后
  • webbench剖析

    webbench 其为linux上一款web性能压力测试工具 它最多可以模拟3万个并发连接数来测试服务器压力 其原理为fork多个子进程 每个子进程都循环做web访问测试 子进程将访问的结果通过管道告诉父进程 父进程做最终结果统计 其主要原
  • 高并发的epoll+线程池,业务在线程池内

    epoll是linux下高并发服务器的完美方案 因为是基于事件触发的 所以比select快的不只是一个数量级 单线程epoll 触发量可达到15000 但是加上业务后 因为大多数业务都与数据库打交道 所以就会存在阻塞的情况 这个时候就必须用
  • linux 网络编程---->多路复用:select实例!

    好吧 我承认找了好久 网上都没有像样的完整的实例 然后自己参照书自己写一个吧 gt server 端代码 gt server c include
  • socket阻塞与非阻塞,同步与异步、I/O模型

    socket阻塞与非阻塞 同步与异步 作者 huangguisu 1 概念理解 在进行网络编程时 我们常常见到同步 Sync 异步 Async 阻塞 Block 非阻塞 Unblock 四种调用方式 同步 所谓同步 就是在发出一个功能调用时
  • Linux网络通信总结

    网络IO之阻塞 非阻塞 同步 异步 单播 多播 组播 广播 多路复用POLL SELECT epoll 超时 read write accept connect 超时 实现 1 用select来设置超时机制 2 使用setsockopt 函
  • 基于tcpdump实例讲解TCP/IP协议

    前言 虽然网络编程的socket大家很多都会操作 但是很多还是不熟悉socket编程中 底层TCP IP协议的交互过程 本文会一个简单的客户端程序和服务端程序的交互过程 使用tcpdump抓包 实例讲解客户端和服务端的TCP IP交互细节
  • linux系统PXE自动装机无人值守

    PXE高效批量网络装机 PXE服务允许客户机通过网络从远程服务器下载引导镜像 并安装文件或整个操作系统 必须满足的前提条件 客户机的网卡支持PXE协议 且主板支持网络引导 网络中有一台DHCP服务器为客户机分配地址 指定引导文件位置 服务器
  • Linux网络和安全:配置、远程访问与防御指南

    文章目录 Linux 网络和安全 引言 网络配置 IP地址配置 配置网络接口 防火墙设置 安全性加强 Linux网络配置及端口管理 网络配置命令 端口管理 防火墙和安全性设置 防火墙管理工具 安全性设置 Linux远程访问技术 SSH和VP
  • Linux网络编程一步一步学 - 目录(汇总)zhuan

    非常好的关于Linux网络编程的文章 我也是无意中在别人的博客里发现的 在网上搜了一下 这类的文章总共还有很多篇 最后我找到了原作者的姓名 周立发 以及作者的博客空间 本人坚决支持原创 在此将其文章进行了汇总 并且空间首页加入了他的博客链接
  • Linux下Socket编程

    什么是Socket Socket接口是TCP IP网络的API Socket接口定义了许多函数或例程 程式员能够用他们来研发TCP IP网络上的应用程式 要学Internet上的TCP IP网络编程 必须理解Socket接口 Socket接
  • linux下异步RPC的阶段性总结-非阻塞SOCKET客户端

    尽可能使用非阻塞socket int flags s flags fcntl fd F GETFL 0 if flags 1 close fd return 1 flags O NONBLOCK s fcntl fd F SETFL fla
  • 组播技术

    1 概述 1 1 产生背景 传统的IP通信有两种方式 一种是在源主机与目的主机之间点对点的通信 即单播 另一种是在源主机与同一网段中所有其它主机之间点对多点的通信 即广播 如果要将信息发送给多个主机而非所有主机 若采用广播方式实现 不仅会将
  • 高并发的epoll+多线程

    epoll是linux下高并发服务器的完美方案 因为是基于事件触发的 所以比select快的不只是一个数量级 单线程epoll 触发量可达到15000 但是加上业务后 因为大多数业务都与数据库打交道 所以就会存在阻塞的情况 这个时候就必须用
  • 【网络自定向下学习】——TCP报文段的详细解析

    个人主页 努力学习的少年 版权 本文由 努力学习的少年 原创 在CSDN首发 需要转载请联系博主 如果文章对你有帮助 欢迎关注 点赞 收藏 一键三连 和订阅专栏哦 目录 一 Tcp报文段的结构 二 首部长度 三 窗口大小 四 序列号和确认序
  • poll()函数详解

    poll提供的功能与select类似 不过在处理流设备时 它能够提供额外的信息 include
  • Http协议详解

    引入 超文本传输协议 HTTP HyperText Transfer Protocol 是互联网上应用最为广泛的一种网络协议 所有的WWW文件都必须遵守这个标准 设计HTTP最初的目的是为了提供一种发布和接收HTML页面的方法 1960年美
  • 超级详细Tcpdump 的用法

    1 抓取回环网口的包 tcpdump i lo 2 防止包截断 tcpdump s0 3 以数字显示主机及端口 tcpdump n 第一种是关于类型的关键字 主要包括host net port 例如 host 210 27 48 2 指明

随机推荐

  • 【SSM】【4】前端后端数据流转

    前后端数据流转图 业务流转图 前端控制器接受用户请求响应 doJsonRequest ursuser login json json function data if getUrlParam session 1 history back e
  • 最新版 phpstudy V8.0如何禁止或允许站点目录列表显示

    phpstudy V8 0如何禁止或允许站点目录列表显示 localhost下显示文件目录 如何在浏览器中打开 最新版phpstudy V8 0 的文件目录 如果你像我一样是小白 这篇文章对你或许有用 1 当你删除了phpstudy pro
  • 《数据库系统内 幕》事务恢复与处理

    数据库系统内幕 章五 缓冲区管理 页缓存 回收 锁定页 页置换策略 同os中的置换策略 恢复 检查点 steal和force策略 不太理解 要回看 并发控制 异常 专栏 数据库系统内 幕 存储引擎 数据库系统内 幕 事务恢复与处理 数据库系
  • 2022Java笔试题选择题整理(附答案解析)走过路过不要错过了喂!

    2022Java笔试题选择题 一 个人主页 编程ID 个人简介 大家好 我是编程ID 一个想要与大家共同进步的程序员儿 欢迎大家 这里是CSDN 我总结知识的地方 欢迎来到我的博客 望能帮到各位想要找工作或者提高自己的小伙伴儿们 如果有什么
  • 将无序数列升序排列,并输出排列结果和排列后的下标

    include
  • mysql驱动协议之loadbalance和replication

    2019独角兽企业重金招聘Python工程师标准 gt gt gt 背景 偶然下和朋友聊到了mysql多节点集群架场景 对应我们各系代码如何去使用它 牵扯到mysql的驱动包中已经支持的策略 发现这块的概念有些模糊 索性就梳理了一番留着后用
  • 如何防止mq的消息丢失

    分为两种情况 1 主要在消费方 当消息从mq传到消费者时 消费者可能消费消息失败 这时mq中的消息已经自动删除了 导致消息的丢失 需要使用ack机制来保证消息不会丢失 当消费者从mq中拿到消息时 这个消息在mq中不删除 而是消费者对消息进行
  • Qt实现2D绘图

    一 介绍 Qt中提供了强大的2D绘图系统 可以使用相同的API在屏幕和绘图设备上进行绘制 它主要基于QPainter QPaintDevice和QPaintEngine这三个类 其中QPainter用来执行绘图操作 QPaintDevice
  • 科技抗老新突破,香港美容仪品牌内地重磅上市

    近年来 新消费时代 颜值经济 的火热促使美容行业市场规模增长迅速 越来越多的人愿意为 美 买单 对美的需求也随之增长 美容行业已经成为成长最快的新锐产业 随着经济和科技的发展 快捷 也成为了当今社会的时代特征 美容行业也从传统的美容院向家用
  • 竞赛选题 opencv 图像识别 指纹识别 - python

    0 前言 优质竞赛项目系列 今天要分享的是 基于机器视觉的指纹识别系统 学长这里给一个题目综合评分 每项满分5分 难度系数 3分 工作量 3分 创新点 4分 该项目较为新颖 适合作为竞赛课题方向 学长非常推荐 更多资料 项目分享 https
  • 前端安装vue-cli报错:npm ERR! notarget No matching version found for vue@cli.

    安装vue cli的版本3以上的内容 命令中 在最前 不在中间 对的命令是npm install g vue cli C Users 15232 gt npm install g vue cli npm ERR code ETARGET n
  • Linux 安装Mysql 详细教程(图文教程)

    首先通过xshell或者 putty 远程进入Linux 命令行操作界面 Xshell 的安装 1 去XShell Download下载需要的版本 XShell免费版 解决官网打不开的问题 百度网盘 https pan baidu com
  • 用6个实例,8段代码,详解Python中的for循环

    目录 前言 01 使用tryexcept的for循环 02 指数运算 03 嵌套的循环 04 在for循环中使用split 函数 1 使用split 函数做单词比较 2 使用split 函数打印指定格式的文本 3 使用split 函数打印固
  • windows 7Z命令行与安装

    7z 全称7 Zip 是一款开源软件 是目前公认的压缩比例最大的压缩解压软件 7z exe在CMD窗口的使用说明如下 7 Zip A 4 57 Copyright c 1999 2007 Igor Pavlov 2007 12 06 Usa
  • colmap代码解读

    clomap是作者在ECCV2016年发表的基于两个概率的深度值和法线估计的论文 开源 下面就开源代码Patch match cuda cu文件做简单的介绍 产生随机法向量和随机深度值 扰动法向量 产生随机三个方位角度 和扰动深度值 根据像
  • 多线程案例(2) - 阻塞队列

    目录 一 阻塞队列 1 1 什么是阻塞队列 1 2 生产者消费者模型 1 3 标准库中的阻塞队列 1 4 阻塞队列的实现 一 阻塞队列 1 1 什么是阻塞队列 阻塞队列 BlockingQueue 是一种特殊的队列 遵循 先进先出 的原则
  • Deep-Learning-YOLOV4实践:ScaledYOLOv4模型训练自己的数据集调试问题总结

    error error1 CUDA out of memory error2 TypeError can t convert cuda error Deep Learning YOLOV4实践 ScaledYOLOv4 数据集制作 Deep
  • 知识库-kafka shell脚本用法

    脚本名称 用途描述 connect distributed sh 连接kafka集群模式 connect standalone sh 连接kafka单机模式 kafka acls sh todo kafka broker api versi
  • 一篇搞定dockerfile定制镜像过程

    一 定制镜像的两种方法 1 docker commit 通过已有容器创建镜像 提交容器快照作为镜像 不推荐 2 docker build 就是本文着重讲的dockerfile创建镜像方式 推荐 docker commit无法还原镜像制作过程
  • 【Linux学习】epoll详解

    什么是epoll epoll是什么 按照man手册的说法 是为处理大批量句柄而作了改进的poll 当然 这不是2 6内核才有的 它是在2 5 44内核中被引进的 epoll 4 is a new API introduced in Linu