I/O多路复用之epoll

在上一章,咱们对select进行了大体的描述,知道了它相对传统的阻塞式服务提升了并发度,可是它也因为轮询而致使效率底下。本文对epoll进行讲解,相比select它的并发度更高,现代高负载服务器不少都采用这种模型。
在讲解epoll的具体用法以前,咱们先看看采用 epoll模型主要用到的三个函数以及一个数据结构
epoll中三个主要的函数:
(1)int epoll_create(int size);
功能 :生成一个epoll专用的文件描述符。
参数 :size:在该epoll fd上关注的最大socket fd数。
返回值:生成的文件描述符。
(2)int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
功能 :控制某个epoll文件描述符上的事件,能够注册事件,修改事件,删除事件。
参数 :epfd :由 epoll_create 生成的epoll专用的文件描述符;
op :EPOLL_CTL_ADD 注册、EPOLL_CTL_MOD 修 改、EPOLL_CTL_DEL 删除;
fd :关联的文件描述符;
event:指向epoll_event的指针;
返回值:0:成功;
-1:失败;
(3)int epoll_wait(int epfd,struct epoll_event * events,int maxevents,int timeout);
功能 :轮询I/O事件的发生。
参数 :epfd :由 epoll_create 生成的epoll专用的文件描述符;
events :用于回传待处理事件的数组;
maxevents:每次能处理的事件数;
timeout :等待I/O事件发生的超时值;-1至关于阻塞,0至关于非阻塞;
返回值:>=0 :返回发生事件数;
-1 :错误;

epoll中的主要数据结构:
01 typedef union epoll_data {
02 void *ptr;
03 int fd;
04 __uint32_t u32;
05 __uint64_t u64;
06 } epoll_data_t;
07
08 struct epoll_event {
09 __uint32_t events; /* Epoll events */
10 epoll_data_t data; /* User data variable */
11 };

其中,events的类型有:
EPOLLIN :文件描述符能够读;
EPOLLOUT:文件描述符能够写;
EPOLLPRI:文件描述符有紧急的数据可读;
EPOLLERR:文件描述符发生错误;
EPOLLHUP:文件描述符被挂断;
EPOLLET :文件描述符有事件发生;
epoll的使用仍是很简单的,请看下面一个简单的采用epoll提供并发服务的服务端程序(注:为了简洁,都没有进行错误处理,实际使用时,必定要记住进行错误处理。):
01 #include <errno.h>
02 #include <string.h>
03 #include <sys/types.h>
04 #include <netinet/in.h>
05 #include <sys/socket.h>
06 #include <sys/wait.h>
07 #include <unistd.h>
08 #include <arpa/inet.h>
09 #include <sys/epoll.h>
10 #include <sys/time.h>
11
12 #define MAXBUF 1024
13 #define MAX_EPOLL_SIZE 10000
14 #define SERVICE_PORT 8888
15
16
17 int main(int argc, char **argv)
18 {
19 int server_fd, new_fd;
20 struct sockaddr_in server_addr, client_addr;
21
22 struct epoll_event ev;
23 struct epoll_event events[MAX_EPOLL_SIZE];
24
25 socklen_t len = sizeof(struct sockaddr_in);
26 server_fd = socket(AF_INET, SOCK_STREAM, 0);
27
28 bzero(&server_addr, sizeof(server_addr));
29 server_addr.sin_family = AF_INET;
30 server_addr.sin_port = htons(SERVICE_PORT);
31 server_addr.sin_addr.s_addr = INADDR_ANY;
32
33 bind(server_fd, (struct sockaddr *) &server_addr, sizeof(struct sockaddr));
34 listen(server_fd, 1000);
35
36 //create epoll fd, and register the server listening fd
37 int epoll_fd = epoll_create(MAX_EPOLL_SIZE);
38 ev.events = EPOLLIN | EPOLLET;
39 ev.data.fd = server_fd;
40 epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_fd, &ev);
41
42 int active_fd_count = 1;
43 while (1)
44 {
45 //wait for some events to happen
46 int event_active_fd_count = epoll_wait(epoll_fd, events, active_fd_count, -1);
47
48 // process all events
49 for (int i = 0; i < event_active_fd_count; ++i)
50 {
51 if (events[i].data.fd == server_fd)
52 {
53 new_fd = accept(server_fd, (struct sockaddr *) &client_addr,&len);
54
55 //register new fd to epoll
56 ev.events = EPOLLIN | EPOLLET;
57 ev.data.fd = new_fd;
58 epoll_ctl(epoll_fd, EPOLL_CTL_ADD, new_fd, &ev);
59 active_fd_count++;
60 }
61 else
62 {
63 handle message on events[i].data.fd
64 if (client close the connection)
65 {
66 epoll_ctl(epoll_fd, EPOLL_CTL_DEL, events[i].data.fd,&ev);
67 active_fd_count--;
68 }
69 }
70 }
71 }
72 close(server_fd);
73 return 0;
74 }

讲完epoll的常规使用方法,这里须要注意的是epoll有两种工做方式:
(1)ET:Edge Triggered,边缘触发。仅当状态发生变化时才会通知,须要细致的处理每一个请求,不然容易发生丢失事件的状况。只支持非阻塞的socket。
(2)LT:Level Triggered,水平触发(默认工做方式)。只要还有没有处理的事件就会一直通知,所以不用担忧事件丢失的状况。效率会低于ET触发,尤为在大并发,大流量的状况下。支持阻塞和非阻塞的socket。

最后讲讲 为何epoll会比select高效,主要从三方面来进行论述。
(1)elect对描述符状态的改变是经过轮询来进行查找的;而epoll是当描述符状态发生改变时主动进行通知内核,这就是所谓的Reactor事件处理机制。能够用“好莱坞原则”进行描述:不要打电话给咱们,咱们会打电话通知你。相比之下,select的机制就比如面试结束后不停给面试官打电话询问面试结果。效率孰高孰低,可见一 斑。
(2)select的文件描述符是使用链表进行组织的;而epoll是使用红黑树这一高效数据结构组织的。
(3)select从内核到用户空间传递文件描述符上发送的信息是使用内存复制的方式进行的;而epoll是采用共享内存的方式。
相关文章
相关标签/搜索