protobuf和brpc

1.Protobuf简介

https://izualzhy.cn/demo-protobuf-rpc

Protobuf(Google Protocol Buffers)提供一种灵活、高效、自动化的机制,用于序列化结构数据

  • Protobuf作用与XML、json类似,但它是二进制编码格式,所以性能更好。
  • 有代码生成机制,易于使用。

syntax="proto2";
package example;

//请求、回复、服务的接口均定义在proto文件中。
//告诉protoc要生成C++ Service基类
option cc_generic_services = true;

message EchoRequest {
    required string message = 1;
};

message EchoResponse {
    required string message = 1;
};

service EchoService {
    rpc Echo(EchoRequest) returns (EchoResponse);
};
 

定义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函数

核心:

  1. 创建一个brpc::Server对象(server)
  2. 创建一个Service对象(echo_service_impl)
  3. 把Service对象添加到Server对象(server)中
  4. 启动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;
    }
  server.RunUntilAskedToQuit();//等待到ctrl+c按下,否则就一直启动服务
  return 0;
  }

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的所有权:

  1. SERVER_OWNS_SERVICE:server持有service的所有权。则在server关闭时会delete掉service,然后service一般是栈上的对象,而不是指针,所以一般不用这个,而用下面的。
  2. SERVER_DOESNT_OWN_SERVICE:server不持有service的所有权

 

posted @ 2022-08-07 20:13  lypbendlf  阅读(591)  评论(0编辑  收藏  举报