RPC框架源码分析学习(二)

RPC框架源码分析与原理解读

前言

在分布式系统开发中,远程过程调用(RPC)是一项基础且关键的技术。通过对KVstorageBaseRaft-cpp项目RPC模块的源码分析,我深入理解了RPC框架的工作原理和实现细节。本文将从程序员视角分享我的学习心得。

框架概述

本项目中的RPC框架是一个基于Google Protobuf和Muduo网络库实现的C++版RPC框架。它支持服务注册、服务发现和远程方法调用,提供了简洁的接口和高效的数据传输机制。

核心组件分析

1. RPC通信协议

首先,我分析了RPC通信协议的定义文件rpcheader.proto

protobuf 复制代码
syntax = "proto3";
package RPC;

// RPC请求和响应的消息格式
message RpcHeader {
    string service_name = 1;  // 服务名
    string method_name = 2;   // 方法名
    uint32 args_size = 3;     // 参数大小
}

这个定义非常精简,包含了服务名、方法名和参数大小三个关键信息,这些信息构成了RPC请求的头部。

2. RPC客户端实现

2.1 MprpcChannel类

MprpcChannel是客户端发起RPC调用的核心类,继承自google::protobuf::RpcChannel

cpp 复制代码
class MprpcChannel : public google::protobuf::RpcChannel {
public:
    // 构造函数,支持立即连接或延迟连接
    MprpcChannel(string ip, short port, bool connectNow);
    // 关键方法:负责序列化请求、发送请求并接收响应
    void CallMethod(const google::protobuf::MethodDescriptor* method,
                   google::protobuf::RpcController* controller,
                   const google::protobuf::Message* request,
                   google::protobuf::Message* response,
                   google::protobuf::Closure* done) override;
    // 其他辅助方法...
};

CallMethod的实现最为关键,它完成了:

  1. 获取服务名和方法名
  2. 序列化请求参数
  3. 构造RPC请求头
  4. 发送请求并等待响应
  5. 解析响应数据

我特别注意到请求消息格式的设计:

复制代码
header_size(4字节变长编码) + header_str + args_str

这种设计保证了网络传输的高效性和兼容性。

3. RPC服务端实现

3.1 RpcProvider类

RpcProvider是服务端的核心类,负责服务注册和请求处理:

cpp 复制代码
class RpcProvider {
public:
    // 注册服务
    void NotifyService(google::protobuf::Service *service);
    // 启动RPC服务
    void Run(int nodeIndex, short port);
private:
    // 连接回调
    void OnConnection(const muduo::net::TcpConnectionPtr &);
    // 消息回调,处理RPC请求
    void OnMessage(const muduo::net::TcpConnectionPtr &, muduo::net::Buffer *, muduo::Timestamp);
    // 发送RPC响应
    void SendRpcResponse(const muduo::net::TcpConnectionPtr &, google::protobuf::Message *);
    // 其他成员...
};
3.2 服务注册机制

服务注册利用了Protobuf的反射机制,通过NotifyService方法实现:

cpp 复制代码
void RpcProvider::NotifyService(google::protobuf::Service *service) {
    ServiceInfo service_info;
    const google::protobuf::ServiceDescriptor *pserviceDesc = service->GetDescriptor();
    std::string service_name = pserviceDesc->name();
    int methodCnt = pserviceDesc->method_count();
    
    for (int i = 0; i < methodCnt; ++i) {
        const google::protobuf::MethodDescriptor *pmethodDesc = pserviceDesc->method(i);
        std::string method_name = pmethodDesc->name();     
        service_info.m_methodMap.insert({method_name, pmethodDesc});
    }
    service_info.m_service = service;
    m_serviceMap.insert({service_name, service_info});
}

这段代码通过Protobuf的反射机制获取服务描述符和方法描述符,并将它们存储在哈希表中,以便后续查找和调用。

3.3 请求处理流程

OnMessage方法处理接收到的RPC请求:

  1. 解析请求头,获取服务名、方法名和参数大小
  2. 查找对应的服务和方法
  3. 反序列化请求参数
  4. 创建响应对象和回调
  5. 调用目标方法

最关键的部分是动态调用目标方法:

cpp 复制代码
service->CallMethod(method, nullptr, request, response, done);

这里用到了Protobuf的动态调用机制,method是之前通过反射获取的方法描述符,done是一个回调对象,用于处理方法执行完成后的操作。

3.4 回调机制实现

回调函数的创建使用了Protobuf提供的NewCallback模板函数:

cpp 复制代码
google::protobuf::Closure *done =
    google::protobuf::NewCallback<RpcProvider, 
                                 const muduo::net::TcpConnectionPtr &, 
                                 google::protobuf::Message *>(
        this, &RpcProvider::SendRpcResponse, conn, response);

这段代码创建了一个绑定了当前对象、连接和响应对象的回调函数,在RPC方法执行完成后将被调用,用于发送响应数据。

实例分析:RPC示例代码

通过分析example/rpcExample目录下的示例,进一步理解了RPC框架的使用方法。

1. 服务定义

protobuf 复制代码
service FiendServiceRpc {
    rpc GetFriendsList(GetFriendsListRequest) returns (GetFriendsListResponse);
}

2. 服务实现

cpp 复制代码
class FriendService : public fixbug::FiendServiceRpc {
public:
    void GetFriendsList(google::protobuf::RpcController* controller,
                      const fixbug::GetFriendsListRequest* request,
                      fixbug::GetFriendsListResponse* response,
                      google::protobuf::Closure* done) override {
        // 业务逻辑实现
        response->mutable_result()->set_errcode(0);
        response->mutable_result()->set_errmsg("");
        done->Run();  // 调用完成,发送响应
    }
};

3. 服务注册与启动

cpp 复制代码
int main(int argc, char** argv) {
    // 创建RPC服务提供者
    RpcProvider provider;
    // 创建服务对象
    FriendService friendService;
    // 注册服务
    provider.NotifyService(&friendService);
    // 启动RPC服务,指定节点ID和端口
    provider.Run(1, 8000);
    return 0;
}

技术难点解析

1. 模板与回调函数

RPC框架中大量使用了C++模板和回调函数,这是一个重要的技术点。特别是NewCallback函数的使用:

cpp 复制代码
google::protobuf::NewCallback<Class, ArgType1, ArgType2>(
    this, &Class::Method, arg1, arg2);

这里模板参数<Class, ArgType1, ArgType2>指定了回调函数的类型和参数类型,而函数参数则提供了具体的对象、方法和参数值。

2. 序列化与网络传输

RPC框架的另一个关键点是如何高效地序列化和网络传输。我分析了其实现方式:

  1. 使用Protobuf进行序列化,保证了跨平台兼容性
  2. 采用"头部大小+头部内容+参数内容"的消息格式,解决了TCP流数据的边界问题
  3. 使用Muduo网络库处理TCP连接和事件回调,提供了高性能的网络IO

2025.5.15

相关推荐
西岸行者5 天前
学习笔记:SKILLS 能帮助更好的vibe coding
笔记·学习
悠哉悠哉愿意5 天前
【单片机学习笔记】串口、超声波、NE555的同时使用
笔记·单片机·学习
别催小唐敲代码5 天前
嵌入式学习路线
学习
毛小茛5 天前
计算机系统概论——校验码
学习
babe小鑫5 天前
大专经济信息管理专业学习数据分析的必要性
学习·数据挖掘·数据分析
范特西.i5 天前
QT聊天项目(8)
开发语言·qt
winfreedoms5 天前
ROS2知识大白话
笔记·学习·ros2
在这habit之下5 天前
Linux Virtual Server(LVS)学习总结
linux·学习·lvs
我想我不够好。5 天前
2026.2.25监控学习
学习
im_AMBER5 天前
Leetcode 127 删除有序数组中的重复项 | 删除有序数组中的重复项 II
数据结构·学习·算法·leetcode