skynet框架应用 (十七) protobuffer

17 protobuffer

​ 假如我们要建立的skynet服务器与客户端的连接方式为长连接,且选择了Google的Protobuf来定制我们的网络协议,那么,接下来我们要解决的问题就是:如何在skynet框架中使用socket+protobuf

​ 由于protobuf的lua版本的支持存在着部分缺陷,为了避免踩坑,这里我们直接使用云风博客中推荐的pbc动态proto解析库。

17.1 安装PBC

1、下载pbc:跟下载skynet源码一样,通过gitpbc的源码克隆到本地:


$ cd skynet/3rd/
$ git clone https://github.com/cloudwu/pbc.git

2、编译安装:


$ cd pbc
$ make

3、注意如果报如下错,表示protobuf未安装,没有报错就跳到第6步


make: protoc:命令未找到
Makefile:79: recipe for target 'build/addressbook.pb' failed
make: *** [build/addressbook.pb] Error 127

4、安装protobuf


$ sudo apt-get install protobuf-c-compiler protobuf-compiler
$ protoc --version

5、再次编译


$ make

6、工具编译


$ cd ./binding/lua53
$ sudo make

7、如果报错如下,没有就跳到第9步


$ make
gcc -O2 -fPIC -Wall -shared -o protobuf.so -I../.. -I/usr/local/include -L../../build pbc-lua.c -lpbc
pbc-lua.c:4:17: fatal error: lua.h: 没有那个文件或目录
compilation terminated.
Makefile:11: recipe for target 'protobuf.so' failed
make: *** [protobuf.so] Error 1
$ 

8、上面的错误是因为没有安装 lua5.3 skynet本身已经有lua5.3,只不过路劲没指定,修改makefile如下


  CC = gcc
  CFLAGS = -O2 -fPIC -Wall
  LUADIR = ../../../lua   #这个路劲就是skynet/3rd/lua
  TARGET = protobuf.so
  
  .PHONY : all clean
  
  all : $(TARGET)
  
  $(TARGET) : pbc-lua53.c
      $(CC) $(CFLAGS) -shared -o $@ -I../.. -I$(LUADIR) -L../../build $^ -lpbc
  
  clean :
      rm -f $(TARGET)

9、编译成功的话,将protobuf.so放在config文件中lua_cpath项配置的目录下面,同时将protobuf.lua放在config文件lua_path配置的目录下,就可以调用protobuf中的库方法


$ cp protobuf.so ../../../../luaclib/
$ cp protobuf.lua ../../../../lualib/

17.2 生成protobuffer文件

1、先在项目根目录下创建一个protos文件夹,用来存放协议文件, 比如创建一个Person.proto协议文件,内容如下:


$ cd skynet
$ mkdir protos
$ cd protos
$ vi test.proto #你也可以取他名字myname.proto

test.proto



package cs;  //定义包名
message test { //定义消息结构
  required string name = 1;   //name为string类型,并且是必须的,1表示第一个字段
  required int32 age = 2;     //age为int32类型,并且是必须的
  optional string email = 3;  //email为string类型,并且是可选的
  required bool online = 4;    //online为bool类型,并且是必须的
  required double account = 5; //account为doubleg类型,并且是必须的
}

​ required 修饰的字段如果没有指定值,将采用默认值填充;

​ optional修饰的字段如果没有指定值,直接为空;

2、将协议文件解析成.pb格式:


$ protoc --descriptor_set_out=test.pb test.proto

17.3 使用protobuffer文件


local protobuf = require "protobuf" --引入文件protobuf.lua
--注册protobuffer文件
protobuf.register_fileprotofile

--根据注册的protofile中的类定义进行序列化,返回得到一个stringbuffer
protobuf.encode("package.message", { ... })

--根据注册的protofile中的类定义进行反序列化
protobuf.decode("package.message", stringbuffer)

示例代码:testpbc.lua


local skynet = require "skynet"
local protobuf = require "protobuf" --引入文件protobuf.lua

skynet.start(function()
    protobuf.register_file "./protos/test.pb"
    skynet.error("protobuf register:test.pb")

    stringbuffer = protobuf.encode("cs.test", --对应person.proto协议的包名与类名
    {
        name = "xiaoming",
        age = 1,
        --email = "[email protected]",
        online = true,
        account = 888.88,
    })

    local data = protobuf.decode("cs.test",stringbuffer)
    skynet.error("------decode------ \nname=",data.name
        , ",\nage=", data.age
        , ",\nemail=", data.email)
        , ",\nonline=", data.online
        , ",\naccount=", data.account)
end)

运行结果:


$ ./skynet examples/config
testpbc
[:01000010] LAUNCH snlua testpbc
[:01000010] protobuf register:test.pb
[:01000010] ------decode------ 
name= xiaoming ,
age= 1 ,
email=  ,
online= true ,
account= 888.88

17.4 编写一个稍微复杂点pbc服务

person.proto


package cs;
message Person {
  required string name = 1;   //Person第一个字段name为string类型,并且是必须的
  required int32 id = 2;        
  optional string email = 3;  //Person第三个字段email为string类型,并且是可选的

  enum PhoneType {   //定义一个枚举类型
    MOBILE = 0;
    HOME = 1;
    WORK = 2;
  }

  message PhoneNumber {           //再定义一个消息类型PhoneNumber不是字段
    required string number = 1;   //PhoneNumber第一个字段number为String类型,并且是必须的
    optional PhoneType type = 2 [default = HOME];  //第二个字段type为PhoneTypeg类型,可选的
  }
  repeated PhoneNumber phone = 4;  //Person第四个字段phone为PhoneNumber类型,是可重复的,相当于是数组
}

将协议文件解析成.pb格式:


$ protoc --descriptor_set_out=person.pb person.proto

示例代码 testpbc.lua


local skynet = require "skynet"
local protobuf = require "protobuf" --引入文件protobuf.lua

skynet.start(function()
    protobuf.register_file "./protos/person.pb"
    skynet.error("protobuf register:person.pb")

    stringbuffer = protobuf.encode("cs.Person", --对应person.proto协议的包名与类名
    {
        name = "xiaoming",
        id = 1,
        email = "[email protected]",
        phone = {
                    {
                        number = "1388888888",
                        type = "MOBILE",
                    },
                    {
                        number = "8888888",
                    },
                    {
                        number = "87878787",
                        type = "WORK",
                    },

                }
    })


    local data = protobuf.decode("cs.Person",stringbuffer)
    skynet.error("decode name="..data.name..",id="..data.id..",email="..data.email)
    skynet.error("decode phone.type="..data.phone[1].type..",phone.number="..data.phone[1].number)
    skynet.error("decode phone.type="..data.phone[2].type..",phone.number="..data.phone[2].number)
    skynet.error("decode phone.type="..data.phone[3].type..",phone.number="..data.phone[3].number)
end)

运行结果:


$ ./skynet examples/conf
testpbc
[:01000010] LAUNCH snlua testpbc
[:01000010] protobuf register:person.pb
[:01000010] decode name=xiaoming,id=1,email=[email protected]
[:01000010] decode phone.type=MOBILE,phone.number=1388888888
[:01000010] decode phone.type=HOME,phone.number=8888888
[:01000010] decode phone.type=WORK,phone.number=87878787

17.5 protobuf数据类型

标量类型列表

proto类型 C++类型 备注
double double
float float
int32 int32 使用可变长编码,编码负数时不够高效——如果字段可能含有负数,请使用sint32
int64 int64 使用可变长编码,编码负数时不够高效——如果字段可能含有负数,请使用sint64
uint32 uint32 使用可变长编码
uint64 uint64 使用可变长编码
sint32 int32 使用可变长编码,有符号的整型值,编码时比通常的int32高效
sint64 int64 使用可变长编码,有符号的整型值,编码时比通常的int64高效
fixed32 uint32 总是4个字节,如果数值总是比228大的话,这个类型会比uint32高效
fixed64 uint64 总是8个字节,如果数值总是比256大的话,这个类型会比uint64高效
sfixed32 int32 总是4个字节
sfixed64 int64 总是8个字节
bool bool
string string 一个字符串必须是UTF-8编码或者7-bit ASCII编码的文本
bytes string 可能包含任意顺序的字节数据

你可能感兴趣的:(skynet)