施磊老师rpc(四)
rpc网络服务简介
从 设置文件 读取 ip和port 后, 就必要 举行连接 传输 了
也就是 rpc框架预备好了, 现在必要 网络, 服务方 才气发布 rpc, 然后 斲丧端 才气 连接 去调用
RpcProvider 的计划目标
[*]让利用者只必要注册服务即可,不暴露 muduo 网络细节。
[*]从设置文件中主动加载 IP 和端口,克制用户手动输入。
[*]封装 muduo 的启动流程,让 RpcProvider::Run() 启动网络服务。
[*]利用基于 Reactor 模子的高性能网络服务。
Eventloop不利用智能指针-弃用
在 muduo 网络库中,EventLoop 通常不利用智能指针管理,而是直接利用原始指针,重要缘故因由如下:
[*]生命周期明白
EventLoop 通常是长期存在的对象(如伴随整个线程或程序生命周期),其烧毁机遇由代码逻辑直接控制,无需智能指针主动管理。
[*]栈对象主导
muduo 的计划中,EventLoop 多作为栈对象(如 EventLoop loop;),依靠作用域主动析构,无需堆内存分配和智能指针介入。
[*]从属关系清晰
TcpServer 仅持有 EventLoop 的指针(体现依靠关系),而非全部权。EventLoop 的现实生命周期由更高层(如 main() 或线程函数)管理,克制全部权紊乱。
[*]性能与简便性
原始指针更轻量,符合 muduo 高性能网络库的计划目标,同时代码更直观(智能指针在此场景无明显上风)。
总结:EventLoop 的生命周期由应用逻辑显式控制,且多为栈对象,利用原始指针更符合 muduo 的计划哲学和现实利用模式。
RpcProvider雷同于集群的服务器
雷同, 并不完全雷同
provider网络实现
[!TIP]
这个 run 函数 就相当于 集群谈天里 的 main 调用 chatserver 差不多
src/include/rpcprovider.h
private:
// // 组合TcpServer ----- 不写成成员了, 只有 run 会访问
// std::unique_ptr<muduo::net::TcpServer> m_pTcpServer; // 智能指针
// 组合 EventLoop
muduo::net::EventLoop m_eventLoop; // 事件循环
void OnConnection(const muduo::net::TcpConnectionPtr &conn); // 连接回调函数
void OnMessage(const muduo::net::TcpConnectionPtr &conn, // 消息回调函数
muduo::net::Buffer *buffer,
muduo::Timestamp time);
src/include/mprpcapplication.h
// 获取配置文件对象
static MprpcConfig &GetConfig();
.cc
// 获取配置文件对象
MprpcConfig &MprpcApplication::GetConfig()
{
return m_config; // 返回配置文件对象
}
[!tip]
如果 有学过 集群项目
run 内里的 muduo部分 将是 融合的 集群服务器的 main, chatserver
src/rpcprovider.cc
// 启动rpc服务发布 节点, 开始提供rpc远程网络调用服务
void RpcProvider::Run()
{
std::string ip = MprpcApplication::GetInstance().GetConfig().Load("rpcserverip");
uint16_t port = atoi(MprpcApplication::GetInstance().GetConfig().Load("rpcserverport").c_str());
muduo::net::InetAddress addr(ip, port); // 绑定ip和端口号
// 创建TcpServer对象
muduo::net::TcpServer server(&m_eventLoop, addr, "RpcProvider");
// 设置线程数量
server.setThreadNum(4);
// 设置连接回调函数
server.setConnectionCallback(std::bind(&RpcProvider::OnConnection, this, std::placeholders::_1));
// 设置消息回调函数
server.setMessageCallback(std::bind(&RpcProvider::OnMessage, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
std::cout << "RpcProvider start service " << std::endl;
// 启动服务
server.start();
// 事件循环
m_eventLoop.loop();
}
// 实现连接回调函数
void RpcProvider::OnConnection(const muduo::net::TcpConnectionPtr &conn)
{
}
// 实现消息回调函数
void RpcProvider::OnMessage(const muduo::net::TcpConnectionPtr &conn, // 消息回调函数
muduo::net::Buffer *buffer,
muduo::Timestamp time)
{
// 1. 解析rpc请求数据
// 2. 生成响应数据
// 3. 发送响应数据
}
由于利用了 muduo, 因此 cmke 要添加 编译选项
错误1
如果 cmake 加的编译选项是 muduo, 一样寻常会 找不到库
find /usr -name "libmuduo*"
/usr/local/lib/libmuduo_base.a
/usr/local/lib/libmuduo_net.a
/usr/local/lib/libmuduo_http.a
/usr/local/lib/libmuduo_inspect.a
[!important]
muduo 变为 muduo_net muduo_base
序次必须一样, net依靠base
一样寻常是这些库, 没有 muduo.a
前两个最告急
库文件功能分析典范依靠关系libmuduo_base.a底子核心库,包罗 EventLoop、Timestamp、Logging 等底子工具类无依靠,是其他模块的底子libmuduo_net.a网络通讯库,提供 TcpServer、TcpClient、Buffer 等网络相干类依靠 libmuduo_base.alibmuduo_http.aHTTP 协议支持库,提供简单的 HTTP 服务器和客户端功能依靠 libmuduo_net.alibmuduo_inspect.a调试监控库,支持通过 HTTP 接口查察服务器内部状态(如连接数、线程状态)依靠 libmuduo_http.a错误2-重点
/usr/bin/ld: /usr/local/lib/libmuduo_net.a(EventLoop.cc.o): relocation R_X86_64_TPOFF32 against `_ZN12_GLOBAL__N_118t_loopInThisThreadE' can not be used when making a shared object; recompile with -fPIC
/usr/bin/ld: failed to set dynamic section sizes: bad value
collect2: error: ld returned 1 exit status
gmake: *** Error 1
gmake: *** Error 2
本项目标 mprpc 是动态库, muduo…是静态库
错误的缘故因由:你在编译动态库时,链接了一个静态库(libmuduo_net.a),但是这个静态库中的对象文件(EventLoop.cc.o)没有利用 -fPIC 编译。这导致链接器出现了 “relocation can not be used when making a shared object” 错误,由于静态库中的代码是位置相干的,不能直接与动态库链接。
[!important]
重点错误!
当你在构建动态库(.so)时,链接了一个静态库(.a),但是静态库中的代码没有利用 位置无关代码(Position Independent Code,PIC) 编译选项(即 -fPIC),就会出现链接错误,通常体现为雷同以下的错误信息:
relocation R_X86_64_TPOFF32 against `symbol` can not be used when making a shared object; recompile with -fPIC
详见知识增补
静态库 一样寻常不必要 -fPIC, 一样寻常只有动态库必要
但是, 当 静态库 必要连接到 动态库时, 静态库必须加 -fPIC 选项
错误3
muduo_net muduo_base
序次必须一样, net依靠base
测试
自行
RpcProvider发布服务方法(一)
[!tip]
发起看 原视频, 过程讲的很清晰
https://img-home.csdnimg.cn/images/20230724024159.png?origin_url=.%2F1-mrpc.assets%2Fimage-20250504161656029.png&pos_id=img-C6juoEMo-1746372538266
RPC Provider的核心功能
[*]已实现功能:(run函数)
[*]网络模块:基于Muduo库实现TCP服务器,处理处罚网络数据收发(绿色部分)。
[*]变乱循环与回调:通过onConnection和onMessage回调处理处罚连接和消息。
[*]待实现功能(黄色部分):
[*]服务方法注册与映射:通过NotifyService记载服务对象及其方法,供长途调用时定位。
RPC Provider 网络模块实现
[*]利用 muduo 网络库,实现了 RPC Provider 的网络通讯本领;
[*]涉及 TcpServer 和 EventLoop 以及两个核心回调函数:
[*]onConnection:新连接创建
[*]onMessage:吸收长途调用哀求(字节流)
RPC 服务方法的发布功能
目标:让利用者可以或许将本地服务对象的方法注册为长途可调用的 RPC 方法。
框架提供的接口:
void notifyService(::google::protobuf::Service* service);
[*]参数:是一个继承自 protobuf::Service 的指针;
[*]利用者将自界说的服务类(如 UserService)传入,即可注册它的全部 RPC 方法。
角色分析
名称分析callerRPC 的调用者(客户端)calleeRPC 的提供者(服务端)mprpc 框架提供 mprpcApplication、RpcProvider 等功能模块[!tip]
任何 分布式节点 都大概成为一个 rpc服务器—collee, 也大概 哀求调用 其他rpc方法 — coller
run函数的流程
流程示例(以UserService::Login为例):
[*]吸收哀求:RPC Provider通过网络模块吸收字节流。
[*]反序列化:Protobuf将数据剖析为LoginRequest对象。
[*]方法调用:框架查表找到UserService的Login方法并调用。
[*]处理处罚相应:用户代码添补LoginResponse,框架序列化后通过网络返回。
NotifyService的作用
[*] 核心目标:创建服务对象与方法调用的映射表,使框架能根据哀求调用精确的本地方法。
[*] 记载服务对象(如 UserServiceRpc);
记载该服务对象中有哪些方法(login、register 等);
利用 protobuf 提供的反射接口,获取:
[*]service 名称;
[*]每个方法的名称;
[*]方法的编号 / 反射调用方式。
框架帮我们做了什么?
步调谁来做?作用吸收字节流网络模块(muduo)收到 RPC 哀求反序列化protobuf还原哀求对象定位方法框架内部映射表找到服务和函数反射调用protobuf + 注册表调用我们自己重写的逻辑回传效果框架负责序列化 + 网络发送客户端吸收效果RpcProvider发布服务方法(二)
实现 RpcProvider::NotifyService
为什么如许做(目标):
[*]将用户界说的 service 对象(如 UserService)注册到框架中。
[*]框架后续通过 service 名和 method 名即可定位到具体的服务方法,实现长途调用。
[*]抽象性计划原则:框架不依靠具体业务类,仅依靠于 protobuf 的 Service 基类。
框架中处理处罚服务的通用做法
[*] 利用 google::protobuf::Service 作为服务对象的基类指针。
[*] 利用 protobuf 的 反射机制(通过 descriptor 描述服务和方法),实现对服务对象的动态管理。
[*] // pb.h 里面 server的子类里
static const ::PROTOBUF_NAMESPACE_ID::ServiceDescriptor* descriptor();
[*] 至于 这个 范例里 有什么, 点进去看即可 , 挺多的, 下面这个不全
[*] https://img-home.csdnimg.cn/images/20230724024159.png?origin_url=.%2F1-mrpc.assets%2Fimage-20250504165531741.png&pos_id=img-oSWeZcWF-1746372538268
具体实现步调(NotifyService 方法逻辑)
[*]获取服务描述信息
利用 service->GetDescriptor() 得到服务的元数据,包括服务名称、方法个数等。
[*]提取服务名
用 descriptor->name() 获取服务的唯一标识名(如 UserServiceRpc)。
[*]提取每个方法的描述信息
通过循环 descriptor->method(i) 获取每个方法的 MethodDescriptor,记载方法名等。
[*]界说数据布局用于存储注册信息
自界说一个 ServiceInfo 布局体:
[*]包罗一个服务对象指针 Service*.
[*]包罗一个 unordered_map<string, const MethodDescriptor*> 存储方法名与其描述对象的映射。
[*]维护全局服务映射表
用 unordered_map<string, ServiceInfo>,键是服务名,值是该服务的全部信息(对象+方法表)。
[*]将服务及其方法插入映射表中
完成注册,后续框架收到哀求时即可根据服务名和方法名快速查找、调用对应的业务逻辑。
错误-1
不答应利用指向不完备范例 “google::protobuf::ServiceDescriptor” 的指针或引用
这个错误表明编译器在处理处罚 google::protobuf::ServiceDescriptor 时以为它是一个不完备范例(incomplete type),即编译器看到了它的声明(比如前向声明),但没有看到完备的界说。这通常是由于缺少对应的头文件包罗。
解决方法
#include <google/protobuf/descriptor.h> // 这个头文件里有 ServiceDescriptor
NotifyService实现
[!important]
每个 ServiceInfo 记载一个服务及其全部方法.
整个服务注册表维护多个如许的服务信息,支持多服务同一管理。
服务注册表(map<string, ServiceInfo>)
└── "UserServiceRpc"→ServiceInfo
├── service指针(UserServiceRpc*)
└── method_map(map<string, const MethodDescriptor*>)
├── "Login" → MethodDescriptor*
└── "Register"→ MethodDescriptor*
└── "FriendServiceRpc" → ServiceInfo
├── service_ptr: FriendServiceRpc*
└── method_map
├── "AddFriend" → AddFriend 方法的描述符
└── "GetList" → GetList 方法的描述符
当你调用 NotifyService(UserServiceRpc*):
[*]通过服务对象获取其描述信息(ServiceDescriptor)。
[*]从描述信息中提取出:
[*]服务名称(如 “UserServiceRpc”)
[*]每一个方法的:
[*]方法名(如 “Login”, “Register”)
[*]输入参数范例(如 LoginRequest)
[*]输出参数范例(如 LoginResponse)
在 RPC 框架中,定名一样寻常如下:
单个服务:Service
指的是一个具体的业务服务,比如 UserService、OrderService,通常是继承自 google::protobuf::Service 的类实例。它包罗多个可以长途调用的方法(RPC 方法)。
全部服务的聚集:服务注册表(Service Registry) 或 服务映射表(Service Map)
src/include/rpcprovider.h
// private 添加
// service 服务类型信息----服务名及方法名
struct ServiceInfo
{
google::protobuf::Service *m_service; // 服务对象
std::unordered_map<std::string, const google::protobuf::MethodDescriptor *> m_methodMap; // 方法名和方法描述对象的映射关系
};
// 存储服务对象的容器
std::unordered_map<std::string, ServiceInfo> m_serviceMap; // 服务名和服务对象的映射关系
src/rpcprovider.cc
// 这里是框架提供给外部使用的, 可以发布rpc方法的函数接口
void RpcProvider::NotifyService(google::protobuf::Service *server)
{
ServiceInfo service_info; // 服务对象信息
// 获取服务对象描述信息---- 去看看 pb.h 和 pb.cc 这个接口, 是const!!
const google::protobuf::ServiceDescriptor *pserviceDesc = server->GetDescriptor();
// 获取服务名称
std::string service_name = pserviceDesc->name(); // 获取服务名称
std::cout << "service_name: " << service_name << std::endl;
// 获取服务方法数量
int method_count = pserviceDesc->method_count(); // 获取服务方法数量
// 获取服务对象指定下标的方法描述信息
for (int i = 0; i < method_count; ++i)
{
const google::protobuf::MethodDescriptor *pMethodDesc = pserviceDesc->method(i); // 获取服务对象指定下标的方法描述信息
// 获取方法名称
std::string method_name = pMethodDesc->name(); // 获取方法名称
service_info.m_methodMap.insert({method_name, pMethodDesc}); // 将方法名称和方法描述对象的映射关系存入容器
std::cout << "method_name: " << method_name << std::endl;
}
service_info.m_service = server; // 将服务对象存入容器
// 将服务名称和服务对象存入容器
m_serviceMap.insert({service_name, service_info});
}
注意
[!warning]
NotifyService函数 每次 只注册一个 服务
想要注册多个 服务, 就要 多次举行调用
示例
// 用户代码:注册多个服务
UserServiceRpc user_service;
OrderServiceRpc order_service;
provider.NotifyService(&user_service); // 注册 UserService
provider.NotifyService(&order_service); // 注册 OrderService
总结:为什么我们选择 Protocol Buffer 而不是 JSON
在网络通讯中,必须选用一种数据传输协议来举行布局化数据的互换。不能直接传输原始的字节流或字符串,由于我们必要明白区分差别的字段、范例和布局,这就必要一种标准化的“数据格式”。
常见的选择:
[*]XML:过于冗长、服从低,已经很少利用。
[*]JSON:布局清晰,易于阅读,学习本钱低,但服从偏低。
[*]Protocol Buffer(Protobuf):谷歌开辟的高效二进制序列化协议。
JSON 和 Protobuf 的对比:
项目JSONProtobuf存储格式文本(可读性好)二进制(服从高)序列化性能较慢非常快传输体积大(包罗键名)小(紧凑、无额外字段名)范例体系弱范例(依靠剖析库)强范例(.proto 明白界说)支持 RPC 方法不支持支持 Service 界说和方法描述(gRPC) 为什么 Protobuf 更得当 RPC 场景:
[*]它不但提供数据布局的序列化与反序列化功能(像 JSON 一样),
[*]还支持对服务(service)和方法(rpc method)的描述,可以用于主动天生代码、举行长途调用处理处罚(这是 JSON 无法做到的)。
因此,在构建谈天服务器等高性能通讯体系时,我们更倾向于利用 Protobuf 而不是 JSON,尤其是在服务之间通过 RPC 调用的场景下,它能极大提升服从与可维护性。
RpcProvider分发rpc服务(一)
本节课使命
完成proto的反序列化
完成 RPC 框架中 Provider 端的 onMessage 方法,实现:
[*]从网络吸收 RPC 哀求数据;
[*]剖析出哀求的目标服务、方法名、参数;
[*]找到对应方法并调用;
[*]返回效果给 Client。
回顾
[*]notifyService() 中已将用户发布的服务和方法注册进 Map 中(类比于“服务表”)。
[*]run() 方法完成了网络监听和服务启动。
[*]onConnection():短连接模子下,当断开连接时关闭 socket(shutdown(fd, SHUT_RDWR))。
onMessage 中要做什么?
收到的数据本质是一个字符流(字节流),必须:
[*]拆包:防止 TCP 粘包;
[*]反序列化:根据协议提取出 serviceName、methodName 和 args;
[*]查找目标方法并调用;
[*]序列化相应并返回给 Client。
明白流程-不要混
【客户端】 【服务端】
RPCStub::login() onMessage()
↓ ↓
序列化:方法名+参数 <----- 收到字节流
↓ ↓
通过 TCP 发出请求 -----> 提取数据(Buffer)
↓
RPC框架反序列化
↓
映射到本地的 login()
↓
执行并获取返回值
↓
序列化返回值,send()
[!important]
RPC 是目标,Muduo 是网络通讯框架,onMessage 是 Muduo 中处理处罚收到消息的“钩子函数”,它帮我们解包网络数据,并触发 RPC 调用。
明白错误
序列化后的字符串 和 传输的字节流
你发送的是字符串(字节数组),网络传输的是流(字节流),收到后要自己从流中“分段”,提取成完备字符串,再反序列化还原数据。
muduo 中的 conn->send() 使得 序列化后的 数据 以字节流 传输
因此, onmessage 基本第一步 都是把 收到的字节流 转为 字符串, 然后 再反序列化----->
以json为例
{
"name": "Tom",
"age": 18
}
// 序列化
std::string jsonStr = "{\"name\":\"Tom\",\"age\":18}";
// 字节流
0x7b 0x22 0x6e 0x61 0x6d 0x65 0x22 0x3a 0x22 0x54 0x6f 0x6d 0x22 0x2c ...
// 再转字符串, 并 反序列化
英语单词
Customer(顾客)
Consumer(斲丧者)
json vs proto反序列化
1. Proto必要预界说数据布局
[*]JSON: 通常是动态的,不必要预界说数据布局,你可以直接利用字符串和数字等常见数据范例。
[*]Proto: 利用 proto 文件 界说数据布局,全部的消息格式必要事先声明和编译天生相应的代码(比方 .proto 文件天生 C++、Java 或 Python 代码)。这意味着你必须严格依照界说好的消息布局才气精确地举行序列化和反序列化。
2. Proto天生代码
[*]JSON: 不必要天生任何代码,直接通过标准库(如 nlohmann/json、rapidjson)举行处理处罚。
[*]Proto: 必要用 protoc 编译器将 .proto 文件转换为特定语言的类。这些类中会包罗天生的 getter 和 setter 方法来访问字段。
3. 数据反序列化过程
[*]JSON: 在反序列化时,直接通过 JSON 库将字符串转为 JSON 对象,通常代码简便。---- json::parse(recvBuf);
[*]Proto: 反序列化时,必要将字节流(比方,网络传输的消息)剖析为特定的 protobuf 对象。这个过程稍微复杂一些,通常涉及到对序列化数据流的剖析,调用 ParseFromString 或雷同的方法。
4. 数据格式
[*]JSON: 是文本格式,便于阅读和调试,但占用空间相对较大。
[*]Proto: 是二进制格式,具有更高的性能和更小的消息体积,但不轻易直接读取和调试。
数据头
json也可以 数据头
#include <iostream>
#include <nlohmann/json.hpp>
using json = nlohmann::json;
struct Header {
std::string message_type;
size_t message_length;
int checksum;
std::string protocol_version;
};
struct Message {
Header header;
json body;
};
int main() {
// 构建 JSON 数据体
json body = {
{"userId", 1},
{"name", "Alice"},
{"age", 30}
};
// 构建数据头
Header header = {
"Request", // 消息类型
body.dump().size(), // 消息体的长度
12345, // 校验和
"1.0" // 协议版本
};
// 构建完整消息
Message message = { header, body };
// 输出完整消息(仅示意)
std::cout << "Header:\n";
std::cout << "Message Type: " << message.header.message_type << "\n";
std::cout << "Message Length: " << message.header.message_length << "\n";
std::cout << "Checksum: " << message.header.checksum << "\n";
std::cout << "Protocol Version: " << message.header.protocol_version << "\n";
std::cout << "\nBody:\n";
std::cout << message.body.dump() << std::endl;
return 0;
}
protobuf利用不太一样—具体看实现
粘包处理处罚机制
TCP 是流式协议,不能保证一次吸收的数据就是一整包。为此我们必须:
[*]用固定长度(4字节)作为 header 长度标志;
[*]然后再根据这个长度去读取 header,再读取参数。
例子
假设客户端连续发送了三个消息,每个消息的长度为 5 字节:
Message 1: "Hello"
Message 2: "World"
Message 3: "Data"
然而,TCP 协议会将这些数据归并成一个连续的字节流举行传输,大概会变成如下情势(这仅是一个例子,现实数据大概更复杂):
HelloWorldData
吸收方收到的就是一个连续的数据流:“HelloWorldData
”,无法直接知道哪个部分属于哪个消息。
为什么不消“加竖杠分隔字符串”的做法?
上不了 台面的 玩意儿!! 太勾八垃圾了
由于:
[*]不规范、性能差;
[*]不支持嵌套布局;
[*]不可扩展;
[*]轻易堕落;
精确做法是接纳 protobuf 作为布局化序列化协议,明白字段范例和布局。
proto反序列化
自界说协议计划(数据格式)
RPC Client 与 RPC Server 之间利用自界说协议通讯。数据格式如下:
[*]header size:4 字节整数(利用二进制情势存储,不能转字符串!)
[*]header 字符串:利用 protobuf 界说的 RpcHeader,包罗:
[*]service_name
[*]method_name
[*]args_size(参数部分的字符串长度)
[*]args 字符串:参数 message 的序列化效果
示例
内容范例十六进制内容寄义头部长度0x00 0x00 0x00 0x1218 字节,体现后面数据头部分长度为 18 字节数据头{"method":"Chat"}一个 JSON 字符串,18 字节,体现调用的 RPC 方法名是 Chat消息体"hello world"现实发送的消息内容 Proto 实现
syntax = "proto3";
package mprpc;
message RpcHeader {
string service_name = 1;
string method_name = 2;
uint32 args_size = 3;
}
天生对应 .pb.h、.pb.cc 后在 onMessage 中利用。
数据处理处罚流程总结(onMessage 中)
一个 char 占用 1个字节(8bit)
string 每个字符 就是一个字节
std::string 操作的是字节
这是整个流程, 本节课仅完成 前三个步调
[*]读取前 4 字节 ➜ 得到 header 的长度; ----- 这个长度 不能用字符串, 由于长度 就不固定了!!! “10” “10000”
[*]读取 header 字符串 ➜ RpcHeader::ParseFromString() 得到 service/method/args_size;
[*]读取 args 部分 ➜ 根据 args_size 读取参数字符串;
[*]从 map 中查找 service 对象;
[*]通过 method_name 查找 MethodDescriptor;
[*]调用 CallMethod() 实行目标服务逻辑;
[*]将相应效果 serialize 成字符串,通过 TCP 发回 client。
对protobuf字节流的明白-不肯定对
重点明白
在写好 proto 文件后, 就有了 雷同于键值对的 东西, 但是 protobuf 主动完成了封装, 也就是 message类, 利用标签 就可以知道 是哪个字段
利用时, 就相当于 是是用类, 界说一个 message 对象, 举行 set_字段 赋值
序列化后 SerializeToString, 大概变为了 标签:值如许的, 1:name 2:pwd
传输大概就是 这么传输
然后 反序列化 ParseFromString后, 利用.字段() 就能直接拿到值, 这在内部 protobuf 自己举行了封装, 使得 调用 直接就拿到了 值
onmessage实现-部分
src/rpcprovider.cc
#include "rpcheader.pb.h" // 这个头文件里有 RpcHeader
/*
在框架内部, rpcprovider 和 rpcconsumer 协商好 之间的协议, 约定好数据的格式
service_name method_name args
UserServiceLoginzhang san123456 传这样的 肯定不行,需要拆分
定义 proto 的 message 类型, 进行 数据头的 序列化和 反序列化\
加上头部长度
16UserServiceLoginzhang san123456
16 4个字节的头部长度
从 4个子节后 取出 16
UserServiceLoginzhang san123456
*/
// 实现消息回调函数
// 已建立连接用户的 读写事件回调, 如果远程有 rpc服务的 调用请求, 那么onMessage 就会被调用
void RpcProvider::OnMessage(const muduo::net::TcpConnectionPtr &conn, // 消息回调函数
muduo::net::Buffer *buffer,
muduo::Timestamp time)
{
// 网络上接收的远程rpc调用请求的字符流
std::string recv_buf = buffer->retrieveAllAsString(); // 获取接收的字符流
uint32_t header_len = 0; // 定义头部长度
recv_buf.copy((char *)&header_len, 4, 0); // 本项目 自定义 是 4个字节的头部长度
// 根据头部长度, 获取到数据头的内容
// recv_buf 是一个字符串, 头部长度是4个字节, 所以从第4个字节开始, 取header_len长度的内容
std::string rpc_header_str = recv_buf.substr(4, header_len);
// 反序列化数据, 得到rpc请求的 详细信息
mprpc::RpcHeader rpc_header; // 定义rpc请求头对象
std::string service_name; // 定义服务名称
std::string method_name; // 定义方法名称
uint32_t args_size = 0; // 调用方法所要的参数大小
if(rpc_header.ParseFromString(rpc_header_str)) // 反序列化数据, 得到rpc请求的 详细信息
{
rpc_header.service_name(); // 获取服务名称
service_name = rpc_header.service_name(); // 获取服务名称
method_name = rpc_header.method_name(); // 获取方法名称
}
else
{
std::cout << "rpc_header parse error" << std::endl;
return;
}
// 获取 rpc的方法参数 的字符流数据
std::string args_str = recv_buf.substr(4 + header_len, recv_buf.size() - 4 - header_len); // 获取方法参数的字符流数据
// 打印调试信息
std::cout<<"============================="<<std::endl;
std::cout<<"header_len: "<<header_len<<std::endl;
std::cout<<"rpc_header_str: "<<rpc_header_str<<std::endl;
std::cout<<"service_name: "<<service_name<<std::endl;
std::cout<<"method_name: "<<method_name<<std::endl;
std::cout<<"args_size: "<<args_str.size()<<std::endl;
std::cout<<"args_str: "<<args_str<<std::endl;
std::cout<<"============================="<<std::endl;
}
总结
本节完成 proto 和 onmessage 实现
小技巧:读取定长二进制整数–临时还没碰到标题
C++ 里读取头部 4 字节为整数的方法(假设 buffer 是 std::string):
uint32_t header_size = 0;
memcpy(&header_size, recv_str.data(), sizeof(uint32_t));
注意字节序标题(本地是小端序)——视项目需求是否利用 ntohl()。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页:
[1]