千家信息网

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

发表于:2025-01-20 作者:千家信息网编辑
千家信息网最后更新 2025年01月20日,多路复用之epoll作为多路复用中最高效的I/O,epoll有着select和poll都不具有的很多能力。不同于poll和select,epoll它用三个函数来实现多路复用这一个功能。 #inc
千家信息网最后更新 2025年01月20日高级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,没有大小限制,并且有不同的模式可以选择,效率之高可想而知。






0