• 关于Protobuf在游戏开发中的运用


      最近在研究protobuf在项目中的使用,由于我们项目服务端采用的是C++,客户端是cocos2dx-cpp,客户端与服务端的消息传输是直接对象的二进制流。如果客户端一直用C++来写,问题到不大,但是后期有想法将客户端用lua来写(可以实现苹果平台的新增更新),这个时候问题就出现了(传输的消息定义无法在lua中得到扩展)。这个时候就想到了protobuf。

      protobuf原本是google的一个开源项目(网上有很多资料),它的功能是将类似的对象(class)转化成字符串,而且这个字符串比json数据少很多,解析也快了很多。为什么说类似,因为它所需的.proto文件的定义与C++中的class有点区别。

      下面我将介绍项目中使用protobuf。

      1、下载所需资源

        a) protobuf2.4.1 版本的下载(将定义的.proto文件转成 xx_pb.cc和 xx_pb_h)

        b) protoc-gen-lua的下载(将定义的.proto文件转成 xx_pb.lua)

      2、protobuf的使用(windows)

        a)解压下载的protobuf2.4.1文件到C:protobuf-2.4.1(这个位置可以随便),进入在该目录下的vsprojects文件夹,用vs打开protobuf.sln,进行编译(最好一个一个编译),如果报错,可参考http://www.cnblogs.com/cindyOne/p/protobuf.html

    编译得到的protoc.exe,libprotobuf.lib,libprotobuf-lite.lib,libprotoc.lib会在项目中用到。

        b)将.proto转成xx_pb.h和xx_pb.cc的方法是:将上步骤得到的 protoc.exe拷贝到.proto文件所在的文件夹,创建一个bat文件,里面写入protoc.exe --proto_path=./ --cpp_out=./ ./person.proto    pause(注:person.proto  是该文件夹下需要转化proto文件)。执行bat文件后,就得到了xx_pb.h和xx_pb.cc文件。

        c)在项目中使用xx_pb.h和xx_pb.cc文件。创建一个c++的控制台程序,项目属性中设置

    红线框是下载的protobuf的解压路径。

       将xx_pb.h和xx_pb.cc文件加入项目工程。创建一个Server.h文件。贴入代码(该项目中我引用了boost库)

     1 #pragma once
     2 #include <boost/asio.hpp>
     3 #include <boost/bind.hpp>
     4 #include <boost/shared_ptr.hpp>
     5 #include "people.pb.h"
     6 #pragma comment(lib, "libprotobuf.lib")  
     7 #pragma comment(lib, "libprotoc.lib")  
     8 #include <string>
     9 #include <iostream>
    10 #include "person.pb.h"
    11 
    12 
    13 using namespace std;
    14 class Server
    15 {
    16 private:
    17     boost::asio::io_service& m_ios;
    18     boost::asio::ip::tcp::acceptor m_apt;
    19     typedef boost::shared_ptr<boost::asio::ip::tcp::socket> sock_prt;
    20     int ncount;
    21 public:
    22     Server(boost::asio::io_service& nios)
    23         :m_ios(nios),
    24         m_apt(m_ios,boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(),6688)),
    25         ncount(0)
    26     {
    27         Start();
    28     }
    29     void Start()
    30     {
    31         sock_prt sock(new boost::asio::ip::tcp::socket(m_ios));
    32         m_apt.async_accept(*sock,
    33                          boost::bind(&Server::accept_callback,this,boost::asio::placeholders::error,sock)
    34             );
    35     }
    36     void accept_callback(const boost::system::error_code& e,sock_prt sock)
    37     {
    38         if(e)
    39         {
    40             cout<<"connect error"<<endl;
    41             return;
    42         }
    43         cout<<"client:"<<sock->remote_endpoint().address()<<endl;
    44         Person per;
    45         per.set_email("calvin@etsoma.com");
    46         per.set_id(ncount++);
    47         per.set_name("calvin");
    48         /*CPFS::People p;
    49         p.set_email("calvin@etsoma.com");
    50         p.set_id(ncount++);
    51         p.set_name("calvin");*/
    52         string strData;
    53         per.SerializePartialToString(&strData);
    54         char data[100];
    55         strcpy(data,strData.c_str());
    56 
    57         sock->async_write_some(boost::asio::buffer(data),
    58                              boost::bind(&Server::write_callback,this,boost::asio::placeholders::error)
    59             );
    60         Start();
    61 
    62     }
    63     void write_callback(const boost::system::error_code& e)
    64     {
    65         if(e)
    66         {
    67             cout<<"write error"<<endl;
    68             return;
    69         }
    70 
    71     }
    72 };
    View Code

        在main函数中调用
      

      

     1 try
     2     {
     3         cout<<"server start"<<endl;
     4         boost::asio::io_service mios;
     5         Server srv(mios);
     6         mios.run();
     7     }
     8     catch (exception& e)
     9     {
    10         cout<<e.what()<<endl;
    11     }
    View Code

       好了,到目前为止,服务端就搭建好了。

      3、protoc-gen-lua的使用(windows)

            a)将下载好的文件解压C:protoc-gen-lua,里面有三个文件夹:example,plugin,protobuf。在plugin文件夹下创建protoc-gen-lua.bat文件,写入 @python "%~dp0protoc-gen-lua",将最开始编译好的protoc.exe文件拷贝到C:protoc-gen-lua目录下,创建build_for_lua.bat文件,写入(proto_demo是用来存放 proto文件的,方便转化成xx_pb.lua文件)

    rem 切换到.proto协议所在的目录
    cd C:protoc-gen-luaproto_demo
    rem 将当前文件夹中的所有协议文件转换为lua文件
    for %%i in (*.proto) do (  
    echo %%i
    "..protoc.exe" --plugin=protoc-gen-lua="..pluginprotoc-gen-lua.bat" --lua_out=. %%i
    
    )
    echo end
    pause

        b)执行build_for_lua.bat文件将会得到xx_pb.lua文件,将该lua文件加入到cocos2dx的Resources文件加下,另外将C:protoc-gen-luaprotobuf目录下的所有lua文件(共9个)全部加入到Resources文件夹下,将C:protoc-gen-luaprotobuf目录下的pb.c文件加入到class文件夹下。修改pb.c文件的 #include <endian.h>为#ifndef _WIN32  #include <endian.h>  #endif。 函数struct_unpack中修改switch(format)之前的代码为

    uint8_t format = luaL_checkinteger(L, 1);
        size_t len;
        const uint8_t* buffer = (uint8_t*)luaL_checklstring(L, 2, &len);
        size_t pos = luaL_checkinteger(L, 3);
        uint8_t out[8];   
        buffer += pos;

           c)新建一个pocoClient.h文件,代码如下 (使用了poco库)

        

     1 #include "Poco/Net/StreamSocket.h"
     2 #include "Poco/Net/SocketAddress.h"
     3 #define BUFFER_SIZE 1024
     4 #include <iostream>
     5 using Poco::Net::SocketAddress;
     6 using Poco::Net::StreamSocket;
     7 #include "CCLuaEngine.h"
     8 #include "cocos2d.h"
     9 extern "C"{
    10 #include "lua.h"
    11 #include "lualib.h"
    12 #include "lauxlib.h"
    13 };
    14 class PocoClient
    15 {
    16 public:
    17     void GetData()
    18     {
    19         SocketAddress address("127.0.0.1", 6688);
    20         StreamSocket socket(address);
    21         char buffer[BUFFER_SIZE];
    22         while (true)
    23         {
    24             if (socket.available())
    25             {
    26                 int len = socket.receiveBytes(buffer, BUFFER_SIZE);
    27                 buffer[len] = '';
    28                 std::cout << "" << buffer << std::endl;
    29                 LuaFunction(buffer);
    30             }
    31         }
    32     }
    33 protected:
    34 private:
    35     void LuaFunction(const char* str)
    36     {
    37         lua_State*  plua=cocos2d::CCLuaEngine::defaultEngine()->getLuaStack()->getLuaState();
    38         int result = -1;
    39         lua_getglobal(plua, "getdata");
    40         if(!lua_isfunction(plua,1))
    41         {
    42             return ;
    43         }
    44         lua_pushstring(plua, str);
    45         int n= lua_pcall(plua, 1, 1,0);
    46         result = (int)lua_tonumber(plua, -1);
    47         lua_pop(plua, 1);
    48         printf("The result is %d
    ", result);
    49     }
    50 };
    View Code

       以及代码是从服务端获取数据后,然后调用lua文件里面的getdata方法(注:getdata是全局唯一的,并且该函数所在的文件需要在main.lua里面进行require,不然无法识别到此lua函数)。

           getdata函数lua代码如下

    1 function getdata(str)
    2     require "person_pb"
    3     local person_pbeee=person_pb.Person()  
    4     person_pbeee:ParseFromString(str);  
    5     print(person_pbeee.id..person_pbeee.name..person_pbeee.email)
    6     return 1;
    7 end
    View Code

           在AppDelegate.cpp文件开始部分加入

    1 extern "C"{
    2 #include <lua.h>    
    3 #include <lualib.h>    
    4 #include <lauxlib.h>    
    5     int luaopen_pb(lua_State *L);
    6 }
    View Code

        applicationDidFinishLaunching()函数中加入luaopen_pb(tolua_s);并在  pEngine->executeScriptFile(path.c_str());代码后加入PocoClient p;p.GetData();就可以调用方法,实现客户端和服务端的通信了。

        4、开发注意事项

        a)在调用lua函数的时候,要得到相同的lua环境(与最开始hello.lua(创建cocos2dx-lua 自带的)环境一直),也许要在hello.lua中去引用 该函数所在的文件以及 xx_pb.lua文件

        

  • 相关阅读:
    【Oracle】IF语句
    【Oracle】PL/SQL中对空字符串的判断
    【读书笔记】沉默的大多数
    【Oracle】包及包的调用
    Android (争取做到)最全的底部导航栏实现方法 ZZ
    一个屌丝程序猿的人生(一百一十七)
    一个屌丝程序猿的人生(一百一十六)
    一个屌丝程序猿的人生(一百一十五)
    DIV_ROUND_UP(x,y)实现x/y向上取整
    SMI(MDC/MDIO)总线接口介绍
  • 原文地址:https://www.cnblogs.com/cindyOne/p/3779595.html
Copyright © 2020-2023  润新知