假如我們要建立的skynet服務器與客戶端的連接方式爲長連接,且選擇了Google的Protobuf來定製我們的網絡協議,那麼,接下來我們要解決的問題就是:如何在skynet框架中使用socket+protobuf。
由於protobuf的lua版本的支持存在着部分缺陷,爲了避免踩坑,這裏我們直接使用雲風博客中推薦的pbc
動態proto解析庫。
17.1 安裝PBC
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/
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_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
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 | 可能包含任意順序的字節數據 |