Sproto c++

Sproto c++

全篇是我skynet学习的收尾部分,大概搞懂了底层逻辑,标志着对skynet 和 sproto核心有了初步认识;这里基于github上一个项目,该项目过老,现将其适配更新后的sproto,用在这自己写的一个网络游戏项目里面,CS通信有了质的飞跃。

sproto 主要逻辑都实现在sproto.c中,开发者云风给需要绑定的使用者设计了callback 和 绑定语言对应的数据结构ud,所以使用者只需设计这两个参数即可。

  • callback的任务是根据不同的数据类型返回在特定语言的字节数
  • ud 在encode时负责数据的输入,decode时负责数据的输出

基于github的开源项目 sproto-cpp

主要时用paser将sproto schema 语言写的字符串生成二进制文件,在C中直接调用该文件,如此就和lua无关了。

但该项目比较久远,sproto做了些更新,我看到的几点有:double类型、控制流程的几个宏(主要时输出判断循环终止,如SPROTO_CB_ERROR、SPROTO_CB_NIL循环break)。

我修改的地方,在encode回调函数里。解码回调函数貌似不用改,能正常使用。
1、整数

case SPROTO_TINTEGER:{
	...
	if(args->extra){
				int64_t v = (int64_t)(round(field_value * args->extra));
				field_value = v;
			}
	...
}

2、递归叶子判断

case SPROTO_TSTRUCT:
		{
			SprotoMessage* submsg = ep->msg->GetStructField(tagname, index);
			if (submsg == NULL){
				std::cout << "SPROTO_TSTRUCT :  error"  <<std::endl;
				//return 0;
				return SPROTO_CB_NIL;// update,定义为-2
			}
		}

sproto 的组织方式

  • 我以为,从sproto_dump()C语言函数的输出可以看出:
    分为两种,数据类型(type) 和 消息类型(protocol)。
    数据类型可用 . 来自定义;消息类型可用已有的数据类型来组织,其格式一定是包含request和response的表,来表明数据接收的格式和发出的格式(针对lua而言);对C++的binding,事实上消息类型由自定义的类来确定,在编码时只用到sproto.pb二进制中自定义的数据类型就可以了;那是否意味着在C++ 的sproto中不需要用到消息类型了呢?否,因为和skynet lua通信,交换的数据包先是“>s2”编码带长度的吧,然后是skynet中规定的package头 和 主体的信息拼接而成,package头包括type 和session,type就是消息类型中对应的tag,所以消息类型必须。
.package {
	type 0 : integer
	session 1 : integer
}
  • C端处理lua发来的数据包
    首先,数据包需要先处理2字节的长度信息再对剩余的二进制字段sproto_unpack得到二进制串bin,然后把bin分为两部分,header和content,header长度由.package编码后的二进制串长度确定,这得事先就用参数记好。然后,先将header 进行decode,得到type 和session(有个问题,session是0代表nil吗,还是0也是有效session,这里假设是前者),用type值通过query_proto函数查询得到 struct protocol 结构体指针,里面有其request和response对应的sproto_type。然后可以用sproto_type来解码content。
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
根据这段host:dispatch的lua源代码可知,根据type(也就是tag)判断是否需要回复(request(非 nil) 或 response(nil值)),也侧面说明了不存在tag为nil的protocol。在消息为request类型的情况,根据session判断是(非nil)否(nil值)有回复函数;在response类型情况,根据session判断收到的回复数据发往何处。
  • 封装C端发送给lua的数据包
    先encode 头header,附上所用的protocol的tag,如果需要恢复,就把session设为非0值。然后encode 所用的协议及填充内容得到content。把两端二进制数据连接起来得到bin。对bin进行sproto_pack,再加上两字节的长度信息,大端。

  • C中的nil
    在设置session时,如要设为nil,可以做如下改动,把0值定为nil:

bool Package::GetIntegerField(const char* name, int index,
	int64_t& value)
{
	if (strcmp(name, "type") == 0)
	{
		value = type;
		return true;
	}
	else if (strcmp(name, "session") == 0)
	{
		if (session == 0) {//规定session为0就为空,对应lua的nil
			return false;
		}
		else
		{
			value = session;
			return true;
		}
	}
	else 
	{
		return false;
	}
}

在绑定的encode函数里,会:

if (!ep->msg->GetIntegerField(tagname, index, field_value)) {
			std::cout << "SPROTO_TINTEGER : NIL" << std::endl;
			return SPROTO_CB_NIL;
		}

在C中,解码时,若数据项为nil,则不会改变message结构体中的对应变量值。

  • sproto_pack和sproto_unpack的输出长度问题
    经过实验发现,两者的输出可能不同,unpack的输出可能会大与pack的输出,但这并不影响数据的解码,且恰好以为这一特性,加之sproto_decode的输入数据size可以大于数据编码长度本身,可以动态地确定编码串中某一类消息所占的长度偏移,从而从左往右依次解码数据。如在package包中,有无session对其编码后的长度有影响,故在解码时根据decode的输出来判断其编码长度。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值