最近在研究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 };
在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 }
好了,到目前为止,服务端就搭建好了。
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] = '