【从零实现Json-Rpc框架】- 项目实现 - 服务端主题实现及整体封装
📢博客主页:https://blog.csdn.net/2301_779549673
📢博客仓库:https://gitee.com/JohnKingW/linux_test/tree/master/lesson
📢欢迎点赞 👍 收藏 ⭐留言 📝 如有错误敬请指正!
📢本文由 JohnKi 原创,首发于 CSDN🙉
📢未来很长,值得我们全力奔赴更美好的生活✨
文章目录
- 📢前言
- 🏳️🌈一、服务端主题管理模块
- 1.1 核心功能
- 1.2 核心设计思路
- 1.3 主题 结构构造
- 1.4 订阅者 结构构造
- 1.5 主题 和 订阅者 统筹管理
- (1) onTopicRequest
- (2) onShutdown
- (3) 细节实现方法
- 1.8 框架代码
- 1.7 整体代码
- 🏳️🌈二、服务端整体实现
- 2.1 逻辑框架
- 2.1.1 注册中心(RegistryServer)
- 2.1.2 RPC 服务核心(RpcServer)
- 2.1.3 主题服务(TopicServer)
- 2.2 逻辑代码
- 2.3 整体代码
- 👥总结
📢前言
截至现在,在项目实现上,我们已经封装好了 零碎接口,对 各种消息 及 常用结构体 进行了封装
也实现了 dispatcher 路由转发的功能。
在 服务端 方面,完成了
- 业务函数回调总结 - rpc_route.hpp
- 服务的提供、发现、注册 - rpc_registry.hpp
在 客户端 方面,完成了
- 消息请求及其回调 - requestor.hpp
- 消息请求发送 - rpc_caller.hpp
服务端 未完成部分
- 服务端主题管理模块 - rpc_topic.hpp
- 服务端功能整合 - rpc_server.hpp
客户端 未完成部分
- 客户的提供、发现、注册 - rpc_registry.hpp
- 客户端主题管理模块 - rpc_topic.hpp
- 客户端功能整合 - rpc_client.hpp
这一篇文章,笔者就为服务端的 主题实现 和 整体封装 画上句号
🏳️🌈一、服务端主题管理模块
1.1 核心功能
这段代码实现了一个 服务端主题管理模块(TopicManager),支持 发布-订阅模式 的核心功能,包括:
- 主题的创建与删除
- 客户端的订阅与取消订阅
- 消息的发布与推送
- 连接断开时的自动清理
1.2 核心设计思路
线程安全:通过 std::mutex
保护共享数据(_topics 和 _subscribers)。
数据映射:
_topics
:维护主题名称到Topic
对象的映射。_subscribers
:维护客户端连接到Subscriber
对象的映射。
操作统一入口:通过 onTopicRequest
分发不同类型的主题操作请求。
1.3 主题 结构构造
作用:表示一个主题,管理其所有订阅者。
成员:
_subscribers
:存储所有订阅者的集合(Subscriber::ptr)。
方法:
appendSubscriber / removeSubscriber
:增删订阅者。pushMessage
:向所有订阅者发送消息。
// 主题名称 和 其订阅者连接 的映射
struct Topic {using ptr = std::shared_ptr<Topic>;std::mutex _mutex;std::string _topic_name;std::unordered_set<Subscriber::ptr> _subscribers; // 当前主题的订阅者的连接Topic(const std::string& topic_name) : _topic_name(topic_name) {}// 增加订阅者void appendSubscriber(const Subscriber::ptr& subscriber);// 删除订阅者void removeSubscriber(const Subscriber::ptr& subscriber);// 推送消息void pushMessage(const BaseMessage::ptr& msg);
};
1.4 订阅者 结构构造
作用:表示一个订阅者,记录其订阅的主题。
成员:
_conn
:订阅者的网络连接对象。_topics
:订阅者当前订阅的所有主题名称集合。
方法:
appendTopic / removeTopic
:增删订阅的主题。
// 定义一个订阅者对象,记录其订阅的 所有主题名称
struct Subscriber {using ptr = std::shared_ptr<Subscriber>;std::mutex _mutex;BaseConnection::ptr _conn;std::unordered_set<std::string> _topics; // 该订阅者所订阅的主题名称Subscriber(const BaseConnection::ptr& conn) : _conn(conn) {};// 增加订阅的主题void appendTopic(const std::string& topic);// 删除订阅的主题void removeTopic(const std::string& topic);
};
1.5 主题 和 订阅者 统筹管理
我们需要建立两个映射关系
- 主题名 -> 主题结构
- 订阅者连接 -> 订阅者结构
std::mutex _mutex;
std::unordered_map<std::string, Topic::ptr> _topics;
std::unordered_map<BaseConnection::ptr, Subscriber::ptr> _subscribers;
核心接口解析
(1) onTopicRequest
功能:处理客户端发送的主题操作请求(总入口)。
操作类型:
TOPIC_CREATE
:调用 topicCreate 创建主题。TOPIC_REMOVE
:调用 topicRemove 删除主题。TOPIC_SUBSCRIBE
:调用 topicSubscribe 订阅主题。TOPIC_CANCEL
:调用 topicCancel 取消订阅。TOPIC_PUBLISH
:调用 topicPublish 发布消息。错误处理
:若操作失败,返回错误响应(errorResponse)。
(2) onShutdown
功能:处理客户端连接断开时的清理逻辑。
步骤:
- 从
_subscribers
中移除订阅者。 - 遍历订阅者的所有主题,从主题的订阅列表中移除该订阅者。
(3) 细节实现方法
变更类
// 构造一个主题对象,添加映射关系的管理
void topicCreate(const BaseConnection::ptr& conn, const TopicRequest::ptr& msg);// 删除一个主题对象,删除映射关系的管理
void topicRemove(const BaseConnection::ptr& conn, const TopicRequest::ptr& msg);// 将订阅者订阅到指定主题。
bool topicSubscribe(const BaseConnection::ptr& conn, const TopicRequest::ptr& msg);// 取消该主题的订阅者
void topicCancel(const BaseConnection::ptr& conn, const TopicRequest::ptr& msg);// 向各个该主题的 订阅者 发布主题消息
bool topicPublish(const BaseConnection::ptr& conn, const TopicRequest::ptr& msg);
返回类
// 返回一个错误响应
void errorResponse(const BaseConnection::ptr& conn, const TopicRequest::ptr& msg, RCode rcode);// 返回一个主题响应
void topicResponse(const BaseConnection::ptr& conn, const TopicRequest::ptr& msg);
1.8 框架代码
#pragma once
#include "../common/net.hpp"
#include "../common/message.hpp"
#include <unordered_set>namespace rpc
{namespace server{class TopicManager{public:using ptr = std::shared_ptr<TopicManager>;// 统一处理有关主题的请求void onTopicRequest(const BaseConnection::ptr &conn, const TopicRequest::ptr &msg);// 关闭连接void onShutdown(const BaseConnection::ptr &conn);private:// 返回一个错误响应void errorResponse(const BaseConnection::ptr& conn, const TopicRequest::ptr& msg, RCode rcode);// 返回一个主题响应void topicResponse(const BaseConnection::ptr& conn, const TopicRequest::ptr& msg);private:// 构造一个主题对象,添加映射关系的管理void topicCreate(const BaseConnection::ptr &conn, const TopicRequest::ptr &msg);// 删除一个主题对象,删除映射关系的管理void topicRemove(const BaseConnection::ptr &conn, const TopicRequest::ptr &msg);// 将订阅者订阅到指定主题。bool topicSubscribe(const BaseConnection::ptr &conn, const TopicRequest::ptr &msg);// 取消该主题的订阅者 void topicCancel(const BaseConnection::ptr &conn, const TopicRequest::ptr &msg);// 向各个该主题的 订阅者 发布主题消息bool topicPublish(const BaseConnection::ptr &conn, const TopicRequest::ptr &msg);private:// 定义一个订阅者对象,记录其订阅的 所有主题名称struct Subscriber{using ptr = std::shared_ptr<Subscriber>;std::mutex _mutex;BaseConnection::ptr _conn;std::unordered_set<std::string> _topics; // 该订阅者所订阅的主题名称Subscriber(const BaseConnection::ptr &conn) : _conn(conn) {};// 增加订阅的主题void appendTopic(const std::string &topic);// 删除订阅的主题void removeTopic(const std::string &topic);};// 主题名称 和 其订阅者连接 的映射struct Topic{using ptr = std::shared_ptr<Topic>;std::mutex _mutex;std::string _topic_name;std::unordered_set<Subscriber::ptr> _subscribers; // 当前主题的订阅者的连接Topic(const std::string &topic_name) : _topic_name(topic_name) {}// 增加订阅者void appendSubscriber(const Subscriber::ptr &subscriber);// 删除订阅者void removeSubscriber(const Subscriber::ptr &subscriber);// 推送消息void pushMessage(const BaseMessage::ptr &msg);};private:std::mutex _mutex;std::unordered_map<std::string, Topic::ptr> _topics;std::unordered_map<BaseConnection::ptr, Subscriber::ptr> _subscribers;};}
}
1.7 整体代码
#pragma once
#include "../common/net.hpp"
#include "../common/message.hpp"
#include <unordered_set>namespace rpc
{namespace server{class TopicManager{public:using ptr = std::shared_ptr<TopicManager>;// 统一处理有关主题的请求void onTopicRequest(const BaseConnection::ptr &conn, const TopicRequest::ptr &msg){TopicOptype topic_optype = msg->optype();bool ret = true;switch(topic_optype){case TopicOptype::TOPIC_CREATE: topicCreate(conn, msg); break; // 主题创建case TopicOptype::TOPIC_REMOVE: topicRemove(conn, msg); break; // 主题删除case TopicOptype::TOPIC_SUBSCRIBE: ret = topicSubscribe(conn, msg); break; // 主题订阅case TopicOptype::TOPIC_CANCEL: topicCancel(conn, msg); break; // 主题取消订阅case TopicOptype::TOPIC_PUBLISH: ret = topicPublish(conn, msg); break; // 主题发布default: return errorResponse(conn, msg, RCode::RCODE_INVALID_OPTYPE); break;}if(!ret) return errorResponse(conn, msg, RCode::RCODE_NOT_FOUND_TOPIC);return topicResponse(conn, msg);}// 关闭连接void onShutdown(const BaseConnection::ptr &conn){// 消息发布者断开连接,不需要任何操作// 1. 判断断开连接的是否为订阅者,不是的话直接返回// 2. 获取到订阅者退出,受影响的主题对象// 3. 从主题对象中,移除订阅者// 4. 从订阅者映射信息中,删除订阅者std::vector<Topic::ptr> topics;Subscriber::ptr subscriber;{std::unique_lock<std::mutex> lock(_mutex);auto sub_it = _subscribers.find(conn);if(sub_it == _subscribers.end()){ELOG("该订阅者连接不存在");return;}// 获取该订阅者锁定月的主题subscriber = sub_it->second;// 2. 获取到订阅者退出,受影响的主题对象for(auto& topic_name : subscriber->_topics){auto topic_it = _topics.find(topic_name);if(topic_it == _topics.end())continue;topics.push_back(topic_it->second);}_subscribers.erase(sub_it);}}private:void errorResponse(const BaseConnection::ptr& conn, const TopicRequest::ptr& msg, RCode rcode){auto msg_rsp = MessageFactory::create<TopicResponse>();msg_rsp->setId(msg->rid());msg_rsp->setMType(MType::RSP_TOPIC);msg_rsp->setRcode(rcode);return conn->send(msg_rsp);}void topicResponse(const BaseConnection::ptr& conn, const TopicRequest::ptr& msg){auto msg_rsp = MessageFactory::create<TopicResponse>();msg_rsp->setId(msg->rid());msg_rsp->setMType(MType::RSP_TOPIC);msg_rsp->setRcode(RCode::RCODE_OK);return conn->send(msg_rsp);}private:// 构造一个主题对象,添加映射关系的管理void topicCreate(const BaseConnection::ptr &conn, const TopicRequest::ptr &msg){std::unique_lock<std::mutex> lock(_mutex);// 获取主题名字std::string topic_name = msg->topicKey();// 构造一个主题对象auto topic = std::make_shared<Topic>(topic_name);// 增加订阅者_topics.insert(std::make_pair(topic_name, topic));std::cout << "创建主题" << topic_name << std::endl;}// 删除一个主题对象,删除映射关系的管理void topicRemove(const BaseConnection::ptr &conn, const TopicRequest::ptr &msg){// 1. 查看当前主题,有哪些订阅者,然后从订阅者中将主题信息删掉// 2. 删除主题的数据 -- 主题名称余出题对象的映射std::string topic_name = msg->topicKey();std::unordered_set<Subscriber::ptr> subscribers; // 记录 当前主题 的 所有订阅者连接{std::unique_lock<std::mutex> lock(_mutex);auto it = _topics.find(msg->topicKey());if (it == _topics.end()){ELOG("没有找到 %s 主题的订阅者", msg->topicKey().c_str());return;}subscribers = it->second->_subscribers;_topics.erase(it); // 删除主题对象}for (auto &subscriber : subscribers){subscriber->removeTopic(topic_name);}}// 将订阅者订阅到指定主题。bool topicSubscribe(const BaseConnection::ptr &conn, const TopicRequest::ptr &msg){Topic::ptr topic;Subscriber::ptr subscriber;{std::unique_lock<std::mutex> lock(_mutex);// 1. 查找或创建订阅者对象(Subscriber)auto topic_it = _topics.find(msg->topicKey());if (topic_it == _topics.end()){return false;}topic = topic_it->second;auto sub_it = _subscribers.find(conn);if (sub_it != _subscribers.end()){subscriber = sub_it->second;}else{subscriber = std::make_shared<Subscriber>(conn);_subscribers.insert(std::make_pair(conn, subscriber));}}// 2. 在主题对象中,新增一个订阅者对象关联的连接; 在订阅者对象中新增一个订阅的主题topic->appendSubscriber(subscriber);subscriber->appendTopic(msg->topicKey());return true;}// 取消该主题的订阅者 void topicCancel(const BaseConnection::ptr &conn, const TopicRequest::ptr &msg){// 1. 先找出主题对象,和订阅者对象Topic::ptr topic;Subscriber::ptr subscriber;{std::unique_lock<std::mutex> lock(_mutex);auto topic_it = _topics.find(msg->topicKey());if (topic_it != _topics.end()){topic = topic_it->second;}auto sub_it = _subscribers.find(conn);if (sub_it != _subscribers.end()){subscriber = sub_it->second;}}// 2. 从主对象中删除当前的订阅者连接if(subscriber)subscriber->removeTopic(msg->topicKey());if(topic && subscriber)topic->removeSubscriber(subscriber);}// 向各个该主题的 订阅者 发布主题消息bool topicPublish(const BaseConnection::ptr &conn, const TopicRequest::ptr &msg){Topic::ptr topic;{std::unique_lock<std::mutex> lock(_mutex);auto topic_it = _topics.find(msg->topicKey());if (topic_it == _topics.end()){ELOG("没有找到 %s 主题的订阅者", msg->topicKey().c_str());return false;}topic = topic_it->second;}topic->pushMessage(msg);return true;}private:// 定义一个订阅者对象,记录其订阅的 所有主题名称struct Subscriber{using ptr = std::shared_ptr<Subscriber>;std::mutex _mutex;BaseConnection::ptr _conn;std::unordered_set<std::string> _topics; // 该订阅者所订阅的主题名称Subscriber(const BaseConnection::ptr &conn) : _conn(conn) {};// 增加订阅的主题void appendTopic(const std::string &topic){std::unique_lock<std::mutex> lock(_mutex);_topics.insert(topic);}void removeTopic(const std::string &topic){std::unique_lock<std::mutex> lock(_mutex);_topics.erase(topic);}};// 主题名称 和 其订阅者连接 的映射struct Topic{using ptr = std::shared_ptr<Topic>;std::mutex _mutex;std::string _topic_name;std::unordered_set<Subscriber::ptr> _subscribers; // 当前主题的订阅者的连接Topic(const std::string &topic_name) : _topic_name(topic_name) {}// 增加订阅者void appendSubscriber(const Subscriber::ptr &subscriber){std::unique_lock<std::mutex> lock(_mutex);_subscribers.insert(subscriber);}// 删除订阅者void removeSubscriber(const Subscriber::ptr &subscriber){std::unique_lock<std::mutex> lock(_mutex);_subscribers.erase(subscriber);}// 推送消息void pushMessage(const BaseMessage::ptr &msg){std::unique_lock<std::mutex> lock(_mutex);for (auto &subscriber : _subscribers){subscriber->_conn->send(msg);}}};private:std::mutex _mutex;std::unordered_map<std::string, Topic::ptr> _topics;std::unordered_map<BaseConnection::ptr, Subscriber::ptr> _subscribers;};}
}
🏳️🌈二、服务端整体实现
2.1 逻辑框架
一个 分布式
RPC 服务端系统
包含三个核心模块:服务注册中心、RPC 服务核心 和 主题服务。
以下是各模块的作用及整体架构:
2.1.1 注册中心(RegistryServer)
功能:
- 服务注册与发现:接收服务提供者(Provider)注册的服务信息,供消费者(Consumer)查询可用服务。
- 连接管理:在客户端断开时清理相关资源(如服务下线通知)。
关键成员:
_pd_manager
:管理服务注册与发现的业务逻辑(如维护服务列表)。_dispatcher
:分发客户端请求到对应的处理逻辑。_server
:底层网络服务器,监听端口并处理连接。
使用场景:
- 服务提供者启动时向注册中心注册自身服务。
- 消费者通过注册中心查询可用的服务地址和方法。
2.1.2 RPC 服务核心(RpcServer)
功能:
- RPC 服务管理:启动 RPC 服务,处理客户端调用请求。
- 服务注册(可选):将服务方法注册到注册中心(若启用)。
- 请求路由:将 RPC 请求路由到对应的业务处理逻辑。
关键成员:
_router
:路由请求到具体的服务方法(如根据方法名匹配处理函数)。_req_client
:与注册中心通信的客户端(用于服务注册或发现)。_dispatcher
:分发网络消息到业务逻辑。
配置选项:
_enableRegistry
:是否启用注册中心(决定是否自动注册服务)。
2.1.3 主题服务(TopicServer)
功能:
- 发布-订阅模式:管理主题的创建、订阅和消息推送。
- 消息广播:向订阅特定主题的客户端推送消息。
关键成员:
_topic_manager
:管理主题和订阅者(如 TopicManager 类)。_dispatcher
:处理客户端的订阅/发布请求。
2.2 逻辑代码
#pragma once#include "../common/dispatcher.hpp"
#include "../client/rpc_client.hpp"#include "rpc_route.hpp"
#include "rpc_registry.hpp"
#include "rpc_topic.hpp"namespace rpc
{namespace server{// 注册中心的服务端实现// 启动服务:监听指定端口,接收客户端(服务提供者/发现者)连接。// 消息路由:将服务注册/发现请求分发给业务处理器(PDManager)。// 连接管理:在客户端断开时清理相关资源(如服务下线通知)。class RegistryServer{// 注册中心服务端,只需要针对服务注册与发现请求进行处理即可public:using ptr = std::shared_ptr<RegistryServer>;RegistryServer(int port);void start();private:void onConnShutdown(const BaseConnection::ptr &conn);private:ProviderDiscovererManager::ptr _pd_manager;Dispatcher::ptr _dispatcher;BaseServer::ptr _server;};// RPC 服务端核心类,负责管理 RPC 服务的生命周期// 启动 RPC 服务:监听指定端口,处理客户端 RPC 请求。// 服务注册(可选):将服务方法注册到注册中心,供客户端发现。// 请求路由:将接收到的 RPC 请求分发给对应的业务处理逻辑。class RpcServer{public:using ptr = std::shared_ptr<RpcServer>;RpcServer(const Address &access_addr, bool enableRegistry = false, const Address ®istry_server_addr = Address());// 注册服务到注册中心void registerMethod(const ServiceDescribe::ptr &service);void start();private:bool _enableRegistry;Address _access_addr;rpc::client::RegistryClient::ptr _req_client;RpcRouter::ptr _router;Dispatcher::ptr _dispatcher;BaseServer::ptr _server;};class TopicServer{public:using ptr = std::shared_ptr<TopicServer>;TopicServer(int port);void start();private:void onConnShutdown(const BaseConnection::ptr &conn);private:TopicManager::ptr _topic_manager;Dispatcher::ptr _dispatcher;BaseServer::ptr _server;};}
}
2.3 整体代码
#pragma once#include "../common/dispatcher.hpp"
#include "../client/rpc_client.hpp" #include "rpc_route.hpp"
#include "rpc_registry.hpp"
#include "rpc_topic.hpp"namespace rpc{namespace server{// 注册中心的服务端实现// 启动服务:监听指定端口,接收客户端(服务提供者/发现者)连接。// 消息路由:将服务注册/发现请求分发给业务处理器(PDManager)。// 连接管理:在客户端断开时清理相关资源(如服务下线通知)。class RegistryServer{// 注册中心服务端,只需要针对服务注册与发现请求进行处理即可public:using ptr = std::shared_ptr<RegistryServer>;RegistryServer(int port): _pd_manager(std::make_shared<ProviderDiscovererManager>()),_dispatcher(std::make_shared<Dispatcher>()){ // 1. 注册服务请求处理器// 将 PDManager::onServiceRequest 绑定到 MType::REQ_SERVICE 消息类型。当收到服务注册或发现请求时,调用此方法处理auto service_cb = std::bind(&ProviderDiscovererManager::onServiceRequest, _pd_manager.get(), std::placeholders::_1, std::placeholders::_2);_dispatcher->registerHandler<ServiceRequest>(MType::REQ_SERVICE, service_cb);// 2. 创建底层服务器并设置回调// 通过 ServerFactory 创建底层服务器,设置消息总入口为 Dispatcher::onMessage_server = rpc::ServerFactory::create(port);auto message_cb = std::bind(&Dispatcher::onMessage, _dispatcher.get(), std::placeholders::_1, std::placeholders::_2);_server->setMessageCallback(message_cb);// 3. 设置连接关闭回调// 当客户端断开连接时,调用 onConnShutdown 清理相关资源auto close_cb = std::bind(&RegistryServer::onConnShutdown, this, std::placeholders::_1);_server->setCloseCallback(close_cb);}void start(){_server->start(); // 启动服务器,开始监听端口}private:void onConnShutdown(const BaseConnection::ptr& conn){_pd_manager->onConnShutdown(conn); // 通知 PDManager 处理连接断开}private:ProviderDiscovererManager::ptr _pd_manager;Dispatcher::ptr _dispatcher;BaseServer::ptr _server;};// RPC 服务端核心类,负责管理 RPC 服务的生命周期// 启动 RPC 服务:监听指定端口,处理客户端 RPC 请求。// 服务注册(可选):将服务方法注册到注册中心,供客户端发现。// 请求路由:将接收到的 RPC 请求分发给对应的业务处理逻辑。class RpcServer{public:using ptr = std::shared_ptr<RpcServer>;RpcServer(const Address& access_addr, bool enableRegistry = false, const Address& registry_server_addr = Address()): _enableRegistry(enableRegistry),_access_addr(access_addr),_router(std::make_shared<rpc::server::RpcRouter>()),_dispatcher(std::make_shared<Dispatcher>()){// 1. 创建注册客户端(若启用注册)if(enableRegistry){_req_client = std::make_shared<client::RegistryClient>(registry_server_addr.first, registry_server_addr.second);}// 2. 注册 RPC 请求处理回调auto rpc_cb = std::bind(&RpcRouter::onRpcRequest, _router.get(), std::placeholders::_1, std::placeholders::_2);_dispatcher->registerHandler<RpcRequest>(MType::REQ_RPC, rpc_cb);// 3. 创建底层服务器并设置回调_server = rpc::ServerFactory::create(access_addr.second);auto message_cb = std::bind(&Dispatcher::onMessage, _dispatcher.get(), std::placeholders::_1, std::placeholders::_2);_server->setMessageCallback(message_cb);}// 注册服务到注册中心void registerMethod(const ServiceDescribe::ptr& service){if(_enableRegistry)_req_client->registryMethod(service->method(), _access_addr);_router->registerMethod(service);}void start(){_server->start();}private:bool _enableRegistry;Address _access_addr;rpc::client::RegistryClient::ptr _req_client;RpcRouter::ptr _router;Dispatcher::ptr _dispatcher;BaseServer::ptr _server;};class TopicServer{public:using ptr = std::shared_ptr<TopicServer>;TopicServer(int port): _topic_manager(std::make_shared<TopicManager>()),_dispatcher(std::make_shared<Dispatcher>()){// 1. 注册主题请求处理器auto topic_cb = std::bind(&TopicManager::onTopicRequest, _topic_manager.get(), std::placeholders::_1, std::placeholders::_2);_dispatcher->registerHandler<TopicRequest>(MType::REQ_TOPIC, topic_cb);// 2. 创建底层服务器并设置回调_server = ServerFactory::create(port);auto message_cb = std::bind(&Dispatcher::onMessage, _dispatcher.get(), std::placeholders::_1, std::placeholders::_2);_server->setMessageCallback(message_cb);// 3. 设置连接关闭回调auto close_cb = std::bind(&TopicServer::onConnShutdown, this, std::placeholders::_1);_server->setCloseCallback(close_cb);}void start(){_server->start();}private:void onConnShutdown(const BaseConnection::ptr& conn){_topic_manager->onShutdown(conn);}private:TopicManager::ptr _topic_manager;Dispatcher::ptr _dispatcher;BaseServer::ptr _server;};}
}
👥总结
本篇博文对 从零实现Json-Rpc框架】- 项目实现 - 服务端主题实现及整体封装 做了一个较为详细的介绍,不知道对你有没有帮助呢
觉得博主写得还不错的三连支持下吧!会继续努力的~
相关文章:
【从零实现Json-Rpc框架】- 项目实现 - 服务端主题实现及整体封装
📢博客主页:https://blog.csdn.net/2301_779549673 📢博客仓库:https://gitee.com/JohnKingW/linux_test/tree/master/lesson 📢欢迎点赞 👍 收藏 ⭐留言 📝 如有错误敬请指正! &…...
JavaScript基础-移动端常用开发框架
随着移动互联网的发展,越来越多的应用和服务需要支持移动设备。为了提高开发效率和用户体验,开发者们依赖于一些成熟的JavaScript框架来构建响应迅速、功能丰富的移动Web应用。本文将介绍几款广泛使用的移动端开发框架,并通过具体的示例展示它…...
Tree - Shaking
Vue 3 的 Tree - Shaking 技术详解 Tree - Shaking 是一种在打包时移除未使用代码的优化技术,在 Vue 3 中,Tree - Shaking 发挥了重要作用,有效减少了打包后的代码体积,提高了应用的加载性能。以下是对 Vue 3 中 Tree - Shaking …...
VSCode历史版本的下载安装
VSCode历史版本的下载安装 文章目录 VSCode历史版本的下载安装VSCode安装下载历史版本地址查询VSCode历史版本的 commit id 安装参考资料 VSCode安装 Windows版本:Windows10VSCode版本:VScode1.65.0(64位User版本)本文编写时间&a…...
Websoft9分享:在数字化转型中选择开源软件可能遇到的难题
引言:中小企业数字化转型的必由之路 全球94.57%的企业已采用开源软件(数据来源:OpenLogic 2024报告),开源生态估值达8.8万亿美元。中小企业通过开源软件构建EPR系统、企业官网、数据分析平台等,可节省80%软件采购成本。…...
【无人机】无人机PX4飞控系统高级软件架构
目录 1、概述(图解) 一、数据存储层(Storage) 二、外部通信层(External Connectivity) 三、核心通信枢纽(Message Bus) 四、硬件驱动层(Drivers) 五、飞…...
新版本Xmind结合DeepSeek快速生成美丽的思维导图
前言 我的上一篇博客(https://quickrubber.blog.csdn.net/article/details/146518898)中讲到采用Python编程可以实现和Xmind的互动,并让DeepSeek来生成相应的代码从而实现对内容的任意修改。但是,那篇博客中提到的Xmind有版本的限…...
Windows查重工具,强烈推荐大家收藏!
我大家在用电脑的时候,是不是发现用得越久,电脑里的软件和文件就越多? 今天我给大家带来的这两款重复文件查找神器,简直就是电脑里的“清洁小能手”,能帮你把那些重复的文件和文件夹找出来。 Easy DupLicate Finder 重…...
数字孪生技术之争:UE、Unity还是飞渡DTS数字孪生平台?
作为深耕数字孪生内容创作的B站UP主,我们创作的内容广受数十万粉丝喜爱。后台私信经常提及两个问题:“这质感绝了!如此丝滑流畅是UE做的吗?”VS “请问用Unity能实现这个效果吗?” Unreal Engine凭借影视级渲染&#…...
【GCC警告报错4】warning: format not a string literal and no format arguments
文章主本文根据笔者个人工作/学习经验整理而成,如有错误请留言。 文章为付费内容,已加入原创保护,禁止私自转载。 文章发布于:《C语言编译报错&警告合集》 如图所示: 原因: snprintf的函数原型&#x…...
【Tauri2】013——前端Window Event与创建Window
前言 【Tauri2】012——on_window_event函数-CSDN博客https://blog.csdn.net/qq_63401240/article/details/146909801?spm1001.2014.3001.5501 前面介绍了on_window_event,这个在Builder中的方法,里面有许多事件,比如Moved,Res…...
修复SSL证书链不完整问题certificate verify failed unable to get local issuer certificate
文章目录 前言排查过程怀疑文章平台图片转存问题尝试使用 Python 代码下载图片使用 SSL Labs Server Test 验证猜想回顾 SSL 安装命令ACME 生成的证书 验证使用 [SSL Labs Server Test](https://www.ssllabs.com/ssltest/index.html) 验证文章发布平台转存验证 个人简介 前言 …...
管家婆财贸ERP BB102.采购销售订金管理
低适用版本: 财贸系列 23.8 插件简要功能说明: 采购订单/销售订单支持查询订金付款情况,联查下游付款/收款信息更多细节描述见下方详细文档 插件操作视频: 进销存类定制插件--采购销售订金管理 插件详细功能文档: …...
前端对接下载文件接口、对接dart app
嵌套在dart app里面的前端项目 1.前端调下载接口 ->后端返回 application/pdf格式的文件 ->前端将pdf处理为blob ->blob转base64 ->调用dart app的 sdk saveFile ->保存成功 async download() {try {// 调用封装的 downloadEContract 方法获取 Blob 数据const …...
牛客 简写单词
简写单词_牛客题霸_牛客网 主要是如何输入 #include <iostream> #include <string>using namespace std;int main() {string str;while(cin>>str){if(str[0]>a&&str[0]<z){cout<<char(str[0]-32);}else cout<<str[0];str.clear(…...
解决STM32CubeMX中文注释乱码
本人采用【修改系统环境变量】的方法 1. 使用快捷键 win X,打开【系统R】,点击【高级系统设置】 2. 点击【环境变量】 3. 点击【新建】 4.按图中输入【JAVA_TOOL_OPTIONS】和【-Dfile.encodingUTF-8】,新建环境变量后重启CubeMX即可。 解释…...
若依——基于AI+若依框架的实战项目(实战篇(下))
目录 前言 6. 设备管理 6.1 需求说明 6.2 生成基础代码 6.2.1 需求 6.2.2 步骤 ①创建目录菜单 ②添加数据字典 ③配置代码生成信息 ④下载代码并导入项目 6.3 设备类型改造 6.3.1 基础页面 需求 代码实现 6.4 设备管理改造 6.4.1 基础页面 需求 代码实现 …...
SpringBoot项目瘦身指南:从臃肿到高效的优化实践
精心整理了最新的面试资料和简历模板,有需要的可以自行获取 点击前往百度网盘获取 点击前往夸克网盘获取 一、问题背景 SpringBoot的"约定优于配置"特性极大提升了开发效率,但默认配置可能导致项目逐渐臃肿。典型的症状包括: 打…...
运筹帷幄:制胜软件开发
运筹学在软件开发项目中的作用主要体现在复杂系统建模、资源优化和决策支持中。通过数学建模、算法设计和数据分析,运筹学能够帮助开发团队更高效地实现软件需求,尤其是在涉及资源分配、路径规划、调度优化等场景时。 案例:电商物流配送系统的…...
代码随想录|动态规划|18完全背包理论基础
leetcode:52. 携带研究材料(第七期模拟笔试) 题目 有N件物品和一个最多能背重量为W的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品都有无限个(也就是可以放入背包多次),求解将哪些…...
52.个人健康管理系统小程序(基于springbootvue)
目录 1.系统的受众说明 2.开发环境与技术 2.1 MYSQL数据库 2.2 Java语言 2.3 微信小程序技术 2.4 SpringBoot框架 2.5 B/S架构 2.6 Tomcat 介绍 2.7 HTML简介 2.8 MyEclipse开发工具 3.系统分析 3.1 可行性分析 3.1.1 技术可行性 3.1.2 经济可行性 3.1.3 操作…...
大语言模型中的嵌入模型
本教程将拆解什么是嵌入模型、为什么它们在NLP中如此重要,并提供一个简单的Python实战示例。 分词器将原始文本转换为token和ID,而嵌入模型则将这些ID映射为密集向量表示。二者合力为LLMs的语义理解提供动力。图片来源:[https://tzamtzis.gr/2024/coding/tokenization-by-an…...
运维之 Centos7 防火墙(CentOS 7 Firewall for Operations and Maintenance)
运维之 Centos7 防火墙 1.介绍 Linux CentOS 7 防火墙/端口设置: 基础概念: 防火墙是一种网络安全设备,用于监控和控制网络流量,以保护计算机系统免受未经授权的访问和恶意攻击。Linux CentOS 7操作系统自带了一个名为iptables的…...
Ubuntu 20.04 出现问号图标且无法联网 修复
在 Ubuntu 中遇到网络连接问题(如出现问号图标且无法联网),可以通过以下命令尝试重启网络服务: 1. 推荐先修改DNS 编辑 -> 虚拟机网络编辑器-> VMnet8 ->NAT 设置 -> DNS 设置 -> 设置DNS 服务器 DNS填什么 取决…...
联想M7400打印机怎么清零
一(粉盒加粉后清零): 开机,打开前盖; 按下 “清除返回” 键,屏幕显示 “更换硒鼓?是否”; 按 “开始” 键,屏幕无显示; 按下 “” 号键 11 次,…...
AIGC7——AIGC驱动的视听内容定制化革命:从Sora到商业化落地
引言:个性化视听时代的到来 2024年,OpenAI发布视频生成模型Sora,可生成60秒高清视频;中国团队推出的Vidu模型实现16秒镜头连贯生成。这些突破标志着AIGC正式进入高质量视听内容定制化阶段。据Gartner预测,到2027年&am…...
Git Restore 命令详解与实用示例
文章目录 Git Restore 命令详解与实用示例1. 恢复工作区文件到最后一次提交的状态基本命令示例恢复所有更改 2. 恢复某个文件到特定提交的状态基本命令示例 3. 恢复暂存区的文件基本命令示例恢复所有暂存的文件 git restore 的常见选项git restore 与 git checkout 比较总结 Gi…...
Sentinel全面解析与实战教程
Sentinel全面解析与实战教程 一、引言 在现代分布式系统中,随着业务的不断发展和流量的日益增长,保障系统的稳定性成为了重中之重。Sentinel作为一款优秀的高可用流量防护组件,为解决系统中的流量控制、熔断降级等问题提供了有效方案。本文…...
考研总结(初试篇)--双非勇闯985
考研终于告一段落了,也是被湖南大学拟录取了,写下这篇总结,回顾一下我的经历吧。 我是2024年3月开学的时候,正式确定考研的。当时纠结于定学校,长理和湖大。最后还是选择湖大,因为反正都是要好好准备的&am…...
【电路笔记】-触发器的转换
触发器的转换 文章目录 触发器的转换1、概述2、置位-复位SR触发器3、门控置位-复位(SR)触发器4、数据(D型)触发器5、JK触发器6、使用主从触发器的触发器转换7、(切换)T型触发器8、总结触发器是时序电路的基本构建模块,可以从一种形式转换到另一种形式,能够存储单个数据…...
QT 中的元对象系统(五):QMetaObject::invokeMethod的使用和实现原理
目录 1.简介 2.原理概述 3.实现分析 3.1.通过方法名调用方法的实现分析 3.2.通过可调用对象调用方法的实现分析 4.使用场景 5.总结 1.简介 QMetaObject::invokeMethod 是 Qt 框架中的一个静态方法,用于在运行时调用对象的成员函数。这个方法提供了一种动态调…...
基数排序算法解析与TypeScript实现
基数排序(Radix Sort)是一种高效的非比较型整数排序算法,通过逐位分配与收集的方式实现排序。本文将深入解析其工作原理,并给出完整的TypeScript实现。 一、算法原理 1. 核心思想 多关键字排序:将整数按位数切割成不同…...
oracle asm 相关命令和查询视图
有关asm磁盘的命令 添加磁盘 alter diskgroup data1 add disk /devices/diska*;---runs with a rebalance power of 5 , and dose not return until the rebalance operation is completealter diskgroup data1 add disk /devices/diskd* rebalance power 5 wait;查询 select …...
THUNLP_Multimodal_Excercise
背景 多模态大模型(Multimodal Large Language Models, MLLM)的构建过程中,模型结构、模型预测、指令微调以及偏好对齐训练是其中重要的组成部分。本次任务中,将提供一个不完整的多模态大模型结构及微调代码,请根据要求…...
AIGC6——AI的哲学困境:主体性、认知边界与“天人智一“的再思考
引言:当机器开始"思考" 2023年,Google工程师Blake Lemoine声称对话AI LaMDA具有"自我意识",引发轩然大波。这一事件将古老的哲学问题重新抛回公众视野:**机器能否拥有主体性?**从东方"天人…...
主题(topic)中使用键(key)来区分同一主题下的多个数据实例
在Fast DDS中,通过在主题(topic)中使用键(key)来区分同一主题下的多个数据实例,具体含义如下: 1. **主题(Topic)**:在DDS中,主题是数据的类型或类…...
[王阳明代数讲义]琴语言类型系统工程特性
琴语言类型系统工程特性 层展物理学组织实务与艺术与琴生生.物机.械科.技工.业研究.所软凝聚态物理开发工具包社会科学气质砥砺学人生意气场社群成员魅力场与心气微积分社会关系力学 意气实体过程图论信息编码,如来码导引 注意力机制道装Transformer架构的发展标度律…...
蜜蜡是什么?蜜蜡与琥珀的区别以及蜜蜡的收藏价值一览
蜜蜡是琥珀的一种,在物理成分和化学成分上都和琥珀没有区别,只是因其“色如蜜,光如蜡”而得名。蜜蜡形成于白垩纪时期,因形成时间较长,形成过程比较复杂等原因,种类较其他产地要多。 蜜蜡是有机矿物&#x…...
第五课:高清修复和放大算法
文章目录 Part.01 高清修复(Hi-Res Fix)Part.02 SD放大(SD Upscale)Part.03 附加功能放大Part.01 高清修复(Hi-Res Fix) 文生图中的高清修复/高分辨率修复/超分辨率修复先低分辨率抽卡,再高分辨率修复。不能突破显存限制放大重绘幅度安全范围是0.3-0.5,如果想让AI更有想象力0…...
SpringSecurity6.0 通过JWTtoken进行认证授权
之前写过一个文章,从SpringSecurity 5.x升级到6.0,当时是为了配合公司的大版本升级做的,里面的各项配置都是前人留下来的,其实没有花时间进行研究SpringSecurity的工作机制。现在新东家有一个简单的系统要搭建,用户的认…...
TypeScript工程集成
以下是关于 TypeScript 工程集成 的系统梳理,涵盖基础配置、进阶优化、开发规范及实际场景的注意事项,帮助我们构建高效可靠的企业级 TypeScript 项目: 一、基础知识点 1. 项目初始化与配置 tsconfig.json 核心配置:{"compilerOptions": {"target": &…...
网络空间安全(51)邮件函数漏洞
前言 邮件函数漏洞,特别是在PHP环境中使用mail()函数时,是一个重要的安全问题。 一、概述 在PHP中,mail()函数是一个用于发送电子邮件的内置函数。其函数原型为: bool mail ( string $to , string $subject , string $message [, …...
怎么让一台云IPPBX实现多家酒店相同分机号码一起使用
下面用到的IPPBX是我们二次开发后的成品,支持各种云服务器一键安装,已经写好了一键安装包,自动识别系统环境,安装教程这里就不再陈述了! 前言需求 今天又遇到了一个客户咨询,关于部署一台云IPPBX…...
qt tcpsocket编程遇到的并发问题
1. 单个socket中接收消息的方法要使用局部变量而非全局,避免消息频发时产生脏数据 优化后的关键代码 recieveInfo() 方法通过返回内部处理后的 msg 进行传递if (data.indexOf("0103") -1) { 这里增加了判断, 对数据(非注册和心跳࿰…...
U盘实现——BOT 常用命令
文章目录 U盘实现——BOT 常用命令命令格式CBWCSW数据传输条件命令传输数据传输状态传输命令汇总INQUIRY Command:12h数据格式抓包READ FORMAT CAPACITIES Command: 23h数据格式抓包READ CAPACITY Command: 25h数据格式抓包TEST UNIT READY Command: 00h数据格式抓包WRITE(10) …...
JavaScript DOM 节点操作
目录 一、DOM 节点 节点类型(Node Types) 二、查找节点 1.查找父节点 1. parentNode 2. parentElement 2.查找子节点 1. childNodes 2. children 3. firstChild / lastChild 4. firstElementChild / lastElementChild 3.查找兄弟节点 1. pre…...
JDBC常用的接口
一、什么是JDBC JDBC是Java语言连接数据库的接口规范。 二、JDBC的体系 1、Java官方提供一个操作数据库的抽象接口 抽象接口有很多的接口和抽象类。 例如:Driver、Connection、Statement。 2、各个数据库厂商提供各自的Java实现类 需要各自实现具体的细节。 例如&am…...
【DLI】Generative AI with Diffusion Models通关秘籍
Generative AI with Diffusion Models,加载时间在20分钟左右,耐心等待。 6.2TODO 这里是在设置扩散模型的参数,代码里的FIXME部分需要根据上下文进行替换。以下是各个FIXME的替换说明: 1.a_bar 是 a 的累积乘积,在 …...
TP6图片操作 Image::open 调用->save()方法时候报错Type is not supported
错误提示: { "code": 0, "msg": "Type is not supported", "data": { "code": 0, "line": 50, "file": "/www/wwwroot/ytems/vendor/topthink/framework/src/think…...
11_常用函数
文章目录 一、概述二、字符函数2.1、获取字符串所占字节数2.2、获取字符个数2.3、拼接字符串2.4、大小写转换2.5、获取子串2.6、获取子串第一次出现的索引2.7、去除字符串前后子字符串2.7.1、去掉左侧空格2.7.2、去掉右侧空格 2.8、左右填充2.9、字符串替换 三、数学函数3.1、四…...