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.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定義消息通常都是經過主協議號、子協議號來映射協議的。即每條協議都有主協議號和子協議號來惟一索引它。定義上通常按功能模塊劃分,主協議指示某個大模塊,子協議指示大模塊下的具體協議。
下面咱們來看看實際項目中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;
}
相關文章
相關標籤/搜索