epoll 是 Linux 特有的 I/O 复用函数。它在实现和使用上与 select、poll 有很大差异。
首先,epoll 使用一组函数来完成任务,而不是单个函数。其次,epoll 把用户关心的文件描述符上的事件放在内核里的一个事件表中。从而无需像select 和 poll 那样每次调用都要重复传入文件描述符或事件集。但 epoll 需要使用一个额外的文件描述符,来唯一标识内核中的这个事件表。
◼ epoll_create()用于创建内核事件表;
◼ epoll_ctl()用于操作内核事件表;
◼ epoll_wait()用于在一段超时时间内等待一组文件描述符上的事件;
读事件 EPOLLIN
写事件 EPOLLOUT
写事件,一开始就是就绪的,
发送缓冲区为空,send() 不会阻塞;
接受缓冲区有数据才能recv(), 可能会阻塞。
ET (epoll )高效模式 边沿触发
LT (select 、poll 、epoll ) 水平触发
LT模式是默认的工作模式,这种模式下的epoll相当于一个效率较高的poll。
当往内核事件表中注册一个文件描述符上的EPOLLET事件,epoll将以ET模式来操作该文件描述符。ET模式是epoll的高效工作模式。
ET和LT的区别:
对于采用LT工作模式的文件描述符,当epoll_wait 检测到其上有事件发生并将此事件通知应用程序后,应用程序可以不立即处理该事件。这样,当应用程序下一次调用epoll_wait时,epoll_wait还会再次向应用程序通告此事件,直到该事件被处理。
对于采用ET工作模式的文件描述符,当epoll _wait 检测到其上有事件发生并将此事件通知应用程序后,应用程序必须立即处理该事件,因为后续的epoll _wait 调用将不会向应用程序通知这一事件。
可见,ET模式在很大程度上降低了同一个opoll事件被重复触发的次数,因此效率要比LT模式高。
(以一次只读一个数据为例)
1 #include<stdio.h>
2 #include<stdlib.h>
3 #include<unistd.h>
4 #include<string.h>
5 #include<assert.h>
6 #include<sys/socket.h>
7 #include<netinet/in.h>
8 #include<arpa/inet.h>
9 #include<sys/epoll.h>
10
11 #define MAX 10
12
13 void epoll_add(int epfd,int fd)//向内核事件表添加文件描述符
14 {
15 struct epoll_event ev;
16 ev.data.fd=fd;
17 ev.events=EPOLLIN;
18 if(epoll_ctl(epfd,EPOLL_CTL_ADD,fd,&ev)==-1)
19 {
20 perror("epoll ctl add err");
21 }
22 }
23 void epoll_del(int epfd,int fd)//移除文件描述符
24 {
25 if(epoll_ctl(epfd,EPOLL_CTL_DEL,fd,NULL)==-1)
26 {
27 perror("epoll ctl del err");
28 }
29 }
30
31 int socket_init();//创建监听套介子
32 int main()
33 {
34 int sockfd=socket_init();
35 assert(sockfd!=-1);
36
37 int epfd=epoll_create(MAX);//创建内核事件表--红黑树
38 assert(epfd!=-1);
39
40 epoll_add(epfd,sockfd);//将sockfd添加到内核事件表中
42
43 struct epoll_event evs[MAX];//收集就绪事件
44
45 while(1)
46 {
47 int n=epoll_wait(epfd,evs,MAX,5000);
48 if(n==-1)
49 {
50 continue;
51 }
52 else if(n==0)
53 {
54 printf("time out\n");
55 continue;
56 }
57 else
58 {
59 for(int i=0;i<n;i++)
60 {
61 int fd=evs[i].data.fd;
62 if(evs[i].events&EPOLLIN)//读事件
63 {
64 if(fd==sockfd)//客户端连接我了 accept
65 {
66
67 struct sockaddr_in caddr;
68 int len=sizeof(caddr);
69 int c=accept(fd,(struct sockaddr*)&caddr,&len);
70 if(c<0)
71 {
72 continue;
73 }
74 printf("accept c=%d\n",c);
75 epoll_add(epfd,c);
76 }
77 else//客户端给我发数据了 recv
78 {
79 char buff[128]={0};
80 int num=recv(fd,buff,1,0);
81 if(num<=0)//客户端关闭了
82 {
83 epoll_del(epfd,fd); //先移除,后关闭
84 close(fd);
85 printf("cilent close\n");
86 }
87 else
88 {
89 printf("buff=%s\n",buff);
90 send(fd,"ok",2,0);
91 }
92 }
93 }
94 }
95 }
96 }
97 }
98 int socket_init()
99 {
100 int sockfd=socket(AF_INET,SOCK_STREAM,0);//创建套接子
101 if(sockfd==-1)
102 {
103 return -1;
104 }
105
106 struct sockaddr_in saddr;
107 memset(&saddr,0,sizeof(saddr));
108 saddr.sin_family=AF_INET;
109 saddr.sin_port=htons(6000);
110 saddr.sin_addr.s_addr=inet_addr("127.0.0.1");
111
112 int res=bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
113 if(res==-1)
114 {
115 return -1;
116 }
117
118 if(listen(sockfd,5)==-1)
119 {
120 return -1;
121 }
122 return sockfd;
123 }
(以一次只读一个数据为例)
当往内核事件表中注册一个文件描述符上的EPOLLET事件,epoll将以ET模式来操作该文件描述符。
数据还在缓冲区中,数据没有读完,因为没有后续事件所以会被阻塞。
1 #include<stdio.h>
2 #include<stdlib.h>
3 #include<unistd.h>
4 #include<string.h>
5 #include<assert.h>
6 #include<sys/socket.h>
7 #include<netinet/in.h>
8 #include<arpa/inet.h>
9 #include<sys/epoll.h>
10 #include<fcntl.h>
11 #include<errno.h>
12
13 #define MAX 10
14
15 void setnonblock(int fd)//将文件描述符设置为非阻塞
16 {
17 int oldfl=fcntl(fd,F_GETFL);
18 int newfl=oldfl |O_NONBLOCK;
19 if(fcntl(fd,F_SETFL,newfl)==-1)
20 {
21 printf("fcntl error\n");
22 }
23 }
24 void epoll_add(int epfd,int fd)//向内核事件表添加文件描述符
25 {
26 struct epoll_event ev;
27 ev.data.fd=fd;
28 ev.events=EPOLLIN|EPOLLET;
29
30 setnonblock(fd);//设置非阻塞
31
32 if(epoll_ctl(epfd,EPOLL_CTL_ADD,fd,&ev)==-1)
33 {
34 perror("epoll ctl add err");
35 }
36 }
37 void epoll_del(int epfd,int fd)//移除文件描述符
38 {
39 if(epoll_ctl(epfd,EPOLL_CTL_DEL,fd,NULL)==-1)
40 {
41 perror("epoll ctl del err");
42 }
43 }
44
45 int socket_init();//创建监听套介子
46 int main()
47 {
48 int sockfd=socket_init();
49 assert(sockfd!=-1);
50
51 int epfd=epoll_create(MAX);//创建内核事件表--红黑树
52 assert(epfd!=-1);
53
54 epoll_add(epfd,sockfd);//将sockfd添加到内核事件表中
55
57 struct epoll_event evs[MAX];//收集就绪事件
58 while(1)
60 {
61 int n=epoll_wait(epfd,evs,MAX,5000);
62 if(n==-1)
63 {
64 continue;
65 }
66 else if(n==0)
67 {
68 printf("time out\n");
69 continue;
70 }
71 else
72 {
73 for(int i=0;i<n;i++)
74 {
75 int fd=evs[i].data.fd;
76 if(evs[i].events&EPOLLIN)//读事件
77 {
78 if(fd==sockfd)//客户端连接我了 accept
79 {
80
81 struct sockaddr_in caddr;
82 int len=sizeof(caddr);
83 int c=accept(fd,(struct sockaddr*)&caddr,&len);
84 if(c<0)
85 {
86 continue;
87 }
88 printf("accept c=%d\n",c);
89 epoll_add(epfd,c);
90 }
91 else//客户端给我发数据了 recv
92 {
93 while(1)
94 //这段代码不会被重复触发,所以我们循环读取数据,把缓冲区中的数据读完
95 {
96 char buff[128]={0};
97 int num=recv(fd,buff,1,0);
98 if(num==-1)
99 {
100 if(errno==EAGAIN||errno==EWOULDBLOCK)
101 {
102 send(fd,"ok",2,0);
103 }
104 else
105 {
106 printf("recv error\n");
107 }
108 break;
109 }
110 else if(num==0)//客户端关闭了
111 {
112 epoll_del(epfd,fd); //先移除,后关闭
113 close(fd);
114 printf("cilent close\n");
115 break;
116 }
117 else
118 {
119 printf("buff=%s\n",buff);
120 }
121 }
122 }
123 }
124 }
125 }
126 }
127 }
-- 插入 --