linux高性能服务器编程之epoll

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了linux高性能服务器编程之epoll相关的知识,希望对你有一定的参考价值。



一.概述:

epoll是多路复用的一种,但它比select和poll更加高效。具体体现在以下几个方面:

(1).select能打开的文件描述符是有一定限制的,默认情况下是2048,这对应那些大型服务器来说h是不足的。但 epoll则没有这个限制,它所支持的fd上限是最大可以打开文件的数目,这个数字一般远大于2048,举个例子,在1GB内存的机器上大约是10万左 右,具体数目可以cat  /proc/sys/fs/file-max察看,一般来说这个数目和系统内存关系很大。

(2).因为文件描述符是内核管理的,所以每次调用select或poll,都需要把fd集合从用户态拷贝到内核态,并且每次检查文件描述符的状态时,都要在内核遍历所有文件描述符,这个开销在fd很多时会很大。而epoll采用了nmap(内存映射)(和共享内存一样),内核和用户空间共用一行份fd集。

(3).另一个本质的改进在于epoll采用基于事件的就绪通知方式。在select/poll中,进程只有在调用一定的方法后,内核才对所有监视的文件描述符进行扫描,而epoll事先通过epoll_ctl()来注册一个文件描述符,一旦基于某个文件描述符就绪时,内核会采用类似callback的回调机制,迅速激活这个文件描述符,当进程调用epoll_wait()时便得到通知。



其它优化:

(4).epoll会给所有要关注的文件描述符建立一个红黑树,这样在查找某一个文件描述符时效率会有所提升。

(5).epoll会给准备好的文件描述符建立一个链表,这样查找一个已准备好的文件描述符时就不用在以前所有要关注的fd集中查找了。




二.epoll用法篇:

epoll是什么?按照man手册的说法:是为处理大批量句柄而作了改进的poll。当然,这不是2.6内核才有的,它是在2.5.44内核中被引进的(epoll(4) is a new API introduced in Linux kernel2.5.44),它几乎具备了之前所说select和poll的一切优点,被公认为Linux2.6下性能最好的多路I/O就绪通知方法。

epoll有epoll_create,epoll_ctl,epoll_wait 3个系统调用。



(1).epoll_create:

int   epoll_create(int size);

创建一个epoll的句柄(后面会根据这个句柄创建红黑树)。自从linux2.6.8之后,size参数是被忽略的(也就是说,可以为任意值)。需要注意的是,当创建好epoll句柄后,它就是会占用一个fd值,在linux下如果查看/proc/进程id/fd/,是能够看到这个fd的,所以在使用完epoll后,必须调用close()关闭,否则可能导致fd被耗尽。

返回值:成功返回一个epoll句柄,失败返回-1;


(2).epoll_ctl

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

函数描述:epoll的事件注册函数,它不同于select()是在监听事件时告诉内核要监听什么类型的事件,而是在这里先注册要监听的事件类型。(一旦基于某个文件描述符就绪时,内核会采用类似callback的回调机制,迅速激活这个文件描述符,当进程调用epoll_wait()时便得到通知。)

返回值:成功返回0,失败返回-1;

epfd参数:epoll_create创建的一个epoll句柄。

op参数:表示要执行的动作,用三个宏来表示:
EPOLL_CTL_ADD:注册新的fd到epfd中;
EPOLL_CTL_MOD:修改已经注册的fd的监听事件;
EPOLL_CTL_DEL:从epfd中删除一个fd;

fd参数:需要监听的文件描述符。

event参数:告诉内核需要监听什么事。struct epoll_event结构如下:

 The event argument describes the object linked to the file descriptor fd.  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 :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);

EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示示有带外数据到来);
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET:将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水水平触发(Level
Triggered)来说的。(epoll默认为水平触发)

EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里。



(3).epoll_wait:

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

函数功能:监听在epoll监控的事件中已经发送的事件。

返回值:成功返回监听文件描述符集中已经准备好的文件描述符,返回0代表timeout,失败返回-1。

epoll参数:epoll_create创建的epoll句柄。

events参数:输出型参数,保存监听文件描述符集中已经准备好的文件描述符集。

maxevents参数:events数组的大小。

timeout参数:超时时间。单位为毫秒。




三.LT模式下的阻塞模式。

相关代码:

server.c:

 1 /****************************************                                                                                                 
  2     > File Name:epoll_server.c
  3     > Author:xiaoxiaohui
  4     > mail:[email protected]
  5     > Created Time:2016年05月28日 星期六 15时38分17秒
  6 ****************************************/
  7 
  8 #include<stdio.h>
  9 #include<stdlib.h>
 10 #include<sys/types.h>
 11 #include<sys/socket.h>
 12 #include<arpa/inet.h>
 13 #include<netinet/in.h>
 14 #include<string.h>
 15 #include<unistd.h>
 16 #include<sys/epoll.h>
 17 
 18 #define LEN 1024地
 19 const char* IP = "127.0.0.1";
 20 const int PORT = 8080;
 21 const int BACKLOG = 5;
 22 int timeout = 5000;
 23 const int MAXEVENTS = 64;
 24 struct sockaddr_in local;
 25 struct sockaddr_in clien153                 }
154                 break;
155         }
156     }
157 }
158 
159 int main()
160 {
161     int listenSock = ListenSock();
162     epoll_fd(listenSock);
163     close(listenSock);
164     return 0;
165 }                                t;
 26 int SIZE_CLIENT = sizeof(client);
 27 
 28 typedef struct data_buf     //用于存储epoll_event中的data中的不同元素
 29 {
 30     int fd;
 31     char buf[LEN];
 32 }data_buf_t, *data_buf_p;地
 33 
 34 
 35 int ListenSock()
 36 {
 37     int listenSock = socket(AF_INET, SOCK_STREAM, 0);
 38     if(listenSock < 0)
 39     {
 40         perror("socket");
 41         exit(1);
 42     }
 43 
 44     local.sin_family = AF_INET;
 45     local.sin_port = htons(PORT);
 46     local.sin_addr.s_addr = inet_addr(IP);
 47     if( bind(listenSock, (struct sockaddr*)&local, sizeof(local)) < 0)
 48     {
 49         perror("bind");
 50         exit(2);
 51     }
 52 
 53     if( listen(listenSock, BACKLOG) < 0)
 54     {
 55         perror("listen");
 56         exit(3);
 57     }
 58 
 59     return listenSock;
 60 }
 61 
 62 static int epoll_fd(int listenSock)
 63 {
 64 
 65     int epoll_fd = epoll_create(256);           //size随便选一个值
 66 
 67     struct epoll_event ev;          //把listenSock设置进epoll_fd中
 68     ev.events = EPOLLIN;
 69     ev.data.fd = listenSock;
 70     epoll_ctl(epoll_fd, EPOLL_CTL_ADD, listenSock, &ev);        //系统会维护一个红黑树
 71 
 72     struct epoll_event ev_outs[MAXEVENTS];      //准备好的队列
 73     int max = MAXEVENTS;
 74     
 75     while(1)                                                                                                                              
 76     {
 77         int num = -1;
 78         switch( num = epoll_wait(epoll_fd, ev_outs, max, timeout))
 79         {
 80             case 0:     //timeout                                                                                                         
 81                 printf("timeout.....\n");
 82                 break;
 83             case -1:    //error
 84                 perror("epoll_wait");
 85                 break;
 86             default:
 87                 for(int index = 0; index < num; index++)   
 88                 {
 89                     if(ev_outs[index].data.fd == listenSock && (ev_outs[index].events & EPOLLIN))      //监听套接字准备就绪
 90                     {
 91                         printf("accept is ready\n");
 92                         int linkSock = accept(listenSock, (s地truct sockaddr*)&client, &SIZE_CLIENT);
 93                         if(linkSock < 0)
 94                         {
 95                             perror("accept");
 96                             continue;           //这次可能是一个新客户端的请求,所以后面可能还有文件描述符准备就绪了
 97                         }地
 98 
 99                         ev.events = EPOLLIN;     //把新套接字放到红黑树中
100                         ev.data.fd = linkSock;
101                         epoll_ctl(epoll_fd, EPOLL_CTL_ADD, linkSock, &ev);
102                     }
103                     else         //已链接套接字准备就绪
104                     {
105                         if(ev_outs[index].events & EPOLLIN)      //读事件准备就绪
106                         {
107                             data_buf_p mem = (data_buf_p)malloc(sizeof(data_buf_t));
108                             memset(mem->buf, ‘\0‘, sizeof(mem->buf));
109                             mem->fd = ev_outs[index].data.fd;
110 
111                             int ret = read(mem->fd, mem->buf, sizeof(mem->buf));
112                             if(ret > 0)
113                             {
114                                 mem->buf[ret] = ‘\0‘;
115                                 printf("client# %s\n", mem->buf);
116                                                                                                                                           
117                                 ev.data.ptr = mem;         //mem中即保持了fd,又保持了buf数据
118                                 ev.events = EPOLLOUT;    //读事件已经完成,现在要关心写事件
119                                 epoll_ctl(epoll_fd, EPOLL_CTL_MOD, mem->fd, &ev);
120                             }
121                             else if(ret == 0 )      //客户端已关闭
122                             {
123                                 printf("client is closed\n");
124                                 epoll_ctl(epoll_fd, EPOLL_CTL_DEL, ev_outs[index].data.fd, NULL);     //把该文件描述符从红黑树中移除
125                                 close(ev_outs[index].data.fd);
126                                 free(mem);
127                             }
128                             else
129                             {
130                                 perror("read");
131                                 continue;
132                             }
133                         }
134                         else if(ev_outs[index].events & EPOLLOUT)   //写事件准备就绪
135                         {
136                             data_buf_p mem = (data_buf_p)ev_outs[index].data.ptr;
137                             int fd = mem->fd;
138                             char* buf = mem->buf;
139 
140                             if( write(fd, buf, strlen(buf)) < 0)
141                             {
142                                 perror("write");
143                                 continue;
144                             }
145 
146                             ev.events = EPOLLIN;       //这个文件描述符的写事件已完成,下次关心读事件
147                             ev.data.fd = mem->fd;
148                             epoll_ctl(epoll_fd, EPOLL_CTL_MOD, fd, &ev);
149                         }
150                         else   //DoNothing
151                         {}
152                     }
153                 }
154                 break;
155         }
156     }
157 }
158 
159 int main()
160 {
161     int listen地Sock = ListenSock();
162     epoll_fd(listenSock);
163     close(listenSock);
164     return 0;
165 }


client.c:

1 /****************************************                                                                                                 
  2     > File Name:client.c
  3     > Author:xiaoxiaohui
  4     > mail:[email protected]
  5     > Created Time:2016年05月23日 星期一 12时30分01秒
  6 ****************************************/
  7 
  8 #include<stdio.h>
  9 #include<stdlib.h>
 10 #include<string.h>
 11 #include<sys/types.h>
 12 #include<sys/socket.h>
 13 #include<netinet/in.h>
 14 #include<arpa/inet.h>
 15 #include<sys/time.h>
 16 #include<unistd.h>
 17 
 18 #define LEN 1024
 19 const int PORT = 8080;
 20 const char* IP = "127.0.0.1";
 21 struct sockaddr_in server;
 22 int clientSock;
 23 char buf[LEN];
 24 
 25 int main()
 26 {
 27     clientSock = socket(AF_INET, SOCK_STREAM, 0);
 28     if(clientSock < 0)
 29     {
 30         perror("socket");
 31         exit(1);
 32     }
 33 
 34     server.sin_family = AF_INET;
 35     server.sin_addr.s_addr = inet_addr(IP);
 36     server.sin_port = htons(PORT);
 37 
 38     if ( connect(clientSock, (struct sockaddr*)&server, sizeof(server)) < 0)
 39     {
 40         perror("connect");
 41         exit(2);
 42     }
 43 
 44     while(1)
 45     {
 46         memset(buf, ‘\0‘, LEN);
 47         printf("please input: ");
 48         gets(buf);
 49         write(clientSock, buf, strlen(buf));
 50 
 51         memset(buf, ‘地\0‘, LEN);
 52         int ret = read(clientSock, buf, LEN);
 53         buf[ret] = ‘\0‘;
 54         printf("echo: %s\n", buf);  
 55     }
 56 
 57     return 0;
 58 }

执行结果:

技术分享


技术分享




四.ET模式下的非阻塞模式:

(1).概述:

ET模式要在epoll_ctl中进行设置(具体设置看代码),并且要把套接字设置为非阻塞模式。

下面概况以下ET与LT的区别:

ET (edge-triggered)是高效的工作方式,只支持no-block socket,它效率要比LT更高。ET与LT的区别在于,当一个新的事件到来时,ET模式下当然可以从epoll_wait调用中获取到这个事件,可是如果这次没有把这个事件对应的套接字缓冲区处理完,在这个套接字中没有新的事件再次到来时,在ET模式下是无法再次从epoll_wait调用中获取这个事件的。而LT模式下,只要一个事件对应的套接字缓冲区还有数据,就总能从epoll_wait中获取这个事件。
因此,LT模式下开发基于epoll的应用要简单些,不太容易出错。而在ET模式下事件发生时,如果没有彻底地将缓冲区数据处理完,则会导致缓冲区中的用户请求得不到响应。


所以在下面代码中要封装一个read_data函数,来确保一次把数据缓冲区内的数据读取完。因为ET模式下只支持非阻塞模式,所以还要把每个套接字设置为非阻塞的。


下面代码实现      在浏览器访问服务器程序,并在浏览器中打印hello world :)         ,当服务器程序发送完给浏览器的数据时,服务器程序关闭链接。

相关代码:

1 /****************************************                                                                                                 
  2     > File Name:epoll_server.c
  3     > Author:xiaoxiaohui
  4     > mail:[email protected]
  5     > Created Time:2016年05月28日 星期六 15时38分17秒
  6 ****************************************/
  7 
  8 #include<stdio.h>
  9 #include<stdlib.h>
 10 #include<sys/types.h>
 11 #include<sys/socket.h>
 12 #include<arpa/inet.h>
 13 #include<netinet/in.h>
 14 #include<string.h>
 15 #include<unistd.h>
 16 #include<sys/epoll.h>
 17 #include<fcntl.h>
 18 #include<errno.h>
 19 
 20 #define LEN 1024
 21 const char* IP = "127.0.0.1";
 22 const int PORT = 8080;
 23 const int BACKLOG = 5;
 24 int timeout = 5000;
 25 const int MAXEVENTS = 64;
 26 struct sockaddr_in local;
 27 struct sockaddr_in client;
 28 int SIZE_CLIENT = sizeof(client);
 29 
 30 typedef struct data_buf     //用于存储epoll_event中的data中的不同元素
 31 {
 32     int fd;
 33     char buf[LEN];
 34 }data_buf_t, *data_buf_p;
 35 
 36 static int set_no_block(int fd)       //把fd设置为非阻塞
 37 {
 38     int oldfd = fcntl(fd, F_GETFL);
 39     if(oldfd < 0)
 40     {
41         perror("fcntl");
 42         return -1;
 43     }
 44 
 45     if( fcntl(fd, F_SETFL, oldfd | O_NONBLOCK))
 46     {
 47         perror("fcntl");
 48         return -1;
 49     }
 50     return 0;
 51 }
 52 
 53 int read_data(int fd, char* buf, int len)      //ET模式下读取数据,因为ET模式下只通知一次,所以要保证把所有数据都读完
 54 {                                              //成功返回读取的个数,失败返回-1,返回0代表读到文件尾
 55     int index = 0;
 56     int ret = -1;
 57 
 58     while(index < len)
 59     {
 60         ret = read(fd, buf + index, len - index);
 61         printf("the read return ret is %d\n", ret);
 62         if(ret > 0)
 63         {
 64             index += ret;
 65         }
 66         else if(ret < 0)
 67         {
 68             printf("the errno is %d\n",errno);
 69             if(errno == EAGAIN)
 70             {
 71                 break;
 72             }
 73         }
 74         else
 75         {                                                                                                                                 
 76             return 0;
 77         }
 78     }
 79 
 80     return index;
 81 }                                                                                                                                         
 82 
 83 
 84 
 85 
 86 static int ListenSock()
 87 {
 88     int listenSock = socket(AF_INET, SOCK_STREAM, 0);
 89     if(listenSock < 0)
 90     {
 91         perror("socket");
 92         exit(1);
 93     }
 94 
 95     int opt = 1;
 96     if( setsockopt(listenSock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)))     //设置端口复用   
 97     {
 98         perror("sersockopt");
 99         exit(2);
100     }
101     if( set_no_block(listenSock) != 0)      //设置为非阻塞    .............修改
102     {
103         printf("set_non_block is error\n");
104         exit(3);
105     }
106 
107     local.sin_family = AF_INET;
108     local.sin_port = htons(PORT);
109     local.sin_addr.s_addr = inet_addr(IP);
110     if( bind(listenSock, (struct sockaddr*)&local, sizeof(local)) < 0)
111     {
112         perror("bind");
113         exit(4);
114     }
115 
116     if( listen(listenSock, BACKLOG) < 0)
117     {
118         perror("listen");
119         exit(5);
120     }
121                                                                                                                                           
122     return listenSock;
123 }
124 
125 static int epoll_fd(int listenSock)
126 {
127 
128     int epoll_fd = epoll_create(256);           //size随便选一个值
129 
130     struct epoll_event ev;          //把listenSock设置进epoll_fd中
131     ev.events = EPOLLIN | EPOLLET;    //....................修改
132     ev.data.fd = listenSock;
133     epoll_ctl(epoll_fd, EPOLL_CTL_ADD, listenSock, &ev);        //系统会维护一个红黑树
134 
135     struct epoll_event ev_outs[MAXEVENTS];      //准备好的队列
136     int max = MAXEVENTS;
137     
138     while(1)
139     {
140         int num = -1;
141         switch( num = epoll_wait(epoll_fd, ev_outs, max, timeout))
142         {
143             case 0:     //timeout
144                 printf("timeout.....\n");
145                 break;
146             case -1:    //error
147                 perror("epoll_wait");
148                 break;
149             default:
150                 for(int index = 0; index < num; index++)   
151                 {
152                     if(ev_outs[index].data.fd == listenSock && (ev_outs[index].events & EPOLLIN))      //监听套接字准备就绪
153                     {
154                         printf("accept is ready\n");
155                         int linkSock = accept(listenSock, (struct sockaddr*)&client, &SIZE_CLIENT);
156                         if(linkSock < 0)
157                         {
158                             perror("accept");
159                             continue;           //这次可能是一个新客户端的请求,所以后面可能还有文件描述符准备就绪了
160                         }
161                                                                                                                                           
162                         if( set_no_block(linkSock) != 0)      //设置为非阻塞    .............修改
163                         {
164                             printf("set_non_block is error\n");
165                             exit(3);
166                         }
167 
168                         ev.events = EPOLLIN | EPOLLET;     //把新套接字放到红黑树中   ...................修改
169                         ev.data.fd = linkSock;
170                         epoll_ctl(epoll_fd, EPOLL_CTL_ADD, linkSock, &ev);
171                     }
172                     else         //已链接套接字准备就绪
173                     {
174                         if(ev_outs[index].events & EPOLLIN)      //读事件准备就绪
175                         {
176                             data_buf_p mem = (data_buf_p)malloc(sizeof(data_buf_t));
177                             memset(mem->buf, ‘\0‘, sizeof(mem->buf));
178                             mem->fd = ev_outs[index].data.fd;
179 
180                             printf("read is ready\n");
181                             int ret = read_data(mem->fd, mem->buf, sizeof(mem->buf)); //.........................修改
182                             printf("read is over, the ret is %d\n", ret);
183                             if(ret > 0)
184                             {
185                                 mem->buf[ret] = ‘\0‘;
186                                 printf("client# %s\n", mem->buf);
187 
188                                 ev.data.ptr = mem;         //mem中即保持了fd,又保持了buf数据
189                                 ev.events = EPOLLOUT;    //读事件已经完成,现在要关心写事件
190                                 epoll_ctl(epoll_fd, EPOLL_CTL_MOD, mem->fd, &ev);
191                             }
192                             else if(ret == 0 )      //客户端已关闭
193                             {
194                                 printf("client is closed\n");
195                                 epoll_ctl(epoll_fd, EPOLL_CTL_DEL, ev_outs[index].data.fd, NULL);     //把该文件描述符从红黑树中移除
196                                 close(ev_outs[index].data.fd);
197                                 free(mem);
198                             }
199                             else
200                             {
201                                 perror("read");                                                                                           
202                                 continue;
203                             }
204                         }
205                         else if(ev_outs[index].events & EPOLLOUT)   //写事件准备就绪
206                         {
207                             data_buf_p mem = (data_buf_p)ev_outs[index].data.ptr;
208                             int fd = mem->fd;
209                             char* buf = mem->buf;
210 
211                             char *msg = "HTTP/1.0 200 OK\r\n\r\nhello world:)\r\n"; //.....................修改
212                             if( write(fd, msg, strlen(msg)) < 0)                //.........................修改
213                             {
214                                 perror("write")

以上是关于linux高性能服务器编程之epoll的主要内容,如果未能解决你的问题,请参考以下文章

Linux IO多路复用之epoll网络编程及源码(转)

Linux编程之Epoll高并发

socket编程:多路复用I/O服务端客户端之epoll

Linux网络编程——多路复用之epoll

linux服务器开发之epoll的封装类实现

linux网络编程 - epoll边沿触发/水平触发内核实现代码分析