Files
Techmino/Zframework/websocket.lua
2024-08-13 21:54:11 +08:00

193 lines
4.9 KiB
Lua

local host='127.0.0.1'
local port='80'
local path=''
-- lua + LÖVE threading websocket client
-- Original pure lua ver. by flaribbit and Particle_G
-- Threading version by MrZ
local type=type
local timer=love.timer.getTime
local WS={}
local wsList=setmetatable({},{
__index=function(l,k)
local ws={
real=false,
status='dead',
lastPongTime=timer(),
sendTimer=0,
alertTimer=0,
pongTimer=0,
}
l[k]=ws
return ws
end
})
function WS.switchHost(_1,_2,_3)
for k in next,wsList do
WS.close(k)
end
host=_1
port=_2 or port
path=_3 or path
end
function WS.connect(name,subPath,head,timeout)
if head then
local l=""
for k,v in next,head do
l=l..(k..": "..v..'\r\n')
end
head=l
else
head=""
end
if wsList[name] and wsList[name].thread then
wsList[name].thread:release()
end
local ws={
real=true,
thread=love.thread.newThread('Zframework/websocket_thread.lua'),
triggerCHN=love.thread.newChannel(),
sendCHN=love.thread.newChannel(),
readCHN=love.thread.newChannel(),
lastPingTime=0,
lastPongTime=timer(),
pingInterval=6,
status='connecting',-- 'connecting', 'running', 'dead'
sendTimer=0,
alertTimer=0,
pongTimer=0,
}
wsList[name]=ws
ws.thread:start(ws.triggerCHN,ws.sendCHN,ws.readCHN)
ws.sendCHN:push(host)
ws.sendCHN:push(port)
ws.sendCHN:push(path..subPath)
ws.sendCHN:push(head)
ws.sendCHN:push(timeout or 2.6)
end
function WS.status(name)
local ws=wsList[name]
return ws.status or 'dead'
end
function WS.getTimers(name)
local ws=wsList[name]
return ws.pongTimer,ws.sendTimer,ws.alertTimer
end
function WS.setPingInterval(name,time)
local ws=wsList[name]
ws.pingInterval=math.max(time or 2.6,2.6)
end
function WS.alert(name)
local ws=wsList[name]
ws.alertTimer=2.6
end
local OPcode={
continue=0,
text=1,
binary=2,
close=8,
ping=9,
pong=10,
}
local OPname={
[0]='continue',
[1]='text',
[2]='binary',
[8]='close',
[9]='ping',
[10]='pong',
}
function WS.send(name,message,op)
if type(message)=='string' then
local ws=wsList[name]
if ws.real and ws.status=='running' then
ws.sendCHN:push(op and OPcode[op] or 2)-- 2=binary
ws.sendCHN:push(message)
ws.lastPingTime=timer()
ws.sendTimer=1
end
else
MES.new('error',"Attempt to send non-string value!")
MES.traceback()
end
end
function WS.read(name)
local ws=wsList[name]
if ws.real and ws.status~='connecting' and ws.readCHN:getCount()>=2 then
local op,message=ws.readCHN:pop(),ws.readCHN:pop()
if op==8 then-- 8=close
ws.status='dead'
elseif op==9 then-- 9=ping
WS.send(name,message or "",'pong')
end
ws.lastPongTime=timer()
ws.pongTimer=1
return message,OPname[op] or op
end
end
function WS.close(name)
local ws=wsList[name]
if ws.real then
ws.sendCHN:push(8)-- 8=close
ws.sendCHN:push("")
ws.status='dead'
end
end
function WS.update(dt)
local time=timer()
for name,ws in next,wsList do
if ws.real and ws.status~='dead' then
if ws.thread:isRunning() then
if ws.triggerCHN:getCount()==0 then
ws.triggerCHN:push(0)
end
if ws.status=='connecting' then
local mes=ws.readCHN:pop()
if mes then
if mes=='success' then
ws.status='running'
ws.lastPingTime=time
ws.lastPongTime=time
ws.pongTimer=1
else
ws.status='dead'
MES.new('warn',text.wsFailed:repD(mes))
end
end
elseif ws.status=='running' then
if time-ws.lastPingTime>ws.pingInterval then
WS.send(name,"",'pong')
end
if time-ws.lastPongTime>6+2*ws.pingInterval then
WS.close(name)
end
end
if ws.sendTimer>0 then ws.sendTimer=ws.sendTimer-dt end
if ws.pongTimer>0 then ws.pongTimer=ws.pongTimer-dt end
if ws.alertTimer>0 then ws.alertTimer=ws.alertTimer-dt end
else
ws.status='dead'
local err=ws.thread:getError()
if err then
MES.new('warn',text.wsClose:repD(err:match(":.-:(.-)\n")))
WS.alert(name)
end
end
end
end
end
return WS