[实现Rpc] 客户端划分 | 框架设计 | common类的实现

目录

3. 客户端模块划分

3.1 Network模块

3.2 Protocol模块

3.3 Dispatcher模块

3.4 Requestor模块

3.5 RpcCaller模块

3.6 Publish-Subscribe模块

3.7 Registry-Discovery模块

3.8 Client模块

4. 框架设计

4.1 抽象层

4.2 具象层

4.3 业务层

⭕4.4 整体设计框架

5. common 类的实现

5.1 常用的零碎功能接口类实现

5.1.1 简单日志宏实现

5.1.2 Json序列化/反序列化的封装

5.1.3 UUID的生成

5.2 fields 消息类型定义

5.2.1 请求字段宏的定义:

5.2.2 消息类型定义:

5.2.3 响应码类型定义:

5.2.4 RPC请求类型定义:

5.2.5 主题操作类型定义:

5.2.6 服务操作类型定义:


前文:[实现Rpc] 项目设计 | 服务端模块划分 | rpc | topic | server

3. 客户端模块划分

在客户端的模块划分中,基于以上理解的功能,可以划分出这么级个模块:

  1. Protocol:应用层通信协议模块。
  2. Network:网络通信模块。
  3. Dispatcher:消息分发处理模块。
  4. Requestor:请求管理模块。
  5. RpcCaller:远端调用功能模块。
  6. Publish-Subscribe:发布订阅功能模块。
  7. Registry-Discovery:服务注册/发现/上线/下线功能模块。
  8. Client:基于以上模块整合⽽出的客户端模块。

3.1 Network模块

网络通信基于muduo库实现网络通信客户端。

3.2 Protocol模块

应用层通信协议处理,与服务端保持⼀致。

3.3 Dispatcher模块

IO数据分发处理,逻辑与服务端⼀致。


3.4 Requestor模块

Requestor模块存在的意义:针对客⼾端的每⼀条请求进⾏管,以便于对请求对应的响应做出合适的操作。

  • 首先,对于客户端来说,不同的地方在于更多时候客户端是请求方,是主动发起请求服务的⼀方
  • 而在多线程的网络通信中,多线程下,针对 多个请求进行响应可能会存在时序的问题,这种情况下,我们则无法保证⼀个线程发送⼀个请求后,接下来接收到的响应就是针对自己这条请求的响应,这种情况是非常危险的⼀种情况。
  • 其次,类似于Muduo库这种异步IO网络通信库,通常 IO操作都是异步操作,即发送数据就是把数据放入发送缓冲区,但是什么时候会发送由底层的网络库来进行协调,并且也并不会提供recv接口,而是在连接触发可读事件后,IO读取数据完成后调用处理回调进行数据处理,因此也无法直接在发送请求后去等待该条请求的响应。(对 于网络通信前文有提到~[Linux#61][UDP] port | netstat | udp缓冲区 | stm32)

针对以上问题,我们则创建出当前的请求管理模块来解决:

  • 它的思想也非常简单,就是给每⼀个请求都设定⼀个请求ID,服务端进行响应的时候标识响应针对的是哪个请求(也就是 响应信息中会包含请求ID
  • 因此客户端这边我们不管收到哪条请求的响应,将数据存储入⼀则hash_map中,以请求ID作为映射,并向外提供获取指定 请求ID响应的阻塞接口,这样只要在发送请求的时候知道自己的请求ID,那么就能获取到自己想要的响应,而不会出现异常。

针对这个思想,我们再进⼀步,可以将每个请求进⼀步封装描述,添加⼊异步的future控制,或者设置回调函数的方式,在不仅可以阻塞获取响应,也可以实现 异步获取响应以及回调处理响应。


3.5 RpcCaller模块

(1)RpcCaller模块存在的意义:向用户提供进行rpc调⽤的模块。

Rpc服务调用模块,这个模块相对简单,只需要向外提供几个rpc调用的接口,内部实现向服务端发送请求,等待获取结果即可,稍微麻烦⼀些的是Rpc调用我们需要提供多种不同方式的调用:

  1. 同步调用:发起调用后,等收到响应结果后返回。
  2. 异步调用:发起调用后立即返回,在想获取结果的时候进行获取。
  3. 回调调用:发起调用的同时设置结果的处理回调,收到响应后⾃动对结果进行回调处理。


3.6 Publish-Subscribe模块

(1)Publish-Subscribe模块存在意义:向用户提供发布订阅所需的接口,针对推送过来的消息进行处理。

  • 发布订阅稍微复杂⼀点,因为在发布订阅中有两种角色,⼀个客户端可能是消息的发布者,也可能是消息的订阅者。
  • 而且不管是哪个角色都是 对主题进行操作,因此其中也包含了主题的相关操作,比如要发布⼀条消息需要先创建主题。
  • ⼀个订阅者可能会订阅多个主题,每个主题的消息可能都会有不同的处理方式,因此需要有订阅者主题回调的管理。

3.7 Registry-Discovery模块

(1)服务注册和发现模块需要实现的功能会复杂⼀些,因为分为两个角色来完成其功能:

  • 注册者:作为Rpc服务的提供者,需要向注册中心注册服务,因此需要实现向服务器注册服务的功能。
  • 发现者:作为Rpc服务的调用者,需要先进行服务发现,也就是向服务器发送请求获取能够提供指定服务的主机地址,获取地址后需要管理起来留用,且作为发现者,需要关注注册中心发送过来的服务上线/下线消息,以及时对已经下线的服务和主机进行管理。

3.8 Client模块

将以上模块进行整合就可以实现各个功能的客户端了。

  • RegistryClient:服务注册功能模块与⽹络通信客户端结合。
  • DiscoveryClient:服务发现功能模块与⽹络通信客户端结合。
  • RpcClient:DiscoveryClient & RPC功能模块与网络通信客户端结合。
  • TopicClient:发布订阅功能模块与⽹络通信客户端结合。



4. 框架设计

在当前项目的实现中,我们将整个项目的实现划分为三层来进行实现:

  1. 抽象层:将底层的网络通信以及应用层通信协议以及请求响应进行抽象,使项目更具扩展性和灵活性。
  2. 具象层:针对抽象的功能进行具体的实现。
  3. 业务层:基于抽象的框架在上层实现项目所需功能。

4.1 抽象层

在本项目的实现当中,网络通信部分采用了第三方库Muduo库,以及通信协议使用了LV格式的通信协议解决粘包问题,数据正文中采用了Json格式进行序列化和反序列化,而这几方面我们都可能会存在继续优化的可能,甚至在序列化⽅方面不⼀定非要采用Json

因此在设计项目框架的时候,我们对于底层通信部分相关功能先进行抽象,形成⼀层抽象层

而上层业务部分根据抽象层来完成功能,这样的好处是在具体的底层功能实现部分,我们可以实现插拔式的模块化替换,以此来提高项目的灵活性和扩展性。


4.2 具象层

具象层就是针对抽象的具体实现。

而具体的实现也比较简单,从抽象类派生出具体功能的派生类,然后在内部实现各个接口功能即可。

  • 基于Muduo库实现网络通信部分抽象。
  • 基于LV通信协议实现Protocol部分抽象。

不过这⼀层中比较特殊的是,我们需要 针对不同的请求,从BaseMessage中派生出不同的请求和响应类型,以便于在针对指定消息处理时,能够更加轻松的获取或设置请求及响应中的各项数据元素。

4.3 业务层

业务层就是基于底层的通信框架,针对项目中具体的业务功能的实现了,比如Rpc请求的处理,发布订阅请求的处理以及服务注册与发现的处理等等。

(1)Rpc:

(2)发布订阅:

(3)服务注册&发现:


⭕4.4 整体设计框架

项目当中会有server和client共同使用的类,也有单独使用的类,所以我们分别实现对应的功能

5. common 类的实现

5.1 常用的零碎功能接口类实现

5.1.1 简单日志宏实现

日志宏意义:快速定位程序运行逻辑出错的位置。

参考前文:简单日志宏实现(C++)

项目在运行中可能会出现各种问题,出问题不可怕,关键的是要能找到问题,并解决问题。解决问题的方式:

  • gdb调试:逐步调试过于繁琐,缓慢。主要⽤于程序崩溃后的定位。
  • 系统运行日志分析:在任何程序运行有可能逻辑错误的位置进行输出提示,快速定位逻辑问题的位置。
#pragma once
#include <cstdio>
#include <ctime>#define LDBG 0
#define LINF 1
#define LERR 2#define LDEFAULT LDBG#define LOG(level, format, ...)                                                                       \{                                                                                                 \if (level >= LDEFAULT)                                                                        \{                                                                                             \time_t t = time(NULL);                                                                    \struct tm *lt = localtime(&t);                                                            \char time_tmp[32] = {0};                                                                  \strftime(time_tmp, 31, "%m-%d %T", lt);                                                   \fprintf(stdout, "[%s][%s:%d] " format "\n", time_tmp, __FILE__, __LINE__, ##__VA_ARGS__); \}                                                                                             \}#define DLOG(format, ...) LOG(LDBG, format, ##__VA_ARGS__);
#define ILOG(format, ...) LOG(LINF, format, ##__VA_ARGS__);
#define ELOG(format, ...) LOG(LERR, format, ##__VA_ARGS__);

5.1.2 Json序列化/反序列化的封装

#include <iostream>
#include <sstream>
#include <string>
#include <memory>
#include <jsoncpp/json/json.h>class JSON
{
public:static bool serialize(const Json::Value &val, std::string &body){std::stringstream ss;// 先实例化一个工厂类对象Json::StreamWriterBuilder swb;// 通过工厂类对象来生产派生类对象std::unique_ptr<Json::StreamWriter> w(swb.newStreamWriter());bool ret = w->write(val, &ss);if (ret != 0){ELOG("json serialize failed!");return false;}body = ss.str();return true;}static bool deserialize(const std::string &body, Json::Value &val){Json::CharReaderBuilder crb;std::unique_ptr<Json::CharReader> r(crb.newCharReader());std::string errs;bool ret = r->parse(body.c_str(), body.c_str() + body.size(), &val, &errs);if (ret == false){ELOG("json deserialize failed : %s", errs.c_str());return false;}return true;}
};

5.1.3 UUID的生成

UUID(Universally Unique Identifier),也叫通用唯⼀识别码,通常由32位16进制数字字符组成。

  • UUID的标准型式包含32个16进制数字字符,以连字号分为五段,形式为8-4-4-4-12的32个字符
  • 如:550e8400-e29b-41d4-a716-446655440000。
  • 在这里,uuid生成,我们采用生成8个随机数字,加上8字节序号,共16字节数组生成32位16进制字符的组合形式来 确保全局唯⼀的同时能够根据序号来分辨数据(随机数肉眼分辨起来真是太难了…)。
#include <iostream>
#include <chrono>
#include <random>
#include <string>
#include <sstream>
#include <atomic>
#include <iomanip>class UUID
{
public:static std::string uuid(){std::stringstream ss;//1. 构造一个机器随机数对象std::random_device rd;//2. 以机器随机数为种子构造伪随机数对象std::mt19937 generator (rd());//3. 构造限定数据范围的对象std::uniform_int_distribution<int> distribution(0, 255);// 4. 生成8个随机数,按照特定格式组织成为16进制数字字符的字符串for (int i = 0; i < 8; i++){if (i == 4 || i == 6){ss << "-";}ss << std::setw(2) << std::setfill('0') << std::hex << distribution(generator);}ss << "-";//5. 定义一个8字节序号,逐字节组织成为16进制数字字符的字符串static std::atomic<size_t> seq(1);size_t cur = seq.fetch_add(1);for (int i = 7; i >= 0; i--){if (i == 5){ss << "-";}ss << std::setw(2) << std::setfill('0') << std::hex << ((cur >> (i * 8)) & 0xFF);}return ss.str();}
};

定义一个生成 UUID(通用唯一标识符) 的工具类 UUID,其核心功能是通过结合随机数和计数器生成一个类似于标准 UUID 格式的字符串。

  1. 使用硬件随机数生成器和梅森旋转算法生成随机数。
  2. 将生成的随机数按十六进制格式组织,并在特定位置插入 "-"
  3. 使用静态原子计数器确保 UUID 的唯一性,并将计数器值按字节格式化为十六进制。
  4. 最终,返回一个形如 xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx 的字符串,表示一个生成的 UUID。

这种方式生成的 UUID 包含了随机性和递增序列,确保了其在大多数情况下的唯一性。


5.2 fields 消息类型定义

5.2.1 请求字段宏的定义:

定义如下:

#define KEY_METHOD "method"
#define KEY_PARAMS "parameters"
#define KEY_TOPIC_KEY "topic_key"
#define KEY_TOPIC_MSG "topic_msg"
#define KEY_OPTYPE "optype"
#define KEY_HOST "host"
#define KEY_HOST_IP "ip"
#define KEY_HOST_PORT "port"
#define KEY_RCODE "rcode"
#define KEY_RESULT "result"

提高代码的可读性:通过给常量字符串定义宏名称,可以使代码更加易读和易于理解。例如,KEY_METHOD 比直接使用 "method" 更直观地表达了该字符串在代码中的作用。

5.2.2 消息类型定义:

(1)主要功能:

  • Rpc请求 & 响应。
  • 主题操作请求 & 响应。
  • 消息发布请求 & 响应。
  • 服务操作请求 & 响应。

(2)具体定义如下:

enum class MType
{
/*
Request
Response
*/REQ_RPC = 0,
RSP_RPC,
REQ_TOPIC,
RSP_TOPIC,
REQ_SERVICE,
RSP_SERVICE
};
5.2.3 响应码类型定义:

(1)主要功能:

  • 成功处理。
  • 解析失败。
  • 消息中字段缺失或错误导致无效消息。
  • 连接断开。
  • 无效的Rpc调用参数。
  • Rpc服务不存在。
  • 无效的Topic操作类型。
  • 主题不存在。
  • 无效的服务操作类型。

(2)具体定义如下:

enum class RCode
{RCODE_OK = 0,RCODE_PARSE_FAILED,RCODE_ERROR_MSGTYPE,RCODE_INVALID_MSG,RCODE_DISCONNECTED,RCODE_INVALID_PARAMS,RCODE_NOT_FOUND_SERVICE,RCODE_INVALID_OPTYPE,RCODE_NOT_FOUND_TOPIC,RCODE_INTERNAL_ERROR
};static std::string errReason(RCode code)
{std::unordered_map<RCode, std::string> err_map = {{RCode::RCODE_OK, "成功处理!"},{RCode::RCODE_PARSE_FAILED, "消息解析失败!"},{RCode::RCODE_ERROR_MSGTYPE, "消息类型错误!"},{RCode::RCODE_INVALID_MSG, "无效消息"},{RCode::RCODE_DISCONNECTED, "连接已断开!"},{RCode::RCODE_INVALID_PARAMS, "无效的Rpc参数!"},{RCode::RCODE_NOT_FOUND_SERVICE, "没有找到对应的服务!"},{RCode::RCODE_INVALID_OPTYPE, "无效的操作类型"},{RCode::RCODE_NOT_FOUND_TOPIC, "没有找到对应的主题!"},{RCode::RCODE_INTERNAL_ERROR, "内部错误!"}};auto iter = err_map.find(code);if (iter == err_map.end()){return "未知错误";}return iter->second;
}
5.2.4 RPC请求类型定义:

(1)主要功能:

  • 同步请求:等待收到响应后返回。
  • 异步请求:返回异步对象,在需要的时候通过异步对象获取响应结果(还未收到结果会阻塞)。
  • 回调请求:设置回调函数,通过回调函数对响应进行处理。

(2)具体定义如下:

enum class RType
{REQ_ASYNC = 0,REQ_CALLBACK
};
5.2.5 主题操作类型定义:

(1)主要功能:

  • 主题创建。
  • 主题删除。
  • 主题订阅。
  • 主题取消订阅。
  • 主题消息发布。

(2)具体定义如下:

enum class TopicOptype
{TOPIC_CREATE = 0,TOPIC_REMOVE,TOPIC_SUBSCRIBE,TOPIC_CANCEL,TOPIC_PUBLISH
};
5.2.6 服务操作类型定义:

(1)主要功能:

  • 服务注册。
  • 服务发现。
  • 服务上线。
  • 服务下线。

(2)具体定义如下:

enum class ServiceOptype
{SERVICE_REGISTRY = 0,SERVICE_DISCOVERY,SERVICE_ONLINE,SERVICE_OFFLINE,SERVICE_UNKNOW
};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/19267.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java里ArrayList和LinkedList有什么区别?

大家好&#xff0c;我是锋哥。今天分享关于【Java里ArrayList和LinkedList有什么区别&#xff1f;】面试题。希望对大家有帮助&#xff1b; Java里ArrayList和LinkedList有什么区别&#xff1f; 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 ArrayList 和 LinkedL…

【Java】分布式锁Redis和Redisson

https://blog.csdn.net/weixin_44606481/article/details/134373900 https://www.bilibili.com/video/BV1nW421R7qJ Redis锁机制一般是由 setnx 命令实现&#xff0c;set if not exists&#xff0c;语法setnx key value&#xff0c;将key设置值为value&#xff0c;如果key不存在…

c++TinML转html

cTinML转html 前言解析解释转译html类定义开头html 结果这是最终效果&#xff08;部分&#xff09;&#xff1a; ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/6cf6c3e3c821446a84ae542bcc2652d4.png) 前言 在python.tkinter设计标记语言(转译2-html)中提到了将Ti…

2.2 反向传播:神经网络如何“学习“?

一、神经网络就像小学生 想象一个刚学算术的小学生&#xff0c;老师每天布置练习题&#xff0c;学生根据例题尝试解题&#xff0c;老师批改后指出错误。神经网络的学习过程与此相似&#xff1a; 输入层&#xff1a;相当于练习题&#xff08;如数字图片&#xff09;输出层&…

QEMU 通过网络实现共享文件

系列文章目录 Linux内核学习 Linux 知识&#xff08;1&#xff09; Linux 知识&#xff08;2&#xff09; WSL Ubuntu QEMU 虚拟机 Linux 调试视频 PCIe 与 USB 的补充知识 vscode 使用说明 树莓派 4B 指南 设备驱动畅想 Linux内核子系统 Linux 文件系统挂载 QEMU 通过网络实现…

当时只道是寻常

晴&#xff0c;2025年2月16日 卸载了油管、脸书和 X 手机 app &#xff0c;太浪费我时间&#xff0c;以后再去经营吧。 教学技能大赛材料需要在明天之内搞定——《教学实施方案》。感觉玄&#xff0c;同部门有经验的老师说至少花一周时间。 只能明天早点继续接着弄&#xff…

Hive之分区表

Hive之分区表 文章目录 Hive之分区表写在前面分区表分区表基本操作引入分区表创建分区表语法加载数据到分区表中查询分区表中数据增加分区删除分区查看分区表有多少分区查看分区表结构 二级分区正常的加载数据分区表和数据产生关联 动态分区开启动态分区参数设置案例实操 写在前…

【线段树模板】

介绍 这段代码看起来是一个基于树结构的数据结构&#xff0c;可能是线段树或者其他类似的数据结构。主要包含了构建数据结构、查询和修改等基本操作的实现函数。以下是对每个函数的简要介绍&#xff1a; pushup(int u): 用于计算结点u的属性。build(int u, int l, int r): 用于…

DeepSeek辅助测试测试一 -- DeepSeek加MaxKB知识库本地部署

文章目录 前言任务拆解最终目标两种技术路径对比知识库检索增强&#xff08;RAG&#xff09; 大语言模型 构建知识库加本地部署DeepSeek目前的问题 前言 开工已经两周啦&#xff0c;开始慢慢的进入工作状态了&#xff0c;新的一年大家一起加油吧~ 任务拆解 最终目标 训练一…

Yuque-DL:一款强大的语雀资源下载工具

语雀是一款常用的文档管理工具&#xff0c;但官方未提供直接下载文档的功能。为此&#xff0c;可以使用第三方工具下载语雀文档。以下是使用步骤&#xff1a; 1. 安装工具 GitHub - gxr404/yuque-dl: yuque 语雀知识库下载 安装步骤&#xff1a; 确保已安装 Node.js&#xff…

【Java 面试 八股文】Spring Cloud 篇

Spring Cloud 篇 1. Spring Cloud 5大组件有哪些&#xff1f;2. 服务注册和发现是什么意思&#xff1f;Spring Cloud 如何实现服务注册发现&#xff1f;3. 我看你之前也用过nacos&#xff0c;你能说下nacos与eureka的区别&#xff1f;4. 你们项目负载均衡如何实现的&#xff1f…

国内外网络安全政策动态(2025年1月)

▶︎ 1.国家互联网信息办公室发布《个人信息出境个人信息保护认证办法&#xff08;征求意见稿&#xff09;》 1月3日&#xff0c;国家互联网信息办公室发布《个人信息出境个人信息保护认证办法&#xff08;征求意见稿&#xff09;》。根据《意见稿》&#xff0c;个人信息出境个…

图论入门算法:拓扑排序(C++)

上文中我们了解了图的遍历(DFS/BFS), 本节我们来学习拓扑排序. 在图论中, 拓扑排序(Topological Sorting)是对一个有向无环图(Directed Acyclic Graph, DAG)的所有顶点进行排序的一种算法, 使得如果存在一条从顶点 u 到顶点 v 的有向边 (u, v) , 那么在排序后的序列中, u 一定…

Anaconda +Jupyter Notebook安装(2025最新版)

Anaconda安装&#xff08;2025最新版&#xff09; Anaconda简介安装1&#xff1a;下载anaconda安装包2&#xff1a; 安装anaconda3&#xff1a;配置环境变量4&#xff1a;检查是否安装成功5&#xff1a;更改镜像源6&#xff1a;更新包7&#xff1a;检查 Jupyter Notebook一.Jup…

VS2022中.Net Api + Vue 从创建到发布到IIS

VS2022中.Net Api Vue 从创建到发布到IIS 前言一、先决条件二、创建项目三、运行项目四、增加API五、发布到IIS六、设置Vue的发布 前言 最近从VS2019 升级到了VS2022,终于可以使用官方的.Net Vue 组合了,但是使用过程中还是有很多问题,这里记录一下. 一、先决条件 Visual …

vue点击左边导航,右边出现页面步骤

vue点击左边导航&#xff0c;右边出现页面 步骤 一定要import不然会出错 index.js Course作为Homeview子路由 Homeview加入<Routerview> 点击跳转<RouterLink to> 父Homeview中有RouterView&#xff08;路由出口&#xff0c;跳转至相应路径&#xff09;和Router…

位运算,双指针,二分,排序算法

文章目录 位运算二进制中1的个数题解代码我们需要0题解代码 排序模版排序1题解代码模版排序2题解代码模版排序3题解代码 双指针最长连续不重复子序列题解代码 二分查找题解代码 位运算 1. bitset< 16 >将十进制数转为16位的二进制数 int x 25; cout << bitset<…

【力扣】102.二叉树的层序遍历

AC截图 题目 思路 维持一个队列&#xff0c;每次容纳一层的元素即可。 代码 /*** Definition for a binary tree node.* struct TreeNode {* int val;* TreeNode *left;* TreeNode *right;* TreeNode() : val(0), left(nullptr), right(nullptr) {}* Tre…

【HarmonyOS Next】图片选择方案

背景 封装一个选择图片和调用拍照相机的按钮&#xff0c;展示api13下选择图片和调用相机&#xff0c;可以使用不申请用户权限的方式&#xff0c;进行图片的选择和修改。但是&#xff0c;目前方案并未包含上传图片保存的功能&#xff0c;仅提供图片选择或者拍照后&#xff0c;图…

25年湖南省考报名流程保姆级教程

2025年湖南省考报名马上就要开始啦&#xff01; 有想要参加湖南省考的姐妹们&#xff0c;可以提前了解一下考试报名流程&#xff0c;熟悉考试报名照上传要求&#xff01; 一、考试时间安排 报名时间&#xff1a;2月17日9:00至2月25日 17:00 审核时间&#xff1a;2月17日9:0…