zoukankan      html  css  js  c++  java
  • 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++–>常规–>附加包含目录,添加extensionsrc目录。
    3.同样…–>链接器–>常规–>附加库目录,添加extensionlib目录。
    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.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;
    }
    
    
  • 相关阅读:
    一个泛型栈类(GenericStack)
    Google Maps API v2初探
    浅谈工作中celery与Redis遇到的一些问题 PTSD
    python PTSD
    77%的Linux运维都不懂的内核问题
    教程 | Linux常用命令大全
    分布式架构系列: 负载均衡技术详解
    10大Python开源项目推荐(Github平均star2135)
    你必须学写 Python 装饰器的五个理由
    五分钟搞定 Linux 文档全部知识,就看这篇文章
  • 原文地址:https://www.cnblogs.com/cxx-blogs/p/9159242.html
Copyright © 2011-2022 走看看