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 的威力!

相关推荐
徐子元竟然被占了!!2 小时前
网络地址转换(NAT)
网络·智能路由器
南境十里·墨染春水2 小时前
linux 学习进展 mysql 事务详解
linux·学习·mysql
Bushlet2 小时前
交换综合实验
网络
白緢2 小时前
二、Linux 开发工具
linux·运维·服务器
思茂信息2 小时前
CST可重构雷达吸波器设计与仿真
网络·算法·平面·智能手机·重构·cst·电磁仿
切糕师学AI2 小时前
Systemd 服务完全指南:从入门到生产实践
linux·systemd·systemctl
计算机安禾2 小时前
【计算机网络】第25篇:Linux网络数据包的解剖路径——从网卡驱动到协议栈的关键路径
linux·网络·计算机网络
深蓝易网2 小时前
工厂目视化实操手册,告别形式主义
运维·网络·数据库·人工智能·汽车
小明同学012 小时前
linux———进程间通信
linux·服务器·网络