千家信息网

高级I/O---多路复用---epoll

发表于:2026-10-31 作者:千家信息网编辑
千家信息网最后更新 2026年10月31日,多路复用之epoll作为多路复用中最高效的I/O,epoll有着select和poll都不具有的很多能力。不同于poll和select,epoll它用三个函数来实现多路复用这一个功能。 #inc
千家信息网最后更新 2026年10月31日高级I/O---多路复用---epoll

多路复用之epoll


作为多路复用中最高效的I/O,epoll有着select和poll都不具有的很多能力。

不同于poll和select,epoll它用三个函数来实现多路复用这一个功能。


    #include        int epoll_create(int size);       //用于创建一个epoll模式的存储空间,返回值是一个文件描述符,后面和函数中       //都会用到这个epoll_fd。               int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);       //epoll_ctl用于添加一个事件到epfd中,op表示方式有EPOLL_ADD,EPOLL_DEL       //EPOLL_MOD方式,fd表示你要添加进去的文件描述符,后面是一个结构体指针,       //结构体在下面会说到。             int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);       //epoll_wait用于等待事件的发生,这个结构体指针会储存返回来的fd,maxevents       //表示最大能够接收到的fd的个数,注意能接收到的fd的个数很多(查阅一些资料这个数据       //在1G内存的机子上大约能有10万余个)。                             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,events是用来描述触发状态,           //可以设置为EPOLLIN,EPOLLOUT,EPOLLD等。           //联合体中我们关注fd和*ptr因为联合体有时候会有bug产生,如果我们用str来           //存放读取的数据的时候,我们可以让这个ptr指向一个结构体,结构体中设置           //fd和*buf参数。


epoll之所以比之前的多路复用高效主要原因有以下几个

1>:epoll的组织方式,它是以两个一个很高效的结构体红黑树,list链表

红黑树用于存放fd,一旦有事件发生它能够以O(1)的时间复杂度找到并且

把它放到list中,这样发回值就是这个event结构体指针,它里面存放的

便是发生事件的fd,从之前多路复用的轮训O(N),减少到O(1),可见它的

高效之处。

2>:触发方式:epoll可以使用两种触发方式来获取事件;下面会说到的水平

触发和边缘触发。使用边缘触发方式可以使epoll更加高效。



边缘触发和水平触发


水平触发PT:epoll_wait一旦fd中发生状态变化假设状态变化是read,并且如果没有读完,下次会继续提醒,直到把缓冲区fd中的数据读完为止。

边缘触发ET:epoll_wait当fd中状态发生变化时假设状态变化是read,它会在第一次提醒,如果没有把它读完则后面不会再提醒,除非有新的数据到来才会接着上次的往后面读。

在epoll_wait下要将套接字用fcntl函数设置为非阻塞,为什么要设置未非阻塞呢?想了好久的我终于发现,在一次ET中因为并不保证把缓冲区中的数据彻底读完,而阻塞模式下的sock是要保证把fd中的数据读完的,两者矛盾,会导致不接受新到来的fd。

ET需要用到的是自定义的read函数,如下所示:

 57 int read_fd(int sock,char *buf,int size) 58 { 59     int _size=-1; 60     int index=0; 61     while((_size=read(sock,buf+index,size-index))) 62     { 63         if(_size<0&&errno==EAGAIN) 64         { 65             break; 66         } 67         index+=_size; 68         _size=-1; 69     } 70     return index; 71 } //因为ET模式的特点必须保证一次把整个sock中的一次数据全部读完,不然如果没有下次的数据 //到来,前面没有读完的数据就会永久性的丢失了。 //当read读到sock中整个数据流的最末尾的时候会产生一个类似errno的信号EAGAIN告诉它已经 //读到了sock中的最后一个数据。



下面是一个epollET模式下的client与server

  1 #include  2 #include  3 #include  4 #include  5 #include  6 #include  7 #include  8 #include  9 #include 10 #include 11 #include 12  13 #define _MAX_LISTEN_ 6 14 #define _MAX_EPFD_ 64 15 #define _MAX_BUF_ 1024 16  17 void nonblock(int sock) 18 { 19     int fl=fcntl(sock,F_GETFL); 20     if(fl<0) 21     { 22         perror("fcntl"); 23         exit(2);  24     } 25     if(fcntl(sock,F_SETFL,fl|O_NONBLOCK)<0) 26     { 27         exit(3); 28     } 29 } 30 int Listensock(char *ip,int port) 31 { 32     int sock=socket(AF_INET,SOCK_STREAM,0); 33     if(sock<0) 34     { 35         perror("socket"); 36         exit(1); 37     } 38     nonblock(sock); 39     struct sockaddr_in local; 40     local.sin_addr.s_addr=inet_addr(ip); 41     local.sin_port=htons(port); 42  43     if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0) 44     { 45         perror("bind"); 46     } 47  48     if(listen(sock,_MAX_LISTEN_)<0) 49     { 50         perror("listen"); 51     }   } 52  53     return sock; 54  55 } 56  57 int read_fd(int sock,char *buf,int size) 58 { 59     int _size=-1; 60     int index=0; 61     while((_size=read(sock,buf+index,size-index))) 62     { 63         if(_size<0&&errno==EAGAIN) 64         { 65             break; 66         } 67         index+=_size; 68         _size=-1; 69     } 70     return index; 71 } 72 void epollserver(int sock) 73 { 74     int epfd=epoll_create(256); 75  76     if(epfd<0) 77     { 78         perror("epoll_create"); 79         exit(4); 80     } 81  82     struct epoll_event ev; 83     ev.data.fd=sock; 84     ev.events=EPOLLIN|EPOLLET; 85  86     if(epoll_ctl(epfd,EPOLL_CTL_ADD,sock,&ev)<0) 87     { 88         perror("epoll_ctl"); 89         exit(5); 90     } 91     struct epoll_event epfds[_MAX_EPFD_]; 92  93     int fd=-1; 94     int i; 95     for(i=0;i<_MAX_EPFD_;i++) 96     { 97         epfds[i].data.fd=fd; 98     } 99     int timeout=5000; 100  101     int fdlen=0; 102     while(1) 103     { 104         switch(fdlen=epoll_wait(epfd,epfds,_MAX_EPFD_,timeout)) 105         { 106             case -1: 107                 { 108                     perror("epoll_wait"); 109                     continue; 110                 } 111             case 0: 112                 { 113                     printf("timeout\n"); 114                 } 115             default: 116                 { 117                     struct sockaddr_in client; 118                     int client_len=sizeof(client); 119                     int i=0; 120                     for(i=0;i0)152                             {153                                 buf[ret-1]='\0';154                                 printf("client ::%s\n",buf);155                                 fflush(stdout);156                             }else if(ret==0){157                                 printf("ip=%s client is leave...\n",\158                                         inet_ntoa(client.sin_addr));159                             }else{160                                 //doing noting161                             }162                         }//else{此处可以改成回显}163                 }164             }165 166     }167 }168 169 int main(int argc,char *argv[])170 {171     if(argc!=3)     if(argc!=3)172     {173         printf("[%s][ip][port]\n",argv[0]);174     }175     char *ip=argv[1];176     int port=atoi(argv[2]);177     int sock=Listensock(ip,port);178 179     int opt=1;180 181     if(setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt))<0)182     {183         perror("setsockopt");184     }185 186     epollserver(sock);187 188     return 0;189 }





改为回显模式:

当读完client的数据后,可以将event改为EPOLLOUT然后将读到的数据保存起来

当下次写事件发生时,将保存的数据扔出去。

注意:因为结构体中的data是一个联合体,当我们存放完fd后再去存放ptr有可能

会有bug,这里的方法是让ptr指向一个结构体,这个结构体中保存着fd和buf。

typedef struct p_buf{      int fd;      char outbuf[_MAX_BUF_];}p_buf;//自定义的缓冲区


164                        else if (epfds[i].events&EPOLLIN)166                         {167                             ptrbuf *p_buf=(ptrbuf *)malloc(sizeof(ptrbuf));                                //将数据直接读到自定义的缓冲区中168                             memset(p_buf->outbuf,'\0',sizeof(p_buf->outbuf));169                             p_buf->fd=retfd;170                             int ret=read_fd(retfd,p_buf->outbuf,_MAX_BUF_);171 172                             if(ret>0)173                             {174                                 p_buf->outbuf[ret-1]='\0';175                                 printf("client ::%s\n",p_buf->outbuf);176                                 fflush(stdout);177                                 ev.events=EPOLLOUT|EPOLLET;178                                 ev.data.ptr=p_buf;                                      //设置为EPOLLOUT模式179                                 if(epoll_ctl(epfd,EPOLL_CTL_MOD,retfd,&ev)<0)180                                 {181                                     perror("epoll_ctl");182                                     continue;183                                 }184                             }else if(ret==0){185                                 if(epoll_ctl(epfd,EPOLL_CTL_DEL,retfd,NULL)<0)186                                 {187                                     perror("epoll_ctl");188                                 }189                                 printf("ip=%s client is leave...\n",\190                                         inet_ntoa(client.sin_addr));191                             }else{192                                 //doing noting193                             }194                         }else{//当写条件满足时,回显消息并且改回为EPOLLIN模式195                             ptrbuf* outptr=(ptrbuf*)epfds[i].data.ptr;196                             int outfd=outptr->fd;197                             outptr->outbuf[strlen(outptr->outbuf)]='\n';198                             out_write(outfd,outptr->outbuf,_MAX_BUF_);199                             free(outptr);200                             ev.events=EPOLLIN|EPOLLET;201                             ev.data.fd=outfd;202                             if(epoll_ctl(epfd,EPOLL_CTL_MOD,outfd,&ev)<0)203                             {204                                 perror("epoll_ctl");205                             }206                         }207                 }208             }209         }210     }211 }212                                                                     194,6-24      96%                                                                                                                                           183,8-32      81%


回显模式:



总结:

epoll对比之前的select和poll都有不小的改进,不用遍历整个buf,没有大小限制,并且有不同的模式可以选择,效率之高可想而知。






数据 结构 模式 事件 方式 多路 复用 状态 函数 缓冲区 联合体 边缘 变化 缓冲 联合 指针 水平 保证 阻塞 个数 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 湖北九亚互联网络科技有限公司 科技互联网企业 科技赋能下的互联网金融 互联网科技行业发展 新华互联网科技学院 万达互联网科技 左右互联网络科技 互联网大会黑科技排名 宝灵互联网科技有限公司 移动互联网科技公司 坤平互联网科技 深圳市罗太互联网科技有限公司 高科技互联网发展前景如何 滨湖区互联网智能科技产品有哪些 九思连强互联网电子科技有限公司 江苏互联网智能科技产品出厂价 米泉互联网科技专业怎么样 唐山双旺合互联网科技产业园 安徽零酒互联网科技有限公司 第三届互联网大会安防黑科技 广州为想互联网科技有限公司 没上市的科技互联网公司 北京互联网科技公司 奇酷互联网络科技 长亮科技 互联网金融 银川新华互联网科技学校联系电话 万物互联网智能科技 三亚科技创新互联网基地 优雇互联网科技有限公司 伍佰万互联网科技是干嘛的
0