C++protobufferを使用するいくつかのピット


サーバーはprotobuffer(以下pbと略称する)をプロトコルパッケージとして2ヶ月使用し、確かに多くの便利な場所(インタフェースコードの書きやすくメンテナンスしやすく、内部符号化が効率的で、伝送が速いなど)を体験したが、C++がそれを使用する際に発生しにくい穴があることは否めない.
c++pbパケットのシーケンス化/逆シーケンス化の方法は,(入力ストリーム,ファイルストリーム,string)からのシーケンス化ネットワーク伝送ではstringシーケンス化/逆シーケンス化のみが用いられる.
すなわち、通常のネットワークプログラミングで使用されるインタフェース関数は、次のとおりです.
string  code= getStringFromClient();             
ProtoTest::OnGame rcvBody;
rcvBody.ParseFromString(code);

......

ProtoTest::OnGame rspBody;
string codeBody;
rspBody.SerializeToString(&codeBody);
SendStringToClient(codeBody);

自分のゲームで使用しているサーバは、既存のフレームワークに基づいて構築された各messageIDが関数ポインタを介してコールバック関数に対応しているため、データを送受信するインタフェースは以下のフォーマットでなければならない.
int32_t process_request_playgame(CMessage& message, const char* body, const int32_t length)

int32_t send_response_to_client(const char* body, int16_t nLength, int16_t nMessageID, int32_t iSequence)


したがって、データを受信する際には、cスタイル文字列(bodyパッケージ)をstringに代入して逆シーケンス化してデータを送信する際に、シーケンス化後のstringをC文字列(.c_str()に代入して送信しなければならない
最初のピットは1日以上変調され、クライアントに多くの時間を費やした.cスタイル文字列をstringに割り当てなかったため、一部のパケットが解釈された(逆シーケンス化された)ため、各フィールドに対応するデータが正しくない.ソリューションはstringに値を付けてからパッケージを解くことです...:
ProtoTest::OnGame rcvBody;
string code = "";
code.assign(&body[0], length);
rcvBody.ParseFromString(code);
	

2番目のピットと3番目のピットは、pbパケット内のbytesフィールドとstringフィールドの長さを設定する必要があります.pbのbytesフィールドは、中国語などのマルチバイトの言語文字を処理するために使用することができる.すなわち、いくつかの符号化された文字配列は、それを用いて伝送することができる.
message OnGame{
	required bytes gameData = 1;
	required int32 m_iRoomID = 2;
	required int32 m_iTableID = 3;
	required int32 m_icmd = 4;               //gameData process CMD
	required int32 gameDataLength = 5;
}

OnGameパッケージのgamedataは、シーケンス化された内層パッケージ(m_icmdで区別)を格納するために使用されます.以前setの場合は以下のように書かれていました.
int32_t CTable::send_game_data_to_player(int32_t cmd, int32_t iPlayerID,const char* pszGameData, int32_t iGameDataSize)
....
rspBody.set_gamedata(pszGameData);
....

しかし、このように書くと、実際にクライアントが逆シーケンス化して得られるデータは必ずしも正しいとは限らない.多分char配列を下に取るため、0に取ってから終わりますが、pszGameDataには複数の0が含まれているか、含まれていない可能性が高いです.pbコンパイルパッケージを見てみましょう.hファイルの関数インタフェース:
void set_gamedata(const ::std::string& value);
#if LANG_CXX11
void set_gamedata(::std::string&& value);
#endif
void set_gamedata(const char* value);
void set_gamedata(const void* value, size_t size);

4つ目を使うべきです.
同じように、最初の2つのピットの3番目のピットもc文字列のポインタと長さに関係しています.
message Friend_resGetInfo{                     // 
    required int32 resultID = 1;
    optional string m_NameOther = 2;
    optional int32 sex = 3;
    optional int32 exp = 4;
    optional int32 battleCnt = 5;
    optional int32 winCnt = 6;
    optional bool isOnline = 7;
}


クライアントに返信するコードは次のとおりです.
void CPlayer::pack_res_getRole(int32_t msgid, int32_t resultID, stRoleInfo* m_stRoleInfo2 ){
	ProtoTest::Friend_resGetInfo rspBody;
	
	rspBody.set_resultid(resultID);
	if(resultID == success){
		
		rspBody.set_m_nameother(m_stRoleInfo2->name);
		rspBody.set_sex(m_stRoleInfo2->sex);
		rspBody.set_exp(m_stRoleInfo2->exp);
		rspBody.set_battlecnt(m_stRoleInfo2->battleCnt);
		rspBody.set_wincnt(m_stRoleInfo2->winCnt);
		rspBody.set_isonline(true);
	}
	
	string codeBody1;
	rspBody.SerializeToString(&codeBody1);
	int16_t outLen = codeBody1.length();
	send_response_to_client(codeBody1.c_str(), outLen, msgid, 0);
}

このコードが複数回実行されるとサーバがクラッシュすることはわかりにくいが、gdbのcoreの位置づけは以下の通りである.
(gdb) where
#0  0x00007fc75b7f95e5 in raise () from /lib64/libc.so.6
#1  0x00007fc75b7fadc5 in abort () from /lib64/libc.so.6
#2  0x00007fc75b8374f7 in __libc_message () from /lib64/libc.so.6
#3  0x00007fc75b83cf3e in malloc_printerr () from /lib64/libc.so.6
#4  0x00007fc75b83fdd0 in _int_free () from /lib64/libc.so.6
#5  0x00000000004735ff in ProtoTest::Friend_resGetInfo::SharedDtor() () at /usr/local/include/google/protobuf/arenastring.h:258
#6  0x00000000004760fc in ProtoTest::Friend_resGetInfo::~Friend_resGetInfo() () at package.pb.cc:17443
#7  0x0000000000422a3e in GameJayo::Server::LogicServer::CPlayer::pack_res_getRole(int, int, GameJayo::Server::LogicServer::stRoleInfo*) () at logicserver_player.cpp:1156


Friendの分析は明らかだresGetInfoオブジェクトの時に失敗して、それからこのバグは自分に吐血を調べて1日調べさせました...最後に(baoge)発見m_nameOtherフィールドインタフェース関数は次のとおりです.
  void set_m_nameother(const ::std::string& value);
  #if LANG_CXX11
  void set_m_nameother(::std::string&& value);
  #endif
  void set_m_nameother(const char* value);
  void set_m_nameother(const char* value, size_t size);

私が使っているのは3つ目です...
最後に、c++がstringにc文字列を割り当てるときは必ず長さを指定します.