在 Linux 高并发网络编程中,epoll 是性能最优的 IO 多路转接技术,它解决了 select/poll 的诸多痛点(如文件描述符数量限制、内核遍历开销),成为 Nginx、Redis 等高性能中间件的核心 IO 模型。本文将从 epoll 的核心系统调用入手,深入解析其 “红黑树 + 就绪链表” 的工作原理,对比水平触发(LT)与边缘触发(ET)的差异,最终通过实战案例演示 epoll 服务器的实现,帮你彻底掌握高性能 IO 的设计思路。
一、epoll 核心认知:为什么它比 select/poll 更高效?
epoll 是 Linux 2.6 内核引入的 IO 多路转接技术,专为 “大批量文件描述符(FD)监控” 设计。相比 select/poll,它的高效源于三个核心改进:
- 无 FD 数量限制:突破 select 的
FD_SETSIZE(默认 1024)限制,支持数万甚至数十万 FD 监控; - 内核事件回调机制:无需像 select/poll 那样每次遍历所有 FD,而是通过回调函数将就绪 FD 加入 “就绪链表”,epoll_wait 直接读取就绪链表,时间复杂度 O (1);
- 减少数据拷贝:仅在 FD 注册(epoll_ctl)时将 FD 信息从用户态拷贝到内核态,后续 epoll_wait 无需重复拷贝,大幅降低开销。
epoll 的本质是 “内核级事件驱动”—— 内核主动跟踪 FD 状态变化,仅在 FD 就绪时通知应用程序,避免应用程序轮询或遍历,是高并发服务的 “性能基石”。
二、epoll 的三个核心系统调用
epoll 的使用流程可概括为 “创建 epoll 句柄→注册 FD 事件→等待就绪事件”,对应三个系统调用:epoll_create、epoll_ctl、epoll_wait。
2.1 epoll_create:创建 epoll 句柄
#include <sys/epoll.h>
int epoll_create(int size);
功能:创建一个 epoll 实例(内核维护的eventpoll结构体),用于管理待监控的 FD 和就绪事件。
参数:size在 Linux 2.6.8 后被忽略,仅需传入一个大于 0 的整数(如 10)。
返回值:成功返回 epoll 句柄(非负整数),失败返回 - 1(错误码存于errno)。
注意:epoll 句柄需用close()关闭,避免资源泄漏。
2.2 epoll_ctl:注册 / 修改 / 删除 FD 事件
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
功能:向 epoll 实例中添加、修改或删除 FD 的监控事件。
参数详解:
| 参数 | 含义 | 关键说明 |
|---|---|---|
epfd | epoll 句柄 | epoll_create的返回值 |
op | 操作类型 | 可选值:- EPOLL_CTL_ADD:添加 FD 到 epoll;- EPOLL_CTL_MOD:修改 FD 的监控事件;- EPOLL_CTL_DEL:从 epoll 中删除 FD(event传 NULL) |
fd | 待监控的文件描述符 | 如 socket、标准输入等 |
event | 监控事件配置 | 结构体包含 “事件类型” 和 “用户数据”,见下文 |
struct epoll_event结构体:
typedef union epoll_data {
void *ptr; // 自定义数据(如FD关联的业务数据)
int fd; // 待监控的FD(最常用)
uint32_t u32; // 32位整数
uint64_t u64; // 64位整数
} epoll_data_t;
struct epoll_event {
uint32_t events; // 监控的事件类型(如EPOLLIN、EPOLLOUT)
epoll_data_t data; // 用户数据(通常存FD,用于epoll_wait后识别就绪FD)
};
常见事件类型:
| 事件宏 | 含义 | 适用场景 |
|---|---|---|
EPOLLIN | FD 可读(如 socket 接收缓冲区有数据、对端关闭连接) | 接收网络数据、读取标准输入 |
EPOLLOUT | FD 可写(如 socket 发送缓冲区有空闲空间) | 发送网络数据 |
EPOLLPRI | FD 有紧急数据可读(如 TCP 带外数据) | 处理紧急数据 |
EPOLLERR | FD 发生错误 | 错误处理(无需主动监控,内核自动触发) |
EPOLLHUP | FD 被挂断(如管道写端关闭) | 连接断开处理(无需主动监控,内核自动触发) |
EPOLLET | 边缘触发(ET)模式 | 高性能场景,需配合非阻塞 IO |
EPOLLONESHOT | 仅监控一次事件 | 避免多个线程同时处理同一 FD |
2.3 epoll_wait:等待 FD 就绪事件
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
功能:阻塞等待 epoll 实例中监控的 FD 就绪,将就绪事件拷贝到用户态数组。
参数详解:
| 参数 | 含义 | 关键说明 |
|---|---|---|
epfd | epoll 句柄 | epoll_create的返回值 |
events | 就绪事件数组 | 用户态预分配的struct epoll_event数组,用于存储就绪事件 |
maxevents | 数组最大长度 | 不能超过epoll_create的size,通常设为数组元素个数(如 1000) |
timeout | 超时时间(毫秒) | -1:永久阻塞;0:非阻塞(立即返回);>0:阻塞指定毫秒后返回 |
返回值:
正数:就绪的 FD 数量(需循环处理前 N 个events元素);
0:超时时间到,无 FD 就绪;
-1:调用失败(如被信号中断),错误码存于errno。
三、epoll 工作原理:红黑树与就绪链表的协同
epoll 的高效源于内核中eventpoll结构体的设计,它通过 “红黑树管理监控 FD” 和 “双链表存储就绪 FD”,实现高效的 FD 管理和事件通知。
3.1 核心数据结构
内核为每个 epoll 实例维护一个eventpoll结构体,关键成员如下:
struct eventpoll {
struct rb_root rbr; // 红黑树:存储所有待监控的FD(epitem结构体)
struct list_head rdlist; // 双链表:存储就绪的FD(epitem结构体)
};
红黑树(rbr):
每个节点是epitem结构体(包含 FD、监控事件、指向eventpoll的指针);
作用:快速添加、删除、查找 FD(时间复杂度 O (logN)),避免重复监控;
就绪链表(rdlist):
当 FD 就绪时(如 socket 接收数据),内核通过回调函数ep_poll_callback将对应的epitem加入该链表;
epoll_wait直接读取该链表,无需遍历所有 FD,时间复杂度 O (1)。
3.2 完整工作流程
- 创建 epoll 实例:调用
epoll_create,内核创建eventpoll结构体(初始化红黑树和就绪链表); - 注册 FD 事件:调用
epoll_ctl(EPOLL_CTL_ADD),内核创建epitem结构体,添加到红黑树,并为 FD 注册设备驱动回调(如网卡接收数据的回调); - 等待就绪事件:调用
epoll_wait,内核检查就绪链表是否为空:- 为空:阻塞等待,释放 CPU;
- 非空:将就绪链表中的
epitem拷贝到用户态events数组,返回就绪数量;
- FD 就绪触发:
- 当 FD 就绪(如网卡收到数据),设备驱动调用
ep_poll_callback; - 回调函数将
epitem从红黑树找到,加入就绪链表; - 若
epoll_wait处于阻塞状态,唤醒进程处理就绪事件。
- 当 FD 就绪(如网卡收到数据),设备驱动调用
四、epoll 的两种工作模式:LT 与 ET
epoll 支持两种事件触发模式 ——水平触发(LT,Level Triggered) 和边缘触发(ET,Edge Triggered),这是 epoll 最核心的特性之一,直接影响程序的性能和复杂度。
4.1 水平触发(LT):默认模式,“亲妈式” 通知
LT 是 epoll 的默认模式,特点是 “只要 FD 就绪(如接收缓冲区有数据),epoll_wait 就会持续返回该 FD”,直到 FD 的就绪状态消失(如数据被读完)。
工作示例(监听 socket 可读事件)
- 客户端向 socket 发送 2KB 数据,socket 接收缓冲区有数据,FD 变为就绪;
- 调用
epoll_wait,返回该 FD(就绪); - 应用程序调用
read读取 1KB 数据,接收缓冲区剩余 1KB 数据; - 再次调用
epoll_wait,仍返回该 FD(因缓冲区还有数据,FD 仍就绪); - 应用程序读取剩余 1KB 数据,缓冲区为空,FD 变为未就绪;
- 下次
epoll_wait不再返回该 FD。
特点与适用场景
优点:逻辑简单,支持阻塞 / 非阻塞 IO,无需一次处理完所有数据;
缺点:若数据未及时处理,epoll_wait会反复返回该 FD,增加内核与用户态的交互次数;
适用场景:对性能要求不极致、代码复杂度需控制的场景(如中小规模服务)。
4.2 边缘触发(ET):高性能模式,“后妈式” 通知
ET 模式需在epoll_ctl时显式指定EPOLLET标志,特点是 “FD 就绪时,epoll_wait 仅返回一次”,无论数据是否处理完,后续不再通知,直到 FD 的就绪状态再次变化(如再次收到数据)。
工作示例(监听 socket 可读事件)
- 客户端向 socket 发送 2KB 数据,FD 就绪,
epoll_wait返回该 FD; - 应用程序调用
read读取 1KB 数据,接收缓冲区剩余 1KB 数据; - 再次调用
epoll_wait,不再返回该 FD(因 FD 就绪状态未变化); - 客户端再次发送 1KB 数据,FD 就绪状态变化,
epoll_wait再次返回该 FD; - 应用程序读取剩余 2KB 数据(上次 1KB + 本次 1KB),缓冲区为空。
关键要求:必须配合非阻塞 IO
ET 模式下,若使用阻塞 IO,可能导致 “数据未读完但无法再次触发 epoll_wait” 的死锁:
例:应用程序用阻塞read读取 1KB 数据(缓冲区剩 1KB),因 ET 不再通知,read下次调用会阻塞(缓冲区无新数据),程序卡死;
解决方案:将 FD 设为非阻塞模式,用循环read直到返回EAGAIN/EWOULDBLOCK(表示缓冲区已空),确保一次处理完所有数据。
特点与适用场景
优点:epoll_wait返回次数少,内核与用户态交互少,性能更高(Nginx 默认使用 ET);
缺点:逻辑复杂,必须配合非阻塞 IO,需一次处理完所有数据;
适用场景:高并发、高性能场景(如百万级连接的网关、Web 服务器)。
4.3 LT 与 ET 对比
| 维度 | 水平触发(LT) | 边缘触发(ET) |
|---|---|---|
| 触发频率 | FD 就绪时持续触发 | FD 就绪时仅触发一次 |
| IO 模式支持 | 阻塞 / 非阻塞 | 仅支持非阻塞 |
| 数据处理要求 | 可分多次处理 | 必须一次处理完所有数据 |
| 代码复杂度 | 低 | 高 |
| 性能 | 中等 | 高 |
| 适用场景 | 中小规模服务、简单逻辑 | 高并发、高性能服务 |
五、epoll 实战:从 LT 模式到 ET 模式的服务器实现
以下通过 “TCP 字典服务器” 案例,分别演示 epoll 的 LT 和 ET 模式实现,核心功能:客户端发送英文单词,服务器返回中文释义。
5.1 基础封装:TcpSocket 类
先封装 socket 的基础操作(创建、绑定、监听、接收、发送),支持非阻塞 IO(用于 ET 模式):
// tcp_socket.hpp
#pragma once
#include <string>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <cstring>
#include <cerrno>
class TcpSocket {
public:
TcpSocket() : _fd(-1) {}
TcpSocket(int fd) : _fd(fd) {}
~TcpSocket() { Close(); }
// 创建socket
bool Socket() {
_fd = socket(AF_INET, SOCK_STREAM, 0);
if (_fd < 0) { perror("socket failed"); return false; }
return true;
}
// 绑定IP和端口
bool Bind(const std::string& ip, uint16_t port) {
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr(ip.c_str());
addr.sin_port = htons(port);
if (bind(_fd, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
perror("bind failed"); return false;
}
return true;
}
// 监听
bool Listen(int backlog = 5) {
if (listen(_fd, backlog) < 0) {
perror("listen failed"); return false;
}
return true;
}
// 接受连接
bool Accept(TcpSocket* new_sock, std::string* client_ip = nullptr, uint16_t* client_port = nullptr) {
struct sockaddr_in client_addr;
socklen_t len = sizeof(client_addr);
int new_fd = accept(_fd, (struct sockaddr*)&client_addr, &len);
if (new_fd < 0) { perror("accept failed"); return false; }
new_sock->_fd = new_fd;
if (client_ip) *client_ip = inet_ntoa(client_addr.sin_addr);
if (client_port) *client_port = ntohs(client_addr.sin_port);
return true;
}
// 阻塞接收数据(LT模式用)
bool Recv(std::string* buf) {
char tmp[1024] = {0};
ssize_t n = read(_fd, tmp, sizeof(tmp)-1);
if (n < 0) { perror("recv failed"); return false; }
if (n == 0) { return false; } // 对端关闭
buf->assign(tmp, n);
return true;
}
// 阻塞发送数据(LT模式用)
bool Send(const std::string& buf) {
ssize_t n = write(_fd, buf.c_str(), buf.size());
if (n < 0) { perror("send failed"); return false; }
return true;
}
// 设置非阻塞IO(ET模式用)
bool SetNonBlock() {
int fl = fcntl(_fd, F_GETFL);
if (fl < 0) { perror("fcntl F_GETFL"); return false; }
if (fcntl(_fd, F_SETFL, fl | O_NONBLOCK) < 0) {
perror("fcntl F_SETFL"); return false;
}
return true;
}
// 非阻塞接收数据(ET模式用,循环读直到缓冲区空)
bool RecvNonBlock(std::string* buf) {
buf->clear();
char tmp[1024*10] = {0};
while (true) {
ssize_t n = read(_fd, tmp, sizeof(tmp)-1);
if (n < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK) break; // 缓冲区空,正常退出
perror("recv nonblock failed"); return false;
}
if (n == 0) { return false; } // 对端关闭
tmp[n] = '\0';
*buf += tmp;
if (n < (ssize_t)sizeof(tmp)-1) break; // 数据已读完
}
return true;
}
// 非阻塞发送数据(ET模式用,循环写直到发送完)
bool SendNonBlock(const std::string& buf) {
ssize_t pos = 0;
ssize_t left = buf.size();
while (left > 0) {
ssize_t n = write(_fd, buf.data() + pos, left);
if (n < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK) continue; // 发送缓冲区满,重试
perror("send nonblock failed"); return false;
}
pos += n;
left -= n;
}
return true;
}
// 关闭socket
void Close() {
if (_fd != -1) { close(_fd); _fd = -1; }
}
// 获取FD
int GetFd() const { return _fd; }
private:
int _fd;
};
5.2 案例 1:epoll LT 模式服务器
LT 模式逻辑简单,无需非阻塞 IO,适合快速开发。
核心代码(epoll_lt_server.cc)
#include <iostream>
#include <vector>
#include <unordered_map>
#include <functional>
#include <sys/epoll.h>
#include "tcp_socket.hpp"
// 全局字典(英文→中文)
std::unordered_map<std::string, std::string> g_dict = {
{"apple", "苹果"}, {"banana", "香蕉"}, {"cat", "猫"},
{"dog", "狗"}, {"book", "书"}, {"happy", "快乐的"}
};
// 业务处理:查询字典
void DictHandler(const std::string& req, std::string* resp) {
if (g_dict.count(req)) {
*resp = req + ": " + g_dict[req];
} else {
*resp = req + ": 未找到释义";
}
}
int main(int argc, char* argv[]) {
if (argc != 3) {
std::cerr << "Usage: " << argv[0] << " server_ip server_port" << std::endl;
return 1;
}
std::string ip = argv[1];
uint16_t port = std::stoi(argv[2]);
// 1. 创建监听socket
TcpSocket listen_sock;
if (!listen_sock.Socket() || !listen_sock.Bind(ip, port) || !listen_sock.Listen()) {
return 1;
}
std::cout << "LT Mode Server Started: " << ip << ":" << port << std::endl;
// 2. 创建epoll实例
int epfd = epoll_create(10);
if (epfd < 0) { perror("epoll_create"); return 1; }
// 3. 添加监听socket到epoll(LT模式)
struct epoll_event ev;
ev.events = EPOLLIN; // 监听可读事件
ev.data.fd = listen_sock.GetFd();
if (epoll_ctl(epfd, EPOLL_CTL_ADD, listen_sock.GetFd(), &ev) < 0) {
perror("epoll_ctl ADD listen_sock");
close(epfd);
return 1;
}
// 4. 事件循环
struct epoll_event events[1000]; // 就绪事件数组
while (true) {
// 等待就绪事件(永久阻塞)
int nfds = epoll_wait(epfd, events, sizeof(events)/sizeof(events[0]), -1);
if (nfds < 0) {
perror("epoll_wait");
continue;
}
// 处理就绪的FD
for (int i = 0; i < nfds; ++i) {
int fd = events[i].data.fd;
if (fd == listen_sock.GetFd()) {
// 5. 监听socket就绪:处理新连接
TcpSocket new_sock;
std::string client_ip;
uint16_t client_port;
if (listen_sock.Accept(&new_sock, &client_ip, &client_port)) {
std::cout << "New Client: " << client_ip << ":" << client_port << " (fd=" << new_sock.GetFd() << ")" << std::endl;
// 添加新连接到epoll
struct epoll_event new_ev;
new_ev.events = EPOLLIN;
new_ev.data.fd = new_sock.GetFd();
epoll_ctl(epfd, EPOLL_CTL_ADD, new_sock.GetFd(), &new_ev);
// 转移socket所有权(避免析构关闭)
_socks.emplace(new_sock.GetFd(), std::move(new_sock));
}
} else {
// 6. 已连接socket就绪:处理请求
auto it = _socks.find(fd);
if (it == _socks.end()) continue;
std::string req, resp;
if (!it->second.Recv(&req)) {
// 客户端关闭,从epoll和_socks中删除
std::cout << "Client Disconnected: fd=" << fd << std::endl;
epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);
_socks.erase(it);
continue;
}
// 处理请求并返回结果
DictHandler(req, &resp);
it->second.Send(resp);
std::cout << "fd=" << fd << " req: " << req << " → resp: " << resp << std::endl;
}
}
}
// 7. 释放资源(实际不会执行,因循环常驻)
close(epfd);
return 0;
}
5.3 案例 2:epoll ET 模式服务器
ET 模式需将 FD 设为非阻塞,并用循环读写确保数据处理完整。
核心代码(epoll_et_server.cc)
#include <iostream>
#include <vector>
#include <unordered_map>
#include <functional>
#include <sys/epoll.h>
#include "tcp_socket.hpp"
// 全局字典(同LT模式)
std::unordered_map<std::string, std::string> g_dict = {
{"apple", "苹果"}, {"banana", "香蕉"}, {"cat", "猫"},
{"dog", "狗"}, {"book", "书"}, {"happy", "快乐的"}
};
// 业务处理(同LT模式)
void DictHandler(const std::string& req, std::string* resp) {
if (g_dict.count(req)) {
*resp = req + ": " + g_dict[req];
} else {
*resp = req + ": 未找到释义";
}
}
int main(int argc, char* argv[]) {
if (argc != 3) {
std::cerr << "Usage: " << argv[0] << " server_ip server_port" << std::endl;
return 1;
}
std::string ip = argv[1];
uint16_t port = std::stoi(argv[2]);
// 1. 创建监听socket(LT模式,避免accept漏接)
TcpSocket listen_sock;
if (!listen_sock.Socket() || !listen_sock.Bind(ip, port) || !listen_sock.Listen()) {
return 1;
}
std::cout << "ET Mode Server Started: " << ip << ":" << port << std::endl;
// 2. 创建epoll实例
int epfd = epoll_create(10);
if (epfd < 0) { perror("epoll_create"); return 1; }
// 3. 添加监听socket到epoll(LT模式,无需EPOLLET)
struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.fd = listen_sock.GetFd();
if (epoll_ctl(epfd, EPOLL_CTL_ADD, listen_sock.GetFd(), &ev) < 0) {
perror("epoll_ctl ADD listen_sock");
close(epfd);
return 1;
}
// 存储已连接socket(避免析构关闭)
std::unordered_map<int, TcpSocket> _socks;
// 4. 事件循环
struct epoll_event events[1000];
while (true) {
int nfds = epoll_wait(epfd, events, sizeof(events)/sizeof(events[0]), -1);
if (nfds < 0) { perror("epoll_wait"); continue; }
for (int i = 0; i < nfds; ++i) {
int fd = events[i].data.fd;
if (fd == listen_sock.GetFd()) {
// 5. 处理新连接(LT模式,无需非阻塞)
TcpSocket new_sock;
std::string client_ip;
uint16_t client_port;
if (listen_sock.Accept(&new_sock, &client_ip, &client_port)) {
std::cout << "New Client: " << client_ip << ":" << client_port << " (fd=" << new_sock.GetFd() << ")" << std::endl;
// 设置新连接为非阻塞(ET模式必需)
new_sock.SetNonBlock();
// 添加到epoll,指定EPOLLET
struct epoll_event new_ev;
new_ev.events = EPOLLIN | EPOLLET; // 边缘触发
new_ev.data.fd = new_sock.GetFd();
epoll_ctl(epfd, EPOLL_CTL_ADD, new_sock.GetFd(), &new_ev);
_socks.emplace(new_sock.GetFd(), std::move(new_sock));
}
} else {
// 6. 处理已连接socket(ET模式,非阻塞读写)
auto it = _socks.find(fd);
if (it == _socks.end()) continue;
std::string req, resp;
if (!it->second.RecvNonBlock(&req)) {
// 客户端关闭,清理资源
std::cout << "Client Disconnected: fd=" << fd << std::endl;
epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);
_socks.erase(it);
continue;
}
// 处理请求并发送结果(非阻塞发送)
DictHandler(req, &resp);
it->second.SendNonBlock(resp);
std::cout << "fd=" << fd << " req: " << req << " → resp: " << resp << std::endl;
}
}
}
close(epfd);
return 0;
}
5.4 测试步骤
- 编译运行服务器:
# LT模式 g++ epoll_lt_server.cc -o epoll_lt_server -std=c++11 ./epoll_lt_server 127.0.0.1 8888 # ET模式 g++ epoll_et_server.cc -o epoll_et_server -std=c++11 ./epoll_et_server 127.0.0.1 8889 - 启动客户端:
# 使用telnet或自定义TCP客户端 telnet 127.0.0.1 8888 - 测试功能:
- 客户端输入 “apple”,服务器返回 “apple: 苹果”;
- 输入 “test”,返回 “test: 未找到释义”。
六、epoll vs select/poll:全方位对比
| 特性 | select | poll | epoll(Linux) |
|---|---|---|---|
| FD 数量限制 | 有(FD_SETSIZE,默认 1024) | 无(受内存限制) | 无(支持数万 FD) |
| 数据拷贝 | 每次 epoll_wait 拷贝所有 FD 集合 | 每次 poll 拷贝所有 pollfd 数组 | 仅注册时拷贝,后续无拷贝 |
| 就绪事件查找 | 遍历所有 FD(O (N)) | 遍历所有 pollfd(O (N)) | 读取就绪链表(O (1)) |
| 触发模式 | 仅水平触发(LT) | 仅水平触发(LT) | 水平触发(LT)、边缘触发(ET) |
| 接口复杂度 | 高(需手动重置 FD 集合) | 中(pollfd 数组,输入输出分离) | 低(三步调用,接口清晰) |
| 适用场景 | 小规模 FD(≤1024) | 中等规模 FD | 大规模高并发(如 Web 服务器、网关) |
七、总结与实战建议
epoll 是 Linux 高并发网络编程的 “利器”,其核心优势在于 “无 FD 限制、低数据拷贝、高效就绪事件查找”,配合 ET 模式和非阻塞 IO,可实现百万级连接的高性能服务。
核心收获
- 工作原理:通过 “红黑树管理监控 FD” 和 “就绪链表存储就绪事件”,实现高效 FD 管理;
- 触发模式:LT 模式简单易用,ET 模式性能更高(需配合非阻塞 IO);
- 实战要点:
- ET 模式下,FD 必须设为非阻塞,且需循环读写直到缓冲区空 / 满;
- 监听 socket 建议用 LT 模式,避免 ET 模式下 accept 漏接连接;
- 需手动管理已连接 socket 的生命周期(避免析构关闭)。
实战建议
中小规模服务(≤1000 连接):用 select/poll 即可,开发成本低;
大规模高并发服务(≥10000 连接):用 epoll ET 模式,配合非阻塞 IO 和线程池,最大化性能;
跨平台需求:用 select/poll(epoll 仅 Linux 支持),或使用封装库(如 libevent、libuv)。
988

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



