Skynet 协议 sproto 协议lua代码阅读,及sproto的使用
main函数 skynet.newservice(“protoloader”) 服务器, 对服务协议进行加载。
local skynet = require "skynet"
local sprotoparser = require "sprotoparser"
local sprotoloader = require "sprotoloader"
local proto = require "proto"
skynet.start(function()
sprotoloader.save(proto.c2s, 1)
sprotoloader.save(proto.s2c, 2)
-- don't call skynet.exit() , because sproto.core may unload and the global slot become invalid
end)
proto.c2s 是在 proto中将文件读入,并保存在
local c2s = require "c2s"
local s2c = require "s2c"
proto.c2s = sprotoparser.parse(typedef .. c2s)
proto.s2c = sprotoparser.parse(typedef .. s2c)
“typedef … c2s”是将定义的协议文件内容拼接在一起。这又将内容传递给了sprotoparser.parse
function convert.protocol(all, obj)
-- 其他逻辑不讨论
return {
request = "city_building_close.request",
tag = 18114,
response = "city_building_close.response",
}
end
function convert.type(all, obj)
-- 其他逻辑不讨论
return {
[1] = {
name = "chapter", -- 字段名
tag = 0, -- 编号
typename = "integer", -- 数据类型
},
}
end
-- c返回内容的结构调整
local function adjust(r)
local result = { type = {} , protocol = {} }
-- result.type[name] 协议结构的定义
-- result.protocol[name] 协议名称及编号
--[[
obj = {
type = protocol
[1] = 协议名
[2] = 协议编号
[3] = { 协议内容
[1] = {
[1] = "request",
[2] = {
[1] = {
[3] = "string",
[1] = "type",
[2] = 0,
type = "field",
},
},
},
[2] = {
[1] = "response",
[2] = {
[1] = {
[3] = "integer",
[1] = "type",
[2] = 0,
type = "field",
},
},
},
}
}
obj = {
type = type
[1] = 协议名
[2] = {
[1] = {
[1] = "heroid",
type = "field",
[2] = 0,
[3] = "string",
},
}
}
]]
for _, obj in ipairs(r) do
local set = result[obj.type]
local name = obj[1]
assert(set[name] == nil , "redefined " .. name)
-- obj.type 的值分别调用 convert.type 或者 convert.protocol, 将处理好的内容放入到 result 中
set[name] = convert[obj.type](result,obj)
end
return result
end
local buildin_types = {
integer = 0,
boolean = 1,
string = 2,
binary = 2, -- binary is a sub type of string
}
local function checktype(types, ptype, t)
if buildin_types[t] then
return t
end
local fullname = ptype .. "." .. t
if types[fullname] then
return fullname
else
ptype = ptype:match "(.+)%..+$"
if ptype then
return checktype(types, ptype, t)
elseif types[t] then
return t
end
end
end
-- r 的结构 func.adjust 中 result = { type = {} , protocol = {} }
local function check_protocol(r)
-- 检查操作
return r
end
local function flattypename(r)
return r
end
local function parser(text,filename)
local state = { file = filename, pos = 0, line = 1 }
-- lpeg.match 同过 lptree.c->lp_match 将text内容转换成C对象, 并返回到lua
local r = lpeg.match(proto * -1 + exception , text , 1, state )
-- flattypename: 调整 typename
return flattypename(check_protocol(adjust(r)))
end
local function packtype(name, t, alltypes)
local fields = {}
local tmp = {}
for _, f in ipairs(t) do
tmp.array = f.array
tmp.name = f.name
tmp.tag = f.tag
tmp.extra = f.decimal
tmp.buildin = buildin_types[f.typename]
if f.typename == "binary" then
tmp.extra = 1 -- binary is sub type of string
end
local subtype
if not tmp.buildin then
subtype = assert(alltypes[f.typename])
tmp.type = subtype.id
else
tmp.type = nil
end
if f.key then
tmp.key = subtype.fields[f.key]
if not tmp.key then
error("Invalid map index :" .. f.key)
end
else
tmp.key = nil
end
table.insert(fields, packfield(tmp))
end
local data
if #fields == 0 then
data = {
"\1\0", -- 1 fields
"\0\0", -- name (id = 0, ref = 0)
packbytes(name),
}
else
data = {
"\2\0", -- 2 fields
"\0\0", -- name (tag = 0, ref = 0)
"\0\0", -- field[] (tag = 1, ref = 1)
packbytes(name),
packbytes(table.concat(fields)),
}
end
return packbytes(table.concat(data))
end
local function packproto(name, p, alltypes)
if p.request then
local request = alltypes[p.request]
if request == nil then
error(string.format("Protocol %s request type %s not found", name, p.request))
end
request = request.id
end
local tmp = {
"\4\0", -- 4 fields
"\0\0", -- name (id=0, ref=0)
packvalue(p.tag), -- tag (tag=1)
}
if p.request == nil and p.response == nil and p.confirm == nil then
tmp[1] = "\2\0" -- only two fields
else
if p.request then
table.insert(tmp, packvalue(alltypes[p.request].id)) -- request typename (tag=2)
else
table.insert(tmp, "\1\0") -- skip this field (request)
end
if p.response then
table.insert(tmp, packvalue(alltypes[p.response].id)) -- request typename (tag=3)
elseif p.confirm then
tmp[1] = "\5\0" -- add confirm field
table.insert(tmp, "\1\0") -- skip this field (response)
table.insert(tmp, packvalue(1)) -- confirm = true
else
tmp[1] = "\3\0" -- only three fields
end
end
table.insert(tmp, packbytes(name))
return packbytes(table.concat(tmp))
end
local function packgroup(t,p)
if next(t) == nil then
assert(next(p) == nil)
return "\0\0"
end
local tt, tp
local alltypes = {}
for name in pairs(t) do
table.insert(alltypes, name)
end
table.sort(alltypes) -- make result stable
for idx, name in ipairs(alltypes) do
local fields = {}
for _, type_fields in ipairs(t[name]) do
if buildin_types[type_fields.typename] then
fields[type_fields.name] = type_fields.tag
end
end
alltypes[name] = { id = idx - 1, fields = fields }
end
tt = {}
for _,name in ipairs(alltypes) do
table.insert(tt, packtype(name, t[name], alltypes))
end
tt = packbytes(table.concat(tt))
if next(p) then
local tmp = {}
for name, tbl in pairs(p) do
table.insert(tmp, tbl)
tbl.name = name
end
table.sort(tmp, function(a,b) return a.tag < b.tag end)
tp = {}
for _, tbl in ipairs(tmp) do
table.insert(tp, packproto(tbl.name, tbl, alltypes))
end
tp = packbytes(table.concat(tp))
end
local result
if tp == nil then
result = {
"\1\0", -- 1 field
"\0\0", -- type[] (id = 0, ref = 0)
tt,
}
else
result = {
"\2\0", -- 2fields
"\0\0", -- type array (id = 0, ref = 0)
"\0\0", -- protocol array (id = 1, ref =1)
tt,
tp,
}
end
return table.concat(result)
end
local function encodeall(r)
return packgroup(r.type, r.protocol)
end
-- text: 协议内容 name: nil
function sparser.parse(text, name)
-- parser 将协议处理成想要的lua表结构
local r = parser(text, name or "=text")
-- 将 r 处理成字符串结构
local data = encodeall(r)
return data
end
从代码可以看出 sprotoparser.parse 的执行流程(func sparser.parse), 将协议转换成了符合使用的结构。
在"protoloader"对协议的内容进行了加载处理,调用sprotoloader.save。接下来看下改函数的实现。
-- sprotoloader.lua
function loader.save(bin, index)
local sp = core.newproto(bin)
core.saveproto(sp, index)
end
大致的协议加载流程就如上所述。
Sproto的实际使用:
在项目中对协议的实际使用:
-- 接口加载
local host = sprotoloader.load(1):host( "package")
local pack = host:attach(sprotoloader.load(2))
-- 消息解析
host:dispatch(msg, sz)
-- 消息打包
pack(pname, args, session)
接口调用的入口从: sprotoloader.load
-- sprotoloader.lua
function loader.load(index)
local sp = core.loadproto(index)
-- no __gc in metatable
return sproto.sharenew(sp)
end
改接口将协议内容从c读取,并传入至 sproto.sharenew 函数
-- sproto.lua
function sproto.sharenew(cobj)
local self = {
__cobj = cobj,
__tcache = setmetatable( {} , weak_mt ),
__pcache = setmetatable( {} , weak_mt ),
}
-- 元表的指定使得能访问 sproto 提供的其他接口
return setmetatable(self, sproto_nogc)
end
回到接口加载处,第一行代码在 sprotoloader.load(1) 返回后继续调用了sproto:host(“package”)
-- sproto.lua
function sproto:host( packagename )
packagename = packagename or "package"
local obj = {
__proto = self,
__package = assert(core.querytype(self.__cobj, packagename), "type package not found"),
__session = {},
}
-- 元表提供了host的访问
return setmetatable(obj, host_mt)
end
回到接口加载处,第二行调用 host:attach(sprotoloader.load(2)), 这里涉及到了sproto.lua 中 host 表提供的函数接口。
-- 服务端主动请求客户端
local function send(pname, args, callback)
local session = nil
if callback then
window = window + 1
tracker[window] = { pname=pname, callback=callback }
session = window
end
-- write 消息的发送函数
return write(pack(pname, args, session))
end
-- 消息的解析
host:dispatch(msg, sz)
基于具体的使用来看看在 sproto.lua 中 host 提供的接口
-- 消息内容的解析
function host:dispatch(...)
local bin = core.unpack(...)
header_tmp.type = nil
header_tmp.session = nil
header_tmp.ud = nil
local header, size = core.decode(self.__package, bin, header_tmp)
local content = bin:sub(size + 1)
if header.type then
-- request
local proto = queryproto(self.__proto, header.type)
local result
if proto.request then
result = core.decode(proto.request, content)
end
if header_tmp.session then
return "REQUEST", proto.name, result, gen_response(self, proto.response, header_tmp.session), header.ud
else
return "REQUEST", proto.name, result, nil, header.ud
end
else
-- response
local session = assert(header_tmp.session, "session not found")
local response = assert(self.__session[session], "Unknown session")
self.__session[session] = nil
if response == true then
return "RESPONSE", session, nil, header.ud
else
local result = core.decode(response, content)
return "RESPONSE", session, result, header.ud
end
end
end
-- 消息内容的打包
function host:attach(sp)
return function(name, args, session, ud)
local proto = queryproto(sp, name)
header_tmp.type = proto.tag
header_tmp.session = session
header_tmp.ud = ud
local header = core.encode(self.__package, header_tmp)
if session then
self.__session[session] = proto.response or true
end
if proto.request then
local content = core.encode(proto.request, args)
return core.pack(header .. content)
else
return core.pack(header)
end
end
end