[转]ZeroMQ用法说明及C++示例
原文链接:https://blog.csdn.net/qq_40344790/article/details/130865273
视频讲解 :C++消息传递库ZeroMQ
比较好的C讲解:
一、ZMQ介绍
Github:https://github.com/zeromq/libzmq
ZMQ(ZeroMQ)是一种高性能的异步消息传递库,它可以在不同的进程和机器之间进行消息传递。它提供了多种传输协议、通信模式和编程语言支持,并且非常易于使用。
ZMQ 的核心思想是将网络通信抽象出来成为 socket 概念,使用不同类型的 socket 可以实现不同的消息传递模式,例如请求-应答模式、发布-订阅模式、推送-拉取模式等。ZMQ 提供了 TCP、IPC、inproc 等多种传输协议,可以根据需要选择合适的协议。
1.ZeroMQ URL格式
使用TCP协议 TCP: "tcp://<address>:<port>"
进程内通信 in-process: "inproc://<name>"
进程间通信 (Windows系统) inter-process: "ipc://<path>" (Unix系统) 或 "ipc://<name>"
多播: (使用PGM协议) "epgm://<address>:<port>"
多播: (使用UDP协议) "epub://<address>:<port>"
2.ZeroMQ 的应用领域
ZeroMQ 在许多领域得到了广泛应用,包括但不限于以下几个方面:
- 分布式系统:作为分布式系统中节点之间的通信桥梁。
- 并发编程:用于多线程或多进程之间的通信和协同。
- 实时数据处理:处理大规模数据流和事件驱动的应用。
- 科学计算:用于分布式计算和任务并行处理。
二、原理介绍
1.点对点通信模式
ZeroMQ 支持点对点通信模式,其中消息发送方和接收方之间建立直接连接。这种模式适用于一对一的通信场景,其中消息通过 ZeroMQ 套接字在发送方和接收方之间传递。
2.多对多通信模式
ZeroMQ 还支持多对多通信模式,其中多个消息发送方和接收方之间建立多个连接。这种模式适用于一对多或多对多的通信场景,其中消息可以在多个节点之间进行广播或发布订阅。
3.ZeroMQ 套接字(Socket)
ZeroMQ 使用套接字作为消息通信的端点。套接字具有多种类型,如REQ/REP、PUB/SUB、PUSH/PULL等,每种类型都有不同的通信模式和语义。
4.消息传递模式
ZeroMQ 提供了多种消息传递模式,包括请求/响应、发布/订阅、推送/拉取等。这些模式定义了消息的传递方式和顺序,开发者可以根据应用需求选择适合的模式。
5.ZeroMQ 上下文(Context)
ZeroMQ 上下文是 ZeroMQ 应用程序的入口点,它负责管理套接字和线程的创建和销毁。上下文为应用程序提供了资源管理和线程安全的机制。
- ZeroMQ 套接字(Socket):用于发送和接收消息的端点。
- ZeroMQ 上下文(Context):管理套接字和线程的创建和销毁。
- ZeroMQ 代理(Proxy):用于连接不同的套接字和路由消息。
三、消息模式及示例
1.请求-应答模式
在请求/响应模式中,一个请求者(REQ)向一个或多个响应者(REP)发送请求,响应者收到请求后发送响应。
工作流程如下:
- 请求者创建一个 REQ 套接字,并连接到一个响应者的地址。
- 响应者创建一个 REP 套接字,并绑定到一个地址。
- 请求者发送请求到 REQ 套接字。
- 响应者从 REP 套接字接收请求,并发送响应。
- 请求者接收响应。
server.cpp
1 #include <zmq.hpp> 2 #include <iostream> 3 4 int main() { 5 // 创建上下文和套接字 6 zmq::context_t context(1); 7 zmq::socket_t socket(context, zmq::socket_type::rep); 8 9 // 绑定到指定地址 10 socket.bind("tcp://*:5555"); 11 12 while (true) { 13 // 接收请求 14 zmq::message_t request; 15 socket.recv(request, zmq::recv_flags::none); 16 17 // 打印请求内容 18 std::cout << "Received request: " << std::string(static_cast<char*>(request.data()), request.size()) << std::endl; 19 20 // 发送响应 21 zmq::message_t response(5); 22 memcpy(response.data(), "World", 5); 23 socket.send(response, zmq::send_flags::none); 24 } 25 26 return 0; 27 }
client.cpp
1 #include <zmq.hpp> 2 #include <iostream> 3 4 int main() { 5 // 创建上下文和套接字 6 zmq::context_t context(1); 7 zmq::socket_t socket(context, zmq::socket_type::req); 8 9 // 连接到服务端地址 10 socket.connect("tcp://localhost:5555"); 11 12 // 发送请求 13 zmq::message_t request(5); 14 memcpy(request.data(), "Hello", 5); 15 socket.send(request, zmq::send_flags::none); 16 17 // 接收响应 18 zmq::message_t response; 19 socket.recv(response, zmq::recv_flags::none); 20 21 // 打印响应 22 std::cout << "Received response: " << std::string(static_cast<char*>(response.data()), response.size()) << std::endl; 23 24 return 0; 25 }
2.发布-订阅模式
在简单的发布/订阅模式中,一个发布者(PUB)将消息发布到一个或多个订阅者(SUB)。订阅者订阅感兴趣的主题,并接收发布者发送的消息。
工作流程如下:
- 发布者创建一个 PUB 套接字,并绑定到一个地址。
- 订阅者创建一个 SUB 套接字,并连接到发布者的地址。
- 发布者将消息发布到 PUB 套接字。
- 订阅者从 SUB 套接字接收发布者发送的消息。
publisher.cpp
1 #include <zmq.hpp> 2 #include <string> 3 #include <iostream> 4 #include <unistd.h> 5 6 int main() { 7 // 创建上下文和套接字 8 zmq::context_t context(1); 9 zmq::socket_t socket(context, zmq::socket_type::pub); 10 11 // 绑定到指定地址 12 socket.bind("tcp://*:5555"); 13 14 // 发布消息 15 int count = 0; 16 while (true) { 17 std::string topic = "Topic"; 18 std::string message = "Message " + std::to_string(count); 19 20 // 发布主题和消息 21 zmq::message_t topicMsg(topic.size()); 22 memcpy(topicMsg.data(), topic.data(), topic.size()); 23 socket.send(topicMsg, zmq::send_flags::sndmore); 24 25 zmq::message_t messageMsg(message.size()); 26 memcpy(messageMsg.data(), message.data(), message.size()); 27 socket.send(messageMsg, zmq::send_flags::none); 28 29 std::cout << "Published: " << topic << " - " << message << std::endl; 30 31 count++; 32 sleep(1); // 每秒发布一条消息 33 } 34 35 return 0; 36 }
subscriber.cpp
1 #include <zmq.hpp> 2 #include <string> 3 #include <iostream> 4 5 int main() { 6 // 创建上下文和套接字 7 zmq::context_t context(1); 8 zmq::socket_t socket(context, zmq::socket_type::sub); 9 10 // 连接到发布者地址 11 socket.connect("tcp://localhost:5555"); 12 13 // 订阅所有主题 14 socket.setsockopt(ZMQ_SUBSCRIBE, "", 0); 15 16 // 接收并处理消息 17 while (true) { 18 // 接收主题 19 zmq::message_t topicMsg; 20 socket.recv(topicMsg, zmq::recv_flags::none); 21 std::string topic(static_cast<char*>(topicMsg.data()), topicMsg.size()); 22 23 // 接收消息 24 zmq::message_t messageMsg; 25 socket.recv(messageMsg, zmq::recv_flags::none); 26 std::string message(static_cast<char*>(messageMsg.data()), messageMsg.size()); 27 28 std::cout << "Received: " << topic << " - " << message << std::endl; 29 } 30 31 return 0; 32 }
3.推送-拉取模式
演示案例可以参阅:https://dongshao.blog.csdn.net/article/details/106922554
pusher.cpp
1 #include <zmq.hpp> 2 #include <string> 3 #include <iostream> 4 #include <unistd.h> 5 6 int main() { 7 // 创建上下文和套接字 8 zmq::context_t context(1); 9 zmq::socket_t socket(context, zmq::socket_type::push); 10 11 // 绑定到指定地址 12 socket.bind("tcp://*:5555"); 13 14 // 发送消息 15 int count = 0; 16 while (true) { 17 std::string message = "Message " + std::to_string(count); 18 19 // 发送消息 20 zmq::message_t messageMsg(message.size()); 21 memcpy(messageMsg.data(), message.data(), message.size()); 22 socket.send(messageMsg, zmq::send_flags::none); 23 24 std::cout << "Pushed: " << message << std::endl; 25 26 count++; 27 sleep(1); // 每秒推送一条消息 28 } 29 30 return 0; 31 }
puller.cpp
1 #include <zmq.hpp> 2 #include <string> 3 #include <iostream> 4 5 int main() { 6 // 创建上下文和套接字 7 zmq::context_t context(1); 8 zmq::socket_t socket(context, zmq::socket_type::pull); 9 10 // 连接到推送者地址 11 socket.connect("tcp://localhost:5555"); 12 13 // 接收消息 14 while (true) { 15 zmq::message_t messageMsg; 16 socket.recv(messageMsg, zmq::recv_flags::none); 17 std::string message(static_cast<char*>(messageMsg.data()), messageMsg.size()); 18 19 std::cout << "Pulled: " << message << std::endl; 20 } 21 22 return 0; 23 }
4.进程间通信示例
多线程并发模式
ZeroMQ 提供了多线程并发模式,允许多个线程通过套接字进行消息通信。这种模式可以用于多线程环境中的并发编程。
工作流程如下:
- 多个线程创建套接字,并绑定或连接到相应的地址。
- 线程之间通过套接字发送和接收消息,实现并发通信。
分布式消息队列模式
ZeroMQ 还可以用于构建分布式消息队列系统,其中多个节点通过消息队列进行通信和协作。消息可以在不同节点之间进行传递和处理。
工作流程如下:
- 多个节点创建套接字,并连接到消息队列。
- 节点之间通过套接字发送和接收消息,实现分布式消息通信。
sender.cpp
1 #include <zmq.hpp> 2 #include <string> 3 #include <iostream> 4 5 int main() { 6 // 创建上下文和套接字 7 zmq::context_t context(1); 8 zmq::socket_t socket(context, zmq::socket_type::push); 9 10 // 连接到接收者的地址 11 socket.connect("ipc:///tmp/zmq_ipc_example"); 12 13 // 发送消息 14 std::string message = "Hello from sender!"; 15 zmq::message_t messageMsg(message.size()); 16 memcpy(messageMsg.data(), message.data(), message.size()); 17 socket.send(messageMsg, zmq::send_flags::none); 18 19 return 0; 20 }
receiver.cpp
1 #include <zmq.hpp> 2 #include <string> 3 #include <iostream> 4 5 int main() { 6 // 创建上下文和套接字 7 zmq::context_t context(1); 8 zmq::socket_t socket(context, zmq::socket_type::pull); 9 10 // 绑定到指定地址 11 socket.bind("ipc:///tmp/zmq_ipc_example"); 12 13 // 接收消息 14 zmq::message_t messageMsg; 15 socket.recv(messageMsg, zmq::recv_flags::none); 16 std::string message(static_cast<char*>(messageMsg.data()), messageMsg.size()); 17 18 std::cout << "Received message: " << message << std::endl; 19 20 return 0; 21 }
5.Router-Dealer消息路由
Router 模式是 ZeroMQ 中的一种复杂通信模式,用于创建灵活的消息路由系统。在 Router 模式下,ROUTER套接字可以接收来自多个客户端的请求,并将这些请求分发给多个工作线程或服务DEALER套接字。
Router-Dealer 通信模式可以用于实现负载均衡、消息路由和复杂的请求-响应模式,非常适合需要多个客户端和多个服务端进行交互的场景。
路由模式用于将消息从一个节点路由到另一个节点,通常在分布式系统中使用。消息经过一系列的路由节点,每个节点根据消息的目的地进行路由。
工作流程如下:
- 路由节点创建一个 ROUTER 套接字,并绑定到一个地址。
- 消息发送方将消息发送到 ROUTER 套接字,指定消息的目的地。
- 路由节点根据消息的目的地将消息转发到下一个节点。
- 最终的路由节点将消息发送到目的地。
server.cpp
1 #include <zmq.hpp> 2 #include <iostream> 3 #include <string> 4 5 int main() { 6 zmq::context_t context(1); 7 zmq::socket_t router(context, ZMQ_ROUTER); 8 9 // 绑定到端口 5555 10 router.bind("tcp://*:5555"); 11 12 while (true) { 13 zmq::message_t identity; 14 zmq::message_t message; 15 16 // 接收来自 Dealer 的身份和消息 17 router.recv(&identity); 18 router.recv(&message); 19 20 std::string identity_str = std::string(static_cast<char*>(identity.data()), identity.size()); 21 std::string message_str = std::string(static_cast<char*>(message.data()), message.size()); 22 23 std::cout << "Received message from " << identity_str << ": " << message_str << std::endl; 24 25 // 回复消息给 Dealer 26 std::string reply_message = "Hello from Router"; 27 zmq::message_t reply(reply_message.size()); 28 memcpy(reply.data(), reply_message.c_str(), reply_message.size()); 29 30 router.send(identity, ZMQ_SNDMORE); 31 router.send(reply); 32 } 33 34 return 0; 35 }
client.cpp
1 // g++ -o client client.cpp -lzmq -lpthread 2 #include <zmq.hpp> 3 #include <iostream> 4 #include <string> 5 #include <thread> 6 7 void client_task() { 8 zmq::context_t context(1); 9 zmq::socket_t dealer(context, ZMQ_DEALER); 10 11 // 连接到 Router 12 dealer.connect("tcp://localhost:5555"); 13 14 std::string identity = "Client1"; 15 16 // 设置 Dealer 的身份 17 dealer.setsockopt(ZMQ_IDENTITY, identity.c_str(), identity.size()); 18 19 // 发送消息给 Router 20 std::string request_message = "Hello from Dealer"; 21 zmq::message_t request(request_message.size()); 22 memcpy(request.data(), request_message.c_str(), request_message.size()); 23 24 dealer.send(request); 25 26 // 接收 Router 的回复 27 zmq::message_t reply; 28 dealer.recv(&reply); 29 30 std::string reply_str = std::string(static_cast<char*>(reply.data()), reply.size()); 31 32 std::cout << "Received reply from Router: " << reply_str << std::endl; 33 } 34 35 int main() { 36 std::thread client_thread(client_task); 37 client_thread.join(); 38 39 return 0; 40 }