为处理大批量句柄而做了改进的poll
epoll解决了poll,select实现不了的问题
epoll相关的系统调用
1.创建一个epoll的句柄

size可以被忽略,用完之后必须调用close()关闭
2.epoll的事件注册函数

不同于select()是在监听事件时告诉内核要监听什么类型的事件,而是先注册要监听的事件类型
epfd:epoll_create的返回值(epoll的句柄)
op:表示动作,用3个宏来表示
fd:需要监听的文件描述符
event:告诉内核需要监听什么事
op的取值:EPOLL_CTL_ADD--注册新的fd到epfd中
EPOLL_CTL_MOD--修改已经注册的fd的监听事件
EPOLL_CTL_DEL--从epfd中删除一个fd
event可以是以下几个宏的集合:
EPOLLIN:表示对应的文件描述符可以读 (包括对端SOCKET正常关闭);
EPOLLOUT : 表⽰对应的⽂件描述符可以写;
EPOLLPRI : 表⽰对应的⽂件描述符有紧急的数据可读 (这⾥应该表⽰有带外数据到来);
EPOLLERR : 表⽰对应的⽂件描述符发⽣错误;
EPOLLHUP : 表⽰对应的⽂件描述符被挂断;
EPOLLET : 将EPOLL设为边缘触发(Edge Triggered)模式, 这是相对于⽔平触发(Level Triggered) 来说的. EPOLLONESHOT:只监听⼀次事件, 当监听完这次事件之后, 如果还需要继续监听这个socket的话, 需要再次把
这个socket加入到EPOLL队列里
3.收集在epoll监控的事件中已经发送的事件

参数events是分配好的epoll_event结构体数组.
epoll将会把发⽣的事件赋值到events数组中 (events不可以是空指针,内核只负责把数据复制到这 个events数组中,不会 去去帮助我们在⽤户态中分配内存).
maxevents告知内核这个events有多⼤,这个maxevents的值不能⼤于创建epoll_create()时的size.
参数timeout是超时时间 (毫秒,0会⽴即返回,-1是永久阻塞).
如果函数调⽤成功,返回对应I/O上已准备好的⽂件描述符数目,如返回0表⽰已超时, 返回⼩于0表示函数失败.
epoll的使用过程
1.调用epoll_create创建一个epoll句柄
2.调用epoll_ctl,将要监控的文件描述符进行注册
3.调用epoll_wait,等待文件描述符就绪
epoll的优点
1.文件描述符无上限
2.基于事件的就绪通知方式
3.维护就绪队列
4.内存映射机制
直到Linux2.6才出现了由内核直接支持的实现方法,那就是epoll,它几乎具备了之前所说的一切优点,被公认为Linux2.6下性能最好的多路I/O就绪通知方法。
epoll可以同时支持水平触发和边缘触发(Edge Triggered,只告诉进程哪些文件描述符刚刚变为就绪状态,它只说一遍,如果我们没有采取行动,那么它将不会再次告知,这种方式称为边缘触发),理论上边缘触发的性能要更高一些,但是代码实现相当复杂。
epoll同样只告知那些就绪的文件描述符,而且当我们调用epoll_wait()获得就绪文件描述符时,返回的不是实际的描述符,而是一个代表就绪描述符数量的值,你只需要去epoll指定的一个数组中依次取得相应数量的文件描述符即可,这里也使用了内存映射(mmap)技术,这样便彻底省掉了这些文件描述符在系统调用时复制的开销。
另一个本质的改进在于epoll采用基于事件的就绪通知方式。在select/poll中,进程只有在调用一定的方法后,内核才对所有监视的文件描述符进行扫描,而epoll事先通过epoll_ctl()来注册一个文件描述符,一旦基于某个文件描述符就绪时,内核会采用类似callback的回调机制,迅速激活这个文件描述符,当进程调用epoll_wait()时便得到通知。
epoll的两种工作方式(水平触发LT和边缘触发ET)
默认模式就是LT模式
当epoll检测到socket上事件就绪的时候,可以不立即处理或者只处理一部分,直到缓冲区上的所有数据被处理完,epoll_wait才不会立即返回。支持阻塞读写和非阻塞读写。
边缘触发ET模式
在第一步将socket添加到epoll描述符的时候使用EPOLLET标志,epoll进入ET工作模式。
1)当epoll检测到socket上事件就绪时,必须立刻处理
2)在ET模式下,文件描述符上的事件就绪后,只有一次处理机会
3)ET的性能比LT高
4)支持非阻塞的读写
select和poll都在LT模式下,epoll可以在LT模式下,也可在ET模式下
epoll的使用场景
在一定的场景下使用,如果在不适宜的场景下使用效果可能适得其反
对于多连接,且多连接只有一部分连接比较活跃时,比较适合用epoll
编程实例:epoll服务器
水平触发--LT模式
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <unistd.h>
4 #include <sys/socket.h>
5 #include <sys/epoll.h>
6 #include <netinet/in.h>
7 #include <arpa/inet.h>
8
9 int main(int argc,char * argv[])
10 {
11 if(argc!=3)
12 {
13 printf("Usage ./sever [ip] [port]\n");
14 return 1;
15 }
16
17 struct sockaddr_in addr;
18 addr.sin_family=AF_INET;
19 addr.sin_addr.s_addr=inet_addr(argv[1]);
20 addr.sin_port=htons(atoi(argv[2]));
21
22 int sock=socket(AF_INET,SOCK_STREAM,0);
23
24 printf("24 socket%d\n",sock);
25 if(sock<0)
26 {
27 perror("socket");
28 return 1;
29 }
30
31 int ret=bind(sock,(struct sockaddr*)&addr,sizeof(addr));
32 if(ret<0){
33 perror("bind");
34 return 1;
35 }
36
37 ret=listen(sock,5);
38 if(ret<0){
39 perror("listen");
40 return 1;
41 }
42 //创建epoll句柄
43 int epoll_fd=epoll_create(10);
44 if(epoll_fd<0)
45 {
46 perror("epoll_create");
47 return 1;
48 }
49
50 struct epoll_event event;
51 event.events=EPOLLIN;
52 event.data.fd=sock;
53
54 ret=epoll_ctl(epoll_fd,EPOLL_CTL_ADD,sock,&event);
55 if(ret<0)
56 {
57 perror("epoll_ctl");
58 return 1;
59 }
60
61 printf("59 socket%d\n",sock);
62 while(1)
63 {
64 struct epoll_event events[10];
65 //参数-1表示一直阻塞,直到状态改变
66 ret=epoll_wait(epoll_fd,events,sizeof(events)/sizeof(events[0]),-1);
67 printf("65 socket%d\n",sock);
68 if(ret<0){
69 perror("epoll_wait");
70 continue;
71 }
72 if(ret==0){
73 printf("time out!\n");
74 continue;
75 }
76 int i=0;
77 for(;i<ret;i++)
78 {
79 //当前的文件描述符不可读
80 if(!(events[i].events&EPOLLIN))
81 {
82 continue;
83 }
84 //socket就绪
85 if(events[i].data.fd==sock)
86 {
87 printf("enter socket ready\n");
88 struct sockaddr_in client;
89 socklen_t len=sizeof(client);
90 int connect_fd=accept(sock,(struct sockaddr*)&client,&len);
91 if(connect_fd<0)
92 {
93 perror("accept");
94 return;
95 }
96 printf("client %s:%d\n",inet_ntoa(client.sin_addr),ntohs(client.sin_port));
97 struct epoll_event env;
98 env.events=EPOLLIN;
99 env.data.fd=connect_fd;
100 int res=epoll_ctl(epoll_fd,EPOLL_CTL_ADD,connect_fd,&env);
101 if(res<0)
102 {
103 perror("epoll_ctl");
104 return 1;
105 }
106 printf("104 socket%d\n",sock);
107 }
108 else//处理connent
109 {
110 printf("enter read ready!\n");
111 char buf[1024]={0};
112 printf("socket:%d\n",sock);
113 ssize_t read_size=read(events[i].data.fd,buf,sizeof(buf)-1);
114 if(read_size<0){
115 perror("read");
116 return 1;
117 }
118 if(read_size==0)
119 {
120 close(events[i].data.fd);
121 epoll_ctl(epoll_fd,EPOLL_CTL_DEL,sock,NULL);
122 printf("read done!\n");
123 continue;
124 }
125 printf("client say:%s\n",buf);
126 write(events[i].data.fd,buf,strlen(buf));
127 }
128 }
129 }
130 return 0;
131 }
本文深入探讨了epoll的工作原理及优势,详细介绍了epoll的系统调用,并提供了水平触发模式下的epoll服务器编程实例。
1786

被折叠的 条评论
为什么被折叠?



