游戏开发:rpc protocol demo

看好项目的源码总是会有重复造轮子的冲动。最近对比测了下我们业务使用的数据序列化协议的性能,review了社区上几个常用库的源码,尝试做了点优化,有些想法。浅浅写了个demo,这里记录下实现的思路,供后面查阅和反思。

协议的实现分为几个部分:

schema text:协议描述文件
compiler :解析器,负责将描述文件解析成FIX驱动层语言类型的协议配置
driver:驱动层,维护编译器生成的协议配置,具备查询、编码解码以及热更新等能力
adapter:适配器,提供多种编程语言链接驱动层的API

  1. 协议描述文件(schema text)
# TAG:
#   @TYPE:struct definition
#   @RPC:rpc definition

TYPE 1001 tinyuser [[
    int id = 0
    str name = 1
    bool online = 3
    list friend = 4
]]

RPC 1001 svr_view_tinyuser [[
    request [[
        int id = 0
    ]]
    response [[
        # allow nesting
        TYPE 1 ret [[
            bool ok = 0
            str msg = 1
        ]]
        type ret result = 0 # use keyword `type` when field is not built in
        type tinyuser user = 1
    ]]
]]

协议格式支持:
1)通用数据格式类型;
2)自定义结构体类型;
3)结构体类型嵌套;
4)请求回应模式的定义(将请求和回应看成是单独的自定义类型)

  1. 解析器(compiler)

解析器将描述文件解析为兼容驱动层语言的配置内容(以lua为例):

local types = {
    [1001] = {
        name = "tinyuser",
        tag = 1001,
        fields = {
            { name = "id", tag = 0, tp = SERIALIZE_TYPE_INT, buildin = true },
            { name = "name", tag = 1, tp = SERIALIZE_TYPE_STR, buildin = true },
            { name = "online", tag = 2, tp = SERIALIZE_TYPE_BOOL, buildin = true },
            { name = "friend", tag = 3, tp = SERIALIZE_TYPE_LIST, buildin = true }
        }
    },
    [1001.1] = {
        name = "svr_view_tinyuser.request",
        tag = 1001.1,
        fields = {
            { name = "id", tag = 0, tp = SERIALIZE_TYPE_INT, buildin = true }
        }
    },
    [1001.2.1] = {
        name = "svr_view_tinyuser.response.ret",
        tag = 1001.2.1,
        fields = {
            { name = "ok", tag = 0, tp = SERIALIZE_TYPE_BOOL, buildin = true },
            { name = "msg", tag = 1, tp = SERIALIZE_TYPE_STR, buildin = true },

        }
    },
    [1001.2] = {
        name = "svr_view_tinyuser.response",
        tag = 1001.2,
        fields = {
            { name = "result", tag = 0, tp = "svr_view_tinyuser.response.ret", buildin = false },
            { name = "player", tag = 1, tp = "tinyuser", buildin = false }
        }
    },
}
local protocols = {
    [1001] = {
        name = "svr_view_tinyuser",
        tag = 1001,
    		nesting = true
    }
}

local group = { types, protocols }

解析器提供两种结果获取方式:1. API实时获取 2. 生成解析文件(支持加密)

function compiler.dumpproto(fname)
  	fname = string.format("%s.pb", fname)
    dump2file(fname, group)
end

function compiler.getproto()
    return group
end

解析器提供解析文件的加载(解密)接口

function compiler.loadbin(bin)
	-- ... decryption
  return proto
end

function compiler.loadproto(pbfile)
		pbfile = string.format("%s.pb", pbfile)
  	local f = assert(io.open(pbfile), "Can't open " .. pbfile)
    local bin = f:read("a")
    f:close()
  	return compiler.loadbin(bin)
end
  1. 驱动层(driver)

驱动层用于维护和驱动协议解析配置相关的信息:

local compiler = require("complier")

local host = {}

local function loadproto(schematext)
		local proto = compiler.loadproto(schematext)
    return setmetatable({_proto = proto}, {__index = host})
end

function host:queryproto(protocol)
    -- search in proto.protocols
end

function host:querytype(tpname)
    -- search in proto.types
end

local serializefuncs = { --[[ SERIALIZE_TYPE = opfunc --]] }
local unserializefuncs = { --[[ SERIALIZE_TYPE = opfunc --]] }

function host:encodeproto(tpname, data)
	-- ... encode lua table to bin by proto formation
  return bins
end

function host:decodeproto(tpname, msg)
		-- decode bin msg to lua by proto formation
    return result
end

local driver = {
    loadproto = loadproto,
  	-- ...
}

local rpc = {
  	send = function(sp, rpc_name, data)
        local typename = string.format("%s.request", rpc_name)
        return sp:encodeproto(typename, data)
    end,
    recv = function(sp, rpc_name, msg)
        local typename = string.format("%s.request", rpc_name)
        return sp:decodeproto(typename, msg)
    end,
    retsend = function(sp, rpc_name, retdata)
        local typename = string.format("%s.response", rpc_name)
        return sp:encodeproto(typename, retdata)
    end,
    retrecv = function(sp, rpc_name, retmsg)
        local typename = string.format("%s.response", rpc_name)
        return sp:decodeproto(typename, retmsg)
    end
}
  1. 适配器(adapter)
import lupa  

lua = lupa.LuaRuntime()

# load comoiler
lua.execute(loadfile(compiler))
compiler_core, compiler_rpc = lua.globals.core, lua.globals.rpc

class Adapter:
    def __init__(self, compiler_core, compiler_rpc, schematext):  
        self._ccompiler_core = compiler_core
        self._compiler_rpc = compiler_rpc
        self.proto = self._compiler_core.loadproto(schematext)
    def rpc_send():
        self._compiler_rpc.send()
    # ...
posted @ 2024-05-27 23:08  linxx-  阅读(23)  评论(0编辑  收藏  举报