ZMQ和MessagePack的简单使用(转)
来源:互联网 发布:c语言编程入门书籍 编辑:程序博客网 时间:2024/05/16 11:31
近段日子在做一个比较复杂的项目,其中用到了开源软件ZMQ和MessagePack。ZMQ对底层网络通信进行了封装,是一个消息处理队列库, 使用起来非常方便。MessagePack是一个基于二进制的对象序列化类库,具有跨语言的特性,同样非常容易使用。在我做的项目中,消息类通过 MessagePack进行压包,然后写入ZMQ的消息结构体,通过ZMQ传递,最后接收者利用MessagePack进行解包,从而分析命令。由于我英 语水平实在不高,所以我并没有通过阅读它们的说明文档来对它们进行了解,而仅仅是通过它们的示例代码进行探索。虽然因此遇到了一些不解问题,但这种方式却 为我节省了很多时间。不过,对于英语好的人,还是应该通过阅读说明文档来去了解它们。
为了说明如何使用它们,在这里构造一个使用场景:有N个Client,一个Server,M个Agent,Client使用ZMQ的请求-响应 模式和Server通信,Server收到Client的命令后,通过ZMQ的发布-订阅模式与各个Agent进行通信。下面的代码封装并使用了ZMQ和 MessagePack,为了简便,我把类的定义和实现都写在了头文件。
1.对ZMQ的简单封装:
1 #include"Msgpack.h" 2 #include<zmq.h> 3 #include<string> 4 #include<cassert> 5 #include<iostream> 6 7 namespace Tool 8 { 9 //网络工具类 10 class Network 11 { 12 public: 13 14 // 功能 :构造函数。 15 // 参数 :无。 16 // 返回 :无。 17 Network() : m_socket(NULL) { } 18 19 // 功能 :初始化socket。 20 // 参数 :zmqType表示ZMQ的模式,address表示socket绑定或连接地址。 21 // 返回 :true表示初始化成功,false表示失败。 22 bool Init(int zmqType,const std::string& address) 23 { 24 try 25 { 26 m_socket = zmq_socket(Context,zmqType); 27 return SetSocket(zmqType,address); 28 } 29 catch(...) 30 { 31 std::cout << "Network初始化失败。" << std::endl; 32 return false; 33 } 34 } 35 36 // 功能 :发送消息。 37 // 参数 :指向Msgpack的指针,isRelease如果为true表示发送消息后即刻释放资源。 38 // 返回 :true表示发送成功,false表示发送失败。 39 bool SendMessage(Msgpack *msgpack,bool isRelease = true) const 40 { 41 try 42 { 43 zmq_msg_t msg; 44 zmq_msg_init(&msg); 45 if(isRelease) 46 { 47 zmq_msg_init_data(&msg,msgpack->GetSbuf().data(),msgpack->GetSbuf().size(),Tool::Network::Release,msgpack); 48 } 49 else 50 { 51 zmq_msg_init_data(&msg,msgpack->GetSbuf().data(),msgpack->GetSbuf().size(),0,0); 52 } 53 zmq_msg_send(&msg,m_socket,0); 54 return true; 55 } 56 catch(...) 57 { 58 std::cout << "Network发送失败。" << std::endl; 59 return false; 60 } 61 } 62 63 // 功能 :接收消息。 64 // 参数 :无。 65 // 返回 :指向消息的指针。 66 zmq_msg_t* ReceiveMessage() const 67 { 68 zmq_msg_t *reply = NULL; 69 try 70 { 71 reply = new zmq_msg_t(); 72 zmq_msg_init(reply); 73 zmq_msg_recv(reply,m_socket,0); 74 return reply; 75 } 76 catch(...) 77 { 78 if( reply != NULL ) 79 { 80 delete reply; 81 } 82 return NULL; 83 } 84 } 85 86 // 功能 :关闭消息。 87 // 参数 :指向消息的指针。 88 // 返回 :无。 89 void CloseMsg(zmq_msg_t* msg) 90 { 91 try 92 { 93 zmq_msg_close(msg); 94 msg = NULL; 95 } 96 catch(...) 97 { 98 msg = NULL; 99 }100 }101 102 // 功能 :析构函数。103 // 参数 :无。104 // 返回 :无。105 ~Network()106 {107 if( m_socket != NULL )108 {109 zmq_close(m_socket);110 m_socket = NULL;111 }112 }113 114 private:115 116 //通信socket117 void *m_socket;118 119 //网络环境120 static void *Context;121 122 private:123 124 // 功能 :设置socket。125 // 参数 :zmqType表示ZMQ的模式,address表示socket绑定或连接地址。126 // 返回 :true表示设置成功,false表示设置失败。127 bool SetSocket(int zmqType,const std::string& address)128 {129 int result = -1;130 switch(zmqType)131 {132 case ZMQ_REP:133 case ZMQ_PUB:134 result = zmq_bind(m_socket,address.c_str());135 break;136 case ZMQ_REQ:137 result = zmq_connect(m_socket,address.c_str());138 break;139 case ZMQ_SUB:140 result = zmq_connect(m_socket,address.c_str());141 assert(result == 0);142 result = zmq_setsockopt(m_socket,ZMQ_SUBSCRIBE,"",0);143 break;144 default:145 return false;146 }147 assert( result == 0 );148 return true;149 }150 151 // 功能 :发送完消息后,释放消息资源。152 // 参数 :function为函数地址,hint指向要释放资源的对象。153 // 返回 :无。154 static void Release(void *function, void *hint)155 {156 Msgpack *msgpack = (Msgpack*)hint;157 if( msgpack != NULL )158 {159 delete msgpack;160 msgpack = NULL;161 }162 }163 };164 165 //整个程序共用一个context166 void *Tool::Network::Context = zmq_ctx_new();167 };
说明:
(1)由zmq_ctx_new创建出来的Context,整个应用程序共用一个就可以了,具体的通信是由zmq_socket创建的socket来完成的。上述代码中没有去释放Context指向的资源。
(2)在zmq_msg_init_data函数的参数中,需要传入一个释放资源的函数地址,在ZMQ发送完消息后就调用这个函数来释放资源。 如果没有传入这个参数,而且传入的信息是临时变量,那么接收方很有可能接收不到信息,甚至抛出异常。如果不传入这个参数,那么就要记得由自己去释放资源 了。
2.对MessagePack的简单封装:
1 #include"BaseMessage.h" 2 #include"ClientMessage.h" 3 #include"ServerMessage.h" 4 #include<zmq.h> 5 #include<msgpack.hpp> 6 7 namespace Tool 8 { 9 using namespace Message; 10 11 //压包/解包工具类 12 class Msgpack 13 { 14 public: 15 16 // 功能 :构造函数。 17 // 参数 :无。 18 // 返回 :无。 19 Msgpack(void) { } 20 21 // 功能 :析构函数。 22 // 参数 :无。 23 // 返回 :无。 24 ~Msgpack(void) { } 25 26 // 功能 :压包数据。 27 // 参数 :要压包的数据。 28 // 返回 :true表示压包成功。 29 template<typename T> 30 bool Pack(const T& t) 31 { 32 try 33 { 34 Release(); 35 msgpack::pack(m_sbuf,t); 36 return true; 37 } 38 catch(...) 39 { 40 std::cout << "Msgpack压包数据失败。" << std::endl; 41 return false; 42 } 43 } 44 45 // 功能 :解包数据。 46 // 参数 :zmq消息体。 47 // 返回 :返回指向基类消息的指针。 48 BaseMessage* Unpack(zmq_msg_t& msg) 49 { 50 try 51 { 52 int size = zmq_msg_size(&msg); 53 if( size > 0 ) 54 { 55 Release(); 56 m_sbuf.write((char*)zmq_msg_data(&msg),size); 57 size_t offset = 0; 58 msgpack::zone z; 59 msgpack::object obj; 60 msgpack::unpack(m_sbuf.data(),m_sbuf.size(),&offset,&z,&obj); 61 return GetMessage(obj); 62 } 63 } 64 catch(...) 65 { 66 //吃掉异常 67 } 68 return NULL; 69 } 70 71 // 功能 :获取压包/解包工具。 72 // 参数 :无。 73 // 返回 :压包/解包工具。 74 inline msgpack::sbuffer& GetSbuf() 75 { 76 return m_sbuf; 77 } 78 79 private: 80 81 //压包/解包工具 82 msgpack::sbuffer m_sbuf; 83 84 private: 85 86 // 功能 :释放上一次的数据资源。 87 // 参数 :无。 88 // 返回 :无。 89 void Release() 90 { 91 m_sbuf.clear(); 92 m_sbuf.release(); 93 } 94 95 // 功能 :获取消息。 96 // 参数 :用于转换的msgpack::object。 97 // 返回 :指向消息基类的指针。 98 BaseMessage* GetMessage(const msgpack::object& obj) 99 {100 BaseMessage bmessage;101 obj.convert(&bmessage);102 switch(bmessage.Type)103 {104 case 1024:105 return Convert<ClientMessage>(obj);106 case 2048:107 return Convert<ServerMessage>(obj);108 default:109 return NULL;110 }111 }112 113 // 功能 :将压包后的数据转换为具体的类。114 // 参数 :用于转换的msgpack::object。115 // 返回 :指向T的指针。116 template<typename T>117 T* Convert(const msgpack::object& obj)118 {119 T *t = new T();120 obj.convert(t);121 return t;122 }123 };124 };
说明:
压包时将zmq_msg_t消息体压包到msgpack::sbuffer,然后就可以关闭这个消息体了。要将解包后的数据转换成具体的某一个类,需要知道这个类是什么类,这里有三种方法:
(1)可以先发送一个消息告知接收者即将收到什么消息,然后接收者将消息解包后转换成对应的类。这种方式需要额外的一次通信,不建议使用。
(2)所有的消息都继承自一个基类,这个基类存储有消息类型的字段。解包后,先将数据转换为基类,然后根据类型再转换为具体的派生类。这种方式需要多转换一次,上面的代码也正是采用这种方式。
(3)压包时先压包一个消息类,然后再压包一个标识这个消息是什么类型的标 识类,即压包两次。解包时,先解包标识类,得知消息类的具体类型,然后再解包消息类,即解包两次,转换两次。与(2)相比,除了要做更多的压包、解包工作 外,这里还需要对解包的偏移量进行计算,否则容易出错。
3.使用到的消息类:
namespace Message{ //消息基类 class BaseMessage { public: MSGPACK_DEFINE(Type); //消息类型 int Type; //默认构造函数 BaseMessage() { Type = 0; } }; //来自客户端的消息 class ClientMessage : public BaseMessage { public: MSGPACK_DEFINE(Type,Information); //信息 std::string Information; //默认构造函数 ClientMessage() { Type = 1024; } }; //来自服务端的消息 class ServerMessage : public BaseMessage { public: MSGPACK_DEFINE(Type,Information); //信息 std::vector<std::string> Information; //默认构造函数 ServerMessage() { Type = 2048; } };};
说明:
(1)MSPACK_DEFINE标识了一个类的哪些成员可以进行压包/解包。派生类中的MSGPACK_DEFINE还需要写上基类的成员,否则无法使用对MessagePack封装说明的第二个方法。
(2)C++版本的MessagePack压/解包的数据成员,只能是一个类、结构或者联合体,不能使用指针(包括boost库的智能指针)、 数组,枚举值也不适用。因此,BaseMessage使用int值来标识派生类属于哪个类型。C#版本的MessagePack可以对枚举值进行压包。
4.Client的示例代码:
1 int _tmain(int argc, _TCHAR* argv[]) 2 { 3 Network network; 4 bool result = network.Init(ZMQ_REQ,"tcp://192.168.10.179:8888"); 5 if(result) 6 { 7 ClientMessage cmessage; 8 cmessage.Information = "I come form Client."; 9 10 Msgpack msgpack;11 result = msgpack.Pack<ClientMessage>(cmessage);12 if(result)13 {14 result = network.SendMessageW(&msgpack,false);15 if(result)16 {17 zmq_msg_t *msg = network.ReceiveMessage();18 if( msg != NULL )19 {20 BaseMessage *bmessage = msgpack.Unpack(*msg);21 network.CloseMsg(msg);22 if( bmessage != NULL && bmessage->Type == 2048 )23 {24 ServerMessage *smessage = static_cast<ServerMessage*>(bmessage);25 if( smessage != NULL && smessage->Information.size() > 0 )26 {27 std::cout << smessage->Information[0] << std::endl;28 }29 delete smessage;30 smessage = NULL;31 bmessage = NULL;32 }33 }34 }35 }36 }37 38 system("pause");39 return 0;40 }
5.Server的示例代码:
1 int _tmain(int argc, _TCHAR* argv[]) 2 { 3 Network responder; 4 bool result = responder.Init(ZMQ_REP,"tcp://192.168.10.179:8888"); 5 if(result) 6 { 7 Network publisher; 8 result = publisher.Init(ZMQ_PUB,"tcp://192.168.10.179:9999"); 9 if(result)10 {11 Msgpack msgpack;12 while(true)13 {14 zmq_msg_t *msg = responder.ReceiveMessage();15 BaseMessage *bmessage = msgpack.Unpack(*msg);16 responder.CloseMsg(msg);17 18 ServerMessage smessage;19 smessage.Information.push_back("I come from Server.");20 msgpack.Pack<ServerMessage>(smessage);21 result = responder.SendMessageW(&msgpack,false);22 23 if( result )24 {25 if( bmessage != NULL && bmessage->Type == 1024 )26 {27 ClientMessage *cmessage = static_cast<ClientMessage*>(bmessage);28 if( cmessage != NULL )29 {30 std::cout << cmessage->Information << std::endl;31 for( int counter = 0 ; counter < 100 ; counter++ )32 {33 publisher.SendMessageW(&msgpack,false);34 }35 }36 delete cmessage;37 cmessage = NULL;38 bmessage = NULL;39 }40 }41 }42 }43 }44 45 return 0;46 }
6.Agent的示例代码:
int _tmain(int argc, _TCHAR* argv[]){ Network network; bool result = network.Init(ZMQ_SUB,"tcp://192.168.10.179:9999"); if(result) { zmq_msg_t *msg = network.ReceiveMessage(); if( msg != NULL ) { Msgpack msgpack; BaseMessage *bmessage = msgpack.Unpack(*msg); network.CloseMsg(msg); if( bmessage->Type == 2048 ) { ServerMessage *smessage = static_cast<ServerMessage*>(bmessage); if( smessage->Information.size() > 0 ) { std::cout << smessage->Information[0] << std::endl; } delete smessage; smessage = NULL; bmessage = NULL; } } } system("pause"); return 0;}
7.启动这三个程序,Client将要发送的消息压包后发给Server,Server接收到消息后反馈一个信息给Client,然后循环发布消息给Agent,Agent不需要回复Server。最后着重说明两点:
(1)ZMQ创建的socket发送数据和接收数据要处在同一条线程。Server接收到Client的数据后,不能通过开一条线程来给Client反馈信息,必须要在接收数据的线程中反馈信息。
(2)ZMQ并不要求发送者和接收者有一定的启动顺序,但在Server中如果只发布一次消息,那么Agent很有可能收不到信息。不管是 Agent先启动,还是Server先启动,Agent都有可能收不到信息。在Server的代码中,通过循环发布一百次,来让Agent收到信息。至于 实际应用中,可以结合请求-响应模式来保证订阅消息者都收到了发布者的消息。
参考资料:
ZMQ:http://zguide.zeromq.org/page:all
MessagePack:http://wiki.msgpack.org/pages/viewpage.action?pageId=1081387#QuickStartforC%2B%2B-ImplementationStatus
- ZMQ和MessagePack的简单使用(转)
- ZMQ和MessagePack的简单使用
- ZMQ和MessagePack的简单使用
- MessagePack Java的使用
- MessagePack使用
- 使用netty和messagepack编写网络请求
- MessagePack for C/C++的使用
- ZMQ的学习和研究
- 在xcode上使用zmq和protobuf
- MessagePack简介及使用
- MessagePack简介及使用
- MessagePack 编码和解码
- twisted、protobuf、zmq的结合使用
- MessagePack二进制序列化动态库的使用
- Zmq中pub和sub的用法
- java MessagePack的数据处理
- .Net与java使用MessagePack
- JSON,MessagePack导出和读取
- Linux下压缩与解压命令tar
- jquery validation 的正确打开方式
- cp mount分区文件
- Insight mvc:interceptor 解析、执行顺序
- 探索C++0x: 3. 右值引用(rvalue reference)
- ZMQ和MessagePack的简单使用(转)
- MFC之ListControl控件
- Retrofit知识点
- hdu 1005 找规律(好坑!!)
- Microsoft SQL Server 2008 基本安装说明
- Android String Reseourse format 格式化字符串问题
- apache kafka监控系列-KafkaOffsetMonitor
- POJ 1274-The Perfect Stall(二分图匹配/最大流问题)
- Python快速学习第二天