protobuf和brpc
1.Protobuf简介
https://izualzhy.cn/demo-protobuf-rpc
Protobuf(Google Protocol Buffers)提供一种灵活、高效、自动化的机制,用于序列化结构数据。
- Protobuf作用与XML、json类似,但它是二进制编码格式,所以性能更好。
- 有代码生成机制,易于使用。
定义message来指定所需要序列化的数据格式。每一个message都是一个小的信息逻辑单元,包含一系列的name-value值对。
2.example echo_C++
按照https://github.com/apache/incubator-brpc/blob/master/docs/cn/getting_started.md编译brpc的echo_c++,echo.pb.cc是中间文件会删除,就算中止make也会删除中间文件:
make: *** Deleting intermediate file `echo.pb.cc'
//或
rm echo.pb.cc
所以无法看到具体的EchoService_Stub类的实现,但这也说明不需要去关心Stub具体是如何做的?直接就实现了端到端?
可以手动使用protoc命令生成.h和.cc文件:
protoc --cpp_out=./ ./echo.proto
3.client端
https://blog.csdn.net/huntinux/article/details/81124091
https://cloud.tencent.com/developer/article/1915035
3.1 首先通过gflags解析参数:
GFLAGS_NS::ParseCommandLineFlags(&argc, &argv, true);
3.2 client端要包含channel头文件,channel可以认为就是client客户端:
#include <brpc/channel.h> //Channel是与Server通信的通道。Channel时thread-safe的,可以被多个线程共享。 brpc::Channel channel;
接下来对Channel进行类初始化,用到了上面定义的命令行参数,主要包括:协议类型、连接类型、超时时间、重试次数、服务器地址、负载均衡策略。
brpc::ChannelOptions options; options.protocol = FLAGS_protocol;//"baidu_std" options.connection_type = FLAGS_connection_type;//"" options.timeout_ms = FLAGS_timeout_ms/*milliseconds*/;//1000 options.max_retry = FLAGS_max_retry;//3 //Init重载分为连接单个服务器,和服务集群。 //连接服务集群: //定期从naming_service_url指定的命名服务中获得服务器列表,并通过load_balancer_name指定的负载均衡算法选择出一台机器发送请求。 if (channel.Init(FLAGS_server.c_str(), FLAGS_load_balancer.c_str(), &options) != 0) { LOG(ERROR) << "Fail to initialize channel"; return -1; }
3.3 将Channel传递给Stub类:
example::EchoService_Stub stub(&channel); //通常并不直接使用Channel,而是把Channel传递给一个stub service, //注意stub也可以被多个线程共享。
3.4 通过Stub的Echo接口发送请求,并接收响应:
example::EchoRequest request; example::EchoResponse response; brpc:: cntl;//控制句柄 request.set_message("hello world"); cntl.set_log_id(log_id ++); // set by user // Set attachment which is wired to network directly instead of // being serialized into protobuf messages. cntl.request_attachment().append(FLAGS_attachment); // Because `done'(last parameter) is NULL, this function waits until // the response comes back or error occurs(including timedout). stub.Echo(&cntl, &request, &response, NULL);
注意传递过去的都是引用参数,函数中会自动修改其值,返回后可读取内容。
stub存根类是调用了channel的CallMethod函数。
上述也定义了一个controll对象,那么它的作用和channel的区别?我认为是channel负责发送和接收消息,cntl负责保存request和response等结果?那么客户端的cntl和服务端的cntl有什么区别?
所以是客户端定义了channel,并且在调用方法时需要定义一个cntl,服务端不需要定义。
4.server端
继承EchoService类并实现远程调用方法:
class EchoServiceImpl : public EchoService {...};
实现:
//同步Echo服务 virtual void Echo(google::protobuf::RpcController* cntl_base,//包含了所有request和response之外的参数集合 const EchoRequest* request, EchoResponse* response, google::protobuf::Closure* done) {//这个对象确保在return时自动调用done->Run(),RAII机制 brpc::ClosureGuard done_guard(done);//Server端的done的逻辑主要是发送response回client //done由框架创建,递给服务回调,包含了调用服务回调后的后续动作,包括检查response正确性,序列化,打包,发送等逻辑。 brpc::Controller* cntl =//在brpc中可以静态转为brpc::Controller(前提是代码运行brpc.Server中) static_cast<brpc::Controller*>(cntl_base); LOG(INFO) << "Received request[log_id=" << cntl->log_id() << "] from " << cntl->remote_side() //获得发送该请求的client地址和端口 << " to " << cntl->local_side()//获得server端的地址 << ": " << request->message() << " (attached=" << cntl->request_attachment() << ")"; // Fill response. response->set_message(request->message()); }
4.1 cntl_base参数
cntl_base是google::protobuf::RpcController*
类型,出自protobuf,是控制信息的基类。使用的时候一般转成brpc的控制信息类型:
brpc::Controller* cntl = static_cast<brpc::Controller*>(cntl_base);
4.2 done参数
brpc::ClosureGuard类是基于RAII机制来调用done->Run(),并且确保它一定能够执行。
~ClosureGuard() { if (_done) { _done->Run(); } }
接口返回(response返回给客户端?)并不代表这个服务的所有逻辑就结束了。
可以自己控制done->Run()的时机:
- 当response数据准备好之后就立即手工调用done->Run()来让Server返回response,而不需要等待ClosureGuard析构;
- 在此之后,还可以做一下其他操作,比如打印NOTICE日志、向消息度队列发送一个消息触发某个异步事件等。这些done->Run()之后的操作都是不占用服务响应耗时的!
当done->Run()执行过之后,cntl、request、response的数据都会失效。如果你要打日志或者执行其他操作,请把需要的数据提前存储到其他变量中,不能再依赖着三个变量。
5.server端main函数
核心:
- 创建一个brpc::Server对象(server)
- 创建一个Service对象(echo_service_impl)
- 把Service对象添加到Server对象(server)中
- 启动server
int main(int argc, char* argv[]) { GFLAGS_NS::ParseCommandLineFlags(&argc, &argv, true); brpc::Server server; example::EchoServiceImpl echo_service_impl; //添加服务到服务器 if (server.AddService(&echo_service_impl, brpc::SERVER_DOESNT_OWN_SERVICE) != 0) { LOG(ERROR) << "Fail to add service"; return -1; } // Start the server. brpc::ServerOptions options; options.idle_timeout_sec = FLAGS_idle_timeout_s;//客户端200s没动作则断开连接 if (server.Start(point, &options) != 0) { LOG(ERROR) << "Fail to start EchoServer"; return -1; }
5.1 AddService
函数声明:
int Server::AddService(google::protobuf::Service* service, ServiceOwnership ownership);
第一个参数service是谷歌的protobuf中的类型,不是brpc创造的。
google::protobuf::Service就是protobuf中service关键字所创建的类型:
service EchoService {
rpc Echo(EchoRequest) returns (EchoResponse);
};
如上就是声明了一个名为EchoService的Service类型。其中的rpc函数名为:Echo
ServiceOwnership表示Service的所有权:
- SERVER_OWNS_SERVICE:server持有service的所有权。则在server关闭时会delete掉service,然后service一般是栈上的对象,而不是指针,所以一般不用这个,而用下面的。
- SERVER_DOESNT_OWN_SERVICE:server不持有service的所有权