uwebsockets是c++中轻量、高性能且api直白的websocket服务器库,自带事件循环,支持linux/macos原生编译,通过app实例即可实现广播;需主动订阅主题、合理管理连接与内存,并用publish而非手动遍历发送以保障安全与性能。

用 uWebSockets 快速启动 WebSocket 服务端
不用自己从头封装 libwebsockets 或啃 RFC 6455,uWebSockets 是目前 C++ 里最轻量、性能最好且 API 最直白的选项。它自带事件循环、无需额外线程管理,一个 App 实例就能跑通广播逻辑。
注意:它不支持 Windows 原生(需 WSL 或 MSVC+WSL2),macOS 和 Linux 直接编译通过。安装时别用系统包管理器装旧版,直接 clone 官方 GitHub 仓库,make 编译后链接 libuWS.a 即可。
- 监听端口用
app.listen(8080, ...),别漏掉回调里的res->end()处理 HTTP 升级请求 - 连接建立后,
ws->subscribe("broadcast")是关键——所有发到该主题的消息都会被广播,但订阅必须在onConnection里主动调用,不是自动的 -
ws->publish("broadcast", message)发送二进制或文本都行,但要注意:如果message是局部std::string,必须用.data()+.length()传,不能传引用,否则 publish 异步执行时内存可能已释放
如何安全地广播消息而不阻塞事件循环
uWebSockets 默认所有回调都在单线程事件循环中执行,publish 是异步非阻塞的,但如果你在回调里做耗时操作(比如 JSON 序列化、数据库查询),整个服务就卡住。广播本身不慢,慢的是你塞进去的前置逻辑。
- 用
nlohmann::json构造消息时,尽量复用json对象,避免每次 new 一堆临时 string - 不要在
onMessage里直接调publish后跟日志打印——std::cout在高并发下有锁竞争,换成无锁日志库(如spdlog的 async logger)或写入 ring buffer - 如果真要处理复杂业务逻辑,把数据丢进
std::thread或线程池,处理完再用app.getLoop()->defer(...)回主线程调publish,否则跨线程调ws->publish会 crash
客户端连接数暴涨时,怎么防止内存泄漏和 fd 耗尽
默认配置下,每个 WebSocket 连接占约 16KB 内存(含缓冲区),Linux 默认单进程打开文件数限制是 1024。一旦连接数超限,listen 会失败,新连接直接被拒,错误日志里看不到明显提示,只看到 onConnection 回调不触发。
- 启动前用
setrlimit(RLIMIT_NOFILE, &rlimit)主动提权,或者改系统配置:echo "* soft nofile 65536" >> /etc/security/limits.conf - 务必实现
onDisconnection回调,并在里面调ws->unsubscribe("broadcast")——不取消订阅,连接断开后残留的订阅关系仍占内存 - 检查
ws->getBufferedAmount(),如果持续 > 64KB,说明客户端接收太慢,应主动ws->close()断连,否则发送缓冲区堆积导致 OOM
为什么用 publish 而不用遍历 ws 列表手动 send
手写循环调 ws->send() 看似可控,实际错在三处:一是无法利用 uWebSockets 内置的批量发送优化;二是容易漏掉刚连上还没完成 upgrade 的 socket;三是并发修改连接列表时没加锁,std::vector 迭代中 erase 会崩溃。
-
publish底层做了连接状态过滤,只对已就绪且订阅了主题的连接发包,省去状态判断 - 它内部用了零拷贝路径(mmap + sendfile 类似机制),比反复调
send快 2–3 倍,尤其在千人以上广播时差异明显 - 如果需要差异化推送(比如只推给某类用户),别改 publish 逻辑,而是用多个主题:例如
"user_type_a"和"user_type_b",让客户端按需subscribe
真正麻烦的从来不是“怎么发”,而是“谁该收到”和“发不出时怎么办”。主题粒度、连接生命周期管理、背压控制——这些才是上线后咬人的地方。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











