应全局唯一初始化zmq::context_t context(1),再用其创建socket;req/rep须严格send→recv或recv→send;pub/sub需订阅后发消息并设超时;发结构体或protobuf需确保内存连续且预分配缓冲区;务必显式close socket和context。

怎么初始化 ZeroMQ 上下文和套接字?
一个进程只该有一个 zmq::context_t,它管理 I/O 线程和底层资源;多建几个不仅没用,还会抢端口、耗句柄。常见错误是每次发消息都 new 一个 context,结果跑几天就 Too many open files。
-
zmq::context_t context(1):1 个 I/O 线程够大多数单机场景;高吞吐服务可试4,但别盲目堆核数,ZeroMQ 的 I/O 线程不等于 CPU 核心数 - 套接字必须从 context 构造:
zmq::socket_t sock(context, ZMQ_REQ),不能脱离 context 单独 new - 线程安全边界要记清:context 可被多线程共享,但 socket 绝对不能跨线程复用(除非显式启用
ZMQ_THREAD_SAFE,且 cppzmq 版本 ≥ 4.10)
REQ/REP 模式为什么总卡死?
这不是 bug,是协议强制要求的交互节奏。REQ 客户端发完不立刻 recv,或 REP 服务端收完不马上 send,连接就会挂起——ZeroMQ 不会报错,只是永远等下去。
- 顺序铁律:
send → recv(客户端),recv → send(服务端),一步都不能跳 - 别在 REP 端做耗时操作(比如读文件、调远程 API),否则整个队列阻塞;真要异步,改用
ZMQ_ROUTER/ZMQ_DEALER配合线程池 - 超时必须自己加:
sock.setsockopt(ZMQ_RCVTIMEO, 5000),否则 recv 会永久阻塞
PUB/SUB 模式收不到第一条消息?
这是 ZeroMQ 最常被吐槽的“特性”:SUB 套接字只接收建立连接之后发布的消息,之前所有广播全丢。不是丢包,是设计如此。
- 订阅前务必调用
sock.setsockopt(ZMQ_SUBSCRIBE, "", 0)(空字符串表示订阅所有主题),否则默认不收任何消息 - 如果需要“历史消息”,得靠外部机制(比如先连 Redis 拉快照,再连 PUB);ZeroMQ 本身不存消息
- TCP 连接建立有毫秒级延迟,PUB 端 bind 后立刻发消息,SUB 还没 connect 完,那条就没了——加
std::this_thread::sleep_for(10ms)不是优雅解法,而是暴露了架构依赖时序
发送结构体或 Protobuf 怎么保证二进制安全?
ZeroMQ 传的是裸字节,不关心内容格式,所以你传什么它就发什么。问题出在序列化和内存生命周期上。
立即学习“C++免费学习笔记(深入)”;
- 发结构体:确保没指针、没虚函数、字段内存连续;用
zmq::message_t msg(&data, sizeof(data)),别传栈变量地址后就 return - 发 Protobuf:先
SerializeToArray(buf, size)到预分配缓冲区,再传zmq::message_t msg(buf, len);别直接传SerializeAsString()返回的临时 string.data() - 多段消息(如 header+body)必须用
ZMQ_SNDMORE:sock.send(header, ZMQ_SNDMORE); sock.send(body);接收端靠msg.more()判断是否继续 recv
最容易被忽略的是资源释放:socket 忘 close 会占 TCP 端口和 fd,context 忘 close 会导致整个进程残留 I/O 线程。哪怕程序只跑几秒,也得写 sock.close(); context.close();——这不是防御性编程,是 ZeroMQ 的契约。











