学习使用epoll

epoll是Linux下多路复用IO接口select/poll的增强版本,它能显著减少程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。

 

 

一、epoll的优点

支持一个进程打开大数目的socket描述符

IO效率不随FD数目增加而线性下降

 

二、epoll的使用

epoll有2种工作方式:LT和ET。   

LT(level triggered,水平触发是缺省的工作方式,并且同时支持block和no-block socket.在这种做法中,

内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果你不作任操作,

内核还是会继续通知你的,所以,这种模式编程出错误可能性要小一点。传统的select/poll都是这种模型

的代表。   

 

ET (edge-triggered,边缘触发是高速工作方式,只支持no-block socket。在这种模式下,当描述符从未

就绪变为就绪时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个文

件描述符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了(比如,你在

发送,接收或者接收请求,或者发送接收的数据少于一定量时导致了一个EWOULDBLOCK 错误)。但是

请注意,如果一直不对这个fd作IO操作(从而导致它再次变成未就绪),内核不会发送更多的通知

(only once)

 

epoll相关的系统调用有3个:epoll_create, epoll_ctl和epoll_wait。在头文件<sys/epoll.h>

 

1. int epoll_create(int size);

创建一个epoll句柄,即图中的epfd, 用来监听事件, size用来告诉内核这个监听的数目一共有多大。

这个参数不同于select()中的第一个参数,给出最大监听的fd+1的值。需要注意的是,当创建好epoll句柄后,它就是会占用一个fd值,所以在使用完epoll后,必须调用close()关闭,否则可能导致fd被耗尽

 

2. int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

参数op是操作类型, 使用这个方法完成3种操作: 

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

(1) 注册新事件

C代码  复制代码  收藏代码
  1. struct epoll_event ev;   
  2. ev.data.fd = fd;   
  3. ev.events = EPOLLIN;   
  4. epoll_cntl(epfd, EPOOL_CTL_ADD, fd, &ev);  
  1. struct epoll_event ev;  
  2. ev.data.fd = fd;  
  3. ev.events = EPOLLIN;  
  4. epoll_cntl(epfd, EPOOL_CTL_ADD, fd, &ev);  
 

 

 

(2) 修改监听事件

 

C代码  复制代码  收藏代码
  1. struct epoll_event *a_event = get_a_event()    
  2. struct epoll_event ev;   
  3. ev.data.fd = a_event->data.fd;   
  4. ev.events = a_event->events | EPOLLOUT;   
  5. epoll_cntl(epfd, EPOOL_CTL_MOD, a_event->data.fd, &ev);  
  1. struct epoll_event *a_event = get_a_event()   
  2. struct epoll_event ev;  
  3. ev.data.fd = a_event->data.fd;  
  4. ev.events = a_event->events | EPOLLOUT;  
  5. epoll_cntl(epfd, EPOOL_CTL_MOD, a_event->data.fd, &ev);  
 

 

(3) 删除事件

 

C代码  复制代码  收藏代码
  1. struct epoll_event *a_event = get_a_event()    
  2. struct epoll_event ev;   
  3. ev.data.fd = a_event->data.fd;   
  4. epoll_cntl(epfd, EPOOL_CTL_DEL, a_event->data.fd, &ev);  
  1. struct epoll_event *a_event = get_a_event()   
  2. struct epoll_event ev;  
  3. ev.data.fd = a_event->data.fd;  
  4. epoll_cntl(epfd, EPOOL_CTL_DEL, a_event->data.fd, &ev);  
 

 

3种操作都使用了一个ev变量, 这个变量用来关联fd和它的监听事件, 是临时的, 可以反复使用.

ev是一个struct epoll_event结构体, 结构如下:

 

  1. typedef union epoll_data {  
  2.    void *ptr;  
  3.    int fd;  
  4.    __uint32_t u32;  
  5.    __uint64_t u64;  
  6. } epoll_data_t;  
  7.   
  8. struct epoll_event {  
  9.    __uint32_t events; /* Epoll events */  
  10.    epoll_data_t data; /* User data variable */  
  11. };  

 

events可以是以下几个宏的集合:
EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。

注意多个socket可以设置不同的触发模式
EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里


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

等待事件的产生, 把产生的事件存放到events数组里, 如图中所示, 调用epoll_wait后, 

fd 1和 fd 3和fd k产生了事件, 把它们分别存放到events[0], events[1], events[2]
参数epfdepoll_create()函数返回的epoll句柄。

参数eventsstruct epoll_event结构指针,用来从内核得到事件的集合

参数 maxevents内核这个events有多大

参数 timeout: 等待时的超时时间,以毫秒为单位。

返回值:成功时,返回需要处理的事件数目。调用失败时,返回0,表示等待超时。

 

 

三 epoll实例 -- 模拟HTTP服务器 

 

 

C代码  复制代码  收藏代码
  1. #include <sys/socket.h>   
  2. #include <sys/wait.h>   
  3. #include <netinet/in.h>   
  4. #include <netinet/tcp.h>   
  5. #include <sys/epoll.h>   
  6. #include <sys/sendfile.h>   
  7. #include <sys/stat.h>   
  8. #include <unistd.h>   
  9. #include <stdio.h>   
  10. #include <stdlib.h>   
  11. #include <string.h>   
  12. #include <strings.h>   
  13. #include <fcntl.h>   
  14. #include <errno.h>    
  15.   
  16. #define MAX_EVENTS 10   
  17. #define PORT 8080   
  18.   
  19. //设置socket连接为非阻塞模式   
  20. void setnonblocking(int sockfd) {   
  21.     int opts;   
  22.   
  23.     opts = fcntl(sockfd, F_GETFL);   
  24.     if(opts < 0) {   
  25.         perror("fcntl(F_GETFL)\n");   
  26.         exit(1);   
  27.     }   
  28.     opts = (opts | O_NONBLOCK);   
  29.     if(fcntl(sockfd, F_SETFL, opts) < 0) {   
  30.         perror("fcntl(F_SETFL)\n");   
  31.         exit(1);   
  32.     }   
  33. }   
  34.   
  35. int main(){   
  36.     struct epoll_event ev, events[MAX_EVENTS];   
  37.     int addrlen, listenfd, conn_sock, nfds, epfd, fd, i, nread, n;   
  38.     struct sockaddr_in local, remote;   
  39.     char buf[BUFSIZ];   
  40.   
  41.     //创建listen socket   
  42.     if( (listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {   
  43.         perror("sockfd\n");   
  44.         exit(1);   
  45.     }   
  46.     bzero(&local, sizeof(local));   
  47.     local.sin_family = AF_INET;   
  48.     local.sin_addr.s_addr = htonl(INADDR_ANY);;   
  49.     local.sin_port = htons(PORT);   
  50.     if( bind(listenfd, (struct sockaddr *) &local, sizeof(local)) < 0) {   
  51.         perror("bind\n");   
  52.         exit(1);   
  53.     }   
  54.     listen(listenfd, 20);   
  55.   
  56.     epfd = epoll_create(MAX_EVENTS);   
  57.     if (epfd == -1) {   
  58.         perror("epoll_create");   
  59.         exit(EXIT_FAILURE);   
  60.     }   
  61.   
  62.     ev.events = EPOLLIN;   
  63.     ev.data.fd = listenfd;   
  64.     if (epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &ev) == -1) {   
  65.         perror("epoll_ctl: listen_sock");   
  66.         exit(EXIT_FAILURE);   
  67.     }   
  68.   
  69.     for (;;) {   
  70.         nfds = epoll_wait(epfd, events, MAX_EVENTS, -1);   
  71.         if (nfds == -1) {   
  72.             perror("epoll_pwait");   
  73.             exit(EXIT_FAILURE);   
  74.         }   
  75.   
  76.         for (i = 0; i < nfds; ++i) {   
  77.             fd = events[i].data.fd;   
  78.             if (fd == listenfd) {   
  79.                 conn_sock = accept(listenfd,   
  80.                         (struct sockaddr *) &remote, &addrlen);   
  81.                 if (conn_sock == -1) {   
  82.                     perror("accept");   
  83.                     exit(EXIT_FAILURE);   
  84.                 }   
  85.                 setnonblocking(conn_sock);   
  86.                 ev.events = EPOLLIN | EPOLLET;   
  87.                 ev.data.fd = conn_sock;   
  88.                 if (epoll_ctl(epfd, EPOLL_CTL_ADD, conn_sock,   
  89.                             &ev) == -1) {   
  90.                     perror("epoll_ctl: add");   
  91.                     exit(EXIT_FAILURE);   
  92.                 }   
  93.                 continue;   
  94.             }     
  95.             if (events[i].events & EPOLLIN) {   
  96.                 n = 0;   
  97.                 while ((nread = read(fd, buf + n, BUFSIZ-1)) > 0) {   
  98.                     n += nread;   
  99.                 }   
  100.                 buf[n] = '\0';   
  101.   
  102.                 ev.data.fd = fd;   
  103.                 ev.events = events[i].events | EPOLLOUT;   
  104.                 if (epoll_ctl(epfd, EPOLL_CTL_MOD, fd, &ev) == -1) {   
  105.                     perror("epoll_ctl: mod");   
  106.                 }   
  107.             }   
  108.             if (events[i].events & EPOLLOUT) {   
  109.                 sprintf(buf, "HTTP/1.1 200 OK\r\nContent-Length: %d\r\n\r\nHello World", 11);   
  110.                 n = strlen(buf);   
  111.                 if (write(fd, buf, n) < n) {   
  112.                     perror("write");   
  113.                 }   
  114.                 close(fd);   
  115.             }   
  116.         }   
  117.     }   
  118.   
  119.     return 0;   
  120. }  
  1. #include <sys/socket.h>  
  2. #include <sys/wait.h>  
  3. #include <netinet/in.h>  
  4. #include <netinet/tcp.h>  
  5. #include <sys/epoll.h>  
  6. #include <sys/sendfile.h>  
  7. #include <sys/stat.h>  
  8. #include <unistd.h>  
  9. #include <stdio.h>  
  10. #include <stdlib.h>  
  11. #include <string.h>  
  12. #include <strings.h>  
  13. #include <fcntl.h>  
  14. #include <errno.h>   
  15.   
  16. #define MAX_EVENTS 10  
  17. #define PORT 8080  
  18.   
  19. //设置socket连接为非阻塞模式  
  20. void setnonblocking(int sockfd) {  
  21.     int opts;  
  22.   
  23.     opts = fcntl(sockfd, F_GETFL);  
  24.     if(opts < 0) {  
  25.         perror("fcntl(F_GETFL)\n");  
  26.         exit(1);  
  27.     }  
  28.     opts = (opts | O_NONBLOCK);  
  29.     if(fcntl(sockfd, F_SETFL, opts) < 0) {  
  30.         perror("fcntl(F_SETFL)\n");  
  31.         exit(1);  
  32.     }  
  33. }  
  34.   
  35. int main(){  
  36.     struct epoll_event ev, events[MAX_EVENTS];  
  37.     int addrlen, listenfd, conn_sock, nfds, epfd, fd, i, nread, n;  
  38.     struct sockaddr_in local, remote;  
  39.     char buf[BUFSIZ];  
  40.   
  41.     //创建listen socket  
  42.     if( (listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {  
  43.         perror("sockfd\n");  
  44.         exit(1);  
  45.     }  
  46.     bzero(&local, sizeof(local));  
  47.     local.sin_family = AF_INET;  
  48.     local.sin_addr.s_addr = htonl(INADDR_ANY);;  
  49.     local.sin_port = htons(PORT);  
  50.     if( bind(listenfd, (struct sockaddr *) &local, sizeof(local)) < 0) {  
  51.         perror("bind\n");  
  52.         exit(1);  
  53.     }  
  54.     listen(listenfd, 20);  
  55.   
  56.     epfd = epoll_create(MAX_EVENTS);  
  57.     if (epfd == -1) {  
  58.         perror("epoll_create");  
  59.         exit(EXIT_FAILURE);  
  60.     }  
  61.   
  62.     ev.events = EPOLLIN;  
  63.     ev.data.fd = listenfd;  
  64.     if (epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &ev) == -1) {  
  65.         perror("epoll_ctl: listen_sock");  
  66.         exit(EXIT_FAILURE);  
  67.     }  
  68.   
  69.     for (;;) {  
  70.         nfds = epoll_wait(epfd, events, MAX_EVENTS, -1);  
  71.         if (nfds == -1) {  
  72.             perror("epoll_pwait");  
  73.             exit(EXIT_FAILURE);  
  74.         }  
  75.   
  76.         for (i = 0; i < nfds; ++i) {  
  77.             fd = events[i].data.fd;  
  78.             if (fd == listenfd) {  
  79.                 conn_sock = accept(listenfd,  
  80.                         (struct sockaddr *) &remote, &addrlen);  
  81.                 if (conn_sock == -1) {  
  82.                     perror("accept");  
  83.                     exit(EXIT_FAILURE);  
  84.                 }  
  85.                 setnonblocking(conn_sock);  
  86.                 ev.events = EPOLLIN | EPOLLET;  
  87.                 ev.data.fd = conn_sock;  
  88.                 if (epoll_ctl(epfd, EPOLL_CTL_ADD, conn_sock,  
  89.                             &ev) == -1) {  
  90.                     perror("epoll_ctl: add");  
  91.                     exit(EXIT_FAILURE);  
  92.                 }  
  93.                 continue;  
  94.             }    
  95.             if (events[i].events & EPOLLIN) {  
  96.                 n = 0;  
  97.                 while ((nread = read(fd, buf + n, BUFSIZ-1)) > 0) {  
  98.                     n += nread;  
  99.                 }  
  100.                 buf[n] = '\0';  
  101.   
  102.                 ev.data.fd = fd;  
  103.                 ev.events = events[i].events | EPOLLOUT;  
  104.                 if (epoll_ctl(epfd, EPOLL_CTL_MOD, fd, &ev) == -1) {  
  105.                     perror("epoll_ctl: mod");  
  106.                 }  
  107.             }  
  108.             if (events[i].events & EPOLLOUT) {  
  109.                 sprintf(buf, "HTTP/1.1 200 OK\r\nContent-Length: %d\r\n\r\nHello World", 11);  
  110.                 n = strlen(buf);  
  111.                 if (write(fd, buf, n) < n) {  
  112.                     perror("write");  
  113.                 }  
  114.                 close(fd);  
  115.             }  
  116.         }  
  117.     }  
  118.   
  119.     return 0;  
  120. }  
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
epoll的优点 支持一个进程打开大数 目的socket描述符(FD) select 最不能忍受的是一个进程所打开的FD是有一定限制的,由FD_SETSIZE设置,默认值是2048。对于那些需要支持的上万连接数目的IM服务器来说显 然太少了。这时候你一是可以选择修改这个宏然后重新编译内核,不过资料也同时指出这样会带来网络效率的下降,二是可以选择多进程的解决方案(传统的 Apache方案),不过虽然linux上面创建进程的代价比较小,但仍旧是不可忽视的,加上进程间数据同步远比不上线程间同步的高效,所以也不是一种完 美的方案。不过 epoll则没有这个限制,它所支持的FD上限是最大可以打开文件的数目,这个数字一般远大于2048,举个例子,在1GB内存的机器上大约是10万左 右,具体数目可以cat /proc/sys/fs/file-max察看,一般来说这个数目和系统内存关系很大。 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之上了。 使用mmap加速内核 与用户空间的消息传递。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值