epoll LT 模式和 ET 模式详解
一、简介
与 poll 的事件宏相比,epoll 新增了一个事件宏 EPOLLET,这就是所谓的边缘触发模式(Edge Trigger,ET),而默认的模式我们称为 水平触发模式(Level Trigger,LT)。这两种模式的区别在于:
- 对于水平触发模式,一个事件只要有,就会一直触发;
- 对于边缘触发模式,只有一个事件从无到有才会触发。
这两个词汇来自电学术语,你可以将 fd 上有数据认为是高电平,没有数据认为是低电平,将 fd 可写认为是高电平,fd 不可写认为是低电平。那么水平模式的触发条件是状态处于高电平,而边缘模式的触发条件是新来一次电信号将当前状态变为高电平,即:
水平模式的触发条件
-
- 1. 低电平 => 高电平
- 2. 处于高电平状态
边缘模式的触发条件
-
- 1. 低电平 => 高电平
说的有点抽象,以 socket 的读事件为例,对于水平模式,只要 socket 上有未读完的数据,就会一直产生 EPOLLIN 事件;而对于边缘模式,socket 上每新来一次数据就会触发一次,如果上一次触发后,未将 socket 上的数据读完,也不会再触发,除非再新来一次数据。对于 socket 写事件,如果 socket 的 TCP 窗口一直不饱和,会一直触发 EPOLLOUT 事件;而对于边缘模式,只会触发一次,除非 TCP 窗口由不饱和变成饱和再一次变成不饱和,才会再次触发 EPOLLOUT 事件。
二、触发条件
2.1 socket 可读事件水平模式触发条件
1、socket上无数据 => socket上有数据
2、socket处于有数据状态
2.2 socket 可读事件边沿模式触发条件
1、socket上无数据 => socket上有数据
2、socket又新来一次数据
2.3 socket 可写事件水平模式触发条件
1、socket可写=>socket可写
2、socket不可写=>socket可写
2.3 socket 可写事件边沿模式触发条件
1、socket不可写=>socket可写
三、使用
如果对于一个非阻塞 socket,如果使用 epoll 边缘模式去检测数据是否可读,触发可读事件以后,一定要一次性把 socket 上的数据收取干净才行,也就是说一定要循环调用 recv 函数直到 recv 出错,错误码是EWOULDBLOCK(EAGAIN 一样)(此时表示 socket 上本次数据已经读完);如果使用水平模式,则不用,你可以根据业务一次性收取固定的字节数,或者收完为止。边缘模式下收取数据的代码写法示例如下:
1 bool TcpSession::RecvEtMode()
2 {
3 //每次只收取256个字节
4 char buff[256];
5 while (true)
6 {
7 int nRecv = ::recv(clientfd_, buff, 256, 0);
8 if (nRecv == -1)
9 {
10 if (errno == EWOULDBLOCK)
11 return true;
12 else if (errno == EINTR)
13 continue;
14
15 return false;
16 }
17 //对端关闭了socket
18 else if (nRecv == 0)
19 return false;
20
21 inputBuffer_.add(buff, (size_t)nRecv);
22 }
23
24 return true;
25 }
下面我们来看几个具体的例子来比较一下 LT 模式与 ET 模式的区别。
先来测试一下 LT 模式 与 ET 模式在处理读事件上的区别。
代码如下:
1 /**
2 * 验证epoll的LT与ET模式的区别, epoll_server.cpp
3 * zhangyl 2019.04.01
4 */
5 #include<sys/types.h>
6 #include<sys/socket.h>
7 #include<arpa/inet.h>
8 #include<unistd.h>
9 #include<fcntl.h>
10 #include<sys/epoll.h>
11 #include<poll.h>
12 #include<iostream>
13 #include<string.h>
14 #include<vector>
15 #include<errno.h>
16 #include<iostream>
17
18 int main()
19 {
20 //创建一个监听socket
21 int listenfd = socket(AF_INET, SOCK_STREAM, 0);
22 if (listenfd == -1)
23 {
24 std::cout << "create listen socket error" << std::endl;
25 return -1;
26 }
27
28 //设置重用ip地址和端口号
29 int on = 1;
30 setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, (char*)&on, sizeof(on));
31 setsockopt(listenfd, SOL_SOCKET, SO_REUSEPORT, (char*)&on, sizeof(on));
32
33
34 //将监听socker设置为非阻塞的
35 int oldSocketFlag = fcntl(listenfd, F_GETFL, 0);
36 int newSocketFlag = oldSocketFlag | O_NONBLOCK;
37 if (fcntl(listenfd, F_SETFL, newSocketFlag) == -1)
38 {
39 close(listenfd);
40 std::cout << "set listenfd to nonblock error" << std::endl;
41 return -1;
42 }
43
44 //初始化服务器地址
45 struct sockaddr_in bindaddr;
46 bindaddr.sin_family = AF_INET;
47 bindaddr.sin_addr.s_addr = htonl(INADDR_ANY);
48 bindaddr.sin_port = htons(3000);
49
50 if (bind(listenfd, (struct sockaddr*)&bindaddr, sizeof(bindaddr)) == -1)
51 {
52 std::cout << "bind listen socker error." << std::endl;
53 close(listenfd);
54 return -1;
55 }
56
57 //启动监听
58 if (listen(listenfd, SOMAXCONN) == -1)
59 {
60 std::cout << "listen error." << std::endl;
61 close(listenfd);
62 return -1;
63 }
64
65
66 //创建epollfd
67 int epollfd = epoll_create(1);
68 if (epollfd == -1)
69 {
70 std::cout << "create epollfd error." << std::endl;
71 close(listenfd);
72 return -1;
73 }
74
75 epoll_event listen_fd_event;
76 listen_fd_event.data.fd = listenfd;
77 listen_fd_event.events = EPOLLIN;
78 //取消注释掉这一行,则使用ET模式
79 //listen_fd_event.events |= EPOLLET;
80
81 //将监听sokcet绑定到epollfd上去
82 if (epoll_ctl(epollfd, EPOLL_CTL_ADD, listenfd, &listen_fd_event) == -1)
83 {
84 std::cout << "epoll_ctl error" << std::endl;
85 close(listenfd);
86 return -1;
87 }
88
89 int n;
90 while (true)
91 {
92 epoll_event epoll_events[1024];
93 n = epoll_wait(epollfd, epoll_events, 1024, 1000);
94 if (n < 0)
95 {
96 //被信号中断
97 if (errno == EINTR)
98 continue;
99
100 //出错,退出
101 break;
102 }
103 else if (n == 0)
104 {
105 //超时,继续
106 continue;
107 }
108 for (size_t i = 0; i < n; ++i)
109 {
110 //事件可读
111 if (epoll_events[i].events & EPOLLIN)
112 {
113 if (epoll_events[i].data.fd == listenfd)
114 {
115 //侦听socket,接受新连接
116 struct sockaddr_in clientaddr;
117 socklen_t clientaddrlen = sizeof(clientaddr);
118 int clientfd = accept(listenfd, (struct sockaddr*)&clientaddr, &clientaddrlen);
119 if (clientfd != -1)
120 {
121 int oldSocketFlag = fcntl(clientfd, F_GETFL, 0);
122 int newSocketFlag = oldSocketFlag | O_NONBLOCK;
123 if (fcntl(clientfd, F_SETFD, newSocketFlag) == -1)
124 {
125 close(clientfd);
126 std::cout << "set clientfd to nonblocking error." << std::endl;
127 }
128 else
129 {
130 epoll_event client_fd_event;
131 client_fd_event.data.fd = clientfd;
132 client_fd_event.events = EPOLLIN;
133 //取消注释这一行,则使用ET模式
134 //client_fd_event.events |= EPOLLET;
135 if (epoll_ctl(epollfd, EPOLL_CTL_ADD, clientfd, &client_fd_event) != -1)
136 {
137 std::cout << "new client accepted,clientfd: " << clientfd << std::endl;
138 }
139 else
140 {
141 std::cout << "add client fd to epollfd error" << std::endl;
142 close(clientfd);
143 }
144 }
145 }
146 }
147 else
148 {
149 std::cout << "client fd: " << epoll_events[i].data.fd << " recv data." << std::endl;
150 //普通clientfd
151 char ch;
152 //每次只收一个字节
153 int m = recv(epoll_events[i].data.fd, &ch, 1, 0);
154 if (m == 0)
155 {
156 //对端关闭了连接,从epollfd上移除clientfd
157 if (epoll_ctl(epollfd, EPOLL_CTL_DEL, epoll_events[i].data.fd, NULL) != -1)
158 {
159 std::cout << "client disconnected,clientfd:" << epoll_events[i].data.fd << std::endl;
160 }
161 close(epoll_events[i].data.fd);
162 }
163 else if (m < 0)
164 {
165 //出错
166 if (errno != EWOULDBLOCK && errno != EINTR)
167 {
168 if (epoll_ctl(epollfd, EPOLL_CTL_DEL, epoll_events[i].data.fd, NULL) != -1)
169 {
170 std::cout << "client disconnected,clientfd:" << epoll_events[i].data.fd << std::endl;
171 }
172 close(epoll_events[i].data.fd);
173 }
174 }
175 else
176 {
177 //正常收到数据
178 std::cout << "recv from client:" << epoll_events[i].data.fd << ", " << ch << std::endl;
179 }
180 }
181 }
182 else if (epoll_events[i].events & EPOLLERR)
183 {
184 // TODO 暂不处理
185 }
186 }
187 }
188
189 close(listenfd);
190 return 0;
191 }
我们先来看水平模式的行为,将代码 79 行和 134 行注释掉则使用 LT 模式,我们编译下程序并运行:
1 [root@localhost testepoll]# g++ -g -o epoll_server epoll_server.cpp
2 [root@localhost testepoll]# ./epoll_server
然后再另外开启一个 shell 窗口,使用 nc 命令模拟一个客户端,连接服务器成功后,我们给服务器发送一个消息"abcef":
1 [root@localhost ~]# nc -v 127.0.0.1 3000
2 Ncat: Version 7.50 ( https://nmap.org/ncat )
3 Ncat: Connected to 127.0.0.1:3000.
4 abcdef
此时服务器端输出:
1 [root@localhost testepoll]# ./epoll_server
2 new client accepted,clientfd: 5
3 client fd: 5 recv data.
4 recv from client:5, a
5 client fd: 5 recv data.
6 recv from client:5, b
7 client fd: 5 recv data.
8 recv from client:5, c
9 client fd: 5 recv data.
10 recv from client:5, d
11 client fd: 5 recv data.
12 recv from client:5, e
13 client fd: 5 recv data.
14 recv from client:5, f
15 client fd: 5 recv data.
16 recv from client:5,
nc 命令实际发送了 a、b、c、d、e、f 和 \n 七个字符,由于服务器端使用的是 LT 模式,每次接收一个字符,只要 socket 接收缓冲区中仍有数据可读,POLLIN 事件就会一直触发,所以服务器一共有 7 次输出,直到 socket 接收缓冲区没有数据为止。
我们将代码 79 行和 134 行注释取消掉,使用 ET 模式再试一下,修改代码并重新编译,然后重新运行一下。再次使用 nc 命令模拟一个客户端连接后发送"abcef",服务器只会有一次输出,效果如下:
由于使用了 ET 模式,只会触发一次 POLLIN 事件,如果此时没有新数据到来,就再也不会触发。所以,如果我们继续给服务器发送一条新数据,如 123,服务器将再次触发一次 POLLIN 事件,然后打印出字母 b,效果如下:
所以如果使用 ET 模式 处理读事件,切记要将该次 socket 上的数据收完。
再来测试一下 LT 模式 与 ET 模式在处理写事件上的区别。
修改上述代码如下:
1 /** 2 * 验证epoll的LT与ET模式的区别, epoll_server.cpp 3 * zhangyl 2019.04.01 4 */ 5 #include<sys/types.h> 6 #include<sys/socket.h> 7 #include<arpa/inet.h> 8 #include<unistd.h> 9 #include<fcntl.h> 10 #include<sys/epoll.h> 11 #include<poll.h> 12 #include<iostream> 13 #include<string.h> 14 #include<vector> 15 #include<errno.h> 16 #include<iostream> 17 18 int main() 19 { 20 //创建一个监听socket 21 int listenfd = socket(AF_INET, SOCK_STREAM, 0); 22 if (listenfd == -1) 23 { 24 std::cout << "create listen socket error" << std::endl; 25 return -1; 26 } 27 28 //设置重用ip地址和端口号 29 int on = 1; 30 setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, (char*)& on, sizeof(on)); 31 setsockopt(listenfd, SOL_SOCKET, SO_REUSEPORT, (char*)& on, sizeof(on)); 32 33 34 //将监听socker设置为非阻塞的 35 int oldSocketFlag = fcntl(listenfd, F_GETFL, 0); 36 int newSocketFlag = oldSocketFlag | O_NONBLOCK; 37 if (fcntl(listenfd, F_SETFL, newSocketFlag) == -1) 38 { 39 close(listenfd); 40 std::cout << "set listenfd to nonblock error" << std::endl; 41 return -1; 42 } 43 44 //初始化服务器地址 45 struct sockaddr_in bindaddr; 46 bindaddr.sin_family = AF_INET; 47 bindaddr.sin_addr.s_addr = htonl(INADDR_ANY); 48 bindaddr.sin_port = htons(3000); 49 50 if (bind(listenfd, (struct sockaddr*) & bindaddr, sizeof(bindaddr)) == -1) 51 { 52 std::cout << "bind listen socker error." << std::endl; 53 close(listenfd); 54 return -1; 55 } 56 57 //启动监听 58 if (listen(listenfd, SOMAXCONN) == -1) 59 { 60 std::cout << "listen error." << std::endl; 61 close(listenfd); 62 return -1; 63 } 64 65 66 //创建epollfd 67 int epollfd = epoll_create(1); 68 if (epollfd == -1) 69 { 70 std::cout << "create epollfd error." << std::endl; 71 close(listenfd); 72 return -1; 73 } 74 75 epoll_event listen_fd_event; 76 listen_fd_event.data.fd = listenfd; 77 listen_fd_event.events = EPOLLIN; 78 //取消注释掉这一行,则使用ET模式 79 //listen_fd_event.events |= EPOLLET; 80 81 //将监听sokcet绑定到epollfd上去 82 if (epoll_ctl(epollfd, EPOLL_CTL_ADD, listenfd, &listen_fd_event) == -1) 83 { 84 std::cout << "epoll_ctl error" << std::endl; 85 close(listenfd); 86 return -1; 87 } 88 89 int n; 90 while (true) 91 { 92 epoll_event epoll_events[1024]; 93 n = epoll_wait(epollfd, epoll_events, 1024, 1000); 94 if (n < 0) 95 { 96 //被信号中断 97 if (errno == EINTR) 98 continue; 99 100 //出错,退出 101 break; 102 } 103 else if (n == 0) 104 { 105 //超时,继续 106 continue; 107 } 108 for (size_t i = 0; i < n; ++i) 109 { 110 //事件可读 111 if (epoll_events[i].events & EPOLLIN) 112 { 113 if (epoll_events[i].data.fd == listenfd) 114 { 115 //侦听socket,接受新连接 116 struct sockaddr_in clientaddr; 117 socklen_t clientaddrlen = sizeof(clientaddr); 118 int clientfd = accept(listenfd, (struct sockaddr*) & clientaddr, &clientaddrlen); 119 if (clientfd != -1) 120 { 121 int oldSocketFlag = fcntl(clientfd, F_GETFL, 0); 122 int newSocketFlag = oldSocketFlag | O_NONBLOCK; 123 if (fcntl(clientfd, F_SETFD, newSocketFlag) == -1) 124 { 125 close(clientfd); 126 std::cout << "set clientfd to nonblocking error." << std::endl; 127 } 128 else 129 { 130 epoll_event client_fd_event; 131 client_fd_event.data.fd = clientfd; 132 //同时侦听新来连接socket的读和写时间 133 client_fd_event.events = EPOLLIN | EPOLLOUT; 134 //取消注释这一行,则使用ET模式 135 //client_fd_event.events |= EPOLLET; 136 if (epoll_ctl(epollfd, EPOLL_CTL_ADD, clientfd, &client_fd_event) != -1) 137 { 138 std::cout << "new client accepted,clientfd: " << clientfd << std::endl; 139 } 140 else 141 { 142 std::cout << "add client fd to epollfd error" << std::endl; 143 close(clientfd); 144 } 145 } 146 } 147 } 148 else 149 { 150 std::cout << "client fd: " << epoll_events[i].data.fd << " recv data." << std::endl; 151 //普通clientfd 152 char recvbuf[1024] = { 0 }; 153 //每次只收一个字节 154 int m = recv(epoll_events[i].data.fd, recvbuf, 1024, 0); 155 if (m == 0) 156 { 157 //对端关闭了连接,从epollfd上移除clientfd 158 if (epoll_ctl(epollfd, EPOLL_CTL_DEL, epoll_events[i].data.fd, NULL) != -1) 159 { 160 std::cout << "client disconnected,clientfd:" << epoll_events[i].data.fd << std::endl; 161 } 162 close(epoll_events[i].data.fd); 163 } 164 else if (m < 0) 165 { 166 //出错 167 if (errno != EWOULDBLOCK && errno != EINTR) 168 { 169 if (epoll_ctl(epollfd, EPOLL_CTL_DEL, epoll_events[i].data.fd, NULL) != -1) 170 { 171 std::cout << "client disconnected,clientfd:" << epoll_events[i].data.fd << std::endl; 172 } 173 close(epoll_events[i].data.fd); 174 } 175 } 176 else 177 { 178 //正常收到数据 179 std::cout << "recv from client:" << epoll_events[i].data.fd << ", " << recvbuf << std::endl; 180 } 181 } 182 } 183 else if (epoll_events[i].events & EPOLLOUT) 184 { 185 //只处理客户端fd的可写事件 186 if (epoll_events[i].data.fd != listenfd) 187 { 188 //打印结果 189 std::cout << "EPOLLOUT triggered,clientfd:" << epoll_events[i].data.fd << std::endl; 190 } 191 } 192 else if (epoll_events[i].events & EPOLLERR) 193 { 194 // TODO 暂不处理 195 } 196 } 197 } 198 199 close(listenfd); 200 return 0; 201 }
上述代码中,我们对新来的连接 fd 同时注册读和写事件(代码 133 行),再次编译程序执行:
1 [root@iZ238vnojlyZ testepollet]# vi epoll.cpp
2 [root@iZ238vnojlyZ testepollet]# g++ -g -o epoll epoll_server.cpp
3 [root@iZ238vnojlyZ testepollet]# ./epoll_server
然后使用 nc 命令模拟一个客户端去连接 epoll_server:
1 [root@iZ238vnojlyZ ~]# nc -v 127.0.0.1 3000
2 Ncat: Version 6.40 ( http://nmap.org/ncat )
3 Ncat: Connected to 127.0.0.1:3000.
此时服务器端(epoll_server)会疯狂的输出可写事件触发消息:
之所以是这样,是因为我们注册了可写事件且使用的是 LT 模式,LT 模式下,由于这里的服务器端对应的客户端 fd 一直是可写的,有写事件一直触发,所以看到屏幕不断输出。
我们再将服务器端与客户端建立连接时新建的 fd 设置为 ET 模式再实验一下:
1 /**
2 * 验证epoll的LT与ET模式的区别, epoll_server.cpp
3 * zhangyl 2019.04.01
4 */
5 #include<sys/types.h>
6 #include<sys/socket.h>
7 #include<arpa/inet.h>
8 #include<unistd.h>
9 #include<fcntl.h>
10 #include<sys/epoll.h>
11 #include<poll.h>
12 #include<iostream>
13 #include<string.h>
14 #include<vector>
15 #include<errno.h>
16 #include<iostream>
17
18 int main()
19 {
20 //创建一个监听socket
21 int listenfd = socket(AF_INET, SOCK_STREAM, 0);
22 if (listenfd == -1)
23 {
24 std::cout << "create listen socket error" << std::endl;
25 return -1;
26 }
27
28 //设置重用ip地址和端口号
29 int on = 1;
30 setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, (char*)& on, sizeof(on));
31 setsockopt(listenfd, SOL_SOCKET, SO_REUSEPORT, (char*)& on, sizeof(on));
32
33
34 //将监听socker设置为非阻塞的
35 int oldSocketFlag = fcntl(listenfd, F_GETFL, 0);
36 int newSocketFlag = oldSocketFlag | O_NONBLOCK;
37 if (fcntl(listenfd, F_SETFL, newSocketFlag) == -1)
38 {
39 close(listenfd);
40 std::cout << "set listenfd to nonblock error" << std::endl;
41 return -1;
42 }
43
44 //初始化服务器地址
45 struct sockaddr_in bindaddr;
46 bindaddr.sin_family = AF_INET;
47 bindaddr.sin_addr.s_addr = htonl(INADDR_ANY);
48 bindaddr.sin_port = htons(3000);
49
50 if (bind(listenfd, (struct sockaddr*) & bindaddr, sizeof(bindaddr)) == -1)
51 {
52 std::cout << "bind listen socker error." << std::endl;
53 close(listenfd);
54 return -1;
55 }
56
57 //启动监听
58 if (listen(listenfd, SOMAXCONN) == -1)
59 {
60 std::cout << "listen error." << std::endl;
61 close(listenfd);
62 return -1;
63 }
64
65
66 //创建epollfd
67 int epollfd = epoll_create(1);
68 if (epollfd == -1)
69 {
70 std::cout << "create epollfd error." << std::endl;
71 close(listenfd);
72 return -1;
73 }
74
75 epoll_event listen_fd_event;
76 listen_fd_event.data.fd = listenfd;
77 listen_fd_event.events = EPOLLIN;
78 //取消注释掉这一行,则使用ET模式
79 //listen_fd_event.events |= EPOLLET;
80
81 //将监听sokcet绑定到epollfd上去
82 if (epoll_ctl(epollfd, EPOLL_CTL_ADD, listenfd, &listen_fd_event) == -1)
83 {
84 std::cout << "epoll_ctl error" << std::endl;
85 close(listenfd);
86 return -1;
87 }
88
89 int n;
90 while (true)
91 {
92 epoll_event epoll_events[1024];
93 n = epoll_wait(epollfd, epoll_events, 1024, 1000);
94 if (n < 0)
95 {
96 //被信号中断
97 if (errno == EINTR)
98 continue;
99
100 //出错,退出
101 break;
102 }
103 else if (n == 0)
104 {
105 //超时,继续
106 continue;
107 }
108 for (size_t i = 0; i < n; ++i)
109 {
110 //事件可读
111 if (epoll_events[i].events & EPOLLIN)
112 {
113 if (epoll_events[i].data.fd == listenfd)
114 {
115 //侦听socket,接受新连接
116 struct sockaddr_in clientaddr;
117 socklen_t clientaddrlen = sizeof(clientaddr);
118 int clientfd = accept(listenfd, (struct sockaddr*) & clientaddr, &clientaddrlen);
119 if (clientfd != -1)
120 {
121 int oldSocketFlag = fcntl(clientfd, F_GETFL, 0);
122 int newSocketFlag = oldSocketFlag | O_NONBLOCK;
123 if (fcntl(clientfd, F_SETFD, newSocketFlag) == -1)
124 {
125 close(clientfd);
126 std::cout << "set clientfd to nonblocking error." << std::endl;
127 }
128 else
129 {
130 epoll_event client_fd_event;
131 client_fd_event.data.fd = clientfd;
132 //同时侦听新来连接socket的读和写时间
133 client_fd_event.events = EPOLLIN | EPOLLOUT;
134 //取消注释这一行,则使用ET模式
135 client_fd_event.events |= EPOLLET;
136 if (epoll_ctl(epollfd, EPOLL_CTL_ADD, clientfd, &client_fd_event) != -1)
137 {
138 std::cout << "new client accepted,clientfd: " << clientfd << std::endl;
139 }
140 else
141 {
142 std::cout << "add client fd to epollfd error" << std::endl;
143 close(clientfd);
144 }
145 }
146 }
147 }
148 else
149 {
150 std::cout << "client fd: " << epoll_events[i].data.fd << " recv data." << std::endl;
151 //普通clientfd
152 char recvbuf[1024] = { 0 };
153 //每次只收一个字节
154 int m = recv(epoll_events[i].data.fd, recvbuf, 1024, 0);
155 if (m == 0)
156 {
157 //对端关闭了连接,从epollfd上移除clientfd
158 if (epoll_ctl(epollfd, EPOLL_CTL_DEL, epoll_events[i].data.fd, NULL) != -1)
159 {
160 std::cout << "client disconnected,clientfd:" << epoll_events[i].data.fd << std::endl;
161 }
162 close(epoll_events[i].data.fd);
163 }
164 else if (m < 0)
165 {
166 //出错
167 if (errno != EWOULDBLOCK && errno != EINTR)
168 {
169 if (epoll_ctl(epollfd, EPOLL_CTL_DEL, epoll_events[i].data.fd, NULL) != -1)
170 {
171 std::cout << "client disconnected,clientfd:" << epoll_events[i].data.fd << std::endl;
172 }
173 close(epoll_events[i].data.fd);
174 }
175 }
176 else
177 {
178 //正常收到数据
179 std::cout << "recv from client:" << epoll_events[i].data.fd << ", " << recvbuf << std::endl;
180
181 epoll_event client_fd_event;
182 client_fd_event.data.fd = epoll_events[i].data.fd;
183 //再次给clientfd注册检测可写事件
184 client_fd_event.events = EPOLLIN | EPOLLOUT | EPOLLET;
185 if (epoll_ctl(epollfd, EPOLL_CTL_MOD, epoll_events[i].data.fd, &client_fd_event) != -1)
186 {
187 std::cout << "epoll_ctl successfully, mode: EPOLL_CTL_MOD, clientfd:" << epoll_events[i].data.fd << std::endl;
188 }
189
190 }
191 }
192 }
193 else if (epoll_events[i].events & EPOLLOUT)
194 {
195 //只处理客户端fd的可写事件
196 if (epoll_events[i].data.fd != listenfd)
197 {
198 //打印结果
199 std::cout << "EPOLLOUT triggered,clientfd:" << epoll_events[i].data.fd << std::endl;
200 }
201 }
202 else if (epoll_events[i].events & EPOLLERR)
203 {
204 // TODO 暂不处理
205 }
206 }
207 }
208
209 close(listenfd);
210 return 0;
211 }
通过上述输出,我们可以发现,epoll_server 使用 ET 模式下即使给客户端 fd 注册了检测可写事件不会一直触发,只会触发一次,触发完后只有再次注册检测可写事件才会继续触发,这里是靠客户端来新消息驱动再次注册检测可写事件。也就是说,如果我们使用 ET 模式去处理可写事件时不必像 LT 模式那样为了避免不必要的可写触发在触发后需要立即移除检测可写事件。
这就意味着,使用 LT 模式,如果你的实现依赖于可写事件触发去发送数据,那么你一定要在数据发送完之后移除检测可写事件,避免没有数据发送时无意义的触发;使用 ET 模式时,如果你的实现也依赖于可写事件触发去发送数据,可写事件触发后,你调用 send 函数(Linux 平台也可以使用 write)去发送数据,如果数据本次不能全部发送完(对于非阻塞的 socket,此时 send 函数返回 -1,错误码为 EAGAIN 或 EWOULDBLOCK),你一定要继续注册检测可写事件,否则你剩余的数据就再也没有机会发送了,因为 ET 模式的可写事件再也不会触发。
总结起来:
-
- LT 模式下,读事件触发后,可以按需收取想要的字节数,不用把本次接收到的数据收取干净(即不用循环到 recv 或者 read 函数返回 -1,错误码为 EWOULDBLOCK 或 EAGAIN);ET 模式下,读事件必须把数据收取干净,因为你不一定有下一次机会再收取数据了,即使有机会,也可能存在上次没读完的数据没有及时处理,造成客户端响应延迟。
- LT 模式下,不需要写事件一定要及时移除,避免不必要的触发,浪费 CPU 资源;ET 模式下,写事件触发后,如果还需要下一次的写事件触发来驱动任务(例如发上次剩余的数据),你需要继续注册一次检测可写事件。
- LT 模式和 ET 模式各有优缺点,无所谓孰优孰劣。使用 LT 模式,我们可以自由决定每次收取多少字节(对于普通 socket)或何时接收连接(对于侦听 socket),但是可能会导致多次触发;使用 ET 模式,我们必须每次都要将数据收完(对于普通 socket)或必须理解调用 accept 接收连接(对于侦听socket),其优点是触发次数少。
四、转载于
https://cloud.tencent.com/developer/article/1636224
本文来自博客园,作者:Mr-xxx,转载请注明原文链接:https://www.cnblogs.com/MrLiuZF/p/15244610.html