假如我们要建立的skynet服务器与客户端的连接方式为长连接,且选择了Google的Protobuf来定制我们的网络协议,那么,接下来我们要解决的问题就是:如何在skynet框架中使用socket+protobuf。
由于protobuf的lua版本的支持存在着部分缺陷,为了避免踩坑,这里我们直接使用云风博客中推荐的pbc
动态proto解析库。
1、下载pbc
:跟下载skynet源码一样,通过git
将pbc
的源码克隆到本地:
$ 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/
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
local protobuf = require "protobuf" --引入文件protobuf.lua --注册protobuffer文件 protobuf.register_file(protofile) --根据注册的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
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
标量类型列表
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 | 可能包含任意顺序的字节数据 |