ZeroMQ: 一款高性能、异步、轻量级的消息传输库

目录

1.简介

2.核心通信模式

3.安装与使用

3.1.包管理器安装(推荐,无需编译)

3.2.源码编译(获取最新版本)

4.核心概念

5.典型场景示例

[5.1.REQ/REP(请求 - 响应)示例](#5.1.REQ/REP(请求 - 响应)示例)

[5.2.PUB/SUB(发布 - 订阅模式)示例](#5.2.PUB/SUB(发布 - 订阅模式)示例)

5.3.PUSH/PULL(任务分发模式)示例

6.进阶特性

[7.ZeroMQ vs 传统消息队列(RabbitMQ/Kafka)](#7.ZeroMQ vs 传统消息队列(RabbitMQ/Kafka))

8.注意事项

9.总结


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.安装与使用

官网:https://zeromq.org/download/

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

vcpkg: 一款免费开源的C++包管理器

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 必须先sendrecv,REP 必须先recvsend,否则进入错误状态;

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 是最佳选择。

相关推荐
superman超哥2 小时前
仓颉语言中循环语句(for/while)的深度剖析与工程实践
c语言·开发语言·c++·python·仓颉
chenyuhao20242 小时前
Linux系统编程:线程概念与控制
linux·服务器·开发语言·c++·后端
晚风(●•σ )2 小时前
【华为 ICT & HCIA & eNSP 习题汇总】——题目集25
网络·计算机网络·交换机
J ..2 小时前
C++ 中的右值引用与移动语义
c++
张人玉3 小时前
LiveCharts WPF MVVM 图表开发笔记
大数据·分布式·wpf·livecharts
乾元3 小时前
用 AI 做联动:当应用层出现问题,网络如何被“自动拉入决策回路”
运维·开发语言·网络·人工智能·ci/cd·自动化
xu_yule3 小时前
算法基础(背包问题)-完全背包
c++·算法·动态规划·完全背包
gfdhy3 小时前
【c++】素数详解:概念、定义及高效实现(判断方法 + 筛法)
开发语言·c++·算法·数学建模·ai编程
lzh200409193 小时前
Set 和 Map 深入详解及其区别
数据结构·c++