sysNow's blog

C++开发学习——IO多路复用epoll代码分析

2025-12-14
开发
C++开发
最后更新:2025-12-14
6分钟
1153字

C++开发学习——IO多路复用epoll代码分析

IO多路复用是指在一个线程中同时处理多个socket连接,其核心优势在于分离了等待数据和处理数据

参考文章https://www.cnblogs.com/horacle/p/17939568

1
#include <iostream>
2
#include <vector>
3
#include <unistd.h>
4
#include <sys/socket.h>
5
#include <netinet/in.h>
6
#include <arpa/inet.h>
7
#include <sys/epoll.h> // 核心头文件
8
#include <cstring>
9
10
#define MAX_EVENTS 10
11
#define PORT 8888
12
13
int main() {
14
// 1. 创建监听 Socket
15
int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
73 collapsed lines
16
sockaddr_in addr;
17
addr.sin_family = AF_INET;
18
addr.sin_addr.s_addr = INADDR_ANY;
19
addr.sin_port = htons(PORT);
20
21
bind(listen_fd, (struct sockaddr*)&addr, sizeof(addr));
22
listen(listen_fd, 128);
23
24
// 2. 创建 epoll 实例
25
int epfd = epoll_create1(0);
26
if (epfd == -1) { perror("epoll_create1"); return 1; }
27
28
// 3. 将 listen_fd 添加到 epoll 中
29
struct epoll_event ev;
30
ev.events = EPOLLIN;
31
ev.data.fd = listen_fd;
32
33
if (epoll_ctl(epfd, EPOLL_CTL_ADD, listen_fd, &ev) == -1) {
34
perror("epoll_ctl: listen_fd"); return 1;
35
}
36
37
struct epoll_event events[MAX_EVENTS];
38
39
std::cout << "Epoll server running on port " << PORT << "..." << std::endl;
40
41
while (true) {
42
// 4. 等待事件发生
43
int nfds = epoll_wait(epfd, events, MAX_EVENTS, -1);
44
45
if (nfds == -1) { perror("epoll_wait"); break; }
46
47
// 5. 只处理发生的事件
48
for (int i = 0; i < nfds; ++i) {
49
50
// 情况 A: 监听 socket 就绪 -> 有新连接
51
if (events[i].data.fd == listen_fd) {
52
sockaddr_in client_addr;
53
socklen_t len = sizeof(client_addr);
54
int client_fd = accept(listen_fd, (struct sockaddr*)&client_addr, &len);
55
56
std::cout << "New client connected: " << client_fd << std::endl;
57
58
// 将新客户端加入 epoll 监控
59
ev.events = EPOLLIN; // 默认是水平触发 (LT)
60
ev.data.fd = client_fd;
61
epoll_ctl(epfd, EPOLL_CTL_ADD, client_fd, &ev);
62
}
63
// 情况 B: 客户端 socket 就绪 -> 有数据发来
64
else {
65
int client_fd = events[i].data.fd;
66
char buffer[1024] = { 0 };
67
ssize_t count = read(client_fd, buffer, sizeof(buffer));
68
69
if (count > 0) {
70
std::cout << "Recv from " << client_fd << ": " << buffer << std::endl;
71
// 回显数据
72
write(client_fd, buffer, count);
73
}
74
else {
75
std::cout << "Client disconnected: " << client_fd << std::endl;
76
77
// 必须先从 epoll 中删除
78
epoll_ctl(epfd, EPOLL_CTL_DEL, client_fd, nullptr);
79
close(client_fd);
80
}
81
}
82
}
83
}
84
85
close(listen_fd);
86
close(epfd);
87
return 0;
88
}

我们一块一块分析代码,同时解释代码具体意义

创建监听socket

1
int listen_fd = socket(AF_INET, SOCK_STREAM, 0);

AF_INET是指 IPv4协议族,如果是IPv6则写AF_INET6,SOCK_STREAM是指使用的TCP协议,0表示使用默认协议

1
sockaddr_in addr;
2
addr.sin_family = AF_INET;
3
addr.sin_addr.s_addr = INADDR_ANY;
4
addr.sin_port = htons(PORT);

AF_INET和上面的协议族应该是一样的,INADDR_ANY是指绑定本机所有网卡,htons(PORT)的作用是将端口转成大端序

1
bind(listen_fd, (struct sockaddr*)&addr, sizeof(addr));
2
listen(listen_fd, 128);

bind函数主要是将之前创建的listen_fd与addr绑定在一起,若端口被占用则返回-1,listen函数就是开启监听,128是指排队上限

创建epoll实例

1
int epfd = epoll_create1(0);
2
if (epfd == -1) { perror("epoll_create1"); return 1; }

epoll_create1是epoll_create函数的新版本,主要是将size变量替换成了flags变量,其参数0的意思是创建默认的epoll实例

这个函数的底层实现为红黑树,通过文件描述符管理

将listen_fd添加到epoll中

1
struct epoll_event ev;
2
ev.events = EPOLLIN;
3
ev.data.fd = listen_fd;
4
5
if (epoll_ctl(epfd, EPOLL_CTL_ADD, listen_fd, &ev) == -1) {
6
perror("epoll_ctl: listen_fd"); return 1;
7
}

程序通过epoll_ctl函数将epfd与待监听事件注册到epfd上,ev.events = EPOLLIN表示水平触发,一般用于读入事件,epoll_ctl函数中EPOLL_CTL_ADD是指注册一个新的fd到epoll中,这个函数的原型如下:

1
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

等待事件发生

1
int nfds = epoll_wait(epfd, events, MAX_EVENTS, -1);
2
if (nfds == -1) { perror("epoll_wait"); break; }

程序通过epoll_wait函数等待事件发生,这是个阻塞函数,它将返回值放在events变量中,同时设置了最多事件数为MAX_EVENTS,-1是超时时间

epoll_wait的返回值是有数据的事件个数,并将nfds个事件放在events的第0至nfds-1位处

给新设备分配fd

1
if (events[i].data.fd == listen_fd) {
2
sockaddr_in client_addr;
3
socklen_t len = sizeof(client_addr);
4
int client_fd = accept(listen_fd, (struct sockaddr*)&client_addr, &len);
5
6
std::cout << "New client connected: " << client_fd << std::endl;
7
8
// 将新客户端加入 epoll 监控
9
ev.events = EPOLLIN; // 默认是水平触发 (LT)
10
ev.data.fd = client_fd;
11
epoll_ctl(epfd, EPOLL_CTL_ADD, client_fd, &ev);
12
}

当程序判断到events[i].data.fd == listen_fd时,此时就需要给连接上的设备创建client_fd以备后续的交互,同时通过epoll_ctl将client_fd上发生的数据读入事件注册在epoll中

已有fd进行交互

1
else {
2
int client_fd = events[i].data.fd;
3
char buffer[1024] = { 0 };
4
ssize_t count = read(client_fd, buffer, sizeof(buffer));
5
6
if (count > 0) {
7
std::cout << "Recv from " << client_fd << ": " << buffer << std::endl;
8
// 回显数据
9
write(client_fd, buffer, count);
10
}
11
else {
12
std::cout << "Client disconnected: " << client_fd << std::endl;
13
14
// 必须先从 epoll 中删除
15
epoll_ctl(epfd, EPOLL_CTL_DEL, client_fd, nullptr);
3 collapsed lines
16
close(client_fd);
17
}
18
}

当程序不满足events[i].data.fd == listen_fd这个条件时,意味着这个设备已经完成了连接可以开始正式交互了,这里就写交互逻辑,我这里是将用户的输入重新输出给用户。若读入失败则表示设备断开连接,则通过epoll_ctl将client_fd对应的事件删除,随后关闭client_fd

退出程序

1
close(listen_fd);
2
close(epfd);

程序的最后通过close关闭listen_fd与epfd

本文标题:C++开发学习——IO多路复用epoll代码分析
文章作者:sysNow
发布时间:2025-12-14