发布-订阅(ZeroMQ) C++实现

2024-06-01 05:12
文章标签 c++ 实现 发布 订阅 zeromq

本文主要是介绍发布-订阅(ZeroMQ) C++实现,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

1、目的

自从发了《发布-订阅(Publish-Subscribe)C++实现》博文,收到不少反馈:主要的问题就是无法跨主机使用。

本次实现主要解决:

  • 简化ZeroMQ的开发过程;
  • 尽可能简化发布订阅的API调用;
  • 订阅者消息处理采用守护线程模式;
  • 支撑跨主机的发布订阅(By ZeroMQ)

下载地址:【免费】PublishSubscribe-ZMQ发布订阅C++实现资源-CSDN文库

2、ZeroMQ库选择

一般,在可以选择的情况下,我比较偏爱C接口的库,主要原因:一致性好,至少比C++接口的库兼容性强。

鉴于这个理由,我就选择了libzmq,库版本4.3.5。

推荐优先使用MD模式的库:libzmq-v142-mt-4_3_5.lib。

3、实现思路

 TOPIC是满足:
        1)可compare的,组合数据类型至少重载 == 操作;
        2)可直接网络传输的,常见的比如整型、结构体。
主题对应的数据一般多见结构体,也可以是字符串、二进制等任何适用于网络传输的数据类型。

4、主要代码

#ifndef __PUBLISHER_HPP__
#define __PUBLISHER_HPP__
#include "TOPIC_DEFS.h"
#include "zmq.h"
#include <string>template<typename _TOPIC_>
class Publisher
{
public://addr :"tcp://*:port"Publisher(const char * addr){// 初始化 ZeroMQ 上下文mContext = zmq_ctx_new();// 创建 PUB 套接字mPublisher = zmq_socket(mContext, ZMQ_PUB);int linger = 0;zmq_setsockopt(mPublisher, ZMQ_LINGER, &linger, sizeof(int64_t));// 绑定地址zmq_bind(mPublisher, addr);}virtual ~Publisher(){// 关闭套接字和 ZeroMQ 上下文if (mPublisher) zmq_close(mPublisher);if(mContext) zmq_ctx_destroy(mContext);}void Publish(_TOPIC_ topic, void* msg, int msg_size){zmq_send(mPublisher, &topic, sizeof(_TOPIC_), ZMQ_SNDMORE);zmq_send(mPublisher, msg, msg_size, 0);}private:void* mContext;		// ZeroMQ 上下文void* mPublisher;	// PUB 套接字
};#endif // !__PUBLISHER_HPP__
#ifndef __SUBCORE_HPP__
#define __SUBCORE_HPP__#include "zmq.h"
#include <thread>
#include <mutex>
#include <map>
#include <list>
#define _MAX_SUBSCRIBE_ 1024template<typename _TOPIC_>
class Subcore
{
public:Subcore(){// 初始化 ZeroMQ 上下文mContext = zmq_ctx_new();mSubscribeSize = 0;memset(mSubscribe, 0, sizeof(zmq_pollitem_t) * _MAX_SUBSCRIBE_);mStop = false;mSubTopic.clear();mSubPollitem.clear();mRecvThreadPtr.reset(new std::thread(&Subcore::EventProcess, this));}virtual ~Subcore(){// 关闭监听事件for (int i = 0; i < mSubscribeSize; ++i){if (mSubscribe[i].socket != nullptr) mSubscribe[i].events = 0;}// 等待事件处理线程退出mStop = true;if (mRecvThreadPtr->joinable()) {mRecvThreadPtr->join();}// 关闭套接字for (int i = 0; i < mSubscribeSize; ++i){if (mSubscribe[i].socket != nullptr) {zmq_close(mSubscribe[i].socket);mSubscribe[i].socket = nullptr;}}mSubscribeSize = 0;// 销毁 ZeroMQ 上下文if (mContext) {zmq_ctx_destroy(mContext);mContext = nullptr;}}//addr :"tcp://ip:port"bool Subscribe(_TOPIC_ topic, const char* addr){auto it = mSubTopic.find(addr);if (it != mSubTopic.end()){for (auto itt = it->second.begin(); itt != it->second.end(); ++itt){if (*itt == topic){return false;}}// 订阅主题auto sock = mSubPollitem.find(addr);if (sock != mSubPollitem.end()){zmq_setsockopt(sock->second.socket, ZMQ_SUBSCRIBE, &topic, sizeof(_TOPIC_));mSubscribeMutex.lock();mSubTopic[addr].push_back(topic);mSubscribeMutex.unlock();return true;}}// 创建 SUB 套接字void* subscriber_temp = zmq_socket(mContext, ZMQ_SUB);if(!subscriber_temp) return false;//连接套接字if(zmq_connect(subscriber_temp, addr)==-1) return false;// 订阅主题zmq_setsockopt(subscriber_temp, ZMQ_SUBSCRIBE, &topic, sizeof(_TOPIC_));int linger = 0;zmq_setsockopt(subscriber_temp, ZMQ_LINGER, &linger, sizeof(int64_t));//准备IO复用zmq_pollitem_t tmp{ subscriber_temp, 0, ZMQ_POLLIN, 0 };mSubscribeMutex.lock();mSubscribe[mSubscribeSize] = tmp;mSubscribeSize++;//更新记录mSubTopic[addr].push_back(topic);mSubPollitem[addr] = tmp;mSubscribeMutex.unlock();return true;}void UnSubscribe(_TOPIC_ topic){	for (auto it = mSubTopic.begin(); it != mSubTopic.end(); it++){for (auto itt = it->second.begin(); itt != it->second.end(); ++itt){if (*itt == topic){auto sock = mSubPollitem.find(it->first);if (sock != mSubPollitem.end()){zmq_setsockopt(sock->second.socket, ZMQ_UNSUBSCRIBE, &topic, sizeof(_TOPIC_));}	}}			}}virtual void EnventHandler(_TOPIC_, void*, int) = 0;private:void EventProcess(){while (!mStop){if (mSubscribeSize > 0){int size = zmq_poll(mSubscribe, mSubscribeSize, 10);if (size == -1)continue;if (size > 0){for (size_t i = 0; i < _MAX_SUBSCRIBE_; i++){if (mSubscribe[i].revents & ZMQ_POLLIN){zmq_msg_t msg;zmq_msg_init(&msg);zmq_msg_recv(&msg, mSubscribe[i].socket, 0);// 第一部分消息(主题,整数类型)_TOPIC_ topic;memcpy(&topic, zmq_msg_data(&msg), sizeof(_TOPIC_));// 第二部分消息(数据)int more;size_t more_size = sizeof(more);zmq_getsockopt(mSubscribe[i].socket, ZMQ_RCVMORE, &more, &more_size);if (more){zmq_msg_init(&msg);zmq_msg_recv(&msg, mSubscribe[i].socket, 0);size_t datasize = zmq_msg_size(&msg);char* data = static_cast<char*>(zmq_msg_data(&msg));EnventHandler(topic, data, datasize);}zmq_msg_close(&msg);}}}}}}private:void* mContext;		// ZeroMQ 上下文int mSubscribeSize;zmq_pollitem_t mSubscribe[_MAX_SUBSCRIBE_];	// IO多路复用std::map<std::string, std::list<_TOPIC_>> mSubTopic;	// 记录订阅的主题  key is addrstd::map<std::string, zmq_pollitem_t> mSubPollitem;	// 记录订阅的远程主机  key is addrstd::mutex mSubscribeMutex;				// 订阅或者取消订阅时保护写数据std::unique_ptr<std::thread> mRecvThreadPtr;bool mStop;
};#endif // !__SUBCORE_HPP__

测试代码

#define _CRT_SECURE_NO_WARNINGS
#include "Publisher.hpp"
#include "Subscriber.hpp"#define ADDRESS_S5555 "tcp://*:5555"               //发布者地址1
#define ADDRESS_S5557 "tcp://*:5557"              //发布者地址2
#define ADDRESS_C5555 "tcp://localhost:5555"       
#define ADDRESS_C5557 "tcp://localhost:5557"void TESTTHREAD()
{Hello tpc1{ "sma",20 };Publisher<TOPIC_TYPE> pub1(ADDRESS_S5557);while (true){zmq_sleep(1);pub1.Publish(TOPIC_WORLD, &tpc1, sizeof tpc1);}
}int main()
{//测试一个Subscriber 订阅来自不同Publisher的主题------------------------------{Hello tpc{ "wxq",18 };Subscriber<TOPIC_TYPE> sub;Publisher<TOPIC_TYPE> pub(ADDRESS_S5555);new std::thread(TESTTHREAD);sub.Subscribe(TOPIC_HELLO, ADDRESS_C5555);//sub.Subscribe(TOPIC_WORLD, ADDRESS_C5555);sub.Subscribe(TOPIC_WORLD, ADDRESS_C5557);//sub.UnSubscribe(TOPIC_WORLD);while (1){zmq_sleep(1);pub.Publish(TOPIC_HELLO,&tpc,sizeof tpc);}}//测试一个Subscriber 订阅来自同一个Publisher的不同主题------------------------------  //{//	Hello tpc{ "wxq",18 };//	Hello tpc1{ "sma",20 };//	Publisher<TOPIC_TYPE> pub(ADDRESS_S5555);//	Publisher<TOPIC_TYPE> pub1(ADDRESS_S5555);//	Subscriber<TOPIC_TYPE> sub;//	sub.Subscribe(TOPIC_HELLO, ADDRESS_C5555);//	sub.Subscribe(TOPIC_WORLD, ADDRESS_C5555);//	while (1)//	{//		zmq_sleep(1);//		pub.Publish(TOPIC_HELLO, &tpc, sizeof tpc);//		pub.Publish(TOPIC_WORLD, &tpc1, sizeof tpc1);//	}//}return 0;
}

这篇关于发布-订阅(ZeroMQ) C++实现的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1020072

相关文章

golang版本升级如何实现

《golang版本升级如何实现》:本文主要介绍golang版本升级如何实现问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录golanwww.chinasem.cng版本升级linux上golang版本升级删除golang旧版本安装golang最新版本总结gola

SpringBoot中SM2公钥加密、私钥解密的实现示例详解

《SpringBoot中SM2公钥加密、私钥解密的实现示例详解》本文介绍了如何在SpringBoot项目中实现SM2公钥加密和私钥解密的功能,通过使用Hutool库和BouncyCastle依赖,简化... 目录一、前言1、加密信息(示例)2、加密结果(示例)二、实现代码1、yml文件配置2、创建SM2工具

Mysql实现范围分区表(新增、删除、重组、查看)

《Mysql实现范围分区表(新增、删除、重组、查看)》MySQL分区表的四种类型(范围、哈希、列表、键值),主要介绍了范围分区的创建、查询、添加、删除及重组织操作,具有一定的参考价值,感兴趣的可以了解... 目录一、mysql分区表分类二、范围分区(Range Partitioning1、新建分区表:2、分

MySQL 定时新增分区的实现示例

《MySQL定时新增分区的实现示例》本文主要介绍了通过存储过程和定时任务实现MySQL分区的自动创建,解决大数据量下手动维护的繁琐问题,具有一定的参考价值,感兴趣的可以了解一下... mysql创建好分区之后,有时候会需要自动创建分区。比如,一些表数据量非常大,有些数据是热点数据,按照日期分区MululbU

MySQL中查找重复值的实现

《MySQL中查找重复值的实现》查找重复值是一项常见需求,比如在数据清理、数据分析、数据质量检查等场景下,我们常常需要找出表中某列或多列的重复值,具有一定的参考价值,感兴趣的可以了解一下... 目录技术背景实现步骤方法一:使用GROUP BY和HAVING子句方法二:仅返回重复值方法三:返回完整记录方法四:

IDEA中新建/切换Git分支的实现步骤

《IDEA中新建/切换Git分支的实现步骤》本文主要介绍了IDEA中新建/切换Git分支的实现步骤,通过菜单创建新分支并选择是否切换,创建后在Git详情或右键Checkout中切换分支,感兴趣的可以了... 前提:项目已被Git托管1、点击上方栏Git->NewBrancjsh...2、输入新的分支的

Python实现对阿里云OSS对象存储的操作详解

《Python实现对阿里云OSS对象存储的操作详解》这篇文章主要为大家详细介绍了Python实现对阿里云OSS对象存储的操作相关知识,包括连接,上传,下载,列举等功能,感兴趣的小伙伴可以了解下... 目录一、直接使用代码二、详细使用1. 环境准备2. 初始化配置3. bucket配置创建4. 文件上传到os

关于集合与数组转换实现方法

《关于集合与数组转换实现方法》:本文主要介绍关于集合与数组转换实现方法,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1、Arrays.asList()1.1、方法作用1.2、内部实现1.3、修改元素的影响1.4、注意事项2、list.toArray()2.1、方

使用Python实现可恢复式多线程下载器

《使用Python实现可恢复式多线程下载器》在数字时代,大文件下载已成为日常操作,本文将手把手教你用Python打造专业级下载器,实现断点续传,多线程加速,速度限制等功能,感兴趣的小伙伴可以了解下... 目录一、智能续传:从崩溃边缘抢救进度二、多线程加速:榨干网络带宽三、速度控制:做网络的好邻居四、终端交互

从入门到精通C++11 <chrono> 库特性

《从入门到精通C++11<chrono>库特性》chrono库是C++11中一个非常强大和实用的库,它为时间处理提供了丰富的功能和类型安全的接口,通过本文的介绍,我们了解了chrono库的基本概念... 目录一、引言1.1 为什么需要<chrono>库1.2<chrono>库的基本概念二、时间段(Durat