搬:【Linux学习】epoll详解

Linux epoll模型详解及源码分析 一、epoll简介 epoll是当前在Linux下开发大规模并发网络程序的热门选择,epoll在Linux2.6内核中正式引入,和select相似,都是IO多路复用(IO multiplexing)技术。 按照man手册的说法,epoll是为处理大批量句柄而做了改进的poll。 Linux下有以下几个经典的服务器模型: 1、PPC模型和TPC模型 PPC(Process Per Co... 阅读详情

【Linux学习】epoll详解

2013年07月05日 11:50:42 江南烟雨 阅读数:119397 标签: Linux epoll 服务器 更多

个人分类: Linux

所属专栏: Nginx模块开发与原理剖析

什么是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:注册新的fd到epfd中;

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()时便得到通知。

 

Epoll的2种工作方式-水平触发(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缺省的工作方式,并且同时支持block和no-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网卡驱动架构。

 

linux下epoll如何实现高效处理百万句柄的

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

 

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

 

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

 

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

 

从上面的调用方式就可以看到epoll比select/poll的优越之处:因为后者每次调用时都要传递你所要监控的所有socket给select/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.youkuaiyun.com/sparkliang/article/details/4770655

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

http://blog.youkuaiyun.com/eroswang/article/details/4481521

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

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

Linux Epoll 详解 Epoll 作为Linux 系统中的核心武器之一,在高吞吐、高并发的IO系统中经常遇见Epoll的身影,Redis、Nginx、Skynet等都使用到了IO多路复用技术。我们可以先创建一个epoll对象,再将fd(File Descriptor 文件描述符对象)注册到epoll对象里,之后便能通过调用epoll对象的查询函数来获取准备好事件的fd对象。epoll提供了三个API接口,分别为epoll_create、epoll_ctrl、epoll_wait,下面分别说明这三个接口的用处。 阅读详情

相关推荐

Delta并联机械手-Codesys实现以及正解逆解研究

由于Codesys的Robotics模块有Delta并联机械手的运动模型,因此借助Codesys的机械手运动模型,研究自己的正解逆解算法。

weixin_42386991的博客 5564

epoll详解(使用、原理、实验)

执行epoll_create时,创建了红黑树和就绪链表,执行epoll_ctl时,如果增加socket句柄,则检查在红黑树中是否存在,存在立即返回,不存在则添加到树干上,然后向内核注册回调函数,用于当中断事件来临时向准备就绪链表中插入数据。epoll_wait在调用时,在给定的timeout时间内,当在监控的所有句柄中有事件发生时,就返回用户态的进程,不用传递socket句柄给内核,因为内核已经在epoll_ctl中拿到了要监控的句柄列表。,当有的连接请求的时候,服务器被通知,执行回调函数来建立起连接。

欢迎来到jiantaoyab博客 1万+

万达电影 com.wandafilm.app headers check

万达电影 安卓逆向 抓包 逆向 check

weixin_43786514的博客 1438

一篇文章让你真正搞懂epoll机制

一篇文章让你真正搞懂epoll机制,揭开epoll神秘面纱

weixin_28673511的博客 5618

V93000 SmarTest 8入门:从零搭建你的第一个半导体测试程序

半导体测试是芯片从设计走向量产的关键环节,而ATE(自动化测试设备)正是支撑这一环节的核心工具。理解ATE测试程序开发的基本原理,不仅需要掌握测试机台的硬件架构,还要熟悉其软件环境中的管脚映射、电平时序、测试流程等底层逻辑。V93000作为爱德万的主力平台,其SmarTest 8环境通过对象化与流程化设计,将复杂的芯片测试任务拆解为可配置、可复用的模块,极大提升了并行测试效率与调试的便利性。无论是数字逻辑芯片的功能验证,还是模拟混合信号器件的参数测量,掌握SmarTest 8的核心操作都能帮助你快速搭建出可

weixin_31476015的博客 240

基于STM32的智能家用节能控制系统设计

该系统可以实时监测家庭的环境参数,如温湿度、光照强度和人体存在情况,根据设定的节能策略自动控制灯光、空调、风扇等家电设备,从而降低不必要的能源消耗。该基于STM32的智能家用节能控制系统通过多种传感器实现了对家庭环境的实时监测,并结合继电器控制模块实现了对家电设备的自动化控制,从而达到节能减排的效果。根据传感器获取的数据,判断是否需要开启或关闭家电设备,从而降低不必要的能源消耗,达到节能的目的。:继电器的控制引脚连接至STM32的GPIO引脚(如PB0、PB1),用于控制灯光、风扇、空调等家电设备的开关。

stm32d1219的博客 1245

Redis IO多路复用技术以及epoll实现原理

redis 是一个单线程却性能非常好的内存数据库, 主要用来作为缓存系统。 redis 采用网络IO多路复用技术来保证在多连接的时候, 系统的高吞吐量。 为什么 Redis 中要使用 I/O 多路复用这种技术呢? 首先,Redis 是跑在单线程中的,所有的操作都是按照顺序线性执行的,但是由于读写操作等待用户输入或输出都是阻塞的,所以 I/O 操作在一般情况下往往不能直接返回,这会导致某一文件的 I/O 阻塞导致整个进程无法对其它客户提供服务,而 I/O 多路复用就是为了解决这个问题而出现的。 redis的i

wo4641771的博客 8195

linux虚拟机和电脑ping通(可上网)

linux虚拟机(Ubuntu)和电脑ping通。

观则明的博客 4117

epoll原理分析,图文并茂讲解epoll,彻底弄懂epoll机制

epoll是网络编程中非常难的一点,通过本文学习能彻底了解epoll原理,初学者少走弯路

weixin_28673511的博客 8971

Ubuntu22.04 apt 安装 ROS1 ROS Noetic Ninjemys

ubuntu 22.04 apt-get 安装 ros1 ros Noetic 最新版

autolabor的博客 5万+

AI OS与LLM as OS:人工智能操作系统的崛起

近年来,随着大语言模型(Large Language Model, LLM)技术的突飞猛进,人工智能领域掀起了新的革命浪潮。以ChatGPT、GPT-4等为代表的LLM模型展现出了惊人的自然语言理解和生成能力,使得人机交互前所未有地拟人化、情境化。在此背景下,业界开始探索将LLM作为操作系统(Operating System, OS)的可能性,由此催生出"AI OS"和"LLM as OS"的概念。本文将深入解读这两个前沿概念,分析其内涵、技术原理、应用前景及面临的挑战。

AI天才研究院 993

epoll详解

epoll的起源:epoll 是Linux内核中的一种I/O事件通知机制。它是 select 和 poll 这两个系统调用的增强版本,专为处理大量文件描述符而设计,特别是在需要高并发处理的应用程序中,如网络服务器和分布式系统。他的起源可以追溯到21世纪初,当时Linux系统在处理大量并发连接时遇到了性能瓶颈。select 和 poll 方法的性能随着文件描述符数量的增加而线性下降,因为它们需要检查每个文件描述符的状态。为了解决这个问题,Linux内核引入了 epoll。

2301_80105928的博客 3471

模型泛化能力提升:领域自适应与迁移学习

模型泛化能力提升:领域自适应与迁移学习​ ,人工智能,计算机视觉,大模型,AI,在机器学习和深度学习的应用中,模型的泛化能力是一个关键问题。泛化能力指的是模型在新的、未见过的数据上表现良好的能力。然而,实际情况中,训练数据和测试数据往往来自不同的领域,存在分布差异,这会导致模型的泛化能力下降。领域自适应与迁移学习正是为了解决这一问题而发展起来的重要技术。

不踩坑,不知道坑有多深;不总结,踩过的坑白踩。 3万+

【Linux & IO多路转接】——epoll详解

epoll的功能一样跟select和poll一样,都是用来检测文件描述符中的事件是否就绪,当有事件就绪,可以通知给应用层,上层调用 read,recv,write,send 等类似接口就不会被阻塞

sjp11的博客 3639

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

温度PID控制PLC程序

温度PID稳定控制程序,适用于欧姆龙PLC,对其他PLC也具有很大参考价值

Linux下的IO复用:epoll详解

EPOLL详解

cooler_water的博客 2292

Devexpress16.2.6破解工具

Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具

Linux epoll详解

Linux epoll详解 日期: 2013年10月 8日 一、什么是epoll epoll是什么?按照man手册的说法:是为处理大批量句柄而作了改进的poll。当然,这不是2.6内核才有的,它是在2.5.44内核中被引进的(epoll(4) is a new API introduced in Linuxkernel 2.5.44),它几乎具备了之前所说的一切优点,被公

lingfengtengfei的专栏 3万+

精雕软件(JDpaint) 5.5 绿色版

精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

epoll学习:epoll_ctl函数详解

epoll_ctl函数:向文件描述符epfd引用的epoll实例执行控制操作(op:添加、修改或者删除)。

个人工作中学到的专业技术与职业发展知识笔记 9591

【Linux学习】epoll详解

什么是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的相关

计算机专业同学的充电站。 15万+

Linux Epoll使用详解

主要详解Linux系统中的Epoll函数用法,包含一个用Epoll实现的简单的回声服务器例子

余识- 4392

epoll机制详解:

大家都明白epoll是一种IO多路复用技术,可以非常高效的处理数以百万计的socket句柄,比起以前的select和poll效率高大发了。在Linux的网络编程中,很长的时间都在使用select来做事件触发。在linux新的内核中,有了一种替换它的机制,就是epoll。相比于select,epoll最大的好处在于它不会随着监听fd数目的增长而降低效率。因为在内核中的select实现中,它是采用轮询...

青山绿水 7998

Linux内核机制之epoll详解

Linux的epoll机制详解

hinewcc的博客 1377

图文详解Linux EPOLL

Linux EPOLL超详细解读,图文并茂,原文请关注知乎-晏舒,同作者

cxjczy1990的博客 1180
上一篇: 搬:epoll使用详解(精髓)
下一篇: 搬:我读过的最好的epoll讲解--转自”知乎“
nedwons
博客等级 码龄9年 311粉丝 572原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值