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)
以上所有代码经过测试,要用的同学直接复制到项目中就可以使用了。