最近项目中需要用到zmq通信,本文中提供了几个demo,便于快速熟悉使用zmq。
1.Ubuntu下利用cmake编译zeromq库
https://blog.csdn.net/fangye945a/article/details/84845325
https://blog.csdn.net/locahuang/article/details/116011417
2.使用zmq进行通信
ZMQ将消息通信分成4种模型:
- 一对一结对模型(Exclusive-Pair),可以认为是一个TCP Connection,但是TCP Server只能接受一个连接。数据可以双向流动,这点不同于后面的请求回应模型。
- 请求回应模型(Request-Reply),由Client发起请求,并由Server响应,跟一对一结对模型的区别在于可以有多个Client。
- 发布订阅模型(Publish-Subscribe),Publish端单向分发数据,且不关心是否把全部信息发送给Subscribe端。如果Publish端开始发布信息时,Subscribe端尚未连接进来,则这些信息会被直接丢弃。Subscribe端只能接收,不能反馈,且在Subscribe端消费速度慢于Publish端的情况下,会在Subscribe端堆积数据。
- 管道模型(Push-Pull),从 PUSH 端单向的向 PULL 端单向的推送数据流。如果有多个PULL端同时连接到PUSH端,则PUSH端会在内部做一个负载均衡,采用平均分配的算法,将所有消息均衡发布到PULL端上。与发布订阅模型相比,管道模型在没有消费者的情况下,发布的消息不会被消耗掉;在消费者能力不够的情况下,能够提供多消费者并行消费解决方案。该模型主要用于多任务并行。
这4种模型总结出了通用的网络通信模型,在实际中可以根据应用需要,组合其中的2种或多种模型来形成自己的解决方案。
zmq通信示例
以下是一个zmq服务端和zmq客户端的通信示例
服务端:
#include <iostream>
#include <zmq.hpp>
int main(int argc, char ** argv)
{
std::cout << "Server is running..." << std::endl;
zmq::context_t context(1);
zmq::socket_t socket (context, ZMQ_REP);
socket.bind ("tcp://172.168.2.130:5555");
zmq::message_t request;
socket.recv (&request);
std::string req = std::string(static_cast<char *>(request.data()), request.size());
std::cout << "Received message: " << req <<std::endl;
std::string send_str = "OK";
zmq::message_t reply(send_str.size());
memcpy(reply.data(), send_str.c_str(), send_str.size());socket.send(reply);
context.close();
socket.close();
return 0;
}
客户端:
#include <iostream>
#include <zmq.hpp>
int main(int argc, char ** argv)
{
zmq::context_t zmq_context_;
zmq::socket_t zmq_socket_;
zmq_context_ = zmq::context_t(1);
zmq_socket_ = zmq::socket_t(zmq_context_, ZMQ_REQ);
board_addr_ = "tcp://172.168.2.130:5555";
zmq_socket_.connect(board_addr_);
// send
std::string send_str = "hello, I'm the client!";
zmq::message_t request(send_str.size());
memcpy(request.data(), send_str.c_str(), send_str.size());
zmq_socket_.send(request);
// recv
zmq::message_t reply;
zmq_socket_.recv(&reply);
std::string replystr =
std::string(static_cast<char *>(reply.data()), reply.size());
std::cout << "Receive message: " << replystr << std::endl;
return 0;
}
zmq_recv()函数的len参数指定接收buf的最大长度,超出部分会被截断,少于的部分会用空字符填满,函数返回的值是接收到的字节数,返回-1表示出错。
3.zmq通信实现传输自定义的数据类型
在zmq通信中,可以使用序列化和反序列化技术将自定义的数据类型转换为字节流进行传输。常用的序列化和反序列化库有protobuf、msgpack、json等。
以下是一个基于json的例子:
服务端:
#include <zmq.hpp>
#include <nlohmann/json.hpp> // json库
#include <iostream>
using json = nlohmann::json;
int main() {
zmq::context_t context(1);
zmq::socket_t socket(context, zmq::socket_type::rep);
socket.bind("tcp://*:5555");
while (true) {
zmq::message_t request;
socket.recv(&request);
json j = json::parse(static_cast<char*>(request.data()));
std::string name = j["name"];
int age = j["age"];
std::cout << "Received person: " << name << ", " << age << std::endl;
json reply;
reply["name"] = "Server";
reply["age"] = 30;
zmq::message_t replyMsg(reply.dump().size());
memcpy(replyMsg.data(), reply.dump().c_str(), reply.dump().size());
socket.send(replyMsg);
}
return 0;
}
客户端:
#include <zmq.hpp>
#include <nlohmann/json.hpp> // json库
#include <iostream>
using json = nlohmann::json;
int main() {
zmq::context_t context(1);
zmq::socket_t socket(context, zmq::socket_type::req);
socket.connect("tcp://localhost:5555");
json person;
person["name"] = "Client";
person["age"] = 20;
zmq::message_t request(person.dump().size());
memcpy(request.data(), person.dump().c_str(), person.dump().size());
socket.send(request);
zmq::message_t reply;
socket.recv(&reply);
json serverPerson = json::parse(static_cast<char*>(reply.data()));
std::string name = serverPerson["name"];
int age = serverPerson["age"];
std::cout << "Received person from server: " << name << ", " << age << std::endl;
return 0;
}
在上面的例子中,我们使用了json库来定义了一个Person类型,并在服务端和客户端中使用该类型进行通信。在服务端中,接收到客户端发送的Person类型数据后,将其解析为json对象,并打印出其name和age属性。然后创建一个新的json对象作为回复,并将其序列化为字符串发送给客户端。
在客户端中,创建一个json对象,并将其序列化为字符串发送给服务端。然后接收服务端的回复,并将其解析为json对象,并打印出其name和age属性。
4.使用zmq实现客户端和服务端之间的文件传输功能
服务端代码:
#include <zmq.hpp>
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
using namespace std;
int main()
{
zmq::context_t context(1);
zmq::socket_t socket(context, ZMQ_REP);
socket.bind("tcp://*:5555");
while (true) {
zmq::message_t request;
socket.recv(&request);
string filename = string(static_cast<char*>(request.data()), request.size());
cout << "Received request for file: " << filename << endl;
ifstream file(filename, ios::binary);
if (!file) {
cout << "File not found: " << filename << endl;
zmq::message_t reply(2);
memcpy(reply.data(), "NF", 2);
socket.send(reply);
continue;
}
stringstream buffer;
buffer << file.rdbuf();
string filedata = buffer.str();
zmq::message_t reply(filedata.size());
memcpy(reply.data(), filedata.c_str(), filedata.size());
socket.send(reply);
}
return 0;
}
客户端代码:
#include <zmq.hpp>
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
using namespace std;
int main()
{
zmq::context_t context(1);
zmq::socket_t socket(context, ZMQ_REQ);
socket.connect("tcp://localhost:5555");
string filename = "test.txt";
zmq::message_t request(filename.size());
memcpy(request.data(), filename.c_str(), filename.size());
socket.send(request);
zmq::message_t reply;
socket.recv(&reply);
string replystr = string(static_cast<char*>(reply.data()), reply.size());
if (replystr == "NF") {
cout << "File not found on server." << endl;
} else {
ofstream file("received.txt", ios::binary);
file.write(replystr.c_str(), replystr.size());
cout << "File received and saved as received.txt." << endl;
}
return 0;
}
void ArmSocketConnect::VelocityCallBack(const arm_perception_msg::velocity_ratio &ratio_msg) {
zmq::message_t zmq_send_msg(sizeof(SendDataType));
double position_time_interval = ratio_msg.sample_period / (ARM_POSITION_NUM - 1);
std::cout<<"sample time interval: "<<position_time_interval<<std::endl;
//这里发下去的采样时间是两个位子之间的时间间隔
SendDataType send_data{};
send_data.sample_period = position_time_interval;
send_data.velocity_ratio = ratio_msg.velocity_ratio;
memcpy((void*)zmq_send_msg.data(),static_cast<const void *>(&send_data),sizeof(SendDataType));
zmq_send(socket_send_,zmq_send_msg.data(),sizeof(SendDataType),0);
}
void ArmSocketConnect::SocketSub() {
zmq::message_t zmq_receive_msg(sizeof(ReceiveDataType)) ;
ReceiveDataType *data_ptr ;
while(ros::ok()){
zmq_recv(socket_receive_,zmq_receive_msg.data(),sizeof(ReceiveDataType),0);
if(zmq_receive_msg.empty()){
continue;
}
data_ptr = zmq_receive_msg.data<ReceiveDataType>();
//if(data_ptr->success){
PublishSocketReceivedMsg(*data_ptr, true);
//}
data_ptr->success = false;
}
}
服务端:
#include <zmq.hpp>
#include <iostream>
int main(int argc, char** argv)
{
// zmq transfer file
std::ofstream fout("caliberation_result.txt");
zmq::context_t context(1);
zmq::socket_t socket(context, ZMQ_REP);
socket.bind("tcp://172.168.2.130:5555");
while (true) {
zmq::message_t request;
socket.recv(&request);
std::string filename = std::string(static_cast<char*>(request.data()), request.size());
std::cout << "Received request for file: " << filename << std::endl;
std::ifstream file(filename, std::ios::binary);
if (!file) {
std::cout << "File not found: " << filename << std::endl;
zmq::message_t reply(2);
memcpy(reply.data(), "NF", 2);
socket.send(reply);
continue;
}
std::stringstream buffer;
buffer << file.rdbuf();
std::string filedata = buffer.str();
zmq::message_t reply(filedata.size());
memcpy(reply.data(), filedata.c_str(), filedata.size());
socket.send(reply);
}
return 0;
}
客户端:
#include <iostream>
#include <zmq.hpp>
int main(int argc, char ** argv)
{
zmq::context_t zmq_context_;
zmq::socket_t zmq_socket_;
zmq_context_ = zmq::context_t(1);
zmq_socket_ = zmq::socket_t(zmq_context_, ZMQ_REQ);
board_addr_ = "tcp://172.168.2.130:5555";
zmq_socket_.connect(board_addr_);
std::string filename = "test.log";
zmq::message_t request(filename.size());
memcpy(request.data(), filename.c_str(), filename.size());
socket.send(request);
zmq::message_t reply;
socket.recv(&reply);
std::string replystr =
std::string(static_cast<char *>(reply.data()), reply.size());
if (replystr == "NF") {
qDebug() << "File not found on server.";
} else {
std::ofstream file("received.log", std::ios::binary);
file.write(replystr.c_str(), replystr.size());
std::endl << "File received and saved as received.log." << std::endl;
}
return 0;
}
参考:
ZeroMQ(ZMQ)函数接口说明:https://blog.csdn.net/Q360079221/article/details/81212597
https://space.bilibili.com/299449780/search/video?keyword=zmq
消息队列库——ZeroMQ:
https://www.cnblogs.com/chenny7/p/6245236.html
https://zhuanlan.zhihu.com/p/95997545
https://blog.csdn.net/qq_27071221/article/details/119209941
https://blog.csdn.net/koganlee/article/details/106511145