文章

protobuf之我见

功能

protobuf是一种将协议结构体序列化为二进制串、将二进制反序列化为协议结构体的一种协议。与xml和json不同,这是一种二进制的通信协议。protobuf几乎完全舍弃了数据结构化(元信息的删减和数据的压缩使数据远没有json那么直观),导向数据序列化。甚至数据包的大小比结构体直接以字节形式塞进流的形式还要小得多。同时在结构化方面依然保留了一部分元信息来实现字段可选、可重复等功能。同时在更改协议时,老代码也能做到部分兼容。

格式

protobuf推荐使用一个proto文件来规范协议,然后使用protoC生成工程代码。

proto支持两种数据类型,message和enum。enum就是平平无奇的枚举。message则由字段、类型、标号、其它选项组成。限定修饰符支持require、optional、repeated三种选项。

require必须出现、optional可以出现0、1次,repeated相当于list,可以出现任意次(包括0)。类型支持的比较全面,可以去查表。类型支持基础类型,或者enum、message。

可选选项有packed,这个在字段的类型是基础类型且限定符为repeated时有用。也有default但是不推荐使用了。

一个protobuf文件可以定义很多message,但是如果相互依赖的话,就会膨胀的厉害。

proto文件里面也可以使用c风格写注释。

更新协议后移除的字段的ID可以复用给新增的字段。但是如果要和生成出的老代码配合,需要给这些标号标注reserved 避免出现解析时的bug。

高阶技巧

oneof指令,类似于union节省内存用的。可以实现optional实现不了的一定要出现一个的功能。

map,就是map。

package,防止冲突的。用处不大

rpc。假如一个协议是用来进行rpc调用的,可以使用rpc语法进行定义。

service SearchService {
  rpc Search(SearchRequest) returns (SearchResponse);
}

protoc可以生成出rpc的代码。而且可以生成出非常完备的框架

using google::protobuf;

protobuf::RpcChannel* channel;
protobuf::RpcController* controller;
SearchService* service;
SearchRequest request;
SearchResponse response;

void DoSearch() {
  // You provide classes MyRpcChannel and MyRpcController, which implement
  // the abstract interfaces protobuf::RpcChannel and protobuf::RpcController.
  channel = new MyRpcChannel("somehost.example.com:1234");
  controller = new MyRpcController;

  // The protocol compiler generates the SearchService class based on the
  // definition given above.
  service = new SearchService::Stub(channel);

  // Set up the request.
  request.set_query("protocol buffers");

  // Execute the RPC.
  service->Search(controller, request, response, protobuf::NewCallback(&Done));
}

void Done() {
  delete service;
  delete channel;
  delete controller;
}
using google::protobuf;

class ExampleSearchService : public SearchService {
 public:
  void Search(protobuf::RpcController* controller,
              const SearchRequest* request,
              SearchResponse* response,
              protobuf::Closure* done) {
    if (request->query() == "google") {
      response->add_result()->set_url("http://www.google.com");
    } else if (request->query() == "protocol buffers") {
      response->add_result()->set_url("http://protobuf.googlecode.com");
    }
    done->Run();
  }
};

int main() {
  // You provide class MyRpcServer.  It does not have to implement any
  // particular interface; this is just an example.
  MyRpcServer server;

  protobuf::Service* service = new ExampleSearchService;
  server.ExportOnPort(1234, service);
  server.Run();

  delete service;
  return 0;
}

protobuf(后续简称pb) 本身没有rpc实现,但是预留了rpc接口,只需要实现其网络交互的部分即可;

grpc就用了这个功能并进行了进一步的包装集成,特别适合protobuf。由于兼容性问题,最好还是用proto3好一点。

使用

可以使用protoc指令生成出c++、go的驱动代码。然后go、c++调用这个就行.

不需要安装任何库,protobuf的序列化和反序列化代码会在生成的文件里。

将pb.go文件移动到对应的为之后。将需要的数据以proto.Type(xxx)的形式填入protocol的结构体后,调用这个结构体的Marshal函数编码成[]bytes就OK了。接收方接收到一串[]bytes然后调用proto.Unmarshal(bytes,xxx),将这串数据解到结构体里面就好。


allow_alias

这个是允许枚举重复的指令。像是有些常量什么的这样设置安全一些,像是协议头这种其实就没有必要。但是设置了也没什么大不了的。

协议的字段

序号一定要从1开始。。。不能从0


svn

全部添加完了以后记得将protocal文件夹和复制目的地的protocal执行add指令用来将proto文件添加到svn中。


import的类似语法可以参考go的。


注意一下,repeated的需要使用image.png

License:  CC BY 4.0