C++使用protobuffer的一些坑

服务器用protobuffer (之后简称pb)做协议包体使用了两个月, 确实体验到了很多方便的地方(接口代码易写易维护, 内部编码高效,传输快等)
但不可否认的是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()) 再发送

第一个坑调了一天多, 还耽误了客户端很多时间, 因为没有把c风格字符串赋值给string
导致部分包体解出来(反序列化后)各字段对应的数据不正确。
解决方案就是给string赋值一下再解包…:

ProtoTest::OnGame rcvBody;
string code = "";
code.assign(&body[0], length);
rcvBody.ParseFromString(code);
	

第二个坑和第三个坑总结来说就是需要设置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);

应该使用第四个。

同理看了前两个坑第三个坑也和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对象的时候失败了 , 然后这个bug让自己查到吐血查了一天…
最后(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);

我使用的是第三个…

最后总结一句: c++给string赋值c字符串的时候一定一定要指定长度。

  • 2
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值