Linux 下网络编程:高性能 IO 多路复用,epoll 事件处理循环

cpp 复制代码
for (int i = 0; i < nfds; ++i) {
            if (events[i].data.fd == listen_sockfd) {
                // --- 处理新连接 ---
                struct sockaddr_in client_addr{};
                socklen_t client_len = sizeof(client_addr);
                int conn_sockfd = accept(listen_sockfd, (struct sockaddr*)&client_addr, &client_len);
                if (conn_sockfd < 0) {
                    perror("accept failed");
                    continue;
                }
                std::cout << "New connection from " << inet_ntoa(client_addr.sin_addr) << ":" << ntohs(client_addr.sin_port) << std::endl;

                set_non_blocking(conn_sockfd); // 新连接的套接字也必须是非阻塞的

                // 将新连接的套接字加入epoll监控
                event.events = EPOLLIN | EPOLLET; // 使用边缘触发(ET)模式
                event.data.fd = conn_sockfd;
                if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, conn_sockfd, &event) < 0) {
                    perror("epoll_ctl add client failed");
                    close(conn_sockfd);
                }
            } else {
                // --- 处理客户端数据 ---
                int conn_sockfd = events[i].data.fd;
                char buffer[1024];
                ssize_t count = read(conn_sockfd, buffer, sizeof(buffer));

                if (count < 0) {
                    // 非阻塞模式下,EAGAIN或EWOULDBLOCK表示数据已读完
                    if (errno == EAGAIN || errno == EWOULDBLOCK) {
                        // std::cerr << "Read finished for fd " << conn_sockfd << std::endl;
                    } else {
                        perror("read failed");
                    }
                } else if (count == 0) {
                    // 客户端关闭连接
                    std::cout << "Client " << conn_sockfd << " disconnected." << std::endl;
                    epoll_ctl(epoll_fd, EPOLL_CTL_DEL, conn_sockfd, nullptr);
                    close(conn_sockfd);
                } else {
                    // 收到数据,回显给客户端
                    // 注意:在实际高性能场景中,write也可能需要处理EAGAIN
                    if (write(conn_sockfd, buffer, count) < 0) {
                        perror("write failed");
                    }
                }
            }
        }

epoll 事件处理循环

这是整个 epoll 服务器的「业务核心」!前面所有代码(socket、bind、listen、epoll_create)都是为了这一段代码服务。

整段代码的核心作用(大白话)

c 复制代码
遍历所有触发事件的 socket:
    如果是【监听套接字】有动静 → 有新客户上门,接待连接
    如果是【普通客户端】有动静 → 客户发消息了,读取并回复

这就是高并发服务器处理成千上万客户端的逻辑!


第一部分:遍历事件

c 复制代码
for (int i = 0; i < nfds; ++i) {
  • nfdsepoll_wait 返回的就绪事件数量
  • 作用:把这次所有触发了事件的 socket挨个处理一遍

第二部分:判断事件类型(核心分支)

c 复制代码
if (events[i].data.fd == listen_sockfd) {

原理:

我们之前把监听套接字 加入了 epoll 监控。

当它触发 EPOLLIN 事件 = 内核队列里有新连接等待被接收

所以进入这个 if一定是来新客户端了!


分支1:处理新客户端连接

1. 接收连接(accept)

c 复制代码
int conn_sockfd = accept(listen_sockfd, ...);
  • 作用 :从内核队列里取出一个连接 ,创建一个通信套接字
  • listen_sockfd:只负责接客
  • conn_sockfd :专门用来和这个客户端收发数据(一对一专属)

2. 打印客户端信息

c 复制代码
inet_ntoa(client_addr.sin_addr) // IP转字符串
ntohs(client_addr.sin_port)     // 端口转主机序
  • 打印:客户端从哪个IP、哪个端口连过来的

3. 设置非阻塞(超级重要)

c 复制代码
set_non_blocking(conn_sockfd);
  • 只要用 epoll(ET模式),所有套接字必须是非阻塞!
  • 保证 read/write 不会卡住服务器

4. 把新客户端加入 epoll 监控

c 复制代码
event.events = EPOLLIN | EPOLLET; // 监控可读 + 边缘触发
event.data.fd = conn_sockfd;      // 带上通信套接字
epoll_ctl(ADD ...);               // 加入监控列表

重点:EPOLLET 边缘触发

  • ET 模式 :epoll 最高性能模式
  • 只在数据从无到有时通知一次
  • 效率极高,是高并发服务器标配

至此:

新客户端连接成功!epoll 开始监控它的消息。


分支2:处理客户端数据(else)

进入 else = 不是新连接,是已连接客户端发数据来了!

1. 获取客户端套接字

c 复制代码
int conn_sockfd = events[i].data.fd;
  • 拿到和这个客户端通信的专属套接字

2. 读取数据

c 复制代码
ssize_t count = read(conn_sockfd, buffer, sizeof(buffer));
  • 从客户端读取消息到缓冲区
  • 返回值 count:读到的字节数

3. 三种返回值处理

① count < 0:读取出错

c 复制代码
if (errno == EAGAIN) {
    // 正常!非阻塞ET模式下,数据读完了
} else {
    // 真出错了
}

② count == 0:客户端断开连接

c 复制代码
// 从epoll移除 + 关闭套接字
epoll_ctl(DEL ...);
close(conn_sockfd);
  • 客户端关闭了连接,服务器清理资源

③ count > 0:读到有效数据

c 复制代码
write(conn_sockfd, buffer, count);
  • 回显服务器:收到什么,原样发回给客户端

完整流程示意图(秒懂)

  1. 客户端发起连接
  2. 监听套接字触发 EPOLLIN
  3. accept 接收,得到 conn_sockfd
  4. 将 conn_sockfd 加入 epoll
  5. 客户端发消息
  6. conn_sockfd 触发 EPOLLIN
  7. read 读取,write 回写
  8. 客户端断开 → close 清理

极简总结(封神级)

  1. if (listen_sockfd) :处理新连接,accept + 加入epoll
  2. else :处理客户端消息,read + write
  3. EPOLLET:高性能边缘触发
  4. 非阻塞:所有socket必须非阻塞
  5. 核心:一个线程,处理成千上万客户端,不卡顿!

整个服务器代码完整闭环

你现在已经看完了Linux高并发epoll服务器全部代码

  1. socket → 创建套接字
  2. setsockopt → 端口复用
  3. bind → 绑定IP端口
  4. listen → 开始监听
  5. epoll_create1 → 创建监控
  6. epoll_ctl(ADD) → 加入监听socket
  7. epoll_wait → 等待事件
  8. for循环处理 → 你刚学的核心逻辑

这就是 Nginx、Redis、SRS 等高并发中间件的底层原理!


最终一句话总结

这段代码,让单线程服务器,能同时处理几万客户端连接和消息,这就是 epoll 的威力!

相关推荐
A小辣椒1 天前
TShark:Wireshark CLI 功能
linux
A小辣椒1 天前
TShark:基础知识
linux
AlfredZhao1 天前
OCI 明明分配了 200G 系统盘,为什么 df 只看到 30G?
linux·oci
AlfredZhao2 天前
vi 删除指定范围的行,不用再反复按 dd
linux·vi
用户9718356334662 天前
银河麒麟 KY10 申威(SW64) 安装 nginx-1.16.1-2.p01.ky10.sw_64.rpm 详细步骤
linux
猪脚踏浪2 天前
linux 拷贝文件或目录到指定的位置
linux
摇滚侠3 天前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
bush43 天前
嵌入式linux学习记录十四、术语
linux·嵌入式
载数而行5203 天前
Linux 11 动态监控指令top
linux
网络研究院3 天前
2026年网络安全
网络·安全·法律·法规·趋势·发展