cocos lua 网络框架 (不限于cocos 使用)

socket 用的是luasocket 解包数据用的是lpack
cocos3.15.1默认没有集成lpack,要使用的同学请先集成
协议定义的包头长度是12
包头包括ver,bodySize,cmd
依次是版本号,报文大小,命令号
本框架大多思路来自http://blog.csdn.net/eastcowboy/article/details/33334283
好了,以下是框架核心对象SubPack(分包器)

--
-- Author: xiaowa
-- Date: 2017-07-20 01:53:55
--
local socket = require("socket")

require("pack")

VER = 1 --客户端当前版本
local headerSize = 12   --定义包头长

local SubPack = SubPack or class("SubPack")

function SubPack:ctor(address,port,out_fun,close_fun)
    local sock = socket.tcp()
    sock:settimeout(0)
    self.sock = sock
    self.out_fun = out_fun  --超时函数
    self.close_fun = close_fun  --断开函数

    self.recvBuffer = ''
    self.sendBuffer = ''
    self.sendIndex = 1
    self.isConnecting = false
    self.isConnected = false
    --self.continue_nil = 0 --服务端连续发空数据的次数
    if sock:connect(address,port) == 1 then
        print('socket connected')
    else
        self.isConnecting = true
        self.connectTime = socket.gettime()
    end

    self.schedule_ = app.scheduler.scheduleGlobal(handler(self,self.ticks),1/60)
end

function SubPack:ticks()
    if self.isConnecting and socket.gettime() - self.connectTime > 10 then
        -- 连接超时,关闭socket并清理所有变量。
        -- 我还没有发现如何通过luasocket的接口来判断连接超时,所以只好用了这个笨办法
        print(" connect time out ")
        app.scheduler.unscheduleGlobal(self.schedule_)
        self.isConnected = false
        self.isConnecting = false
        self.sock = nil
        self.out_fun()
        return
    end
    local clientSocket = self.sock
    local arr = {clientSocket}

    local r, s, e = socket.select(arr, (self.isConnecting and arr) or nil, 0)

    if r and #r >= 1 and r[1] == clientSocket then
        local recvData, recvError, recvParticialData = clientSocket:receive('*a')

        if recvError == 'closed' then
            -- socket已经断开
            print(" connect close ")
            app.scheduler.unscheduleGlobal(self.schedule_)
            self.isConnected = false
            self.isConnecting = false
            self.sock = nil
            self.close_fun()
            return
        end
        --print((recvData==nil and 'nil') or string.len(recvData),(recvParticialData==nil and 'nil') or string.len(recvParticialData))
        -- 如果是大块数据,可能被拆分成多段,需要多次调用clientSocket:receive才能完成接收。
        -- 因此可以把接收到数据先放到recvBuffer缓存起来。
        if (recvData==nil or string.len(recvData)==0) and (recvParticialData==nil or string.len(recvParticialData)==0) then
            print(" connect close ")
            app.scheduler.unscheduleGlobal(self.schedule_)
            self.isConnected = false
            self.isConnecting = false
            self.sock:close()
            self.sock = nil
            self.close_fun()
            return
        elseif recvData then
            self.recvBuffer = self.recvBuffer .. recvData
        elseif recvParticialData then
            self.recvBuffer = self.recvBuffer .. recvParticialData
        end
        -- 处理缓存起来的数据。
        self:processRecvBuffer()
    end

    if s and #s >= 1 and s[1] == clientSocket then
        if self.isConnecting then
            -- 执行到此,说明连接已经成功了
            self.isConnecting = false
            self.isConnected = true
        end
        -- 可以调用send
        self:processSendBuffer()
    end

    if self.isConnected then
        self:processSendBuffer()
    end
end

function SubPack:processSendBuffer()
    if string.len(self.sendBuffer) < 1 then
        return
    end
    local clientSocket = self.sock
    local num, err, num2 = clientSocket:send(self.sendBuffer,self.sendIndex)
    if num then
        -- 所有数据发送完毕
        self.sendBuffer = ''
        self.sendIndex = 1
        print("send all. num="..num)
    else
        -- 只有部分数据发送完毕
        self.sendIndex = num2 + 1
        print("send cell. num2="..num2)
    end
end

function SubPack:send(buffer)
    self.sendBuffer = self.sendBuffer .. buffer
end

function SubPack:processRecvBuffer()
    while true do
        local len = string.len(self.recvBuffer)
        --[[
        if len == 0 then
            self.continue_nil = self.continue_nil + 1
            if self.continue_nil >= 1000 then--判断连接已断开
                print(" connect time out ")
                app.scheduler.unscheduleGlobal(self.schedule_)
                self.isConnected = false
                self.isConnecting = false
                self.sock = nil
                self.out_fun()
                return
            end
        else
            self.continue_nil = 0
        end
        --]]
        if len < headerSize then
            print("header not full")
            break
        end
        local idx,ver,bodySize,cmd = string.unpack(self.recvBuffer,">III")
        assert(ver == VER,string.format("Does not match the server version"))
        if string.len(self.recvBuffer) < headerSize+bodySize then
            print("body not full")
            break
        end
        local body = string.sub(self.recvBuffer,headerSize+1,headerSize+bodySize)
        print("compute one msg")
        self:dataHandle(cmd,body)
        self.recvBuffer = string.sub(self.recvBuffer,headerSize+bodySize+1)
    end
end
function SubPack:close()--主动关闭连接
    print(" action connect close ")
    app.scheduler.unscheduleGlobal(self.schedule_)
    self.isConnected = false
    self.isConnecting = false
    self.sock:close()
    self.sock = nil
    --self.close_fun()
end
function SubPack:dataHandle(cmd,body)
    --子类重写此方法
end
return SubPack

以下是框架socket服务对象,主要负责消息的分发

--
-- Author: xiaowa
-- Date: 2017-07-21 00:21:38
--
--require("pack")

local CallBack = import(".CallBack")
local SubPack = import(".SubPack")

--#================以下是APP发给服务端的消息====================
--[['''
    APP登陆命令
    body格式:用户名长(4字节,整数),用户名字符串,密码长(4字节,整数),密码字符串
'''--]]
APP_LOGIN = 301
--[['''
    APP注册命令
    body格式:用户名长(4字节,整数),用户名字符串,密码长(4字节,整数),密码字符串,昵称长(4字节,整数),昵称字符串,电话长(4字节,整数),电话字符串,头像id(4字节,整数)
'''--]]
APP_REGISTER = 302
--[['''
    ADMIN命令
    body格式:命令JSON{'cmd':'上分','name':'liangxiaowa','point':1000}
'''--]]
APP_ADMIN = 303
--[['''
    APP获得系统消息命令
    body格式:未获得消息id(4字节,整数),服务器返回大于这个id的所有消息
'''--]]
APP_SYS_MSG = 304
--[['''
    APP获得个人消息命令
    body格式:未获得消息id(4字节,整数),服务器返回大于这个id的所有消息
'''--]]
APP_PERSON_MSG = 305

--#================以下是服务端发给APP的消息====================
--[['''
    APP金币数下发
    body格式:用户名长(4字节,整数),用户名字符串,昵称长(4字节,整数),昵称字符串,金币数(4字节,整数),img(4字节,整数)
'''--]]
TO_APP_UPDATE_GOLD = 401
--[['''
    APP注册返回
    body格式:状态(4字节,整数,0失败1成功),消息长(4字节,整数),消息字符串
'''--]]
TO_APP_REGISTER = 402
--[['''
    ADMIN命令返回
    body格式:返回JSON{'cmd':'上分','info':'上分命令成功,[liangxiaowa]当前金币为:1000'}
'''--]]
TO_APP_ADMIN = 403
--[['''
    APP系统消息返回
    body格式:系统消息JSON
'''--]]
TO_APP_SYS_MSG = 404
--[['''
    APP个人消息返回
    body格式:个人消息JSON
'''--]]
TO_APP_PERSON_MSG = 405

--#==================以下是APP发给客户端的消息======================
--[['''
    APP登陆命令
    body格式:用户名长(4字节,整数),用户名字符串,密码长(4字节,整数),密码字符串
'''
APP_LOGIN = 301
'''
    APP下注命令
    body格式:用户名长(4字节,整数),用户名字符串,下注字符串
'''--]]
APP_PUT_STAKES = 353
--[['''
    APP消息
    body格式:消息字符串
'''--]]
APP_MSG = 354

--#==================以下是客户端发给APP的消息======================
--[['''
    APP金币数下发
    body格式:用户名长(4字节,整数),用户名字符串,昵称长(4字节,整数),昵称字符串,金币数(4字节,整数),img(4字节,整数)
'''
TO_APP_UPDATE_GOLD = 401
'''
    APP下注提示
    body格式:昵称长(4字节,整数),昵称字符串,img(4字节,整数),下注字符串
'''--]]
TO_APP_PUT_STAKES = 453
--[['''
    APP下注错误返回
    body格式:错误说明字符串
'''--]]
TO_APP_PUT_ERROR = 454
--[['''
    APP消息提示
    body格式:昵称长(4字节,整数),昵称字符串,img(4字节,整数),消息字符串
'''--]]
TO_APP_MSG = 455
--[['''
    APP本期下注开始
    body格式:期数(4字节,整数)
'''--]]
TO_APP_START = 456
--[['''
    APP封盘消息(用户下注返回,如果封盘了就返回封盘)
    body格式:期数(4字节,整数)
'''--]]
TO_APP_SEAL = 457
--[['''
    APP截止消息
    body格式:期数(4字节,整数),状态(4字节,0失败1成功)
'''--]]
TO_APP_CLOSE = 458
--[['''
    APP开奖消息
    body格式:期数(4字节,整数),号码(4字节,整数,红球为13)
'''--]]
TO_APP_LOTTERY = 459
--[['''
    APP金币更新消息
    body格式:期数(4字节,整数),元宝数(4字节,整数)
'''--]]
TO_APP_UPDATE = 460



local SocketService = SocketService or class("SocketService",SubPack)

function SocketService:ctor(address,port,out_fun,close_fun)
    SocketService.super.ctor(self,address,port,out_fun,close_fun)
    --加入回掉
    self.to_app_update_gold = CallBack.new(TO_APP_UPDATE_GOLD)
    self.to_app_register = CallBack.new(TO_APP_REGISTER)
    self.to_app_admin = CallBack.new(TO_APP_ADMIN)
    self.to_app_sys_msg = CallBack.new(TO_APP_SYS_MSG)
    self.to_app_person_msg = CallBack.new(TO_APP_PERSON_MSG)
    self.to_app_put_stakes = CallBack.new(TO_APP_PUT_STAKES)
    self.to_app_put_error = CallBack.new(TO_APP_PUT_ERROR)
    self.to_app_msg = CallBack.new(TO_APP_MSG)
    self.to_app_start = CallBack.new(TO_APP_START)
    self.to_app_seal = CallBack.new(TO_APP_SEAL)
    self.to_app_close = CallBack.new(TO_APP_CLOSE)
    self.to_app_lottery = CallBack.new(TO_APP_LOTTERY)
    self.to_app_update = CallBack.new(TO_APP_UPDATE)

    self.call_list = {}
    local call = self.call_list
    call[TO_APP_UPDATE_GOLD] = self.to_app_update_gold
    call[TO_APP_REGISTER] = self.to_app_register
    call[TO_APP_ADMIN] = self.to_app_admin
    call[TO_APP_SYS_MSG] = self.to_app_sys_msg
    call[TO_APP_PERSON_MSG] = self.to_app_person_msg
    call[TO_APP_PUT_STAKES] = self.to_app_put_stakes
    call[TO_APP_PUT_ERROR] = self.to_app_put_error
    call[TO_APP_MSG] = self.to_app_msg
    call[TO_APP_START] = self.to_app_start
    call[TO_APP_SEAL] = self.to_app_seal
    call[TO_APP_CLOSE] = self.to_app_close
    call[TO_APP_LOTTERY] = self.to_app_lottery
    call[TO_APP_UPDATE] = self.to_app_update
end
function SocketService:dataHandle(cmd,body)
    if self.call_list[cmd] then
        self.call_list[cmd]:call(body)
    else
        print("find undefined cmd!")
    end
end
function SocketService:send(cmd,body)
    local len = string.len(body)
    local buffer = string.pack('>III',VER,len,cmd)
    buffer = buffer .. body
    SocketService.super.send(self,buffer)
end
return SocketService

网络框架所用到的回调对象

--
-- Author: xiaowa
-- Date: 2015-10-17 07:53:29
--
--框架回调对象
local CallBack = CallBack or class("CallBack")
local call_hash_ = call_hash_ or app.util.HashMap.new()
--e_name 注册回调的事件名
function CallBack:ctor(e_name)
    self.e_name_ = e_name
    call_hash_:setElement(e_name,self)
end
function CallBack:call(...)
    local n = table.getn(self)
    for i=1,n do
        self[i](...)
    end
end
function CallBack:register_call(fun)
    table.insert(self,fun)
end
function CallBack.register(e_name,fun)
    local callback = call_hash_:getValue(e_name)
    callback:register_call(fun)
end
return CallBack

回调对象里面使用的hashmap对象

--[[
    --HashMap 
    --1. 支持添加 长整型量级 次元素,更多就溢出了
    --2. HashMap 的实例数目也是 长整型量级 限制的
    @author xiaowa
    @date 2015.5.17
--]]

local HashMap = HashMap or class("HashMap")

--HashMap 的实例编号
HashMap.Instance_num = HashMap.Instance_num or 0

function HashMap:ctor()
    self.element_count_ = 0
    self.map_key_ = {}
    self.map_value_ = {}
    --用于存字符串key 的表
    self.map_strIskey_ = {}
    --hashmap 实例 id 号
    self.class.Instance_num = self.class.Instance_num+1
    self.map_id_ = self.class.Instance_num
end
--[[
    @function 为hashmap 添加元素
    @reutrn nil
--]]
function HashMap:setElement(key,value)
    assert(type(key) == "table" or type(key) == "string" or type(key) == "number", string.format("%s:setElement() - invalid key", self.class.__cname))
    --这里值是未限定类型的,只会从key 查找到value
    --assert(type(value) == "table", string.format("%s:setElement() - invalid value", self.class.__cname))

    if type(key) == "string" or type(key) == "number" then
        self.map_strIskey_[key] = value
    elseif type(key) == "table" then
        local num = self:addElement_(key, value)
        --为用户表添加 hashmap 字段
        key["hashmap_" .. self.map_id_ .. "_@@key_"] = num --意思是key表是作为那个hashmap 实例的key
    else
        printLog("BUG","HashMap:setElement invalid")
    end
end
--[[
    @function 只能执行小于长整型量级次
    @return element NO.
--]]
function HashMap:addElement_(key,value)
    self.element_count_ = self.element_count_+1
    self.map_key_[self.element_count_] = key
    self.map_value_[self.element_count_] = value
    return self.element_count_
end
--[[
    @function 判断是否有某个键
    @return bool
--]]
function HashMap:findKey(key)
    if type(key) == "string" or type(key) == "number" then
        if self.map_strIskey_[key] then
            return true
        end
        return false
    elseif type(key) == "table" then
        if key["hashmap_" .. self.map_id_ .. "_@@key_"] then
            return true
        end
        return false
    end
end
--[[
    @function 获得值
    @param key
    @return 
--]]
function HashMap:getValue(key)
    if type(key) == "string" or type(key) == "number" then
        return self.map_strIskey_[key]
    elseif type(key) == "table" then
        local num = key["hashmap_" .. self.map_id_ .. "_@@key_"]
        if num then
            return self.map_value_[num]
        end
        return nil
    end
end
--[[
    @function 删除元素
    @param table#key
    @return table#key or string#key,table#value
--]]
function HashMap:delElement(key)
    local key_,value_ = key
    if type(key) == "string" or type(key) == "number" then
        value_ = self.map_strIskey_[key_]
        self.map_strIskey_[key_] = nil
        return key_,value_
    elseif type(key) == "table" then
        local num = key["hashmap_" .. self.map_id_ .. "_@@key_"]
        if num then
            key["hashmap_" .. self.map_id_ .. "_@@key_"] = nil
            self.map_key_[num] = nil
            self.map_value_[num] = nil
        end
    end
end

return HashMap

使用示例代码


    function out()
        print("与服务器连接超时")
        self.landing_view_:register_State("与服务器连接超时",false)
    end
    function close()
        print("与服务器连接关闭")
        self.landing_view_:register_State("与服务器连接关闭",false)
    end

    function register_call_back(body)
        --#body格式:状态(4字节,整数,0失败1成功),消息长(4字节,整数),消息字符串
        local idx,state,info_len = string.unpack(body,">II")
        local info = string.sub(body,idx)
        if state == 0 then
            self.landing_view_:register_State(info)
        elseif state == 1 then
            self.landing_view_:register_State(info.."。请点击返回登陆页进行登陆。")
        end
    end

    if app.server == nil then
        app.server = app.compose.SocketService.new(app.server_host,app.server_port,out,close)
        app.myApp:register(TO_APP_REGISTER,register_call_back)
    else
        app.myApp:register(TO_APP_REGISTER,register_call_back)
    end
    local password = app.b64(password)
    --#body格式:用户名长(4字节,整数),用户名字符串,密码长(4字节,整数),密码字符串,昵称长(4字节,整数),昵称字符串,电话长(4字节,整数),电话字符串,头像id(4字节,整数)
    local user_len = string.len(user)
    local password_len = string.len(password)
    local address_len = string.len(address)
    local phone_len = string.len(phone)

    local buffer = string.pack('>IAIAIAIAI',user_len,user,password_len,password,address_len,address,phone_len,phone,img_id)
    app.server:send(APP_REGISTER,buffer)

以下是注册回调部分
cocos lua 网络框架 (不限于cocos 使用)_第1张图片

以上所有代码经过测试,要用的同学直接复制到项目中就可以使用了。

你可能感兴趣的:(lua,cocos2d-x,lua工具类,网络框架)