最近在学习分布式系统,学一点皮毛分布式系统,了解一下分布式系统。
目前我对分布式的理解就是一个子模块请求调用某个服务,通过zookper知道调用那个服务,目前的zookper还没有学习,这份事例假设程序已经知道调用的服务在那台进程/主机上,代码太多了,本文采取伪代码形式,主要是讲清楚protobuf中的作用以及rpc框架的思路
详解protobuf
syntax = "proto3";
package fixbug;
option cc_generic_services = true;
message ResultCode{
int32 errcode = 1;// 0为成功
bytes errmsg = 2;
}
message LoginRequest{
bytes name = 1;
bytes pwd = 2;
}
message LoginResponse{
ResultCode result = 1;// 类的对象
bool sucess = 2;
}
message RegisterRequest{
uint32 id = 1;
bytes name = 2;
bytes pwd = 3;
}
message RegisterResponse{
ResultCode result = 1;// 类的对象
bool sucess = 2;
}
// protobuf中service的函数才是rpc提供的函数
service UserServiceRpc{
rpc Login(LoginRequest)returns(LoginResponse);
rpc Register(RegisterRequest)returns(RegisterResponse);
}
protobuf中的UserServiceRpc类型是service,他会生成两个类,一个类UserServiceRpc另一个类是UserServiceRpc_Stub。Service是基类,UserServiceRpc继承了Service,UserServiceRpc_Stub继承了UserServiceRpc。
大概长这个样子的,新学的UML画图,嘻嘻UML在线画图链接
UserService是服务端定义的类,我们暂时不用理会。目前知道类与类之间的关系即可。
客户端需要两个类RpcChannle和UserServiceRpc_Stub,但是RpcChannle是protobuf提供的,但是他是一个抽象类,需要我们继承重写RpcChannle中的CallMethod函数。类的关系大概是这个样子的
main.cpp创建一个请求对象,设置好要请求的值。通过UserServiceRpc_Stub来调用,调用Login函数,protobuf内部通过descriptor就能感知客户端调用的是那一个函数。stub调用Login函数之后,就会调用客户端自定义的RpcChannel类中的callmethod的函数
int main(int argc,char** argv){
/*
..... // 加载配置项呀,获得rpc服务的ip和端口号
*/
// 没有参数的构造函数,只能set去设置值
fixbug::LoginRequest request;
request.set_name("zhangsan");
request.set_pwd("123456");
// 到时候获得响应
fixbug::LoginResponse response;
// stub给远程调用请求者使用的
fixbug::UserServiceRpc_Stub stub(new MpRpcChannel());
stub.Login(nullptr,&request,&response,nullptr);//RpcChannel::callMethod 集中来做所有rpc方法调用的参数序列化和网络发送
// 一次rpc调用完成,读调用结果
if(response.result().errcode() == 0){
std::cout << "rpc login response:" << response.sucess() << std::endl;
}
else{
std::cout << "rpc login response error : " << response.result().errmsg() << std::endl;
}
return 0;
}
重写CallMethod这个方法,准备发送给服务端调用请求,需要发送的有请求的是那个类?请求类中的那个函数?请求这个类中的要携带那些参数?这三行代码帮我们解决前两个问题,参数通过request这个对象序列化来解决,然后发送给服务端,再等待服务端发回来的数据,然后将返回来的response反序列化变成对象,返回给main函数,至此客户端的问题我们就梳理完成了!
const google::protobuf::ServiceDescriptor* sd = method->service();
std::string service_name = sd->name();
std::string method_name = method->name();
#include "MpRpcChannel.h"
#include "google/protobuf/descriptor.h"
#include "google/protobuf/message.h"
#include "rpcheader.pb.h"
#include "MprpcApplication.h"
#include
#include
#include
#include
#include
void MpRpcChannel::CallMethod(const MethodDescriptor* method,
RpcController* controller, const Message* request,
Message* response, Closure* done)
{
const google::protobuf::ServiceDescriptor* sd = method->service();
std::string service_name = sd->name();
std::string method_name = method->name();
// 获取参数的序列化字符串长度args_size
std::string args_str;
uint32_t args_size = 0;
if(request->SerializeToString(&args_str)){
args_size = args_str.size();
}
else
{
std::cout << "serialize request error!" << std::endl;
return ;
}
//定义rpc的请求header
mprpc::RpcHeader rpcHeader;
rpcHeader.set_service_name(service_name);
rpcHeader.set_method_name(method_name);
rpcHeader.set_args_size(args_size);
std::string rpc_header_str;
uint32_t header_size = 0;
if(rpcHeader.SerializeToString(&rpc_header_str)){
header_size = rpc_header_str.size();
}
else{
std::cout << "serialize rpc header error!" << std::endl;
return ;
}
// 组织待发送的rpc请求的字符串
std::string send_rpc_str;
send_rpc_str.insert(0,std::string((char*)&header_size,4));
send_rpc_str += rpc_header_str;
send_rpc_str += args_str;
std::cout << "================" << std::endl;
std::cout << "header_size" << header_size << 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_str" << args_str << std::endl;
//使用tcp编程,完成rpc方法的远程调用
int clientfd = ::socket(AF_INET,SOCK_STREAM,0);
if(clientfd == -1){
std::cout << "create socket errno:" << errno << std::endl;
exit(EXIT_FAILURE);
}
std::string ip = MprpcApplication::GetInstance().getConfig().Load("rpcServerIp");
uint16_t port = std::atoi(MprpcApplication::GetInstance().getConfig().Load("rpcServerPort").c_str());
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
::inet_pton(AF_INET,ip.c_str(),&addr.sin_addr);
if(-1 == ::connect(clientfd,(sockaddr*)&addr,sizeof(addr))){
std::cout << "connect error! errno: " << errno << std::endl;
close(clientfd);
exit(EXIT_FAILURE);
}
// 发送rpc请求
if(-1 == send(clientfd,send_rpc_str.c_str(),send_rpc_str.size(),0)){
std::cout << "send error" << errno << std::endl;
close(clientfd);
return ;
}
// 接收rpc请求的响应值
char buf[1024]={0};
int recv_size = 0;
if(-1 == (recv_size = ::recv(clientfd,buf,sizeof(buf),0))){
std::cout << "recv error " << std::endl;
close(clientfd);
return ;
}
// std::string response_str(buf);// 遇到\0后面的数据就存不下来了
if(!response->ParseFromArray(buf,recv_size)){
std::cout << "parse error! response_str: " << buf << std::endl;
close(clientfd);
return ;
}
else{
std::cout << "rpc 反序列化成功" << std::endl;
}
close(clientfd);
}
服务端需要有两个protobuf,一个是提供具体的服务,就是上面的那个protobuf,一个是rpc框架的porotobuf,要有请求的类名、请求的函数名、请求参数的个数,还有请求的参数的值,相当于要接收两个protobuf文件
mprpc的protobuf文件
syntax = "proto3";
package mprpc;
message RpcHeader{
bytes service_name = 1;
bytes method_name = 2;
uint32 args_size = 3;
}
main.cpp这边
#include
#include
#include "user.pb.h"
#include "MprpcApplication.h"
#include "RpcProvider.h"
using namespace std;
using namespace fixbug;
/**
* UserService原来是一个本地服务,提供了两个进程内的本地方法,Login和GetFrienLists
* 继承proto文件中的service的抽象类中的方法,内部通过框架来进行操作
*/
class UserService:public UserServiceRpc{ // 使用rpc服务发布端()
public:
bool Login(std::string name,std::string pwd){
cout << "doing local service:Login" << endl;
cout << "name: " << name << "pwd: " << endl;
return false;
}
/**
* 重写基类UserService的虚函数 下面这些方法都是框架直接调用的
* 1、caller ==》 login(LoginRequest) => muduo => callee
* 2、然后调用下面这个虚函数Login方法
*/
void Login(::google::protobuf::RpcController* controller,
const ::fixbug::LoginRequest* request,
::fixbug::LoginResponse* response,
::google::protobuf::Closure* done)override
{
// 框架给业务上报了请求参数LoginRequest,应用获取相应数据做本地业务,已经做好了序列化了
string name = request->name();
string pwd = request->pwd();
bool login_result = Login(name,pwd);
// 把结果发回去,对象序列化也交给框架做了
ResultCode* code = response->mutable_result();
code->set_errcode(-1);
code->set_errmsg("request faile");
response->set_sucess(login_result);
// 执行回调操作,在RpcProvider中提供了这个done
done->Run();
}
};
int main(int argc,char** argv){
// 调用框架的初始化操作,端口号和ip地址
MprpcApplication::Init(argc,argv);
// 把UserService对象发布到rpc节点上
RpcProvider provider;
provider.NotifyService(new UserService());
// 启动一个rpc服务结点 Run以后进程阻塞状态,等待远程的rpc调用请求
provider.Run();
}
RpcProvider.cpp
NotifyService函数感知这个类是那个类,获取类的名字,这个类的方法名字都有什么,都记录在哈希表当中。
Run函数就是启动网络通信,如果一台机器不同进程可以考虑其他进程通信的手段,但是不同机器中通信只能采取网络通信了。这里我用的网络库是自己改写的muduo网络库,也可以用正宗的陈硕大牛写的muduo网络库。
OnMessage函数,收到调用的请求了,解析两个protobuf文件,再根据以前记录的哈希表,找到要请求的类、类中的函数、函数所需要的参数,都一并给到位
#include "RpcProvider.h"
#include "MprpcApplication.h"
#include "rpcheader.pb.h"
#include
#include
void RpcProvider::NotifyService(google::protobuf::Service* service){
ServiceInfo service_info;
// 获取要服务的对象描述信息
const google::protobuf::ServiceDescriptor* pserviceDescriptor = service->GetDescriptor();
// 获取服务的名字 有invaild的问题基本上就是找不到头文件了
std::string service_name = pserviceDescriptor->name();
std::cout << "service_name: " << service_name << std::endl;
// 获取服务对象service的方法的数量
int methodCnt = pserviceDescriptor->method_count();
for(int i = 0;i < methodCnt;++i){
// 返回第几号方法,只能看不能修改,获取服务对象指定下标的服务方法描述
const google::protobuf::MethodDescriptor* pmethodDesc = pserviceDescriptor->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});
}
void RpcProvider::Run(){
std::string ip = MprpcApplication::GetInstance().getConfig().Load("rpcServerIp");
uint16_t port = std::atoi(MprpcApplication::GetInstance().getConfig().Load("rpcServerPort").c_str());
InetAddress address(port,ip);
TcpServer server(&m_eventLoop_,address,"RpcProvider");
// 绑定连接回调函数和消息读写回调方法 分离网络代码和业务代码
server.setConnectCallback(std::bind(&RpcProvider::OnConnection,this,std::placeholders::_1));
server.setMessageCallback(std::bind(&RpcProvider::OnMessage,this,std::placeholders::_1,std::placeholders::_2,std::placeholders::_3));
// 设置muduo网络库的线程数量
server.setThreadNum(4);
// 启动网络服务
server.start();
m_eventLoop_.loop();
}
void RpcProvider::OnConnection(const TcpConnectionPtr& conn){
if(!conn->connected()){
// 和rpc client的连接断开了
conn->shutdown();
}
}
/**
* RpcProvider 和 RpcConsumer协商之间通信用的protobufu数据类型
* service_name method_name args定义proto的message类型,进行数据的序列化和反序列化
* 有4个字节的头记录头的长度
*/
// 响应请求方请求的操作,并且在把他发送回去
void RpcProvider::OnMessage(const TcpConnectionPtr& coon,Buffer* buffer,Timestamp t){
// 网络上接收的远程rpc调用请求的字符流 里头的字符有调用的函数名字和参数
std::string recv_buf = buffer->retrieveAllAsString();
// 从字符流中读取前4个字节的内容
uint32_t header_size = 0;
recv_buf.copy((char*)&header_size,4,0);
// 根据header_size读取数据头的原始字符流,反序列化数据,得到rpc请求的详细信息
std::string rpc_header_str = recv_buf.substr(4,header_size);
mprpc::RpcHeader rpcHeader;
std::string service_name;
std::string method_name;
uint32_t args_size;
if(rpcHeader.ParseFromString(rpc_header_str)){
// 数据头反序列化成功
service_name = rpcHeader.service_name();
method_name = rpcHeader.method_name();
args_size = rpcHeader.args_size();
}
else{
// 数据头反序列失败
std::cout << "rpc_header_str:" << rpc_header_str << "parse error" << std::endl;
return ;
}
// 获取rpc方法参数的字符流数据
std::string args_str = recv_buf.substr(4+header_size,args_size);
std::cout << "================" << std::endl;
std::cout << "header_size" << header_size << 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_str" << args_str << std::endl;
// 获取service对象和method对象
auto it = m_serviceMap_.find(service_name);
if(it == m_serviceMap_.end()){
std::cout << "service name is not exits" << std::endl;
return ;
}
auto mit = it->second.m_methodMap_.find(method_name);
if(mit == it->second.m_methodMap_.end()){
std::cout << "method name is not exits " << std::endl;
return ;
}
google::protobuf::Service* service = it->second.m_service_;// 获取service对象
const google::protobuf::MethodDescriptor* method = mit->second;// 获取method对象
// 生成rpc方法调用的请求request和response参数
google::protobuf::Message* request = service->GetRequestPrototype(method).New();
// 进行反序列化
if(!request->ParseFromString(args_str)){
std::cout << "request parse error content: " << args_str << std::endl;
return ;
}
google::protobuf::Message* response = service->GetResponsePrototype(method).New();
// 给下面的method方法的调用,绑定一个Closure的回调函数,done用于服务端调用
google::protobuf::Closure* done = google::protobuf::NewCallback<RpcProvider,
const TcpConnectionPtr&,google::protobuf::Message*>
(this,&RpcProvider::SendRpcResponse,coon,response);
// 在框架上根据远端rpc请求,调用当前rpc节点发布的方法
// new UserService().login(controller,request,response,done);
// 相当于回调函数,调用rpc服务端的函数。
service->CallMethod(method,nullptr,request,response,done);
}
void RpcProvider::SendRpcResponse(const TcpConnectionPtr& conn,google::protobuf::Message* response){
std::string response_str;
// 序列化
if(response->SerializeToString(&response_str)){
conn->send(response_str);
}
else{
std::cout << "serialize response_str error" << std::endl;
}
conn->shutdown();// 发送完自动断开
}
远程请求调用端过于理想化,想着是一定成功,那么失败的化要怎么处理?
之前的代码如下
stub.Login(nullptr,&request,&response,nullptr);
现在我们重写下这个类,让这个类来判断一下请求过程中是否有错误产生
MprRpcController mprRpcController;
stub.Login(&mprRpcController,&request,&response,nullptr);/
MprRpcController.h
#pragma once
#include
#include
class MprRpcController:public google::protobuf::RpcController
{
public:
void Reset() override;
bool Failed() const override;
std::string ErrorText() const override;
void StartCancel() override;
void SetFailed(const std::string& reason) override;
bool IsCanceled() const override;
void NotifyOnCancel(google::protobuf::Closure* callback)override;
private:
bool m_failed_ = false;
std::string m_errText_ = "";
};
MprRpcController.cpp,用不到的地方就没有写
#include "MprRpcController.h"
// 置成最初的状态,false表示未发生错误
void MprRpcController::Reset() {
m_failed_ = false;
m_errText_ = "";
}
// 意思是是否失败
bool MprRpcController::Failed() const {
return m_failed_;
}
std::string MprRpcController::ErrorText()const {
return m_errText_;
}
void MprRpcController::StartCancel() {}
void MprRpcController::SetFailed(const std::string& reason) {
m_failed_ = true;
m_errText_ = reason;
}
bool MprRpcController::IsCanceled() const { return true;}
void MprRpcController::NotifyOnCancel(google::protobuf::Closure* callback){}