[实现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. 客户端模块划分
在客户端的模块划分中,基于以上理解的功能,可以划分出这么级个模块:
- Protocol:应用层通信协议模块。
- Network:网络通信模块。
- Dispatcher:消息分发处理模块。
- Requestor:请求管理模块。
- RpcCaller:远端调用功能模块。
- Publish-Subscribe:发布订阅功能模块。
- Registry-Discovery:服务注册/发现/上线/下线功能模块。
- 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调用我们需要提供多种不同方式的调用:
- 同步调用:发起调用后,等收到响应结果后返回。
- 异步调用:发起调用后立即返回,在想获取结果的时候进行获取。
- 回调调用:发起调用的同时设置结果的处理回调,收到响应后⾃动对结果进行回调处理。

3.6 Publish-Subscribe模块
(1)Publish-Subscribe模块存在意义:向用户提供发布订阅所需的接口,针对推送过来的消息进行处理。
- 发布订阅稍微复杂⼀点,因为在发布订阅中有两种角色,⼀个客户端可能是消息的发布者,也可能是消息的订阅者。
- 而且不管是哪个角色都是 对主题进行操作,因此其中也包含了主题的相关操作,比如要发布⼀条消息需要先创建主题。
- 且⼀个订阅者可能会订阅多个主题,每个主题的消息可能都会有不同的处理方式,因此需要有订阅者主题回调的管理。

3.7 Registry-Discovery模块
(1)服务注册和发现模块需要实现的功能会复杂⼀些,因为分为两个角色来完成其功能:
- 注册者:作为Rpc服务的提供者,需要向注册中心注册服务,因此需要实现向服务器注册服务的功能。
- 发现者:作为Rpc服务的调用者,需要先进行服务发现,也就是向服务器发送请求获取能够提供指定服务的主机地址,获取地址后需要管理起来留用,且作为发现者,需要关注注册中心发送过来的服务上线/下线消息,以及时对已经下线的服务和主机进行管理。

3.8 Client模块
将以上模块进行整合就可以实现各个功能的客户端了。
- RegistryClient:服务注册功能模块与⽹络通信客户端结合。
- DiscoveryClient:服务发现功能模块与⽹络通信客户端结合。
- RpcClient:DiscoveryClient & RPC功能模块与网络通信客户端结合。
- TopicClient:发布订阅功能模块与⽹络通信客户端结合。




4. 框架设计
在当前项目的实现中,我们将整个项目的实现划分为三层来进行实现:
- 抽象层:将底层的网络通信以及应用层通信协议以及请求响应进行抽象,使项目更具扩展性和灵活性。
- 具象层:针对抽象的功能进行具体的实现。
- 业务层:基于抽象的框架在上层实现项目所需功能。
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 格式的字符串。
- 使用硬件随机数生成器和梅森旋转算法生成随机数。
- 将生成的随机数按十六进制格式组织,并在特定位置插入
"-"。 - 使用静态原子计数器确保 UUID 的唯一性,并将计数器值按字节格式化为十六进制。
- 最终,返回一个形如
xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx的字符串,表示一个生成的 UUID。
这种方式生成的 UUID 包含了随机性和递增序列,确保了其在大多数情况下的唯一性。
5.2 fields 消息类型定义
5.2.1 请求字段宏的定义:
消息ID:[具体的ID]
• 消息类型:◦ Rpc请求▪ 方法名称:[具体的方法名称]▪ 方法参数:[具体的参数]◦ 发布订阅相关请求▪ 主题名称:[具体的主题名称]▪ 操作类型:[如:订阅、发布等]▪ 主题消息:[具体的消息内容]◦ 服务操作相关请求▪ 方法名称:[具体的服务方法名称]▪ 操作类型:[如:启动、停止等]▪ 主机信息:• IP地址:[具体的IP地址]• PORT端口:[具体的端口号]
• 响应码:[具体的响应码,表示请求的处理结果]
• Rpc响应◦ 调用结果:[描述调用结果的具体内容]
宏定义参考 目录中具象层的设计

定义如下:
#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
};
⭕

相关文章:
[实现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 整体设计框架…...
【SFRA】笔记
GK_SFRA_INJECT(x) SFRA小信号注入函数,向控制环路注入一个小信号。如下图所示,当前程序,小信号注入是在固定占空比的基础叠加小信号,得到新的占空比,使用该占空比控制环路。 1.2 GK_SFRA_COLLECT(x, y) SFRA数据收集函数,将小信号注入环路后,该函数收集环路的数据,以…...
基于Python的Diango旅游数据分析推荐系统设计与实现+毕业论文(15000字)
基于Python的Diango旅游数据分析推荐系系统设计与实现毕业论文指导搭建视频,带爬虫 配套论文1w5字 可定制到某个省份,加40 基于用户的协同过滤算法 有后台管理 2w多数据集 可配套指导搭建视频,加20 旅游数据分析推荐系统采用了Python语…...
为什么docker 容器有的没有PORTS
容器的 PORTS 列没有显示端口映射信息,而 sonatype/nexus3:3.77.1 容器有显示,可能是由以下几个原因导致的: 1. --networkhost 参数的使用 正如前面提到的,当你使用 --networkhost 参数运行容器时,容器会直接使用宿主…...
国自然青年基金|针对罕见神经上皮肿瘤的小样本影像深度数据挖掘关键技术研究|基金申请·25-02-15
小罗碎碎念 今天和大家分享一个国自然青年基金项目,执行年限为2021.01~2023.12,直接费用为24万元。 该项目聚焦罕见神经上皮肿瘤小样本影像深度数据挖掘技术,致力于攻克小样本数据和临床经验缺乏带来的难题。项目围绕影像规范化、…...
《解锁自然语言处理:让公众正确拥抱AI语言魔法》
在当今数字化浪潮中,自然语言处理(NLP)技术作为人工智能领域的璀璨明珠,正以惊人的速度融入我们的生活。从智能语音助手到智能客服,从机器翻译到内容创作辅助,NLP技术无处不在。然而,如同任何强…...
算法刷题-哈希表的总结
什么时候用数组、什么时候用map呢? 经常会混淆。 混淆1:例如有时候题目可能要求在一大堆元素里找目标元素,要求不能利用用过的字母,这就会让我想到只包含一个键值的set或者是map,但实际上忽略了字母(限定大…...
单细胞转录组画小提琴VlnPlot只显示需要类型细胞
探序基因肿瘤研究院 整理 在R语言中,单细胞转录组数据画小提琴图中,我们如果想只展示出需要的特定细胞类型,可以把这需要展示的细胞类型单独构建成一个Seurat对象,再画图。 例如我们在Seurat对象中的meta.data表格中,…...
Linux 网络安全技巧
网络安全是一个非常重要的课题,基本上你运行的服务后台越多,你就可能打开更多的安全漏洞.如果配置的恰当的话,Linux本身是非常安全可靠的,假使在Linux系统中有某个安全缺陷,由于Linux的源码是开放的,有成千上万的志愿者会立刻发现并修补它。本文旨在介绍用来增强你的…...
泰山派RK3566移植QT,动鼠标时出现屏幕闪烁
总结: 交叉编译到 泰山派rk3566跑调海康摄像头的qt应用程序失败了。 X11无效窗口。 移植QT注意 屏幕分辨率不要改。改了执行QT的时候,framebuffer识别不出设备。 命令行安装QT-Creator sudo install 类似的指令安装Qt-Creator时,可能找不到编…...
Windows桌面系统管理7:国产操作系统与Linux操作系统
Windows桌面系统管理0:总目录-CSDN博客 Windows桌面系统管理1:计算机硬件组成及组装-CSDN博客 Windows桌面系统管理2:VMware Workstation使用和管理-CSDN博客 Windows桌面系统管理3:Windows 10操作系统部署与使用-CSDN博客 Wi…...
百度百舸 DeepSeek 一体机发布,支持昆仑芯 P800 单机 8 卡满血版开箱即用
在私有云环境中成功部署 DeepSeek 满血版并实现性能调优,并不是一件容易的事情。选择合适的 GPU 配置、安装相应的环境、成功部署上线业务、加速推理任务加速、支撑多用户并发 …… 完成业务测试,成功融入生产业务中。 为了帮助企业快速实现 DeepSeek 服…...
解锁 AIoT 无限可能,乐鑫邀您共赴 Embedded World 2025
2025 年 3 月 11-13 日,全球规模最大的嵌入式展览会——Embedded World 2025 将在德国纽伦堡盛大开幕。作为物联网和嵌入式技术领域的领先企业,乐鑫信息科技 (688018.SH) 将展示在 AI LLM、HMI、双频 Wi-Fi 6、低功耗 MCU 和 Matter 等领域的最新技术及解…...
LlamaFactory可视化模型微调-Deepseek模型微调+CUDA Toolkit+cuDNN安装
LlamaFactory https://llamafactory.readthedocs.io/zh-cn/latest/ 安装 必须保证版本匹配,否则到训练时,找不到gpu cuda。 否则需要重装。下面图片仅供参考。因为cuda12.8装了没法用,重新搞12.6 cudacudnnpytorch12.69.612.6最新…...
【GPT】从GPT1到GPT3
every blog every motto: Although the world is full of suffering, it is full also of the overcoming of it 0. 前言 从GPT1 到GPT3 1. GPT1 论文: https://s3-us-west-2.amazonaws.com/openai-assets/research-covers/language-unsupervised/lan…...
openGauss 3.0 数据库在线实训课程18:学习视图管理
前提 我正在参加21天养成好习惯| 第二届openGauss每日一练活动 课程详见:openGauss 3.0.0数据库在线实训课程 学习目标 掌握openGauss视图的管理:创建视图、删除视图、查询视图的信息、修改视图的信息。 课程作业 1.创建表,创建普通视图…...
基于flask+vue的租房信息可视化系统
✔️本项目利用 python 网络爬虫抓取某租房网站的租房信息,完成数据清洗和结构化,存储到数据库中,搭建web系统对各个市区的租金、房源信息进行展示,根据各种条件对租金进行预测。 1、数据概览 将爬取到的数据进行展示ÿ…...
手写简易RPC(实践版)
首先了解rpc rpc-远程过程调用,openFeign,Dubbo都可以算作rpc,以微服务来具体说明,就是在本地不需要去发送请求,通过rpc框架,像调用本地方法一样调用其他服务的方法,本质上还是要经过网络&…...
mysql学习笔记-多版本并发控制
1、什么是ReadView 在 MVCC机制中,多个事务对同一个行记录进行更新会产生多个历史快照,这些历史快照保存在 Undo Log里。如果一个事务想要查询这个行记录,需要读取哪个版本的行记录呢?这时就需要用到 ReadView 了,它帮我们解决了…...
问题记录汇总
记录一些问题 如何分析错误帧问题-CSDN博客...
算法日记20:SC72最小生成树(prim朴素算法)
一、题目: 二、题解 2.1:朴素prim的步骤解析 O ( n 2 ) O(n^2) O(n2)(n<1e3) 0、假设,我们现在有这样一个有权图 1、我们随便找一个点,作为起点开始构建最小生成树(一般是1号),并且存入intree[]状态数组中…...
requests.exceptions.JSONDecodeError: Expecting value: line 2 column 1 (char 1)
requests.exceptions.JSONDecodeError: Expecting value: line 2 column 1 (char requests.exceptions.JSONDecodeError 是 Python 中使用 requests 库进行 HTTP 请求时,当期望返回的响应体为 JSON 格式,但实际响应体不符合 JSON 格式时出现的错误。这个…...
Redis7——基础篇(五)
前言:此篇文章系本人学习过程中记录下来的笔记,里面难免会有不少欠缺的地方,诚心期待大家多多给予指教。 基础篇: Redis(一)Redis(二)Redis(三)Redis&#x…...
spring boot知识点1
1.什么是spring boot spring boot是spring框架的子项目,主要特点是自动配置,以及内置的tomcat服务器,适合快速开发web与微服务架构 2.spring boot和spring cloud俩者之间的联系 spring boot可单独运行, spring cloud则是用于多…...
从零搭建微服务项目Base(第7章——微服务网关模块基础实现)
前言: 在前面6章的学习中已经完成了服务间的调用实现,即各微服务通过nacos或eureka服务器完成服务的注册,并从nacos中拉取配置实现热更新。当某个服务接口需要调用其他服务时,通过feign定义接口,并通过注解配置服务名…...
pdf转换成word在线 简单好用 支持批量转换 效率高 100%还原
pdf转换成word在线 简单好用 支持批量转换 效率高 100%还原 在数字化办公的浪潮中,文档格式转换常常让人头疼不已,尤其是 PDF 转 Word 的需求极为常见。PDF 格式虽然方便阅读和传输,但难以编辑,而 Word 格式却能灵活地进行内容修…...
嵌入式音视频开发(二)ffmpeg音视频同步
系列文章目录 嵌入式音视频开发(零)移植ffmpeg及推流测试 嵌入式音视频开发(一)ffmpeg框架及内核解析 嵌入式音视频开发(二)ffmpeg音视频同步 嵌入式音视频开发(三)直播协议及编码器…...
SpringBoot速成概括
视频:黑马程序员SpringBoot3Vue3全套视频教程,springbootvue企业级全栈开发从基础、实战到面试一套通关_哔哩哔哩_bilibili 图示:...
微信小程序image组件mode属性详解
今天学习微信小程序开发的image组件,mode属性的属性值不少,一开始有点整不明白。后来从网上下载了一张图片,把每个属性都试验了一番,总算明白了。现总结归纳如下: 1.使用scaleToFill。这是mode的默认值,sc…...
Matlab写入点云数据到Rosbag
最近有需要读取一个点云并做处理后,重新写回rosbag。网上有很多读取的教程,但没有写入。自己写入时也遇到了很多麻烦,踩了一堆坑进行记录。 1. rosbag中一个lidar的msg有哪些信息? 通过如下代码,先读取一个rosbag的l…...
