抱歉,您的浏览器无法访问本站

本页面需要浏览器支持(启用)JavaScript


了解详情 >

1. I/O多路复用(I/O多路转接)

I/O 多路复用使得程序能同时监听多个文件描述符,能够提高程序的性能,Linux 下实现 I/O 多路复用的 系统调用主要有 select、poll 和 epoll。

IO多路复用图解

阻塞 BIO模型

image-20220701112457144

image-20220701112504556

非阻塞,忙轮询

accept() // 不断的检测客户的连接 - 不阻塞

read() // 不断的循环 - 不阻塞

image-20220701112632841

image-20220701114725894

IO多路转接技术 - select/poll - epoll

image-20220701114811065

image-20220701120108819

2. select

主旨思想:

​ 1 .首先要构造一个关于文件描述符的列表,将要监听的文件描述符添加到该列表中。

​ 2. 调用系统函数(select), 监听该列表中的文件描述符,直到这些描述符中的一个或者多个进行I\O操作时, 该函数才会返回

​ a. 这个函数是阻塞的

​ b. 函数对文件描述符的检测的操作是由内核完成

​ 3. 在返回时, 它会告诉进程有多少(哪些)描述符要进行I/O操作

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
// sizeof(fd_set) = 128位	1024 (bt位)
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/select.h>
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
- 参数:
- nfds: 委托内核的检测最大文件描述符的值 + 1
- readfds: 要检测的文件描述符的读的集合,委托内核检测哪些文件描述符的读的属性
- 一班检测读操作
- 对应的是对方发送来的数据, 因为读被的接收数据, 检测的就是读缓冲区
- 是一个传入传出参数
- writefds: 要检测的文件描述符的写的集合, 委托内核检测哪些文件描述符的写的属性
- 委托内核检测写缓存区是不是还可以写数据 (不满就可以写)
- exceptfds: 检测发生异常的文件描述符的集合
- timeout: 要设置的超时时间
struct timeout {
long tv_sec; /* seconds */
long tv_usec; /* microseconds */
}
- 值为 NULL: 永久阻塞, 直到检测到了文件描述符的变化
- tv_sec = 0, tv_usec: = 0 : 表示不阻塞
- tv_sec > 0, tv_usec: > 0 : 阻塞对应的时间
- 返回值:
- -1: 失败
- >0 (n): 检测的集合中n个文件描述符发生了变化

// 将参数文件描述符fd对应的标志位设置为0
void FD_CLR(int fd, fd_set *set);
// 判断fd对应的标志位是0还是1, 返回值 : fd对应的标志位的值,0,返回0, 1,返回1
int FD_ISSET(int fd, fd_set *set);
// 将参数文件描述符fd 对应的标志位,设置为1
void FD_SET(int fd, fd_set *set);
// fd_set一共有1024 bit, 全部初始化为0
void FD_ZERO(fd_set *set);

image-20220701222536002

image-20220702164919541

server.c

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
/*************************************************************************
> File Name: select.c
> Author: 秃头王
> Mail: 1658339000@qq.com
> Created Time: 2022年07月02日 星期六 11时52分50秒
************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/time.h>
#include <sys/types.h>

int main() {

// 创建socket

int lfd = socket(AF_INET,SOCK_STREAM ,0);

if(lfd == -1) {
perror("socket");
exit(-1);
}

// 绑定
struct sockaddr_in saddr;
saddr.sin_family = AF_INET;
saddr.sin_addr.s_addr = 0; // INADDR_ANY
saddr.sin_port = htons(5200);
int ret = bind(lfd, (struct sockaddr *)&saddr, sizeof(saddr));

if(ret == -1) {
perror("bind");
exit(-1);
}

// 监听
ret = listen(lfd, 8);
if(ret == -1) {
perror("listen");
exit(-1);
}

// 创建一个fd_set的集合, 存放的是需要检测的文件描述符

fd_set rdset, tmp;
FD_ZERO(&rdset);
FD_SET(lfd, &rdset);
int maxfd = lfd;

while (1) {

tmp = rdset;

// 调用select系统函数, 让内核帮忙检测哪些文件描述符有数据
ret = select(maxfd + 1, &tmp, NULL, NULL, NULL);
if(ret == -1) {
perror("select");
exit(-1);
} else if(ret == 0) {
// 设置的超时时间到了, 返回值才是, 才会执行, 设置的为NULL之 后检测到消息才会不阻塞
continue;
} else if(ret > 0) {
// 说明检测到了有文件描述符的对应的缓冲区的数据发生了改变
if(FD_ISSET(lfd, &tmp)) {
// 检测有新客户端连接进来了
// 接受连接
struct sockaddr_in cliaddr;
socklen_t len = sizeof(cliaddr);
int cfd = accept(lfd, (struct sockaddr *)&cliaddr, &len);
char cliIP[16];
int cliport = ntohs(cliaddr.sin_port);
inet_ntop(AF_INET, &cliaddr.sin_addr.s_addr, cliIP, sizeof(cliIP));
printf("client IP : %s, prok %d\n", cliIP, cliport);
if(cfd == -1) {
perror("accept");
exit(-1);
}

// 将新新的文件描述符加入的集合中
FD_SET(cfd, &rdset);
// 更新最大文件描述符
maxfd = maxfd > cfd ? maxfd : cfd;
}

for(int i = lfd + 1; i <= maxfd; i++) {
if(FD_ISSET(i, &tmp)) {
// 说明这个文件描述符对应的客户端发来了数据
char buf[1024];
memset(buf, 0, sizeof(buf));
int len = read(i, buf, sizeof(buf));
if(len == -1) {
perror("read");
exit(-1);
} else if(len == 0) {
printf("client closed ... ...\n");
FD_CLR(i, &rdset);
close(i);
} else if(len > 0) {
printf("read buf = %s\n", buf);
write(i, buf, sizeof(buf));
}
}
}

}

}

close(lfd);

return 0;
}

client.c

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
/*************************************************************************
> File Name: client.c
> Author: 秃头王
> Mail: 1658339000@qq.com
> Created Time: 2022年06月23日 星期四 14时41分42秒
************************************************************************/

#include <stdio.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
// TCP 通信客户端

int main() {

// 1.创建套接字
int fd = socket(AF_INET, SOCK_STREAM, 0);
if(fd == -1) {
perror("socket");
exit(-1);
}

// 2.连接服务
struct sockaddr_in serveraddr;
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(5200);
inet_pton(AF_INET, "127.0.0.1", &serveraddr.sin_addr.s_addr);
int ret = connect(fd, (struct sockaddr *)&serveraddr, sizeof(serveraddr));

if(ret == -1) {
perror("connect");
exit(-1);
}


// 通信
char recvBuf[1024] = {0};
char data[1024];
while (1) {
memset(data, 0, sizeof(data));
// 获取标准输入(stdin)的数据
fgets(data, sizeof(data), stdin);
write(fd, data, strlen(data));
int readLen = read(fd, recvBuf, sizeof(recvBuf));
if(readLen == -1) {
perror("read");
exit(-1);
} else if(readLen > 0) {
printf("recv server data : %s\n", recvBuf);
} else if(readLen == 0) {
// 表示服务端断开连接
printf("server closed ... ...\n");
break;
}
}

// 关闭
close(fd);

return 0;
}

3.poll

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <poll.h>
struct pollfd {
int fd; /* 委托内核检测的文件描述符 */
short events; /* 委托内核检测文件描述符的什么事件 */
short revents; /* 文件描述符实际发生的事件 */
};

struct pollfd myfd;
myfd.fd = 5;
myfd.events = POLLIN | POLLOUT;

int poll(struct pollfd *fds, nfds_t nfds, int timeout);
- 参数:
- fds : 是一个struct pollfd 结构体数组,这是一个需要检测的文件描述符的集合
- nfds : 这个是第一个参数数组中最后一个有效元素的下标 + 1
- timeout : 阻塞时长(毫秒)
0 : 不阻塞
-1 : 阻塞,当检测到需要检测的文件描述符有变化,解除阻塞
>0 : 阻塞的时长
- 返回值:
-1 : 失败
>0(n) : 成功,n表示检测到集合中有n个文件描述符发生变化

image-20220702180026781

image-20220702215413782

poll_server.c

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
119
120
121
122
123
124
125
126
127
/*************************************************************************
> File Name: select.c
> Author: 秃头王
> Mail: 1658339000@qq.com
> Created Time: 2022年07月02日 星期六 11时52分50秒
************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/time.h>
#include <sys/types.h>
#include <poll.h>

int main() {

// 创建socket

int lfd = socket(AF_INET,SOCK_STREAM ,0);

printf(" --- %d ---\n", lfd);

if(lfd == -1) {
perror("socket");
exit(-1);
}

// 绑定
struct sockaddr_in saddr;
saddr.sin_family = AF_INET;
saddr.sin_addr.s_addr = 0; // INADDR_ANY
saddr.sin_port = htons(5200);
int ret = bind(lfd, (struct sockaddr *)&saddr, sizeof(saddr));

if(ret == -1) {
perror("bind");
exit(-1);
}

// 监听
ret = listen(lfd, 8);
if(ret == -1) {
perror("listen");
exit(-1);
}

// 初始化检测文件描述符数组
const int MAX_N = 1024;
struct pollfd fds[MAX_N];
for(int i = 0; i < MAX_N; ++i) {
fds[i].fd = -1;
fds[i].events = POLLIN;
}

fds[0].fd = lfd;
int nfds = 0;

while (1) {

// 调用poll系统函数, 让内核帮忙检测哪些文件描述符有数据
ret = poll(fds, nfds + 1, -1);
if(ret == -1) {
perror("poll");
exit(-1);
} else if(ret == 0) {
// 设置的超时时间到了, 返回值才是, 才会执行, 设置的为NULL之后检测到消息才会不阻塞
continue;
} else if(ret > 0) {
// 说明检测到了有文件描述符的对应的缓冲区的数据发生了改变
// 不能用 ==
if(fds[0].revents & POLLIN) {
// 检测有新客户端连接进来了
// 接受连接
struct sockaddr_in cliaddr;
socklen_t len = sizeof(cliaddr);
int cfd = accept(lfd, (struct sockaddr *)&cliaddr, &len);
char cliIP[16];
int cliport = ntohs(cliaddr.sin_port);
inet_ntop(AF_INET, &cliaddr.sin_addr.s_addr, cliIP, sizeof(cliIP));
printf("client IP : %s, prok : %d fd : %d\n", cliIP, cliport, cfd);
if(cfd == -1) {
perror("accept");
exit(-1);
}

// 将新新的文件描述符加入的集合中
for(int i = 1; i < MAX_N; i++) {
if(fds[i].fd != -1) continue;
fds[i].fd = cfd;
fds[i].events = POLLIN;
break;
}
// 更新最大文件描述符
nfds = nfds > cfd ? nfds : cfd;
}

for(int i = 1; i <= nfds; i++) {
if(fds[i].revents & POLLIN) {
// 说明这个文件描述符对应的客户端发来了数据
char buf[1024];
memset(buf, 0, sizeof(buf));
int len = read(fds[i].fd, buf, sizeof(buf));
if(len == -1) {
perror("read");
exit(-1);
} else if(len == 0) {
printf("client closed ... ...\n");
fds[i].fd = -1;
close(fds[i].fd);
} else if(len > 0) {
printf("read buf = %s\n", buf);
write(fds[i].fd, buf, sizeof(buf));
}
}
}

}

}

close(lfd);

return 0;
}

client.c

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
/*************************************************************************
> File Name: client.c
> Author: 秃头王
> Mail: 1658339000@qq.com
> Created Time: 2022年06月23日 星期四 14时41分42秒
************************************************************************/

#include <stdio.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
// TCP 通信客户端

int main() {

// 1.创建套接字
int fd = socket(AF_INET, SOCK_STREAM, 0);
if(fd == -1) {
perror("socket");
exit(-1);
}

// 2.连接服务
struct sockaddr_in serveraddr;
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(5200);
inet_pton(AF_INET, "127.0.0.1", &serveraddr.sin_addr.s_addr);
int ret = connect(fd, (struct sockaddr *)&serveraddr, sizeof(serveraddr));

if(ret == -1) {
perror("connect");
exit(-1);
}


// 通信
char recvBuf[1024] = {0};
char data[1024];
while (1) {
memset(data, 0, sizeof(data));
// 获取标准输入(stdin)的数据
fgets(data, sizeof(data), stdin);
write(fd, data, strlen(data));
int readLen = read(fd, recvBuf, sizeof(recvBuf));
if(readLen == -1) {
perror("read");
exit(-1);
} else if(readLen > 0) {
printf("recv server data : %s\n", recvBuf);
} else if(readLen == 0) {
// 表示服务端断开连接
printf("server closed ... ...\n");
break;
}
}

// 关闭
close(fd);

return 0;
}

4. epoll

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
#include <sys/epoll.h>
// 创建一个新的epoll实例。在内核中创建了一个数据,这个数据中有两个比较重要的数据,一个是需要检测的文件描述符的信息(红黑树),还有一个是就绪列表,存放检测到数据发送改变的文件描述符信息(双向链表)。
int epoll_create(int size);
- 参数:
size : (以前是使用哈希实现的需要用到size)目前没有意义了。随便写一个数,必须大于0
- 返回值:
-1 : 失败
> 0 : 文件描述符,操作epoll实例的

typedef union epoll_data {
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;

struct epoll_event {
uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};

常见的Epoll检测事件:
- EPOLLIN
- EPOLLOUT
- EPOLLERR
... ...

// 对epoll实例进行管理:添加文件描述符信息,删除信息,修改信息
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
- 参数:
- epfd : epoll实例对应的文件描述符
- op : 要进行什么操作
EPOLL_CTL_ADD: 添加
EPOLL_CTL_MOD: 修改
EPOLL_CTL_DEL: 删除
- fd : 要检测的文件描述符
- event : 检测文件描述符什么事情
// 检测函数
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int
timeout);
- 参数:
- epfd : epoll实例对应的文件描述符
- events : 传出参数,保存了发送 了变化的文件描述符的信息
- maxevents : 第二个参数结构体数组的大小
- timeout : 阻塞时间
- 0 : 不阻塞
- -1 : 阻塞,直到检测到fd数据发生变化,解除阻塞
- > 0 : 阻塞的时长(毫秒)
- 返回值:
- 成功,返回发送变化的文件描述符的个数 > 0
- 失败 -1

epoll_server.c

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
/*************************************************************************
> File Name: epoll.c
> Author: 秃头王
> Mail: 1658339000@qq.com
> Created Time: 2022年07月03日 星期日 15时07分46秒
************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <string.h>
#include <sys/epoll.h>

int main() {

// 创建socket
int lfd = socket(AF_INET, SOCK_STREAM, 0);

if(lfd == -1) {
perror("socket");
exit(-1);
}

// 绑定
struct sockaddr_in saddr;
saddr.sin_family = AF_INET;
saddr.sin_addr.s_addr = INADDR_ANY;
saddr.sin_port = htons(5200);
int ret = bind(lfd, (struct sockaddr *)&saddr, sizeof(saddr));

if(ret == -1) {
perror("bind");
exit(-1);
}

// 监听
ret = listen(lfd, 5);

if(ret == -1) {
perror("listen");
exit(-1);
}

// 创建一个epoll实例 调用epoll_create
int epfd = epoll_create(100);

// 将监听的文件描述符相关的检测信息添加到epoll实例中
struct epoll_event epev;
epev.events = EPOLLIN;
epev.data.fd = lfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &epev);

// 用于放检测后的数据
const int MAX_N = 1e3;
struct epoll_event epevs[MAX_N];

while(1) {

ret = epoll_wait(epfd, epevs, MAX_N, -1);
if(ret == -1) {
perror("epoll_wait");
exit(-1);
}

// 有几个改变了
printf("ret = %d \n", ret);

for(int i = 0; i < ret; i++) {

int curfd = epevs[i].data.fd;

if(curfd == lfd) {



// 监听的文件描述符有数据到达, 有客户端连接
struct sockaddr_in cliaddr;
socklen_t len = sizeof(cliaddr);
int cfd = accept(lfd, (struct sockaddr *) &cliaddr, &len);
epev.events = EPOLLIN;
epev.data.fd = cfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &epev);

char cliIP[16];
int cliport = ntohs(cliaddr.sin_port);
inet_ntop(AF_INET, &cliaddr.sin_addr.s_addr, cliIP, sizeof(cliIP));
printf("client IP : %s, prok %d\n", cliIP, cliport);

} else {
// 有数据到达了, 需要通信
char buf[1024];
memset(buf, 0, sizeof(buf));
int len = read(curfd, buf, sizeof(buf));
if(len == -1) {
perror("read");
exit(-1);
} else if(len == 0) {
printf("client closed ... ...\n");
epoll_ctl(epfd, EPOLL_CTL_DEL, curfd, NULL);
close(curfd);
} else if(len > 0) {
printf("read buf = %s\n", buf);
write(curfd, buf, sizeof(buf));
}
}
}

}

close(lfd);
close(epfd);

return 0;
}

client.c 点我向上

Epoll 的工作模式:

  • LT 模式 (水平触发)

​ 假设委托内核检测读事件 -> 检测fd的读缓冲区

​ 读缓冲区有数据 - > epoll检测到了会给用户通知

​ a.用户不读数据,数据一直在缓冲区,epoll 会一直通知

​ b.用户只读了一部分数据,epoll会通知

​ c.缓冲区的数据读完了,不通知

LT(level - triggered)是缺省的工作方式,并且同时支持 block 和 no-block socket。在这 种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的 fd 进行 IO 操 作。如果你不作任何操作,内核还是会继续通知你的。

  • ET 模式(边沿触发)

​ 假设委托内核检测读事件 -> 检测fd的读缓冲区

​ 读缓冲区有数据 - > epoll检测到了会给用户通知

​ a.用户不读数据,数据一致在缓冲区中,epoll下次检测的时候就不通知了

​ b.用户只读了一部分数据,epoll不通知

​ c.缓冲区的数据读完了,不通知

ET(edge - triggered)是高速工作方式,只支持 no-block socket。在这种模式下,当描述 符从未就绪变为就绪时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪, 并且不会再为那个文件描述符发送更多的就绪通知,直到你做了某些操作导致那个文件描述 符不再为就绪状态了。但是请注意,如果一直不对这个 fd 作 IO 操作(从而导致它再次变成 未就绪),内核不会发送更多的通知(only once)。 ET 模式在很大程度上减少了 epoll 事件被重复触发的次数,因此效率要比 LT 模式高。epoll 工作在 ET 模式的时候,必须使用非阻塞套接口,以避免由于一个文件句柄的阻塞读/阻塞写 操作把处理多个文件描述符的任务饿死。

1
2
3
4
5
6
7
8
9
struct epoll_event {
uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
常见的Epoll检测事件:
- EPOLLIN
- EPOLLOUT
- EPOLLERR
- EPOLLET // 边沿触发

epoll_LT

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
/*************************************************************************
> File Name: epoll.c
> Author: 秃头王
> Mail: 1658339000@qq.com
> Created Time: 2022年07月03日 星期日 15时07分46秒
************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <string.h>
#include <sys/epoll.h>

int main() {

// 创建socket
int lfd = socket(AF_INET, SOCK_STREAM, 0);

if(lfd == -1) {
perror("socket");
exit(-1);
}

// 绑定
struct sockaddr_in saddr;
saddr.sin_family = AF_INET;
saddr.sin_addr.s_addr = INADDR_ANY;
saddr.sin_port = htons(5200);
int ret = bind(lfd, (struct sockaddr *)&saddr, sizeof(saddr));

if(ret == -1) {
perror("bind");
exit(-1);
}

// 监听
ret = listen(lfd, 5);

if(ret == -1) {
perror("listen");
exit(-1);
}

// 创建一个epoll实例 调用epoll_create
int epfd = epoll_create(100);

// 将监听的文件描述符相关的检测信息添加到epoll实例中
struct epoll_event epev;
epev.events = EPOLLIN;
epev.data.fd = lfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &epev);

// 用于放检测后的数据
const int MAX_N = 1e3;
struct epoll_event epevs[MAX_N];

while(1) {

ret = epoll_wait(epfd, epevs, MAX_N, -1);
if(ret == -1) {
perror("epoll_wait");
exit(-1);
}

// 有几个改变了
printf("ret = %d \n", ret);

for(int i = 0; i < ret; i++) {

int curfd = epevs[i].data.fd;

if(curfd == lfd) {

// 监听的文件描述符有数据到达, 有客户端连接
struct sockaddr_in cliaddr;
socklen_t len = sizeof(cliaddr);
int cfd = accept(lfd, (struct sockaddr *) &cliaddr, &len);
epev.events = EPOLLIN;
epev.data.fd = cfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &epev);

char cliIP[16];
int cliport = ntohs(cliaddr.sin_port);
inet_ntop(AF_INET, &cliaddr.sin_addr.s_addr, cliIP, sizeof(cliIP));
printf("client IP : %s, prok %d\n", cliIP, cliport);
memset(cliIP, 0, sizeof(cliIP));

} else {
// 有数据到达了, 需要通信
char buf[5] = {0};
// memset(buf, 0, sizeof(buf));
int len = read(curfd, buf, sizeof(buf));
if(len == -1) {
perror("read");
exit(-1);
} else if(len == 0) {
printf("client closed ... ...\n");
epoll_ctl(epfd, EPOLL_CTL_DEL, curfd, NULL);
close(curfd);
} else if(len > 0) {
printf("read buf = %s\n", buf);
write(curfd, buf, sizeof(buf));
}
}
}

}

close(lfd);
close(epfd);

return 0;
}

epoll_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
119
120
121
122
123
124
125
126
127
128
129
130
131
/*************************************************************************
> File Name: epoll.c
> Author: 秃头王
> Mail: 1658339000@qq.com
> Created Time: 2022年07月03日 星期日 15时07分46秒
************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <string.h>
#include <sys/epoll.h>
#include <fcntl.h>
#include <errno.h>

int main() {

// 创建socket
int lfd = socket(AF_INET, SOCK_STREAM, 0);

if(lfd == -1) {
perror("socket");
exit(-1);
}

// 绑定
struct sockaddr_in saddr;
saddr.sin_family = AF_INET;
saddr.sin_addr.s_addr = INADDR_ANY;
saddr.sin_port = htons(5200);
int ret = bind(lfd, (struct sockaddr *)&saddr, sizeof(saddr));

if(ret == -1) {
perror("bind");
exit(-1);
}

// 监听
ret = listen(lfd, 5);

if(ret == -1) {
perror("listen");
exit(-1);
}

// 创建一个epoll实例 调用epoll_create
int epfd = epoll_create(100);

// 将监听的文件描述符相关的检测信息添加到epoll实例中
struct epoll_event epev;
epev.events = EPOLLIN;
epev.data.fd = lfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &epev);

// 用于放检测后的数据
const int MAX_N = 1e3;
struct epoll_event epevs[MAX_N];

while(1) {

ret = epoll_wait(epfd, epevs, MAX_N, -1);
if(ret == -1) {
perror("epoll_wait");
exit(-1);
}

// 有几个改变了
printf("ret = %d \n", ret);

for(int i = 0; i < ret; i++) {

int curfd = epevs[i].data.fd;

if(curfd == lfd) {

// 监听的文件描述符有数据到达, 有客户端连接
struct sockaddr_in cliaddr;
socklen_t len = sizeof(cliaddr);
int cfd = accept(lfd, (struct sockaddr *) &cliaddr, &len);

// 设置cfd属性非阻塞
int flag = fcntl(cfd, F_GETFL);
flag |= O_NONBLOCK;
fcntl(cfd, F_SETFL, flag);

epev.events = EPOLLIN | EPOLLET; // 设置边沿触发
epev.data.fd = cfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &epev);

char cliIP[16];
int cliport = ntohs(cliaddr.sin_port);
inet_ntop(AF_INET, &cliaddr.sin_addr.s_addr, cliIP, sizeof(cliIP));
printf("client IP : %s, prok %d\n", cliIP, cliport);
memset(cliIP, 0, sizeof(cliIP));

} else {

// 循环读取所有的数据
int len = 0;
char buf[5] = {0};
while((len = read(curfd, buf, sizeof(buf))) > 0) {
// 打印数据
// printf("recv data : %s \n", buf);
write(STDOUT_FILENO, buf, len);
write(curfd, buf, len);
}

if(len == 0) {
printf("client closed ... ...\n");
} else if(len == -1) {
// 数据读完又读非阻塞的就会产生一个错误
if(errno == EAGAIN) {
printf("data over ... ...\n");
} else {
perror("read");
exit(-1);
}
}

}
}

}

close(lfd);
close(epfd);

return 0;
}

评论