ZMQ第三方通讯库的编译与使用
ZMQ简介
ZeroMQ(简称ZMQ)是一个基于消息队列的多线程网络库,其对套接字类型、连接处理、帧、甚至路由的底层细节进行抽象,提供跨越多种传输协议的套接字。ZMQ是网络通信中新的一层,介于应用层和传输层之间(按照TCP/IP划分),其是一个可伸缩层,可并行运行,分散在分布式系统间。ZMQ不是单独的服务,而是一个嵌入式库,它封装了网络通信、消息队列、线程调度等功能,向上层提供简洁的API,应用程序通过加载库文件,调用API函数来实现高性能网络通信。
ZMQ源码编译(Windows平台)
环境介绍
- 操作系统:Windows10专业版(1909)
- 编译工具:Microsoft Visual Studio 2017专业版
- 源码版本:zmq4.2.5
- 编辑器:VS Code
源码下载
可以在github的仓库中找到相对应的源码进行下载。如果下载速度较慢,可以转存到coding或者gitee上然后再下载。可以使用git clone
命令或者直接下载zip压缩包的方式下载到源码,这里我选择了4.2.5版本。
如果使用git clone
命令,则默认应在libzmq
文件夹下;如果采用直接下载压缩包的方式,则解压后一般带有版本号。需要保证存放的文件夹路径为libzmq
,否则后面编译时会报错找不到某些文件。
编译过程
打开Visual Studio工程文件
在libzmq/builds/msvc/vs2017
文件夹中找到.sln
后缀的工程文件,使用Microsoft Visual Studio打开,如果提示sdk版本匹配更新的问题,请同意更新。
如下图示:
如果提示sdk版本匹配更新时,没有选择同意更新,导致Windows的sdk不匹配报错,则可以在Solution
菜单上使用右键选择Retarget solution
选项,然后选择电脑上已安装的sdk版本完成更新即可。
选择要编译生成的版本
在工具栏选择要生成的版本,然后使用Build
菜单生成即可。比如我这里的DynDebug
和x64
选项,表示生成64位包含debug信息的动态库。根据需要选择编译生成相应的版本即可,每个版本都会分开存放。
生成文件说明
生成的库文件存放在libzmq文件夹中的bin目录下,而且按照不同的版本分文件夹存放,可以复制相应的头文件和库文件到需要的地方以备使用。
cppzmq
cppzmq包含了专门用于C++版本的头文件。如果需要用到,可以直接去cppzmq仓库中下载两个.hpp
后缀的文件放在需要使用的位置。
发布者与订阅者模式
接下来,编写一个简单的发布-订阅模式的demo,以供学习使用。ZMQ的使用非常简单,具有固定的套路,只需要根据官方提供的API编写相应的操作即可,这里仅仅给出非常简单的发布者和订阅者模式的demo。
其文件基本结构如下所示:
.
|-- example1
| |-- publisher.cpp //发布者节点
| |-- subscriber.cpp //订阅者节点
| |-- CMakeLists.txt //CMakeLists.txt文件
| `-- build //build目录
| `-- CMakeFiles
| |-- 3.21.0-rc1
| | |-- CompilerIdC
| | | `-- tmp
| | `-- CompilerIdCXX
| | `-- tmp
| |-- CMakeTmp
| |-- publisher.dir
| `-- subscriber.dir
`-- tool //zmq的第三方库,使用tool文件夹统一放置其他库文件
`-- zmq425
|-- bin
| `-- x64
| |-- Debug
| | `-- v141
| | |-- dynamic//动态库文件
| | `-- static
| `-- Release
| `-- v141
| |-- dynamic
| `-- static
`-- include //头文件路径
`-- cpp //cppzmq仓库中的头文件路径文件夹
基本概念
ZMQ的一个节点需要先定义一个上下文(context,可以理解为句柄,或者该节点的总管家,用于维护该节点的相关资源),然后定义一个套接字(socket,用于绑定到某个地址上用于通讯)。很多API都会有返回值,而且很多代码都会在接收到返回值之后,使用assert
编写断言,这个作用是为了确保API的调用符合预期,否则会输出报错提示,方便我们定位错误。ZMQ数据的发送以字节流的方式进行,所以对于字符串而言,并不关心最后一位的字符串结尾标记位,需要程序员自行处理。
以上说明,仅为个人理解,可能并不严谨,仅供参考。
编写发布者
编写发布者,通过提示使用者输入一串字符串,将使用者输入的字符串作为消息发送到一个固定的地址上,并对每一次发送的消息通过打印的方式提示使用者已经完成发送。代码实现如下:
#include<cpp/zmq.hpp> //c++的头文件专门用了一个单独的文件夹存放
#include<iostream>
#include<string>
int main(void){
using namespace std;
const string address="tcp://127.0.0.1:8888"; //发布地址
void* context=zmq_ctx_new(); //创建上下文,名称可以自定义,就是个变量
void* socket=zmq_socket(context,ZMQ_PUB); //创建套接字,名称可以自定义,就是个变量,类型为ZMQ_PUB表示为发布者
int rc=zmq_bind(socket,address.c_str()); assert(rc==0); //绑定套接字到tcp协议的某个端口上,并对返回值进行断言判定
int i=1; //计数器
void* buffer=malloc(256); //创建buffer,用于存放消息,假定最长不超过256个字符
while(true){
cout<<"Please enter your message:"<<endl; //提示输入
string msg_string;
getline(cin,msg_string); //用于接收整行的消息
if(msg_string=="end") break; //如果接收到end字符串则停止接收消息,否则一直接收
int size=msg_string.size(); //将接收到的字符串复制到buffer中
char *temp=(char*)buffer;
for(int i=0;i<size;i++)
temp[i]=msg_string[i];
rc=zmq_send(socket,buffer,size,0); assert(rc==size); //返回值为发送的数据字节数,使用send将buffer中的数据发送出去,需要指定发送的数据长度
cout<<"Send NO."<<i++<<" message: "<<msg_string<<endl; //提示消息已经发出
Sleep(1); //稍作停顿
}
zmq_close(socket); //结束后关闭套接字和上下文环境
zmq_ctx_shutdown(context);
return 0;
}
编写订阅者
编写订阅者,订阅者在发布者发布的端口上接收消息,然后转换为字符串,并输出接收到的消息。代码实现如下:
#include<cpp/zmq.hpp>
#include<iostream>
int main(void){
using namespace std;
const string address="tcp://127.0.0.1:8888"; //确定接收地址
void* context=zmq_ctx_new(); //创建上下文
void* socket=zmq_socket(context,ZMQ_SUB); //创建套接字,类型为ZMQ_SUB表示为订阅者
int rc=zmq_connect(socket,address.c_str()); assert(rc==0); //链接到发布者发布的地址上,并对返回值进行断言判定
rc = zmq_setsockopt (socket, ZMQ_SUBSCRIBE, "", 0); assert(rc==0);//设置订阅该端口上的所有信息,没有这一句,则收不到消息,可以使用过滤器过滤接收
int i=1; //计数器
void* buffer=malloc(256); //创建buffer,用于接收消息
while(true){
rc=zmq_recv(socket,buffer,256,0); assert(rc!=-1); //接收一条消息
int size=rc;
char *rec_msg=(char*)malloc(sizeof(size+1)); //创建一个字符数组,用于存放消息
memcpy(rec_msg,buffer,size); rec_msg[size]='\0'; //构建字符串
cout<<"Receive NO."<<i++<<" message: "<<rec_msg<<endl; //输出消息
free(rec_msg); //释放字符数字空间
}
zmq_close(socket); //由于循环不会结束,所以以下两句并不会执行
zmq_ctx_shutdown(context); //但是依然要有回收清理的收尾习惯
return 0;
}
编写CMakeLists.txt文件
使用cmake+MinGW编译整个demo,因此需要编写CMakeLists.txt文件,内容如下:
# 版本和项目名称
cmake_minimum_required(VERSION 3.20)
project(example1)
# 设定编译标准为C++11
# 设定头文件路径和动态库路径
set(CXX_STANDARD 11)
set(INCLUDE_DIR "../tool/zmq425/include")
set(LIB_DIR "../tool/zmq425/bin/x64/Debug/v141/dynamic")
INCLUDE_DIRECTORIES(${INCLUDE_DIR})
LINK_DIRECTORIES(${LIB_DIR})
LINK_LIBRARIES(libzmq.lib)
# 生成可执行文件并连接到库文件
add_executable(publisher publisher.cpp)
add_executable(subscriber subscriber.cpp)
target_link_libraries(publisher libzmq.lib)
target_link_libraries(subscriber libzmq.lib)
编译
在项目文件夹的build目录下使用以下命令进行编译:
- cmake
# 命令 cmake .. -G "MinGW Makefiles" # 执行结果显示 -- The C compiler identification is GNU 8.1.0 -- The CXX compiler identification is GNU 8.1.0 -- Detecting C compiler ABI info -- Detecting C compiler ABI info - done -- Check for working C compiler: D:/MinGW/mingw/mingw64/bin/gcc.exe - skipped -- Detecting C compile features -- Detecting C compile features - done -- Detecting CXX compiler ABI info -- Detecting CXX compiler ABI info - done -- Check for working CXX compiler: D:/MinGW/mingw/mingw64/bin/g++.exe - skipped -- Detecting CXX compile features -- Detecting CXX compile features - done -- Configuring done -- Generating done -- Build files have been written to: F:/code/learnZMQ/learncode/example1/build
- make
# 命令 mingw32-make # 执行结果显示 [ 25%] Building CXX object CMakeFiles/publisher.dir/publisher.cpp.obj [ 50%] Linking CXX executable publisher.exe [ 50%] Built target publisher [ 75%] Building CXX object CMakeFiles/subscriber.dir/subscriber.cpp.obj [100%] Linking CXX executable subscriber.exe [100%] Built target subscriber
测试
因为需要用到动态库,需要先把动态库.dll文件的路径添加到系统变量中去,才能保证可执行文件的正常运行,否则会报错找不到.dll文件。
打开build目录中生成的可执行文件,在发布者窗口中发布消息,则能够看到订阅者可以正常订阅到消息。
Comments | NOTHING