网络协议头部添加自定义字段 be

在之前公司的项目中,就有同事提到在头部类中预留一个 int 类型字段以备紧急情况,当时我不是太能体体会到这种用法。日常开发中就是定义一个继承至头部类的 C# 类,里面添加通信用到的字段,底层会把对象中头部类和添加的 C# 类中的字段一起序列化,头部类包含一些通用字段比如 session 和 errorcode 。当时这种方式足以满足日常需求,就没有再继续考虑在头部类中预留一个字段。
后来接触到 skynet 配套的序列化库 sproto 。云大给的 rpc 示例中日常开发也是类似。如下所示。

.package {
	type 0 : integer
	session 1 : integer
}

foobar 1 {
	request {
		what 0 : string
	}
	response {
		ok 0 : boolean
	}
}

package 是底层用到的包头类型。假设 foobar 是日常开发时添加的协议,假设这里是客户端发送请求并且在 what 字段中添加需要发送的信息,服务端处理完毕后返回,并在 ok 字段中告知处理结果。日常开发便是如此,只需要关注添加的协议即可。
具体到 sproto.lua 文件中,查看接收数据时解码处理,片段如下。阅读代码可知 sproto 先解析头部,再解析内容。所以定义头部类型 .packege 时,必须要有 type session 字段,头部的 ud 字段是可选的 userdata 。函数返回值 result 就是收到的内容,而 ud 就是附加在头部中,用户发送的可选数据。

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)
	assert(header == header_tmp)
	local content = bin:sub(size + 1)
	...
	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
	...
end

再看发送数据时编码处理的两个函数代码。和接收数据的操作对比来看,发送数据时先编码头部,再编码内容。函数 gen_responsehost:attach 返回的 closure 用于编码需要发送的内容。参数 args 就是用户层发送的内容,而 ud 就是可选的 userdata 。

local function gen_response(self, response, session)
	return function(args, ud)
		header_tmp.type = nil
		header_tmp.session = session
		header_tmp.ud = ud
		local header = core.encode(self.__package, header_tmp)
		...
	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)
		...
	end
end

sproto 提供了 rpc 通信流向:定义一条协议,以 request 格式请求,以 response 格式回应。并封装了消息的格式:头部 + 内容,并且允许用户在头部增加用户数据 userdata 来对协议进行统一处理。通过头部 ud 字段,可以不修改协议而为协议增加附加的信息。之前项目同事提到固定的 int 字段,而 sproto 这里允许自定义用户数据,更加灵活,并且字段不设置并不会被编码。举个例子,这样定义头部类型。

.userdata {
    int 0 : integer
    str 1 : string
}
.package {
	type 0 : integer
	session 1 : integer
	ud 2 : userdata
}

假设,需要为所有协议添加客户端的时间戳以及一段描述字符串,可以使用头部 ud 实现,示例片段如下。

-- 客户端发送数据
local server = server_proto:host "package"
local client = client_proto:host "package"
local client_request = client:attach(server_proto)
local req = client_request("foobar", {what = "login"}, 1, {int = os.time(), str = "hello, test"})

-- 服务端接收数据
local type, name, request, response, ud = server:dispatch(req)
print(ud.int, ud.str) -- 获取添加的时间戳以及描述字符串 hello, test

我们现在的 skynet 项目并未在项目中使用 ud ,甚至发送数据和接收数据都屏蔽了 ud 。因为确实日常开发中不需要。写这篇文字,也就是记录下设计通信协议时在头部添加自定义字段,会在需要时很实用,比如统计协议时,在协议中添加额外的信息便于统计。

猜你喜欢

转载自my.oschina.net/iirecord/blog/1795810