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 出错,错误码是EWOULDBLOCKEAGAIN 一样)(此时表示 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 }
View Code

  我们先来看水平模式的行为,将代码 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 命令实际发送了 abcdef\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 }
View Code

  上述代码中,我们对新来的连接 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 }
View Code
  上述逻辑中,服务器端在每次收到客户端消息时会重新给客户端 fd 注册检测可写事件(EPOLLOUT),重新编译代码,启动 epoll_server,再次使用 nc 命令模拟客户端给 epoll_server 发送几条消息,结果如下:

  通过上述输出,我们可以发现,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

posted @ 2021-09-08 21:05  Mr-xxx  阅读(1079)  评论(0编辑  收藏  举报