RpcProvider(rpc服务提供者)实现思路

RpcProvider(服务提供者)实现思路

上一节说到,如何将一个本地服务发布成远程服务,但没有说明一个rpc框架怎么进行调用的,看看上节代码

#include <iostream>
#include <string>
#include "user.pb.h"
class UserService : public fixbug::UserServiceRpc    //使用rpc服务发布端(rpc服务提供者)
{
public:
    bool Login(std::string name, std::string pwd)
    {
        std::cout<<"doing local service : Login" << std::endl;
        std::cout<< "name:" << name << "pwd :" << pwd << std::endl;
    }    

/**
 * 重写基类UserServiceRpc的虚函数 下面这些方法都是框架直接调用的
 * caller   ===> Login(LoginRequest)  ==> transmit ==>  callee
 * callee   ===> Login(LoginRequest) ===> 调用下述的Login方法
 * */
    void Login(::google::protobuf::RpcController* controller,
                       const ::fixbug::LoginRequest* request,
                       ::fixbug::LoginResponse* response,
                       ::google::protobuf::Closure* done)
    {
        //框架给业务上报了请求参数LoginRequest 应用获取相应数据做本地业务
        std::string name = request->name();
        std::string pwd = request->pwd();
        
        //本地业务
        bool res = Login(name, pwd);

        //把响应写入 包括错误码、错误消息、返回值
        response->set_success(0);
        fixbug::ResultCode* rc = response->mutable_result();
        rc->set_errcode(0);
        rc->set_errmsg("");

        //执行回调操作   执行响应对象数据的序列化和网络发送(由框架来完成)
        done->Run();
    }
};

现在实现的发布服务方,那么显然Login方法是rpc框架帮我们调用的,什么时候调用?当rpc客户端通过网络发送rpc调用请求之后,这边接收到rpc请求,解析请求然后调用发布的服务方法,获取服务方法的响应在回传给rpc客户端即完成了一次远程rpc调用过程。
下面一一分析上面步骤的实现思路。
1、 首先,RpcProvider肯定是一个服务器,接收来自rpc客户端的请求,且能在一定程度上承载高并发的需求(考虑多个rpcClient给当前rpcProvider发送rpc调用请求)。显而易见RpcProvider需要一个高性能的网络库来做这些任务,那么可以考虑muduo网络库。
2、 网络库有了,用什么消息格式?–protobuf(优势就不赘述了)。 消息包含哪些内容?一个rpcclient发送请求过来调用一个远程方法,那么rpcProvider收到这个请求之后,能根据请求所携带的数据自动调用发布的rpc方法,那么请求必须包含服务名、方法名、以及参数,这样rpcProvider才知道怎么调用。即buffer = service_name + method_name + args
由于使用的是protobuf二进制消息存储,所以涉及到读取这几个参数的问题,可以将buffer加个4字节(int不会超过4字节)的head_size,head_size表示service_name和method_name的长度,这样可以先读取head_size,在根据head_size读取service_name+method_name。
最后读取args,思考一个问题,剩下的数据是不是都是args?不一定,考虑Tcp粘包的情况,一次接收到2个rpc调用请求,那么args也需要一个长度args_size来表示当前解析的rpc调用的参数长度。
因此这里借助protobuf定义一个消息格式即

message RpcHeader{
	bytes service_name = 1;
	bytes method_name = 2;
	uint32 args_size = 3;
}

那么header_size表示是该消息的长度,先读header_size,在读RpcHeader所包含的字节流,最后借助protobuf反序列化出这三个参数,那么得到了service_name 、method_name 、args_size。在接着读取args_size长度的字符流数据得到args。最后通过protobuf定义的参数格式反序列化出request。最终所需要的参数都已经拿到了。

3、 那么如何通过上面得到的参数自动调用rpc服务?首先得在本地存在这个远程服务,前面提到rpc框架来调用Login方法,那么 rpcProvider得预先对发布的 rpc方法做一下映射

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; // service_name ---> ServiceInfo

例如UserService存在Login 和 Register两个方法, 由于UserService继承自UserServiceRpc ,那么可以借助protobuf来实现这预先实现这个映射。具体如下:

//框架提供给外部使用的,可以发布rpc方法的函数接口
void RpcProvider::NotifyService(google::protobuf::Service* service)
{

        ServiceInfo service_info;

        //获取服务对象的描述信息
        const google::protobuf::ServiceDescriptor *pserviceDesc = service->GetDescriptor();
        //获取服务的名字
        std::string service_name = pserviceDesc->name();
        //获取服务对象service的方法的数量
        int methodCnt = pserviceDesc->method_count();

        std::cout<< " service_name " << service_name << std::endl;

        for(int i = 0; i<methodCnt; ++i){
                //获取了服务对象指定下标的服务方法的描述(抽象描述) UserService Login
                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 = service;
        m_serviceMap.insert({service_name, service_info});

}

RpcProvider可以借助这个方法注册一个rpc服务的站点,这样由RpcClient发送过来的rpc调用请求,通过muduo和protobuf解析得到上述几个参数之后,即可通过映射表调用指定的rpc服务。
即:

1、server_name, method_name, args= parse(buffer);  //解析参数
it = m_serviceMap.find(service_name);
method =  it->second.m_methodMap.find(method_name);
request = service->GetRequestPrototype(method).New();
2、request->ParseFromString(args_str) //序列化请求参数
response = service->GetResponsePrototype(method).New();
3、service->CallMethod(method, nullptr ,request, response, done); //调用具体的rpc方法  
---->  
4、最终调用:LoginLogin(::google::protobuf::RpcController* controller,
                       const ::fixbug::LoginRequest* request,
                       ::fixbug::LoginResponse* response,
                       ::google::protobuf::Closure* done)
--->
5、回调 done()

最后服务器调用完指定rpc方法后(此时response已填入响应),那么需要将respnse借助protobuf反序列化字节流数据,再通过muduo网络库发回给rpcClient,这就是done回调函数所做的事情。
即:

void done(fd, response)
{
   string response_str;
       if (response->SerializeToString(&response_str)) //将响应进行序列化
       {
               //序列化成功后,通过网络把rpc方法执行的结果发送给rpc的调用方
               fd->send(response_str);
       }
       fd->shutdown(); //模拟http的短链接服务, 由rpcprovider主动断开连接以免占用资源
}

到此RpcProvider基本实现。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1.项目代码均经过功能验证ok,确保稳定可靠运行。欢迎下载体验!下载完使用问题请私信沟通。 2.主要针对各个计算机相关专业,包括计算机科学、信息安全、数据科学与大数据技术、人工智能、通信、物联网等领域的在校学生、专业教师、企业员工。 3.项目具有丰富的拓展空间,不仅可作为入门进阶,也可直接作为毕设、课程设计、大作业、初期项目立项演示等用途。 4.当然也鼓励大家基于此进行二次开发。在使用过程中,如有问题或建议,请及时沟通。 5.期待你能在项目中找到乐趣和灵感,也欢迎你的分享和反馈! 【资源说明】 基于C++ module库 Protobuf Zookeeper实现Rpc框架源码+项目说明.zip 编译: `./autobuild.sh` 运行 bin目录下: `./provider -i test.conf ` (启动Rpc提供者) `./consumer -i test.conf`(启动Rpc调用者) 启动zookeeper,发现有rpc节点注册上 技术选型 - Protobuf - Zookeeper - C++ - Module库 `autobuild.sh `:启动脚本 - `bin`:可执行文件 - `build`:CMake构建编译 - `example`使用框架(RPC服务提供者和消费者) - `callee`:提供RPC服务服务端) - `caller`:调用RPC服务(客户端) - `lib`:编译成动态库 - `src`:框架源码(主要包含下面) - `rpcprovider.cpp`:rpc提供者 - `mprpcrpcchannel.cpp`:rpc的channel - `logger.cpp`:日志 - `zookeeperutil.cpp`:zookeeper - `test`:测试用例
RPC(Remote Procedure Call)是一种远程调用协议,它允许客户端程序调用远程服务器上的函数或方法。C++可以使用一些库来实现RPC服务,其中比较流行的有 gRPC 和 Apache Thrift。 以下是一个使用 gRPC 的简易 RPC 服务的示例: 1. 首先,需要安装 gRPC 和 Protocol Buffers: ``` sudo apt install -y build-essential autoconf libtool pkg-config grpc libgrpc++-dev protobuf-compiler-grpc ``` 2. 创建一个 Protocol Buffers 文件 `example.proto`,定义 RPC 服务的接口: ``` syntax = "proto3"; package example; service ExampleService { rpc SayHello (HelloRequest) returns (HelloResponse) {} } message HelloRequest { string name = 1; } message HelloResponse { string message = 1; } ``` 3. 使用 Protocol Buffers 编译器生成 C++ 代码: ``` protoc --grpc_out=. --cpp_out=. example.proto ``` 4. 实现 RPC 服务的接口: ``` #include <iostream> #include <memory> #include <string> #include <grpcpp/grpcpp.h> #include "example.grpc.pb.h" using grpc::Server; using grpc::ServerBuilder; using grpc::ServerContext; using grpc::Status; using example::HelloRequest; using example::HelloResponse; using example::ExampleService; class ExampleServiceImpl final : public ExampleService::Service { Status SayHello(ServerContext* context, const HelloRequest* request, HelloResponse* response) override { std::string prefix("Hello "); response->set_message(prefix + request->name()); return Status::OK; } }; void RunServer() { std::string server_address("0.0.0.0:50051"); ExampleServiceImpl service; grpc::ServerBuilder builder; builder.AddListeningPort(server_address, grpc::InsecureServerCredentials()); builder.RegisterService(&service); std::unique_ptr<Server> server(builder.BuildAndStart()); std::cout << "Server listening on " << server_address << std::endl; server->Wait(); } int main(int argc, char** argv) { RunServer(); return 0; } ``` 5. 编译并运行服务器代码: ``` g++ -std=c++11 -I. -I/usr/local/include -L/usr/local/lib example.pb.cc example.grpc.pb.cc example_server.cc -lgrpc++ -lgrpc -lgpr -lprotobuf -lpthread -o example_server ./example_server ``` 6. 编写客户端代码: ``` #include <iostream> #include <memory> #include <string> #include <grpcpp/grpcpp.h> #include "example.grpc.pb.h" using grpc::Channel; using grpc::ClientContext; using grpc::Status; using example::HelloRequest; using example::HelloResponse; using example::ExampleService; class ExampleClient { public: ExampleClient(std::shared_ptr<Channel> channel) : stub_(ExampleService::NewStub(channel)) {} std::string SayHello(const std::string& name) { HelloRequest request; request.set_name(name); HelloResponse response; ClientContext context; Status status = stub_->SayHello(&context, request, &response); if (status.ok()) { return response.message(); } else { return "RPC failed"; } } private: std::unique_ptr<ExampleService::Stub> stub_; }; int main(int argc, char** argv) { ExampleClient client(grpc::CreateChannel("localhost:50051", grpc::InsecureChannelCredentials())); std::string name("World"); std::string reply = client.SayHello(name); std::cout << "Received: " << reply << std::endl; return 0; } ``` 7. 编译并运行客户端代码: ``` g++ -std=c++11 -I. -I/usr/local/include -L/usr/local/lib example.pb.cc example.grpc.pb.cc example_client.cc -lgrpc++ -lgrpc -lgpr -lprotobuf -lpthread -o example_client ./example_client ``` 以上是一个简易的使用 gRPC 实现RPC 服务和客户端的示例。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值