一、实现思路(多 FD 监听核心步骤)
- 创建 epoll 实例:作为管理所有监听 FD 的「总控制器」;
- 准备待监听的 FD:比如 TCP 监听 FD(监听网络连接)、标准输入 FD(STDIN_FILENO,监听控制台输入);
- 将所有 FD 加入 epoll 实例:为每个 FD 设置要监听的事件(如 EPOLLIN 读事件);
- 循环调用 epoll_wait:阻塞等待任意 FD 就绪,返回就绪的事件列表;
- 遍历就绪事件:根据 FD 类型(标准输入 / 监听 FD / 客户端 FD)分逻辑处理;
- 资源清理:FD 失效时从 epoll 移除并关闭,避免泄漏。
二、完整可运行代码(C/C++ 兼容)
cpp
运行
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <unistd.h>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
// 常量定义
#define PORT 8888
#define MAX_EVENTS 1024 // 单次最多处理的就绪事件数
#define BUF_SIZE 1024 // 数据缓冲区大小
// 错误处理封装(简化代码)
void err_exit(const char* msg) {
perror(msg);
exit(EXIT_FAILURE);
}
// 向 epoll 添加要监听的 FD
void add_epoll_fd(int epoll_fd, int fd, uint32_t events) {
struct epoll_event ev;
ev.events = events;
ev.data.fd = fd;
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &ev) == -1) {
err_exit("epoll_ctl add fd failed");
}
}
int main() {
// ===================== 步骤1:创建 epoll 实例 =====================
int epoll_fd = epoll_create1(0); // 创建 epoll 句柄,参数 0 即可
if (epoll_fd == -1) {
err_exit("epoll_create1 failed");
}
// ===================== 步骤2:准备要监听的多个 FD =====================
// 1. 监听标准输入 FD(STDIN_FILENO = 0):监听控制台输入
add_epoll_fd(epoll_fd, STDIN_FILENO, EPOLLIN);
std::cout << "已监听标准输入(控制台输入任意内容按回车触发)\n";
// 2. 创建 TCP 监听 FD:监听网络连接
int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
if (listen_fd == -1) {
err_exit("socket create failed");
}
// 端口复用(避免程序重启时端口被占用)
int opt = 1;
setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
// 绑定端口
struct sockaddr_in serv_addr;
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = INADDR_ANY; // 监听所有网卡
serv_addr.sin_port = htons(PORT);
if (bind(listen_fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1) {
err_exit("bind failed");
}
// 开始监听(backlog=5)
if (listen(listen_fd, 5) == -1) {
err_exit("listen failed");
}
// 将 TCP 监听 FD 加入 epoll
add_epoll_fd(epoll_fd, listen_fd, EPOLLIN);
std::cout << "已监听 TCP 端口 " << PORT << "(可通过 telnet 127.0.0.1 8888 测试)\n";
// ===================== 步骤3:循环监听并处理就绪事件 =====================
struct epoll_event events[MAX_EVENTS]; // 存放就绪事件的数组
while (true) {
// 调用 epoll_wait,阻塞等待 FD 就绪(timeout=-1 永久阻塞)
int nfds = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);
if (nfds == -1) {
// 处理信号中断(如 Ctrl+C),非真正错误,重试
if (errno == EINTR) {
std::cout << "epoll_wait 被信号中断,重试...\n";
continue;
}
err_exit("epoll_wait failed");
}
// 遍历所有就绪事件(仅遍历前 nfds 个,epoll 高效的核心)
for (int i = 0; i < nfds; ++i) {
int fd = events[i].data.fd;
uint32_t revents = events[i].events;
// ------------- 处理1:标准输入 FD 就绪(控制台输入)-------------
if (fd == STDIN_FILENO) {
char buf[BUF_SIZE] = {0};
ssize_t n = read(STDIN_FILENO, buf, sizeof(buf)-1);
if (n > 0) {
std::cout << "[标准输入] 收到内容:" << buf; // buf 包含换行符,无需额外换行
// 若输入 "exit" 则退出程序
if (strncmp(buf, "exit", 4) == 0) {
std::cout << "收到 exit 指令,程序退出...\n";
close(epoll_fd);
close(listen_fd);
exit(EXIT_SUCCESS);
}
}
}
// ------------- 处理2:TCP 监听 FD 就绪(新客户端连接)-------------
else if (fd == listen_fd) {
struct sockaddr_in cli_addr;
socklen_t cli_len = sizeof(cli_addr);
// 接受新连接
int conn_fd = accept(listen_fd, (struct sockaddr*)&cli_addr, &cli_len);
if (conn_fd == -1) {
perror("accept failed");
continue;
}
// 将新客户端 FD 加入 epoll,监听读事件(ET 模式)
add_epoll_fd(epoll_fd, conn_fd, EPOLLIN | EPOLLET);
std::cout << "[网络连接] 新客户端接入:"
<< inet_ntoa(cli_addr.sin_addr) << ":"
<< ntohs(cli_addr.sin_port) << " (fd=" << conn_fd << ")\n";
}
// ------------- 处理3:客户端 FD 就绪(有数据可读/连接断开)-------------
else if (revents & (EPOLLIN | EPOLLERR | EPOLLHUP)) {
char buf[BUF_SIZE] = {0};
ssize_t n = read(fd, buf, sizeof(buf)-1);
if (n > 0) {
// 读取到客户端数据,回显
std::cout << "[客户端 fd=" << fd << "] 收到数据:" << buf << std::endl;
write(fd, buf, n); // 回显给客户端
} else {
// n<=0:客户端断开或出错,清理资源
if (n == 0) {
std::cout << "[客户端 fd=" << fd << "] 正常断开连接\n";
} else {
perror("[客户端 fd=%d] 读取错误");
}
// 从 epoll 移除 FD 并关闭
epoll_ctl(epoll_fd, EPOLL_CTL_DEL, fd, nullptr);
close(fd);
}
}
}
}
// 资源清理(实际循环不会走到这里,exit 会提前退出)
close(epoll_fd);
close(listen_fd);
return 0;
}
三、代码关键说明(多 FD 监听核心)
-
多 FD 监听的核心:只需通过
epoll_ctl(EPOLL_CTL_ADD)将所有需要监听的 FD(如STDIN_FILENO、listen_fd、conn_fd)添加到同一个 epoll 实例,epoll 会统一管理这些 FD 的事件。 -
不同 FD 的事件区分:通过
events[i].data.fd区分就绪的 FD 类型(标准输入 / 监听 FD / 客户端 FD),然后分逻辑处理 —— 这是多 FD 监听的关键,确保不同 FD 的事件不会混淆。 -
ET 模式的使用:客户端 FD 使用
EPOLLIN | EPOLLET(边缘触发),提升高并发下的效率;标准输入和监听 FD 用默认的 LT 模式(水平触发),更易处理。 -
错误处理与资源清理:
- 每个 FD 失效时(如客户端断开),必须调用
epoll_ctl(EPOLL_CTL_DEL)从 epoll 移除,再close,避免 FD 泄漏; - 处理
epoll_wait的EINTR中断(如 Ctrl+C),避免程序误退出。
- 每个 FD 失效时(如客户端断开),必须调用
四、编译与运行步骤
1. 编译(C/C++ 均可)
bash
运行
# C++ 编译(推荐)
g++ epoll_multi_fd.cpp -o epoll_multi_fd
# C 编译(将代码后缀改为 .c,去掉 std:: 前缀,用 printf 替代 cout)
gcc epoll_multi_fd.c -o epoll_multi_fd
2. 运行程序
bash
运行
./epoll_multi_fd
3. 测试多 FD 监听
- 测试标准输入:在控制台输入任意内容(如
hello),按回车,程序会打印「[标准输入] 收到内容:hello」;输入exit可退出程序。 - 测试网络连接:打开新终端,执行
telnet 127.0.0.1 8888,程序会打印「新客户端接入」;在 telnet 中输入内容,程序会回显,关闭 telnet 则程序提示「客户端断开」。
五、扩展:监听更多类型的 FD
你可以基于这个示例,轻松扩展监听其他类型的 FD:
- 本地文件 FD:打开文件(
open())后,将 FD 加入 epoll(需注意普通文件默认是「一直就绪」,需结合业务逻辑处理); - 管道 FD:创建管道(
pipe())后,监听读 / 写端 FD; - UDP socket FD:创建 UDP socket 后,加入 epoll 监听读事件。
总结
- epoll 监听多 FD 的核心是:将所有待监听 FD 通过
epoll_ctl(ADD)加入同一个 epoll 实例; - 通过
epoll_wait获取就绪事件后,根据data.fd区分 FD 类型,分逻辑处理; - 每个 FD 失效时必须「从 epoll 移除 + 关闭 FD」,避免资源泄漏;
- 示例同时监听了「标准输入」和「TCP 监听 FD」,体现了 epoll 对任意 FD 类型的通用监听能力。
 的监听&spm=1001.2101.3001.5002&articleId=156992177&d=1&t=3&u=bb87d050f6b24d90af59c94d6777785f)
1929

被折叠的 条评论
为什么被折叠?



