长连接不能用std::thread为每个连接开线程,因线程栈内存占用大(1–8MB/个)、调度开销高、系统线程数限制(如Linux默认几万),千级连接易崩溃;应采用事件驱动+单/多线程事件循环(如epoll/kqueue),配合boost::asio等成熟库,并严格管理连接生命周期、定时器和资源清理。

长连接为什么不能用 std::thread 一把梭
直接为每个连接开一个 std::thread,在连接数超百后就会崩溃或卡死——不是因为代码写错,而是系统线程栈默认占 1–8MB,1000 连接 ≈ 1–8GB 内存,且线程调度开销爆炸。Linux 默认 /proc/sys/kernel/threads-max 通常才几万,还没到业务瓶颈就先触顶。
真正能跑千级长连接的,得靠事件驱动 + 单线程或多线程事件循环。核心是让一个线程“同时盯住”成百上千个 socket,只在有数据、断连、可写时才处理,而不是死等某一个。
- 推荐起点:用
epoll(Linux)或kqueue(macOS/BSD),别碰select——它有 1024 FD 限制且每次都要全量扫描 - 不要自己从零写 reactor;优先用成熟封装,比如
libevent、libuv或现代 C++ 的boost::asio - 若必须手写,
epoll_ctl加EPOLLIN | EPOLLET(边缘触发)是基础,否则水平触发容易漏事件或忙轮询
如何用 boost::asio 管理千级长连接不泄漏
boost::asio 是目前 C++ 里最稳的跨平台异步 I/O 库,但它默认的 io_context + strand 模式容易让人误以为“只要用 async_read 就不会阻塞”,其实不然:连接对象生命周期没管好,照样内存暴涨、fd 耗尽、回调调用空指针。
关键不在怎么读,而在谁 owns 这个 connection。
立即学习“C++免费学习笔记(深入)”;
- 每个连接必须用
std::shared_ptr<connection>持有自身,且所有 async 回调(如async_read、async_write)都捕获该 shared_ptr,防止连接中途析构后回调还去访问 this - 关闭连接时,必须显式调用
socket_.close(),并置空所有 pending 的async_*操作(socket_.cancel()可触发 cancel handler,但 handler 里不能再发新 async 调用) - 别把
io_context::run()放在主线程里长期堵着——它会阻塞;应起独立线程池(如std::thread+io_context::work)来跑多个io_context实例,或用单个io_context配合poll_one()做非阻塞驱动
心跳和超时怎么设才不互相干扰
长连接必须保活,但 SO_KEEPALIVE 太粗放(默认 2 小时才探测)、不可控。业务层心跳 + 定时器才是正解,而难点在于:一个连接既要收包、又要发心跳、又要检测对方是否失联,三件事的时间逻辑不能串乱。
常见错误是给每个连接单独启一个 steady_timer,结果几千连接就是几千定时器,io_context 内部红黑树插入/删除压力大,延迟飙升。
- 统一用一个全局
steady_timer做 tick(比如每 100ms 触发一次),遍历所有活跃连接检查 last_activity 时间戳——O(1) 定时器 + O(N) 扫描,比 O(N) 定时器更可控 - 心跳发送用
async_write,但要加 flag 标记“这是心跳包”,避免和业务数据包混在一起导致粘包解析错乱 - 超时判断别只看 recv 时间:TCP 连接可能静默断开(如 NAT 超时、客户端拔网线),需结合 send 返回
0或errno == EPIPE+ 心跳失败双重判定
连接断开时资源清理最容易漏哪几处
你以为 shared_ptr 析构就万事大吉?实际中 70% 的 fd 泄漏和内存残留,出在三个地方:socket 关闭顺序、缓冲区未清空、以及 event loop 里残留的 pending 操作。
- 务必在
connection::close()中先调socket_.cancel(),再socket_.close(),否则 cancel handler 可能还在排队,而 socket 已失效 - 发送缓冲区(如
std::queue<std::string>)里的待发数据,在 close 前要丢弃或强制 flush;否则async_write回调里还会尝试操作已销毁的 buffer - 如果用了
strand,确保所有对该 connection 的操作都在同一条 strand 上 dispatch;跨 strand 的post若目标 connection 已销毁,shared_ptr会变空,但没人帮你判空——得自己在 lambda 开头加if (!self_) return;
高并发长连接不是堆并发数,而是让每个连接活得久、死得干净、出错有迹可循。最难的部分永远不在 accept 或 read,而在 shutdown 那 20 行代码里——多一个 if,少一个 lock,差的是线上凌晨三点的告警。


















