首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 企业软件 > 行业软件 >

由google protocol buffer想到协议序列化的1点简化

2012-10-11 
由google protocol buffer想到协议序列化的一点简化谷歌的PB协议很强大,主要是模拟了一个宿主语言,而C++原

由google protocol buffer想到协议序列化的一点简化

谷歌的PB协议很强大,主要是模拟了一个宿主语言,而C++原生语言对一些代码的动态生成是很弱势的。在游戏协议上,其实很多字段虽然有定义,但是都不一定每回发送的时候都使用到。如何的去灵活定制这里面的序列化?

?

因为谷歌是先用宿主语言描述了一遍,把字段和flag给绑定起来,然后生成代码。而在项目中,即使再强大的代码库,还是觉得很冗余。这里的冗余指的是说,从成本上考虑,一边是一个庞大的库,一边可以自己写个简单的但是稍微复杂的,虽然麻烦点,但是理解和维护成本肯定低于前者。

?

针对后者,我们来做一些定制序列化的分析:

肯定做不到谷歌那样根据单独一个flag变量动态的生成,那样代码越来越复杂;

写入的时候先记录写入变量的偏移位置,然后读的时候根据偏移位置自动读取?

似乎是个好主意,但是实施的时候,会发现光有个偏移位置还不够,读取的时候还要记录大小,那么结构就变成

【偏移量,2个字节】【字段大小,2个字节】【字段值】

也就是说把一个协议体的字段独立的拆开,但是容量上似乎不太合算,每个字段都要增加4个字节。


那么换种思路考虑,既然根据打FLAG无法做到很满意的效果,那能不能从代码上考虑?比如函数有read,write,我只要写一遍 write,保持写入的流顺序,解析的时候自动反解析出来即可。我觉得这样的成本可以接受。


那么继续分析现有的代码,

write(buffer,variable,sizeof(variable)),

read(buffer,variable,sizeof(variable)),


我们发现write和read的区别其实就是buffer到variable,或者variable到buffer。那么我们可以把这个函数给抽象出来,做成虚函数,分别实现相反的读写。然后上层对其进行封装,根据行为传入不同的参数:

?

class CIProtocolSerialLite{public:virtual char* Serial(char* pBuffer, char* pVariable, int nVariableSize)=0;private:public:private:};class CProtocolSerialLiteSend:public CIProtocolSerialLite{public://pVariable拷贝到pBuffervirtual char* Serial(char* pBuffer, char* pVariable, int nVariableSize);private:public:private:};class CProtocolSerialLiteRecv:public CIProtocolSerialLite{public://pBuffer拷贝到pVariablevirtual char* Serial(char* pBuffer, char* pVariable, int nVariableSize);private:public:private:};
?

?

?

/*CProtocolSerialLiteSend*/char* CProtocolSerialLiteSend::Serial(char* pBuffer, char* pVariable, int nVariableSize){memcpy(pBuffer, pVariable, nVariableSize);pBuffer+=nVariableSize;return pBuffer;}/*CProtocolSerialLiteRecv*/char* CProtocolSerialLiteRecv::Serial(char* pBuffer, char* pVariable, int nVariableSize){memcpy(pVariable, pBuffer, nVariableSize);pBuffer+=nVariableSize;return pBuffer;}

?

?假如有这样一个协议体

?

#include "CProtocolSerialLite.h"class CProtocolA{public:CProtocolA();bool Init();void Send(char* pBuffer);void Recv(char* pBuffer);int GetSize();void SetT1(int nValue);void SetT2(int nValue);void SetT3(int nValue);private:void Serial(CIProtocolSerialLite *pSerialLite, char* pBuffer);public:private:int m_nSize;//实际发送的大小//连续发送int m_nT1;int m_nT2;int m_nT3;};

?

CProtocolA::CProtocolA()

?

{Init();}bool CProtocolA::Init(){m_nSize = 0;//-1为无效值m_nT1 = -1;m_nT2 = -1;m_nT3 = -1;return true;}int CProtocolA::GetSize(){return m_nSize;}void CProtocolA::SetT1(int nValue){m_nT1=nValue;}void CProtocolA::SetT2(int nValue){m_nT2=nValue;}void CProtocolA::SetT3(int nValue){m_nT3=nValue;}void CProtocolA::Send(char* pBuffer){CProtocolSerialLiteSend SerialLiteSend;Serial(&SerialLiteSend, pBuffer);}void CProtocolA::Recv(char* pBuffer){CProtocolSerialLiteRecv SerialLiteRecv;Serial(&SerialLiteRecv, pBuffer);}void CProtocolA::Serial(CIProtocolSerialLite *pSerialLite, char* pBuffer){pBuffer=pSerialLite->Serial(pBuffer, (char*)&m_nT1, sizeof(m_nT1));m_nSize += sizeof(m_nT1);pBuffer=pSerialLite->Serial(pBuffer, (char*)&m_nT2, sizeof(m_nT2));m_nSize += sizeof(m_nT2);pBuffer=pSerialLite->Serial(pBuffer, (char*)&m_nT3, sizeof(m_nT3));m_nSize += sizeof(m_nT3);}

?

对外暴露的是send和write,接口可以根据具体的代码情况修改参数。并且在公共的Serial函数中,顺便把该协议真正的发送长度也求出来了,而且利用C++的指针也非常容易的插入到pBuffer中的任意位置(代码中无)。

?

如果只要修改Serial函数,那么之前的flag所带来的好处就完全可以抵消。至于选择性的字段那就很简单啦。自己实现个flag,在Serial函数里先写入flag, 剩下根据flag函数进行读取的代码都是一摸一样的

热点排行