目录
[5.1.REQ/REP(请求 - 响应)示例](#5.1.REQ/REP(请求 - 响应)示例)
[5.2.PUB/SUB(发布 - 订阅模式)示例](#5.2.PUB/SUB(发布 - 订阅模式)示例)
[7.ZeroMQ vs 传统消息队列(RabbitMQ/Kafka)](#7.ZeroMQ vs 传统消息队列(RabbitMQ/Kafka))
1.简介
ZeroMQ(简称 ZMQ)是一款高性能、异步、轻量级的消息传输库,并非传统意义上的消息队列(如 RabbitMQ、Kafka)------ 它无中心 Broker(可自选实现),专注于底层消息传递机制,支持多种通信模式和传输协议,广泛用于低延迟、分布式 / 并发系统的消息通信。
- 不是 "消息队列产品",而是 "消息传输框架 / 库";
- 无中心节点(默认无需 Broker),直接在进程 / 线程 / 网络节点间通信;
- 封装了复杂的网络 I/O、异步处理逻辑,提供简洁的套接字接口。
2.核心通信模式
ZMQ 的核心价值在于预定义的通信模式,每种模式适配特定场景,需严格匹配套接字类型。
1.请求 - 响应(REQ/REP)
- 模式逻辑:REQ 端(客户端)发送请求 → 必须等待 REP 端(服务端)响应;REP 端必须先接收请求,再发送响应,严格 "一问一答"。
- 特点:简单 RPC 调用场景,不支持并发处理(REQ 一次只能发一个请求,REP 一次只能处理一个请求)。
- 适用场景:简单的同步 RPC 调用、客户端 - 服务端单次交互。
2.发布 - 订阅(PUB/SUB)
- 模式逻辑:PUB 端(发布者)广播消息,SUB 端(订阅者)按需订阅主题(前缀匹配),消息单向流动,PUB 不关心 SUB 是否接收。
- 特点 :
- 一对多广播,SUB 会丢失 "订阅前" 的消息(默认无持久化);
- SUB 默认过滤空主题,需显式订阅
""才能接收所有消息; - 慢订阅者可能导致 PUB 队列溢出,需结合 HWM(高水位线)控制。
- 适用场景:实时日志分发、聊天室、行情推送、系统通知。
3.推拉(PUSH/PULL)
- 模式逻辑 :PUSH 端推送消息,PULL 端拉取消息,ZMQ 自动实现轮询负载均衡(多 PULL 端时,消息均匀分发)。
- 特点:多对多通信,消息单向,适合任务分发与结果汇总。
- 适用场景:分布式计算(任务分发到多个工作节点)、日志收集、流水线处理。
4.独占对(PAIR/PAIR)
- 模式逻辑:严格一对一双向通信,仅支持两个端点,无负载均衡 / 广播。
- 特点:简单双向通道,不支持自动重连,慎用跨网络场景。
- 适用场景:线程间 / 进程间专用通信通道。
5.路由器 - 经销商(ROUTER/DEALER)
- 高级模式:REQ/REP 的底层实现,ROUTER 为每个连接的节点分配唯一标识,支持异步 REQ/REP、多路复用、反向通信。
- 特点:突破 REQ/REP 的 "一问一答" 限制,可实现并发请求响应、自定义路由。
- 适用场景:构建复杂代理 / 网关、微服务通信、多路 RPC。
3.安装与使用
3.1.包管理器安装(推荐,无需编译)
Ubuntu/Debian
cpp
# 更新包列表
sudo apt update
# 安装核心库(含头文件和动态库)
sudo apt install -y libzmq3-dev
# 验证安装(查看库版本)
pkg-config --modversion libzmq # 输出如 4.3.4 则成功
CentOS/RHEL
cpp
# 先安装EPEL源(CentOS 7/8)
sudo yum install -y epel-release
# 安装ZeroMQ开发包
sudo yum install -y zeromq-devel
# 验证
pkg-config --modversion libzmq
Windows 推荐用vcpkg自动安装(无需手动配置路径),也可下载预编译库手动配置。
安装vcpkg
cpp
# 打开PowerShell(管理员模式),克隆vcpkg
git clone https://github.com/microsoft/vcpkg.git
cd vcpkg
# 编译vcpkg(自动下载依赖)
./bootstrap-vcpkg.bat
# 可选:将vcpkg加入系统环境变量(方便全局调用)
setx PATH "%PATH%;%cd%"
安装 ZeroMQ
cpp
# 安装x64版本的ZeroMQ(适配VS 64位工程)
vcpkg install zeromq:x64-windows
# 若需静态库,安装:vcpkg install zeromq:x64-windows-static
vcpkg 支持自动集成到 Visual Studio:
cpp
# 集成到VS(管理员权限)
vcpkg integrate install
3.2.源码编译(获取最新版本)
若包管理器版本过旧,可手动编译最新版(以 4.3.5 为例):
cpp
# 安装编译依赖
sudo apt install -y libtool pkg-config build-essential autoconf automake
# 克隆源码
git clone https://github.com/zeromq/libzmq.git
cd libzmq
# 初始化编译配置
./autogen.sh
./configure --prefix=/usr/local # 安装到/usr/local(系统默认库路径)
# 编译(-j后接CPU核心数,加速编译)
make -j$(nproc)
# 安装
sudo make install
# 刷新动态库缓存(关键!否则运行时找不到库)
sudo ldconfig
# 验证
pkg-config --modversion libzmq # 输出版本号则成功
CMake编译:
cpp
# 在libzmq目录下创建build目录(存放编译中间文件和最终库)
mkdir build
cd build
cpp
# 生成VS2022的64位工程文件(核心参数说明见下文)
cmake .. -G "Visual Studio 17 2022" -A x64 ^
-DCMAKE_INSTALL_PREFIX=D:\zmq-install ^ # 编译后安装路径(自定义)
-DZMQ_BUILD_SHARED=ON ^ # 编译动态库(ON=动态,OFF=静态)
-DZMQ_BUILD_STATIC=ON ^ # 同时编译静态库(可选)
-DZMQ_BUILD_TESTS=OFF ^ # 关闭测试编译(加速编译)
-DCMAKE_BUILD_TYPE=Release # 发布模式(Debug=调试,Release=发布)
#
cmake --build . --config Release
4.核心概念
1.上下文(Context) :ZMQ 的核心管理对象,负责创建套接字、管理 I/O 线程。一个应用通常只需一个 Context(可共享)。
2.套接字(Socket):类似 BSD 套接字,但更高层,绑定通信模式(如 ZMQ_REQ),是消息收发的核心载体。
3.地址格式:
tcp://<host>:<port>:跨网络通信(如tcp://*:5555绑定所有网卡的 5555 端口);ipc://<path>:进程间通信(如ipc:///tmp/zmq.sock);inproc://<name>:线程间通信(最快,仅限同一进程内)。
4.高水位线(HWM):每个套接字的消息队列上限(默认 1000),达到后会阻塞发送或丢弃消息,防止内存溢出。
5.背压(Backpressure):接收端处理速度慢于发送端时,ZMQ 自动缓存消息并触发 HWM 机制,避免数据丢失 / 内存暴涨。
5.典型场景示例
5.1.REQ/REP(请求 - 响应)示例
服务端(REP)
cpp
#include <zmq.hpp>
#include <iostream>
#include <string>
int main() {
try {
// 1. 创建上下文(I/O 线程数 1)
zmq::context_t ctx(1);
// 2. 创建 REP 类型套接字
zmq::socket_t sock(ctx, zmq::socket_type::rep);
// 3. 绑定地址(* 表示绑定所有网卡)
sock.bind("tcp://*:5555");
std::cout << "服务端启动,等待请求..." << std::endl;
while (true) {
// 4. 接收客户端请求(阻塞)
zmq::message_t request;
// recv 会自动填充 message_t 的数据和长度
auto recv_res = sock.recv(request, zmq::recv_flags::none);
if (!recv_res) {
std::cerr << "接收请求失败" << std::endl;
continue;
}
// 解析请求内容
std::string req_str(static_cast<char*>(request.data()), request.size());
std::cout << "收到请求:" << req_str << std::endl;
// 5. 发送响应
std::string resp_str = "World";
zmq::message_t response(resp_str.size());
memcpy(response.data(), resp_str.c_str(), resp_str.size());
sock.send(response, zmq::send_flags::none);
}
} catch (const zmq::error_t& e) {
std::cerr << "ZeroMQ 错误:" << e.what() << " (错误码:" << e.num() << ")" << std::endl;
return 1;
}
return 0;
}
客户端(REQ)
cpp
#include <zmq.hpp>
#include <iostream>
#include <string>
int main() {
try {
zmq::context_t ctx(1);
zmq::socket_t sock(ctx, zmq::socket_type::req);
// 连接服务端(客户端用 connect,服务端用 bind)
sock.connect("tcp://localhost:5555");
std::cout << "客户端已连接服务端" << std::endl;
// 发送请求
std::string req_str = "Hello";
zmq::message_t request(req_str.size());
memcpy(request.data(), req_str.c_str(), req_str.size());
sock.send(request, zmq::send_flags::none);
// 接收响应
zmq::message_t response;
sock.recv(response, zmq::recv_flags::none);
std::string resp_str(static_cast<char*>(response.data()), response.size());
std::cout << "收到响应:" << resp_str << std::endl;
// RAII 自动释放 ctx 和 sock,无需手动关闭
} catch (const zmq::error_t& e) {
std::cerr << "ZeroMQ 错误:" << e.what() << " (错误码:" << e.num() << ")" << std::endl;
return 1;
}
return 0;
}
5.2.PUB/SUB(发布 - 订阅模式)示例
发布端(PUB)
cpp
#include <zmq.hpp>
#include <iostream>
#include <string>
#include <chrono>
#include <thread>
int main() {
try {
zmq::context_t ctx(1);
zmq::socket_t sock(ctx, zmq::socket_type::pub);
sock.bind("tcp://*:5556");
int count = 0;
while (true) {
// 发送带主题的消息(主题+内容,空格分隔)
std::string msg = "weather temp " + std::to_string(25 + count % 10);
zmq::message_t message(msg.size());
memcpy(message.data(), msg.c_str(), msg.size());
sock.send(message, zmq::send_flags::none);
std::cout << "发布消息:" << msg << std::endl;
count++;
std::this_thread::sleep_for(std::chrono::seconds(1));
}
} catch (const zmq::error_t& e) {
std::cerr << "错误:" << e.what() << std::endl;
return 1;
}
return 0;
}
订阅端(SUB)
cpp
#include <zmq.hpp>
#include <iostream>
#include <string>
int main() {
try {
zmq::context_t ctx(1);
zmq::socket_t sock(ctx, zmq::socket_type::sub);
sock.connect("tcp://localhost:5556");
// 订阅主题:仅接收以 "weather" 开头的消息(前缀匹配)
// 若订阅空字符串 "",则接收所有消息
sock.set(zmq::sockopt::subscribe, "weather");
std::cout << "订阅端启动,接收 weather 主题消息..." << std::endl;
while (true) {
zmq::message_t message;
sock.recv(message, zmq::recv_flags::none);
std::string msg_str(static_cast<char*>(message.data()), message.size());
std::cout << "收到订阅消息:" << msg_str << std::endl;
}
} catch (const zmq::error_t& e) {
std::cerr << "错误:" << e.what() << std::endl;
return 1;
}
return 0;
}
5.3.PUSH/PULL(任务分发模式)示例
任务分发端(PUSH)
cpp
#include <zmq.hpp>
#include <iostream>
#include <string>
#include <chrono>
#include <thread>
int main() {
try {
zmq::context_t ctx(1);
zmq::socket_t sock(ctx, zmq::socket_type::push);
sock.bind("tcp://*:5557");
// 分发 10 个任务
for (int i = 0; i < 10; ++i) {
std::string task = "Task " + std::to_string(i);
zmq::message_t message(task.size());
memcpy(message.data(), task.c_str(), task.size());
sock.send(message, zmq::send_flags::none);
std::cout << "发送任务:" << task << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(500));
}
} catch (const zmq::error_t& e) {
std::cerr << "错误:" << e.what() << std::endl;
return 1;
}
return 0;
}
工作端(PULL)
cpp
#include <zmq.hpp>
#include <iostream>
#include <string>
int main() {
try {
zmq::context_t ctx(1);
zmq::socket_t sock(ctx, zmq::socket_type::pull);
sock.connect("tcp://localhost:5557");
std::cout << "工作端启动,等待任务..." << std::endl;
while (true) {
zmq::message_t message;
sock.recv(message, zmq::recv_flags::none);
std::string task(static_cast<char*>(message.data()), message.size());
std::cout << "处理任务:" << task << std::endl;
}
} catch (const zmq::error_t& e) {
std::cerr << "错误:" << e.what() << std::endl;
return 1;
}
return 0;
}
6.进阶特性
1.多帧消息(Multi-Part Messages)
ZeroMQ 支持将消息拆分为多个帧(无需手动拼接),适合 "主题 + 内容" 分离的场景:
cpp
// 发送多帧消息(主题帧 + 内容帧)
zmq::message_t frame1("weather", 7); // 主题帧
zmq::message_t frame2("temp 28", 6); // 内容帧
// sndmore 标志表示后续还有帧
sock.send(frame1, zmq::send_flags::sndmore);
sock.send(frame2, zmq::send_flags::none);
// 接收多帧消息
zmq::message_t frame;
while (true) {
sock.recv(frame, zmq::recv_flags::none);
std::string frame_str(static_cast<char*>(frame.data()), frame.size());
std::cout << "帧内容:" << frame_str << std::endl;
// 检查是否还有后续帧
if (!sock.get(zmq::sockopt::rcvmore)) {
break;
}
}
2.非阻塞 I/O
通过 zmq::send_flags::dontwait/zmq::recv_flags::dontwait 实现非阻塞收发,避免线程阻塞:
cpp
// 非阻塞发送
std::string msg = "non-block send";
zmq::message_t message(msg.size());
memcpy(message.data(), msg.c_str(), msg.size());
try {
sock.send(message, zmq::send_flags::dontwait);
} catch (const zmq::error_t& e) {
if (e.num() == EAGAIN) {
std::cerr << "发送队列已满,非阻塞发送失败" << std::endl;
} else {
std::cerr << "发送错误:" << e.what() << std::endl;
}
}
// 非阻塞接收
zmq::message_t recv_msg;
try {
auto res = sock.recv(recv_msg, zmq::recv_flags::dontwait);
if (!res) {
std::cerr << "无可用消息(非阻塞)" << std::endl;
} else {
// 处理消息
}
} catch (const zmq::error_t& e) {
if (e.num() != EAGAIN) {
std::cerr << "接收错误:" << e.what() << std::endl;
}
}
3.轻量级代理(Proxy)
C++ 实现 ZeroMQ 代理(Broker),解决 PUB/SUB 慢订阅、REQ/REP 多路复用问题:
cpp
#include <zmq.hpp>
#include <iostream>
int main() {
try {
zmq::context_t ctx(1);
// 前端:接收 PUB 消息(XSUB)
zmq::socket_t frontend(ctx, zmq::socket_type::xsub);
frontend.bind("tcp://*:5558");
// 后端:转发给 SUB(XPUB)
zmq::socket_t backend(ctx, zmq::socket_type::xpub);
backend.bind("tcp://*:5559");
std::cout << "代理启动,前端 5558,后端 5559" << std::endl;
// 启动代理(阻塞,直到进程退出)
zmq::proxy(frontend, backend, nullptr);
} catch (const zmq::error_t& e) {
std::cerr << "代理错误:" << e.what() << std::endl;
return 1;
}
return 0;
}
7.ZeroMQ vs 传统消息队列(RabbitMQ/Kafka)
| 维度 | ZeroMQ | RabbitMQ/Kafka |
|---|---|---|
| 架构 | 无中心 Broker(可选自建) | 有中心 Broker |
| 部署复杂度 | 极低(仅链接库) | 高(需部署服务集群) |
| 消息持久化 | 无(需自行实现) | 原生支持(磁盘 / 日志) |
| 可靠性 | 低(无 ACK、无重试) | 高(ACK、持久化、重试) |
| 吞吐量 | 极高(内存级) | 高(Broker 有开销) |
| 延迟 | 微秒级 | 毫秒级 |
| 适用场景 | 低延迟、实时通信、分布式计算 | 高可靠、持久化、大规模消息分发 |
8.注意事项
1.套接字类型匹配:如 REQ 只能与 REP 通信,PUB 只能与 SUB 通信,否则无法正常工作;
2.REQ/REP 限制 :REQ 必须先send再recv,REP 必须先recv再send,否则进入错误状态;
3.SUB 初始延迟:SUB 连接 PUB 后需短暂时间完成订阅,可能丢失初期消息(可延迟发布或用代理);
4.上下文销毁 :zmq_ctx_term()会阻塞,直到所有套接字关闭,确保消息处理完毕;
5.线程安全 :套接字不线程安全,每个线程应创建独立套接字,上下文可共享;
zmq::socket_t不线程安全 :禁止多个线程同时调用同一个 socket 的send/recv;zmq::context_t线程安全:可在多线程间共享,建议全局唯一;- 多线程通信:每个线程创建独立的
socket_t,通过inproc://地址通信(最快)。
6.资源管理
- 利用 RAII:
context_t/socket_t析构时自动释放资源,无需手动调用zmq_ctx_term/zmq_close; - 避免提前销毁:确保
socket_t先于context_t销毁(RAII 已保证,无需手动干预)。
7.错误处理
- 捕获
zmq::error_t异常:所有 ZeroMQ 操作都可能抛出该异常,需针对性处理(如EAGAIN是非阻塞正常情况); - 检查返回值:
recv/send的返回值需验证,尤其是非阻塞场景。
9.总结
关键特性总结:
| 特性 | 说明 |
|---|---|
| 多通信模式 | 内置 REQ/REP、PUB/SUB、PUSH/PULL 等核心模式,适配不同业务场景 |
| 多传输协议 | 支持 TCP(跨网络)、IPC(进程间)、inproc(线程间)、PGM/EPGM(组播) |
| 异步非阻塞 I/O | 基于事件驱动,高并发处理,避免线程阻塞 |
| 轻量级 | 体积小、部署简单,无外部依赖(仅需链接库) |
| 多语言绑定 | 支持 C/C++、Python、Java、Go、Node.js 等主流语言 |
| 背压(Backpressure) | 自动处理发送 / 接收速率不匹配问题,通过高水位线(HWM)防止内存溢出 |
| 零拷贝消息 | 消息传输避免冗余内存拷贝,提升性能 |
| 自动重连 | 默认支持断开连接后的自动重连,可自定义重连间隔 |
ZeroMQ 是低延迟、轻量级、无中心的消息传输库,核心优势是灵活的通信模式和极致的性能,适合实时性要求高、无需强持久化的场景:
- 低延迟的进程 / 线程间通信(
inproc:///ipc://); - 分布式计算的任务分发(PUSH/PULL);
- 实时消息推送(PUB/SUB);
- 轻量级 RPC 调用(REQ/REP/ROUTER/DEALER)。
若需高可靠、持久化、集群化的消息分发,建议选择 RabbitMQ/Kafka;若追求轻量、低延迟、无依赖,ZeroMQ 是最佳选择。