服务器|c++服务器protobuf使用

环境配置和使用 一:准备工作
1.vs2012
2.下载protobuf,我使用的版本是protobuf-2.6.1,git地址: https://github.com/google/protobuf.git
3.编译,会在vsprojects/Debug/下生成两个静态库libprotobuf.lib、libprotoc.lib和一个protoc.exe
二:配置c++环境
1.源文件包括:protobuf解压目录下的src目录和编译后的生成的libprotobuf.lib、libprotoc.lib两个库,把它们拷贝出来放到我们自己工程根目录。目录结构如这样:extension[lib(libprotobuf.lib,libprotoc.lib), src(…)]
2.工程右击–>属性–>C/C++–>常规–>附加包含目录,添加extension\src目录。
3.同样…–>链接器–>常规–>附加库目录,添加extension\lib目录。
4.同样…->链接器–>输入–>附加依赖项,添加libprotobuf.lib、libprotoc.lib这两个库。
三:proto定义和转目标代码
1.简单proto文件定义

syntax = "proto2"; //proto版本(2.x) package test; //包名//消息定义 message People{ required string name = 1; required int32 age = 2; optional string email = 3; }

2.转成c++文件使用
使用开始编译出来的protoc.exe工具,可以把proto编译生成不同的语言版本。这里我们一般使用.bat批处理,把.exe,.proto和.bat都放一个目录下,点击.bat自动编译目录下所有.proto文件。
转c++目标语言批处理如下:
@echo off for /r %%i in (*.proto) do ( echo %%~ni.proto protoc.exe--cpp_out=../../server/proto/./%%~ni.proto ) pause

运行后,会在../../server/proto/目录下生成对应的xx.pb.h,xx.pb.cc文件。这就是c++项目中我们要使用的协议文件了,把它们全部导入我们的项目中。
四:使用示例
1.序列化
test::People* p = new test::People(); p->set_email("cxx@gmail.com"); p->set_id(10086); p->set_name("cxx"); int buffsize = p->ByteSize(); void* buff = malloc(buffsize); p->SerializeToArray(buff, buffsize);

2.反序列化
test::People person; person.ParseFromArray(buff, length); string name = person.name(); int age = person.age(); string email= person.email();

注意:proto中定义的字段名大小写,转到c++全部会自动转为小写形式。
实际网络游戏proto定义格式 实际网络游戏开发中,我们使用proto定义消息一般都是通过主协议号、子协议号来映射协议的。即每条协议都有主协议号和子协议号来唯一索引它。定义上一般按功能模块划分,主协议指示某个大模块,子协议指示大模块下的具体协议。
下面我们来看看实际项目中proto应该怎么定义框架:
1.主协议proto定义(协议命名格式:enumName_childModelName)
//Pmd.protosyntax = "proto2"; //指定proto版本2.x package PlatPmd; //包名,c++中对应namespaceenum PlatCommand { PlatCommand_NullPmd= 0; //基础模块(网络包结构、心跳等协议) PlatCommond_LoginPmd= 10; //登入模块 }

2.子协议proto定义(协议命名格式:XxxModelName_CS[SC],对应发送/回复名字前缀统一)
//基础模块NullPmd.protosyntax = "proto2"; package PlatPmd; message NullPmd { enum Param { NetPackageNullPmd_CS= 1; NetTickNullPmd_SC= 2; } }message NetPackageNullPmd_CS { optional uint32 byCmd= 1; optional uint32 byParam= 2; optional uint32 seq= 3; // 发送序列号,断线重连用 optional uint64 fid= 4; // server used for forward optional bytes data= https://www.it610.com/article/5; // data optional uint64 prototype= 6; // 0:proto,1:json,2:http optional uint32 bitmask= 7; // FrameHeader位枚举 optional uint32 time= 8; // client use }message NetTickNullPmd_SC { optional uint32 requesttime = 1; // 对方的请求时间原封返回 optional uint32 mytime= 2; // 当前应答的本地时间,秒,必须填,用来防止加速 }

//登入模块LoginPmd.protosyntax = "proto2"; package PlatPmd; message LoginPmd { enum Param { StartLoginLoginPmd_CS= 1; StartLoginLoginPmd_SC= 2; } }enum VerifyReturnReason { LoginOk= 0; // 登录成功 TokenFindError= 1; // 服务器没有token TokenDiffError= 2; // token错误 VersionError= 3; // 版本验证 }// 登入 message StartLoginLoginPmd_CS { required string account= 1; // 平台账号 required string token= 2; // token 可以是第三方认证 required uint32 version= 3; // 当前客户端login版本号Version_Login optional uint32 gameid= 4; // if filled, will send ZoneInfoListLoginUserPmd_S for select, else auto select zone optional string mid= 5; // 机器码 optional uint32 platid= 6; // 平台编号 optional uint32 zoneid= 7; // if filled, will auto login UserLoginRequestLoginUserPmd_C, UserLoginTokenLoginSmd_SC optional uint32 gameversion= 8; // 当前客户端game版本号Version_Login optional string compress= 9; // 压缩算法 optional string encrypt= 10; // 加密算法 optional string encryptkey= 11; // 加密key }//登录返回 message StartLoginLoginPmd_SC { required VerifyReturnReason retcode= 1; // 返回值 optional string desc= 2; // 返回错误描述,正确时不填 }

注意命名规则的定义!
协议映射关系 1.使用示例
//注册proto协议 MessageSerializer serializer; if(registerMessage(serializer) == false) return -1; //通过主、子协议获取对应协议对象,然后反序列化 google::protobuf::Message* prototype = serializer.getMessageByCmdParam(byCmd, byParam) google::protobuf::Message* message = prototype->New(); message->ParseFromArray(buff, buffsize);

【服务器|c++服务器protobuf使用】2.相关源代码
//Message.cpp初始化proto协议文件#include "proto/Pmd.pb.h" #include "proto/NullPmd.pb.h" #include "proto/LoginPmd.pb.h"//初始化子协议信息 void initParamDescriptor() { PlatPmd::NullPmd_Param_descriptor(); PlatPmd::LoginPmd_Param_descriptor(); }//注册proto协议(初始化入口,映射信息保存在serializer对象中) bool registerMessage(MessageSerializer* serializer) { initParamDescriptor(); //主协议信息 if (serializer->Register(PlatPmd::PlatCommand_descriptor(), "PlatPmd") == false) return false; return true; }

//MessageSerializer.hproto协议映射绑定#pragma onceclass MessageSerializer { public: MessageSerializer(); private: //保存所有协议结构对象 const google::protobuf::Message* m_unserializeTable[65536]; public: /** 注册proto协议. * @param byCmdEnum 主协议enum信息ns 命名空间 * @return true or false. */ bool Register(const google::protobuf::EnumDescriptor* byCmdEnum, const std::string ns); /** 具体一条协议注册. * @param byCmd 主协议号byParam 子协议号typeDescriptor 具体一条协议信息 * @return true or false. */ bool Register(unsigned char byCmd, unsigned char byParam, const google::protobuf::Descriptor* typeDescriptor); /** 通过主、子协议号获取协议对象. * @param byCmd 主协议号byParam 子协议号 * @return google::protobuf::Message. */ google::protobuf::Message* getMessageByCmdParam(unsigned char byCmd, unsigned char byParam); }; //Message.cpp 接口 void initParamDescriptor(); bool registerMessage(MessageSerializer* serializer);

//MessageSerializer.cpp#include "MessageDispatcher.h"MessageSerializer::MessageSerializer() { memset(m_unserializeTable, 0, sizeof(m_unserializeTable)); }Message* MessageSerializer::getMessageByCmdParam(unsigned char byCmd, unsigned char byParam) { unsigned int uMsgID = (byCmd << 8) + byParam; return m_unserializeTable[uMsgID]; }bool MessageSerializer::Register(const EnumDescriptor* byCmdEnum,const std::string ns) { if(byCmdEnum == NULL) { printError("MessageSerializer::Register insert err"); return false; }for(int i = 0; i < byCmdEnum->value_count(); i++) { const EnumValueDescriptor* item = byCmdEnum->value(i); const int c = item->number(); std::size_t found = item->name().find_last_of("_"); std::string cmdname = item->name().substr(found+1); const std::string paramtype = ns + "." + cmdname + ".Param"; printInfo("MessageSerializer::Register byCmdEnum:%d,%s,%s",c,paramtype.c_str(),item->name().c_str()); const EnumDescriptor* byParamEnum = DescriptorPool::generated_pool()->FindEnumTypeByName(paramtype); if(byParamEnum == NULL) { printError("MessageSerializer::Register err:%d,%s,%s",c,paramtype.c_str(),item->name().c_str()); return false; } for(int i = 0; i < byParamEnum->value_count(); i++) { const EnumValueDescriptor* item = byParamEnum->value(i); if(c > 0 && c < 200 && item->name().find(cmdname.c_str()) == std::string::npos) { printError("MessageSerializer::Register name err:%d,%s,%s,需要名字严格匹配规则",c,cmdname.c_str(),item->name().c_str()); return false; } const int t = item->number(); printInfo("MessageSerializer::Register byParamEnum:[%d,%d],%s,%s",c,t,byParamEnum->full_name().c_str(),item->name().c_str()); const Descriptor* message = DescriptorPool::generated_pool()->FindMessageTypeByName(ns + "." + item->name()); if(message == NULL) { printError("MessageSerializer::Register find err:[%d,%d],%s,%s",c,t,byParamEnum->full_name().c_str(),(ns + "." + item->name()).c_str()); return false; }if(Register(c, t, message) == false) { printError("MessageSerializer::Register insert err:[%d,%d],%s",c,t,byParamEnum->full_name().c_str()); return false; } } }return true; }bool MessageSerializer::Register(unsigned char byCmd, unsigned char byParam, const Descriptor* typeDescriptor) { if(typeDescriptor == NULL) { printError("MessageSerializer::Register err"); return false; } const Message* prototype = MessageFactory::generated_factory()->GetPrototype(typeDescriptor); if(m_unserializeTable[(byCmd<<8) + byParam] != NULL && m_unserializeTable[(byCmd<<8) + byParam] != prototype) { printError("MessageSerializer::Register insert err[%u,%u],%p,%p",byCmd,byParam,m_unserializeTable[(byCmd<<8) + byParam],prototype); return false; } m_unserializeTable[(byCmd<<8) + byParam] = prototype; return true; }

    推荐阅读