Google protocol bufferがプロトコルのシーケンス化を考える少し簡略化

4709 ワード

GoogleのPBプロトコルは強力で、主にホスト言語をシミュレートしていますが、C++原生言語はいくつかのコードの動的生成に弱いです.ゲームプロトコルでは、多くのフィールドが定義されていますが、毎回送信されるとは限りません.この中のシーケンス化を柔軟にカスタマイズするにはどうすればいいですか?
 
Googleはまずホスト言語で説明し、フィールドとflagをバインドしてコードを生成するからです.プロジェクトでは、どんなに強力なコードライブラリでも冗長だと思います.ここでの冗長とは、コスト的には膨大なライブラリでありながら、簡単なものを自分で書くことができるが、やや複雑で、面倒な点はあるが、理解とメンテナンスコストは前者より低いに違いないということです.
 
後者について、カスタムシーケンス化の分析を行います.
グーグルのように単独のflag変数に基づいて動的に生成することはできないに違いない.コードはますます複雑になっている.
書き込み時には書き込み変数のオフセット位置を記録しておき、読み出し時にオフセット位置に応じて自動的に読み込みますか?
良いアイデアのようですが、実装すると、オフセット位置だけでは不十分で、読み取り時にサイズを記録しなければならないので、構造は
【オフセット量、2バイト】【フィールドサイズ、2バイト】【フィールド値】
すなわち,1つのプロトコルのフィールドを独立して取り外すが,容量的にはあまり採算が合わないようで,各フィールドに4バイト増加する.
では、考え方を変えて考えると、FLAGを打つことによって満足のいく効果が得られない以上、コードから考えることができますか?例えば関数はread,writeがあり,私はwriteを1回書いて,書き込むストリームの順序を維持し,解析するときに自動的に逆解析すればよい.このようなコストは納得できると思います.
既存のコードを分析し続け、
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   pBuffer
	virtual char* Serial(char* pBuffer, char* pVariable, int nVariableSize);

private:

public:

private:

};

class CProtocolSerialLiteRecv:public CIProtocolSerialLite
{
public:
	//pBuffer   pVariable
	virtual 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関数によって読み取るコードはすべて同じです