IO多路复用之epoll

IO多路复用之 epoll

epoll初识

select和poll会随着文件描述符数量的增多,而使其性能下降;但epoll不会。
所以为了解决select和poll的问题,引入了epoll。

epoll的相关系统调用

epoll有3个相关的系统调用。

epoll_create
int epoll_create(int size);  
  • 创建一个epoll的句柄,此处的句柄实际上就是文件描述符。因为epoll的生命周期随进程;而消息队列、共享内存中创建的句柄,不是文件描述符,因为消息队列和共享内存的生命周期是随内核的,即:进程退出,但资源还存在。而文件描述符会随着进程的退出而消失。
  • 自从linux2.6.8之后,size参数是被忽略的。但size还存在的原因是,要达到向前兼容的目的,以防之前写的代码不能用。
  • 调用完该函数后,记得调用 close() 关闭。
epoll_ctl
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

参数:

  • 第一个参数是epoll_create()的返回值(epoll的句柄);
  • 第二个参数表示要对文件描述符进行具体的什么样的操作,用三个宏来表示;
  • 第三个参数是需要监听的fd,表示要对该文件描述符进行操作。
  • 第四个参数告诉内核需要监听什么事。

第二个参数的取值:

  • EPOLL_CTL_ADD:注册新的fd到epfd中;
  • EPOLL_CTL_MOD:修改已经注册的fd的监听事件;
  • EPOLL_CTL_DEL:从epfd中删除一个fd.

struct epoll_event 结构体:

The struct epoll_event is defined as :

           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:表示对应的文件描述符可以读;
  • EPOLLOUT:表示对应的文件描述符可以写;
  • EPOLLPRI:表示对应的文件描述符有紧急的数据可以读;
epoll_wait
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);

参数解释:

  • 参数events是分配好的epoll_event结构体数组;
  • epoll将会把发生的事件赋值到events数组中(events不可以是空指针,内核只负责把数据复制到)
  • 参数timeout 是超时时间(毫秒,0表示立即返回,-1表示永久阻塞);
  • 如果函数调用成功,返回对应IO上已经准备好的文件描述符数目;如果返回0,表示已超时;如果返回值小于0,表示函数失败。
epoll 工作原理
  • 每⼀个epoll对象都有⼀个独⽴的eventpoll结构体,⽤于存放通过epoll_ctl⽅法向epoll对象中添加进来的事件.
    • 这些事件都会挂载在红⿊树中,如此,重复添加的事件就可以通过红⿊树⽽⾼效的识别出来(红⿊树的插⼊时间效率是lgn,其中n为树的⾼度).
    • ⽽所有添加到epoll中的事件都会与设备(网卡)驱动程序建⽴回调关系,也就是说,当响应的事件发⽣时会调⽤这个回调⽅法.
    • 这个回调⽅法在内核中叫eppollcallback,它会将发⽣的事件添加到rdlist双链表中.
    • 在epoll中,对于每⼀个事件,都会建⽴⼀个epitem结构体。
    • 当调⽤epoll_wait检查是否有事件发⽣时,只需要检查eventpoll对象中的rdlist双链表中是否有epitem元素即可.
    • 如果rdlist不为空,则把发⽣的事件复制到⽤户态,同时将事件数量返回给⽤户. 这个操作的时间复杂度是O(1)。
epoll的使用步骤:
  1. 调用 epoll_create 创建一个epoll句柄;
  2. 调用 epoll_ctl ,将要监控的文件描述符进行注册;
  3. 调用 epoll_wait,等待文件描述符就绪。
epoll 的优点
  • 从接口角度上讲:不必每次都重新设置文件描述符,使接口使用更加方便,也能够避免频繁地用户态和内核态之间拷贝文件描述符的结构;
  • 基于事件的就绪通知方式:一旦被监听的某个文件描述符就绪,内核就会采取回调机制(而select和poll采取的是轮询机制),迅速激活这个文件描述符。这样随着文件描述符数量的增加,也不会影响就绪的性能;
  • 维护就绪队列:当文件描述符就绪,就会被放到内核中的就绪队列中。这样调用epoll_wait获取就绪文件描述符时,就可以直接从这个就绪队列中取,操作的时间复杂度位O(1),相比于select和poll的时间复杂度为O(N).
  • 文件描述符无上限:通过 epoll_ctl() 来注册一个文件描述符,内核中通过红黑树来管理所有需要监控的文件描述符。

注意:struct epoll_event 是我们在用户空间中分配好的内存,所以还是要将内核的数据拷贝到这个用户空间的内存空间中。

epoll 工作方式

这里写图片描述
水平触发方式(LT)
再次调用 epoll_wait,epoll_wait 就会立即返回,提示还有同一个未读完的文件描述符就绪。

边缘触发方式(ET)
再次调用 epoll_wait,epoll_wait 就不会返回,此时缓冲区中剩下的1K数据就不能立刻被读取到。就需要等到对应的socket再次收到数据触发读就绪,才有机会将之前残留的1K数据读出来。

注:

  • epoll 默认的方式是水平触发。通过设置选项EPOLLET标记,才可以将epoll设置成边缘触发。
  • select 和 poll 只有水平触发。
  • 边缘触发要求我们一旦文件描述符就绪,必须把缓冲区中所有数据的读出来,以防后续数据丢失。所以,边缘触发的效率高,也将此方式称为高速模式。
  • 边缘触发(ET)要将fd设置成非阻塞轮询的方式进行操作。
epoll 的使用场景

对于多连接,且多连接中只有一部分连接比较活跃时,才适合使用epoll。
例如:
典型的一个需要处理上万个客户端的服务器,例如各种互联网APP的入口服务器,这样的服务器就很适合epoll。

epoll 示例:epoll 服务器
  1 /////////////////////////////////////////////////////////                                                                        
  2 // 基于 epoll 实现一个 TCP 服务器(回显服务器)                                                                                  
  3 ////////////////////////////////////////////////////////                                                                         
  4 #include <stdio.h>                                                                                                               
  5 #include <string.h>                                                                                                              
  6 #include <stdlib.h>                                                                                                              
  7 #include <unistd.h>                                                                                                              
  8 #include <sys/socket.h>                                                                                                          
  9 #include <sys/epoll.h>                                                                                                           
 10 #include <netinet/in.h>                                                                                                          
 11 #include <arpa/inet.h>                                                                                                           
 12                                                                                                                                  
 13 typedef struct sockaddr sockaddr;                                                                                                
 14 typedef struct sockaddr_in sockaddr_in;                                                                                          
 15 typedef struct epoll_event epoll_event;                                                                                          
 16                                                                                                                                  
 17 int ServerInit(const char* ip, short port)                                                                                       
 18 {                                                                                                                                
 19   // 1.创建 socket                                                                                                               
 20   int listen_sock = socket(AF_INET, SOCK_STREAM, 0);                                                                             
 21   if(listen_sock < 0)  
 22   {                                                                                                                              
 23     perror("socket");                                                                                                            
 24     return -1;                                                                                                                   
 25   }                                                                                                                              
 26   // 2.绑定端口号                                                                                                                
 27   sockaddr_in addr;                                                                                                              
 28   addr.sin_family = AF_INET;                                                                                                     
 29   addr.sin_addr.s_addr = inet_addr(ip);                                                                                          
 30   addr.sin_port = htons(port);                                                                                                   
 31   int ret = bind(listen_sock, (sockaddr*)&addr, sizeof(addr));                                                                   
 32   if(ret < 0)                                                                                                                    
 33   {                                                                                                                              
 34     perror("bind");                                                                                                              
 35     return -1;                                                                                                                   
 36   }                                                                                                                              
 37   // 3.监听                                                                                                                      
 38   ret = listen(listen_sock, 5);                                                                                                  
 39   if(ret < 0)                                                                                                                    
 40   {                                                                                                                              
 41     perror("listen");                                                                                                            
 42     return -1;     
  43   }                                                                                                                              
 44   return listen_sock;                                                                                                            
 45 }                                                                                                                                
 46                                                                                                                                  
 47 void ProcessListenSock(int epoll_fd, int listen_sock)                                                                            
 48 {                                                                                                                                
 49   // 1.调用 accpet                                                                                                               
 50   sockaddr_in peer;                                                                                                              
 51   socklen_t len = sizeof(peer);                                                                                                  
 52   int new_sock = accept(listen_sock, (sockaddr*)&peer, &len);                                                                    
 53   if(new_sock < 0)                                                                                                               
 54   {                                                                                                                              
 55     perror("accept");                                                                                                            
 56     return;                                                                                                                      
 57   }                                                                                                                              
 58   // 2.把 new_sock 加入到 epoll 之中                                                                                             
 59   epoll_event event;                                                                                                             
 60   event.events = EPOLLIN;                                                                                                        
 61   // 一定要记得带上这里的文件描述符!                                                                                            
 62   event.data.fd = new_sock;                                                                                                      
 63   int ret = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, new_sock, &event);    
 64   if(ret < 0)                                                                                                                    
 65   {                                                                                                                              
 66     perror("epoll_ctl ADD");                                                                                                     
 67     return;                                                                                                                      
 68   }                                                                                                                              
 69   printf("[client %d] connected!\n",new_sock);                                                                                   
 70   return;                                                                                                                        
 71 }                                                                                                                                
 72                                                                                                                                  
 73 void ProcessNewSock(int epoll_fd, int new_sock )                                                                                 
 74 {                                                                                                                                
 75   // 1.进行读写数据                                                                                                              
 76   char buf[1024]={0};                                                                                                            
 77   ssize_t read_size = read(new_sock, buf, sizeof(buf)-1);                                                                        
 78   if(read_size < 0)                                                                                                              
 79   {                                                                                                                              
 80     perror("read");                                                                                                              
 81     return;                                                                                                                      
 82   }                                                                                                                              
 83   if(read_size ==0)                                                                                                              
 84   {         
  85     // 2.一旦读到返回值为0,对端关闭了文件描述符。                                                                               
 86     //  本端也应该关闭文件描述符。并且,也应该把                                                                                 
 87     //  文件描述符从epoll之中删除掉。                                                                                            
 88     close(new_sock);                                                                                                             
 89     epoll_ctl(epoll_fd, EPOLL_CTL_DEL, new_sock, NULL);                                                                          
 90     printf("[client %d] disconnected!\n", new_sock);                                                                             
 91     return;                                                                                                                      
 92   }                                                                                                                              
 93   buf[read_size]='\0';                                                                                                           
 94   printf("[client %d] say: %s\n", new_sock, buf);                                                                                
 95   write(new_sock, buf, strlen(buf));                                                                                             
 96   return;                                                                                                                        
 97 }                                                                                                                                
 98 int main(int argc, char* argv[])                                                                                                 
 99 {                                                                                                                                
100   if(argc != 3)                                                                                                                  
101   {                                                                                                                              
102     printf("Usage /.server_epoll [ip] [port]\n");                                                                                
103     return 1;                                                                                                                    
104   }                                                                                                                              
105
106   // 1.创建并初始化 socket                                                                                                       
107   int listen_sock = ServerInit(argv[1], atoi(argv[2]));                                                                          
108   if(listen_sock < 0)                                                                                                            
109   {                                                                                                                              
110     printf("ServerInit failed\n");                                                                                               
111     return 1;                                                                                                                    
112   }                                                                                                                              
113                                                                                                                                  
114   // 2.创建并初始化 epoll 对象                                                                                                   
115   //   把 listen_sock 放到 epoll 对象之中                                                                                        
116   int epoll_fd = epoll_create(10);                                                                                               
117   if(epoll_fd < 0)                                                                                                               
118   {                                                                                                                              
119     perror("epoll_create");                                                                                                      
120     return 1;                                                                                                                    
121   }                                                                                                                              
122   epoll_event event;                                                                                                             
123   event.events = EPOLLIN;                                                                                                        
124   // 由于epoll_event 结构体在返回时,只返回了值,                                                                                
125   // 而没有返回具体的文件描述符的种类(即键值对的键),                                                                          
126   // 所以,为了后续操作中的需要,将文件描述符手动保存到   
127   // epoll_event 结构体的data.fd中。                                                                                             
128   event.data.fd = listen_sock;                                                                                                   
129   int ret = epoll_ctl(epoll_fd, EPOLL_CTL_ADD,                                                                                   
130                 listen_sock, &event);                                                                                            
131   if(ret < 0)                                                                                                                    
132   {                                                                                                                              
133     perror("epoll_ctl");                                                                                                         
134     return 1;                                                                                                                    
135   }                                                                                                                              
136   // 服务器初始化完成                                                                                                            
137   printf("ServerInit OK\n");                                                                                                     
138                                                                                                                                  
139   // 3.进入循环                                                                                                                  
140   while(1)                                                                                                                       
141   {                                                                                                                              
142     epoll_event output_event[100];                                                                                               
143     int nfds = epoll_wait(epoll_fd, output_event, 100, -1);                                                                      
144     if(nfds < 0)                                                                                                                 
145     {                                                                                                                            
146       perror("epoll_wait");                                                                                                      
147       continue;     
148     }                                                                                                                            
149     // epoll_wait 返回之后,都有哪些文件描述符就绪,                                                                             
150     // 就写到了 output_event 中了。遍历该缓冲区,由于                                                                            
151     // epoll 适用场景:同一时刻有大量的客户端连接,但活跃的数量比较少                                                            
152     // 所以缓冲区里就绪的文件描述符较少,所以遍历该缓冲区时,效率也是很高的。                                                    
153     int i=0;                                                                                                                     
154     for(; i < nfds; ++i)                                                                                                         
155     {                                                                                                                            
156       // 根据就绪的文件描述符的类别,分情况讨论                                                                                  
157       if(listen_sock == output_event[i].data.fd)                                                                                 
158       {                                                                                                                          
159         // a) listen_sock 就绪,调用accept                                                                                       
160         ProcessListenSock(epoll_fd, listen_sock);                                                                                
161       }                                                                                                                          
162       else                                                                                                                       
163       {                                                                                                                          
164         // b) new_sock 就绪,调用一次read/write                                                                                  
165         ProcessNewSock(epoll_fd, output_event[i].data.fd);                                                                       
166       } // end if                                                                                                                
167     } // end for                                                                                                                 
168   } // end while(1)      
169                                                                                                                                  
170   return 0;                                                                                                                      
171 }              
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值