用epoll实现高并发I/O的核心是将socket设为非阻塞并配合epoll_create1、epoll_ctl和epoll_wait构建事件驱动循环;需用RAII封装epoll fd,监听socket注册EPOLLIN,客户端socket注册EPOLLIN|EPOLLET,并循环recv直至EAGAIN/EWOULDBLOCK。
用 epoll 实现高并发 I/O,在 C++ 中核心是把 socket 设置为非阻塞模式,配合 epoll_create、epoll_ctl 和 epoll_wait 构建事件驱动循环,避免为每个连接开线程或进程。
调用 epoll_create1(0) 创建一个 epoll 实例,返回的 fd 是后续操作的句柄。建议用 RAII 封装,确保析构时自动 close()。注意不要重复创建多个 epoll 实例——一个线程一个就够了,多线程可考虑每个线程独立 epoll + 独立事件循环,或用主从 reactor 模式。
EPOLL_CLOEXEC,防止 fork 后意外继承bind + listen,再用 epoll_ctl(EPOLL_CTL_ADD) 注册 EPOLLIN
EPOLLIN | EPOLLET(边缘触发更高效)ET 模式下,epoll 只在 fd 状态变化时通知一次,必须一次性读完所有可用数据(直到 recv 返回 EAGAIN 或 EWOULDBLOCK),否则可能丢事件。因此每个 socket 对应的缓冲区要自己管理,不能依赖一次 read 就清空。
fcntl(fd, F_SETFL, O_NONBLOCK) 设置非阻塞recv,检查返回值:>0 正常,==0 表示对端关闭,errno == EAGAIN 才算读完EPOLLOUT,等可写时继续发送,发完再删掉 EPOLLOUT
每次 epoll_wait 返回一批就绪事件,需遍历处理。关键是要把 fd 和业务逻辑(如连接对象、读写缓冲区、状态机)绑定起来。推荐用 std::unordered_map 或更安全的 std::map(fd 为 key),避免裸指针悬挂。
EPOLLIN,调用 Connection::handle_read();收到 EPOLLOUT,调用 handle_write()epoll_ctl(EPOLL_CTL_DEL),close fd,从 map 移除并 delete 对象
待删除,循环结束后统一清理,防止迭代器失效epoll 本身不解决粘包、协议解析、超时、内存泄漏等问题,这些都得自己补全。一个稳定的服务至少要:
errno、fd、当前状态,方便定位 ECONNRESET、EMFILE 等问题-O2 -D_GLIBCXX_DEBUG(调试期);生产环境用 SO_REUSEPORT 配合多进程提升吞吐