cocos2dx和skynet通信

最近使用skynet作為服務(wù)器開發(fā)前端使用cocos2dx-lua。由于前后端都是用lua編寫。
在搭建雙方的通信環(huán)節(jié)是有一個(gè)步驟就是選擇通信的協(xié)議。通用選擇是protobuf+socket的方式。由于protobuf對lua的支持問題。我們選擇了pbc這個(gè)proto解析庫。

本文解決的問題

前端cocos2dx-lua引擎,使用luasocket+protobuf 和skynet進(jìn)行通信?

前端使用: luasocket pbc lpack
后端使用: socket pbc string.pack string.unpack

skynet在數(shù)據(jù)發(fā)送時(shí)使用的是string.pack() string.unpack() 進(jìn)行打包解包。然后這兩個(gè)函數(shù)是lua5.3的。前端的cocos是lua5.1沒有這個(gè)API。
因此才有了lpack這個(gè)庫。

pbc: https://github.com/cloudwu/pbc.git
lpack: https://github.com/LuaDist/lpack.git
skynet: https://github.com/cloudwu/skynet.git

已經(jīng)定好協(xié)議并且可通信的skynet服務(wù)器和cocos2dx客戶端:
https://github.com/gameloses/cocos2dx_lua_skynet_client.git
git clone https://github.com/gameloses/skynet_pbc.git

接下來就是進(jìn)行一個(gè)前后端的對接邏輯。

cocos2dx集成pbc和lpack

這是一個(gè)已經(jīng)集成好的cocos2dx的例子,代碼開源在github
https://github.com/gameloses/cocos2dx_lua_skynet_client.git
這里例子已經(jīng)可以可skynet進(jìn)行通信。 進(jìn)入這個(gè)鏈接會有u詳細(xì)的教程。
下面是詳細(xì)的集成教程。

集成pbc

  1. 編譯pbc
    pbc下的src拷貝到編譯目錄。假設(shè)vs。環(huán)境下你得建一個(gè)pbc目錄然后把src下的代碼copy到pbc下。編譯。
    發(fā)現(xiàn)pbc的.c在VS中不能按C代碼編譯,而應(yīng)該按照C++編譯,在所有.c的屬性頁中的“C/C++ => 高級”中,設(shè)置“編譯為C++代碼”后編譯通過。因?yàn)閜bc全是c代碼索引得設(shè)置一下。
  2. 添加luabindings代碼
    將pbc目錄下binding/lua/pbc-lua.c文件copy到項(xiàng)目中。
    添加頭文件:pbc-lua.h。代碼如下:
#ifndef __PBC_LUA_H__
#define __PBC_LUA_H__

#ifdef __cplusplus
extern "C" {
#endif
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"

#ifdef __cplusplus
}
#endif

#ifdef __cplusplus
extern "C" {
#endif
    int luaopen_protobuf_c(lua_State *L);
#ifdef __cplusplus
}
#endif

#endif

編寫橋接代碼添加到工程:lua_cocos2dx_pbc_manual.h,lua_cocos2dx_pbc_manual.cpp

//lua_cocos2dx_pbc_manual.h
#ifndef lua_cocos2dx_pbc_manual_h__
#define lua_cocos2dx_pbc_manual_h__

#ifdef __cplusplus
extern "C" {
#endif
#include "tolua++.h"
#ifdef __cplusplus
}
#endif

TOLUA_API int  register_pbc_module(lua_State* L);

#endif
//lua_cocos2dx_pbc_manual.cpp
#include "lua_bindings/lua_cocos2dx_pbc_manual.h"

#include "platform/CCPlatformConfig.h"
#include "base/ccConfig.h"
#include "scripting/lua-bindings/manual/tolua_fix.h"
#include "scripting/lua-bindings/manual/LuaBasicConversions.h"
#include "scripting/lua-bindings/manual/CCLuaEngine.h"

#include "lua_bindings/pbc-lua.h"

#include "cocos/platform/CCFileUtils.h"

int read_protobuf_file(lua_State *L){
    const char *buff = luaL_checkstring(L, -1);
    Data data = cocos2d::FileUtils::getInstance()->getDataFromFile(buff);
    lua_pushlstring(L, (const char*)data.getBytes(), data.getSize());
    return 1;
}

TOLUA_API int register_pbc_module(lua_State* L)
{
    lua_getglobal(L, "_G");
    if (lua_istable(L, -1))//stack:...,_G,
    {
        lua_register(L, "read_protobuf_file_c", read_protobuf_file);
        luaopen_protobuf_c(L);
    }
    lua_pop(L, 1);
    return 1;
}
  1. AppDelegate中添加一下代碼
#include "lua_bindings/lua_cocos2dx_pbc_manual.h"   
register_pbc_module(L);
  1. 編譯一下就ok。
    android和mac平臺比較簡單就不說了。
  2. 將binding/lua 下的 protobuf.lua parser.lua 拷貝到lua代碼目錄下。
  3. 測試
    添加協(xié)議
    pbhead.proto
package PbHead;
message MsgHead{
    required int32 msgtype = 1;
    required string msgname = 2; 
    required int32 msgret = 3; 
}

協(xié)議生成

protoc --descriptor_set_out  proto/pbhead.pb proto/pbhead.proto

解析協(xié)議

require "protobuf"
local buffer = io.read("proto/pbhead.pb") --讀出文件內(nèi)容
protobuf.register(buffer)

編碼解碼

local msg_head={msgtype = 1, msgname = msg_name, msgret = 0};
local pb_head = protobuf.encode("PbHead.MsgHead", msg_head)
local data = protobuf.decode("PbHead.MsgHead", pb_head);

集成lpack

  1. 下載lpack.添加lpack.c到項(xiàng)目中。
  2. 添加一個(gè)lpack.h
#ifndef __LAPCK_H__
#define __LAPCK_H__

#ifdef __cplusplus
extern "C" {
#endif
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"

#ifdef __cplusplus
}
#endif

#ifdef __cplusplus
extern "C" {
#endif
    int luaopen_pack(lua_State *L);
#ifdef __cplusplus
}
#endif

#endif

3.在Appdelegate中添加代碼

#include "lpack/lpack.h"
luaopen_pack(L);
  1. 測試
    lpack是注冊在string類里面的。
    string.pack
    string.unpack
    pack(f,...)
    unpack(s,f,[init])
    注意unpack多返回了一個(gè)長度信息。(坑)

前后端消息格式定義

protocol buffer數(shù)據(jù)包結(jié)構(gòu)如下:

1 2 3 4 5
2字節(jié) 2字節(jié) PBMsgHead PBMsgBody 't'

開頭2字節(jié)存放一個(gè)short數(shù)據(jù),表示整個(gè)數(shù)據(jù)包的長度。即:
數(shù)據(jù)包總長度 = 2 byte + 2 byte + PBMsgHead字節(jié)數(shù) + PBMsgBody字節(jié)數(shù) + 1 byte

隨后的2字節(jié)存放一個(gè)short數(shù)據(jù),表示PBMsgHead數(shù)據(jù)的長度。
PBMsgBody數(shù)據(jù)長度 = 數(shù)據(jù)包總長度 - 2 - 2 - 1 - PBMsgHead數(shù)據(jù)長度。

需要注意的是 PBMsgBody有可能為空,即沒有這個(gè)數(shù)據(jù)。因?yàn)殄e(cuò)誤碼放在PBMsgHead里面,當(dāng)邏輯出錯(cuò)時(shí)候,只有錯(cuò)誤碼,沒有PBMsgBody。

luasocket進(jìn)行數(shù)據(jù)收發(fā)

local LuaSock = class("LuaSock")

function LuaSock:connect()
    local socket = require('luasocket.socket'); 
    self.m_sock = socket.tcp(); 
    self.m_sock:settimeout(0);  --非阻塞
    self.m_sock:setoption("tcp-nodelay", true) --去掉優(yōu)化 不用處理粘包
    self.m_sock:connect(self.m_ip, self.m_port); 
    
    --定時(shí)檢測是否可寫以判斷socket的狀態(tài)
    self.check_ = schedule(function()
        if self:connect_is_success() then
            unschedule(self.check_)
            
            
        end
    end)
end

function LuaSock:connect_is_success( ... )
    local for_write = {};
    table.insert(for_write,self.m_sock);
    local ready_forwrite;
    _,ready_forwrite,_ = socket.select(nil,for_write,0);
    if #ready_forwrite > 0 then
        return true;
    end
    return false;
end


function LuaSock:receive() 
    local recvt, sendt, status = socket.select({self.m_sock}, nil, 1)
    print("input", #recvt, sendt, status)
    if #recvt <= 0 then
        return;
    end

    local buffer,err = self.m_sock:receive(2);
    if buffer then 
        --讀取二進(jìn)制數(shù)據(jù)流
        local first, sencond = string.byte(buffer,1,2);
        local len=first*256+sencond;--通過位計(jì)算長度
        print("收到數(shù)據(jù)長度=",len)
        local buffer,err = self.m_sock:receive(len); 
        
        --unpack 使用pbc decode
        local  pb_len, pb_head,pb_body,t = string.unpack(buffer, ">PPb"); 
        local msg_head = protobuf.decode("PbHead.MsgHead", pb_head) 
        local msg_body = protobuf.decode(msg_head.msgname, pb_body)
        print("t:"..t..":"..string.char(t))
    
    end
end

function LuaSock:send()  
    --拼裝頭
    local msg_head={msgtype = 1, msgname = msg_name, msgret = 0};
    local pb_head = protobuf.encode("PbHead.MsgHead", msg_head)
    local pb_body = protobuf.encode(msg_name, msg_body);
    --計(jì)算長度
    local pb_head_len=#pb_head;
    local pb_body_len=#pb_body;
    local pb_len=2+pb_head_len+2+pb_body_len+1; 

    local data=string.pack(">HPPb",pb_len, pb_head, pb_body, string.byte('t'));

    --數(shù)據(jù)發(fā)送
    local _len ,_error = self.m_sock:send(data);
    if _len ~= nil and _len == #data then 
        --表示發(fā)送成功 
    end

end

至此客戶端的工作是完成了。我放在了github上。

skynet 使用pbc

skynet使用pbc解析protobuf,使用socket和客戶端通信。
前提是搭建編譯skynet的環(huán)境。
下載pbc編譯make得到protobuf.so
將pbc/binding/lua53/protobuf.lua復(fù)制到根目錄的lualib目錄
protobuf.so文件復(fù)制到根目錄的luaclib目錄。pbc就算是集成好了。
具體的步驟看下面。

服務(wù)器編譯步驟

clone完整的代碼 代碼中引用了skynet和pbc

git clone https://github.com/gameloses/skynet_pbc.git

cd skynet_pbc
git submodule init
git submodule update

cd skynet
git submodule init
git submodule update

編譯skynet

cd skynet
make linux

編譯pbc

cd ../3rd/pbc
make

cd binding
cd lua53
make

如果提示找不到lua.h則需要安裝一下lua5.3. make && make install
(或者修改一下Makefile文件,設(shè)置lua.h的路徑)

將protobuf.lua復(fù)制到根目錄的lualib目錄
protobuf.so文件復(fù)制到根目錄的luaclib目錄

編譯proto文件

回到根目錄
make

如果沒有安裝protobuf的話會報(bào)錯(cuò),因?yàn)橐褂玫絧rotoc
yum install protobuf-devel.x86_64
yum install protobuf.x86_64

運(yùn)行

. run.sh
具體教程見wiki

使用cocos2dx 測試一下連接。

clinet的地址: https://github.com/gameloses/cocos2dx_lua_skynet_client.git

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容