一:準備工做
1.vs2012
2.下載protobuf,我使用的版本是protobuf-2.6.1,git地址: https://github.com/google/protobuf.git
3.編譯,會在vsprojects/Debug/下生成兩個靜態庫libprotobuf.lib、libprotoc.lib和一個protoc.exejava
二:配置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這兩個庫。c++
三:proto定義和轉目標代碼
1.簡單proto文件定義git
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++目標語言批處理以下:github
@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.序列化json
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.反序列化ruby
test::People person;
person.ParseFromArray(buff, length);
string name = person.name();
int age = person.age();
string email = person.email();
注意:proto中定義的字段名大小寫,轉到c++所有會自動轉爲小寫形式。服務器
實際網絡遊戲開發中,咱們使用proto定義消息通常都是經過主協議號、子協議號來映射協議的。即每條協議都有主協議號和子協議號來惟一索引它。定義上通常按功能模塊劃分,主協議指示某個大模塊,子協議指示大模塊下的具體協議。
下面咱們來看看實際項目中proto應該怎麼定義框架:
1.主協議proto定義(協議命名格式:enumName_childModelName)網絡
//Pmd.proto
syntax = "proto2"; //指定proto版本2.x
package PlatPmd; //包名,c++中對應namespace
enum PlatCommand
{
PlatCommand_NullPmd = 0; //基礎模塊(網絡包結構、心跳等協議)
PlatCommond_LoginPmd = 10; //登入模塊
}
2.子協議proto定義(協議命名格式:XxxModelName_CS[SC],對應發送/回覆名字前綴統一)框架
//基礎模塊NullPmd.proto
syntax = "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 = 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.proto
syntax = "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);
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.h proto協議映射綁定
#pragma once
class 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;
}