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

相关推荐
顺风尿一寸1 分钟前
深入Linux内核:mkdir系统调用的完整实现解析
linux
用户2367829801682 分钟前
Linux free 命令深度解析:从内存监控到 OOM 排查的完整指南
linux
gs8014018 分钟前
网络隐形杀手:从 Could not connect to SMTP host 报错深度剖析 Docker MTU 黑洞理论与实战
网络·docker·容器
无足鸟ICT29 分钟前
【RHCA+】boxes命令(艺术框)
linux
wanhengidc38 分钟前
云手机搬砖 像僵尸开炮
运维·网络·智能手机·云计算
_Voosk1 小时前
FreeBSD 使用代理运行命令
linux·运维·freebsd
星恒讯工业路由器1 小时前
5G‑A大上行:七大技术补短板
网络·信息与通信·6g·5g‑a·5g-a大上行
蒸蛋一级爱好者1 小时前
IO多路复用和并发服务器
网络
二等饼干~za8986681 小时前
geo优化源码开发搭建技术分享
大数据·网络·数据库·人工智能·音视频
G_dou_1 小时前
Linux 搭建 Rust 开发环境:从 rustup 安装到 Cargo 镜像
linux·rust