Skynet 协议 sproto 协议lua代码阅读,及sproto的使用

本文详细介绍了Skynet框架中Sproto协议的解析过程,包括`sprotoparser.parse`函数的执行流程,以及如何通过`sprotoloader`加载服务协议。在`protoloader`中,`save`函数用于保存协议到内存,而`load`函数则从C读取并创建共享的协议对象。实际项目中,Sproto协议用于接口加载、消息解析和打包。通过对协议内容的处理,实现了高效的消息交互。
摘要由CSDN通过智能技术生成

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
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值