epoll中 LT 和 ET 区别

Last updated on 3 months ago

上文解释过epoll 原理,现在梳理下epoll 的用法和 epoll 中两个读取数据的模式

现在用epoll 实现一个多路复用的服务器(代码在最后)

  • LT 水平触发 有数据到来就一直读,直到没有数据可以读取了
  • ET 边缘触发 有到无,读规定的字节数,没读完,下次有事件了再读取

读取客户端发来的数据时候,只读五个字节,看下ET和LT会有什么不同

image-20220311222200962

  • ET模式下 必须使用非阻塞fd

image-20220311222904069

连接后:

image-20220311222323848

第一次客户端发了 7 个字节,而服务器只接受五个字节就结束,第二次客户端发送一个字节,客户读取了三个字节,显然还是读取上一次未读取的完的, 所以et模式就是 自己能在缓冲区读多少就读多少,读不完下次还来读

  • LT模式下:
    如果没有设置,默认的是LT模式
    image-20220311222834196

image-20220311223210774

​ 客户端发送了15个字节,服务器读了三次,也就是说只要缓冲区没读完,就一直有事件响应, LT 等效于 (一个while + ET)

​ 实现原理还有待后续挖掘(待填坑)

服务端代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <netinet/tcp.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/epoll.h>
#include <sys/poll.h>
#include <unistd.h>
#define BUFFER_LENGTH 1024
#define POLL_SIZE 1024
#define EPOLL_SIZE 1024

int main(int argc, char *argv[]) {
if (argc < 2) {
printf("Paramter Error\n");
return -1;
}
int port = atoi(argv[1]);

int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) {
perror("socket");
return -1;
}

struct sockaddr_in addr;
memset(&addr, 0, sizeof(struct sockaddr_in));

addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = INADDR_ANY;

if (bind(sockfd, (struct sockaddr *)&addr, sizeof(struct sockaddr_in)) < 0) {
perror("bind");
return 2;
}

if (listen(sockfd, 5) < 0) {
perror("listen");
return 3;
}
//相当于建立一颗 红黑树
int epoll_fd = epoll_create(EPOLL_SIZE);
// ev 是保存 fd的结构体
struct epoll_event ev, events[EPOLL_SIZE] = {0};
//初始化 ev
ev.events = EPOLLIN; //事件类型
ev.data.fd = sockfd; // sockfd是监听有无用户连接的io
//向红黑树中插入 节点
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, sockfd, &ev);
//开始
while (1) {
// nready 是意思是有几个事件产生,并且会将产生的事件放在 events数组中
int nready = epoll_wait(epoll_fd, events, EPOLL_SIZE, -1);
if (nready == -1) {
printf("epoll_wait\n");
break;
}
//开始遍历
int i = 0;
for (i = 0; i < nready; i++) {
// sockfd有响应说明 有新的客户端来连接了
if (events[i].data.fd == sockfd) {
struct sockaddr_in client_addr;
memset(&client_addr, 0, sizeof(struct sockaddr_in));
socklen_t client_len = sizeof(client_addr);

int clientfd =
accept(sockfd, (struct sockaddr *)&client_addr, &client_len);
if (clientfd <= 0) continue;

char str[INET_ADDRSTRLEN] = {0};
printf("recvived from %s at port %d, sockfd:%d, clientfd:%d\n",
inet_ntop(AF_INET, &client_addr.sin_addr, str, sizeof(str)),
ntohs(client_addr.sin_port), sockfd, clientfd);

ev.events = EPOLLIN;
ev.data.fd = clientfd;
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, clientfd, &ev);
} else { //这种情况是连接得用户有io响应
int clientfd = events[i].data.fd;

char buffer[BUFFER_LENGTH] = {0};
//读取数据
int ret = recv(clientfd, buffer, 5, 0);
if (ret < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
printf("read all data");
continue;
}
//释放 fd
close(clientfd);
//设置为 ET模式
ev.events = EPOLLIN | EPOLLET;
ev.data.fd = clientfd;
//从红黑树删除
epoll_ctl(epoll_fd, EPOLL_CTL_DEL, clientfd, &ev);
} else if (ret == 0) {
printf(" disconnect %d\n", clientfd);
// ret == 0; ret
close(clientfd);

ev.events = EPOLLIN;
ev.data.fd = clientfd;
epoll_ctl(epoll_fd, EPOLL_CTL_DEL, clientfd, &ev);

break;
} else {
printf("Recv: %s, %d Bytes\n", buffer, ret);
}
}
}
}
return 0;
}

客户代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
// struct sockaddr_in
#include <netinet/in.h>
// inet_addr
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include<signal.h>
#include <iostream>
#include <string>
int x;
void handler(int s) //信号到来,则执行这个函数,输出超时
{
close(x);
exit(1);
}
int main() {
int socket_desc;
struct sockaddr_in server;
char *message, server_reply[2000];
signal(SIGINT, handler);
// 创建socket
socket_desc = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == socket_desc) {
perror("cannot create socket");
exit(1);
}
x = socket_desc;
// 设置远程服务器的信息
server.sin_addr.s_addr = inet_addr("127.0.0.1");
server.sin_family = AF_INET;
server.sin_port = htons(1234);

// 连接
if (connect(socket_desc, (struct sockaddr *)&server, sizeof(server)) < 0) {
perror("cannot connect");
return 0;
}
// 发送数据

while (1) {
std::string str;
std::cin >> str;
str.c_str();
if (send(socket_desc, str.c_str(), str.size(), 0) < 0) {
perror("send data error");
return 2;
}
sleep(1);
}
printf("connect success");
return 0;
}

-