Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- local socket = require'socket'
- -- link you'r favorite struct-encode lib (json/xml/bson/yaml etc)
- local json = require'json'
- -- mapping is nice!
- local function map(t, func, ...)
- for k, v in ipairs(t) do
- t[k] = func(v, ...)
- end
- return t
- end
- -- printf, if debug is active
- local log = {debug = false}
- setmetatable(log, log)
- function log:__call(s, ...)
- if self.debug and type(s) == 'string' then
- print(s:format(...))
- end
- end
- local Connect = {}
- Connect.__index = Connect
- Connect.json = json
- function Connect:new(channel, ip, port, ...)
- print(self, channel, ip, port, ...)
- log('Create new connection to "%s:%s"', ip, port)
- local o = {
- ip = ip,
- port = port,
- id = ip..':'..port,
- ping = {
- lag = 0,
- timer = 0,
- delay = 10,
- value = 0
- },
- state = 'unconnected',
- delta = 0,
- speed = 0,
- bytes = 0,
- channel = channel
- }
- return setmetatable(o, self)
- end
- function Connect:send(data, type)
- local s = self.json.encode{data = data, type = type or 'msg'}
- self.channel.sock:sendto(s, self.ip, self.port)
- end
- function Connect:status()
- return self.state
- end
- function Connect:pingAccept()
- if self.ping.timer < 0 then
- -- five is magic constant, seconds for high-delay pinging
- self.ping.value = 5 - self.ping.timer
- end
- self.ping.lag = 0
- self.ping.timer = self.ping.delay
- self.state = 'connected'
- end
- function Connect:update(dt)
- self.ping.timer = self.ping.timer - dt
- self.ping.lag = self.ping.lag + dt
- -- ping time!
- if self.ping.timer < 0 then
- self:send('1', 'ping')
- self.ping.timer = 5
- end
- if self.ping.lag > self.channel.ping.unconnected then
- self.state = 'unconnected'
- end
- if self.ping.lag > self.channel.ping.disconnect then
- self:close('timeout')
- return
- end
- self.speed = self.bytes * dt
- self.bytes = 0
- end
- -- autosend disconnect
- function Connect:close(msg)
- self:send(msg, 'disconnect')
- self.channel.connection[self.id] = nil
- collectgarbage()
- end
- -- channel is main class
- -- service - service-callbacks for msgs by types 'connect', 'disconnect', 'ping', 'pong'
- local channel, service = {}, {}
- channel.json = json
- channel.__index = channel
- channel.toip = socket.dns.toip
- channel.time = socket.gettime
- channel.sleep = socket.sleep
- -- calling witn port create 'server', else ephemeral port
- setmetatable(channel, {
- __call = function(self, port)
- local o = {}
- o.ping = {disconnect = 30, unconnected = 10}
- o.sock = socket.udp()
- o.sock:settimeout(0)
- o.delta = 0
- o.timer = 0
- o.service = service
- o.callback = {} -- user callbacks, can be named as service callbacks (two calls for one msg)
- o.connection = {} -- list of connects
- o.connectionCustom = {} -- connects to custom clients, callbacks worked, no updates
- o = setmetatable(o, self)
- if port then o:listen(port) end
- return o
- end
- })
- function channel:listen(port)
- log('Listen %s port', port)
- self.sock = self.sock and self.sock:close()
- self.sock = socket.udp() -- reload sock anyway
- self.sock:setsockname('*', port)
- self.sock:settimeout(0)
- end
- -- return connection handler with methods like 'send',
- -- receives is going to callbacks or main loop.
- -- msg is message with any data, may be, for example, login data
- function channel:connect(host, port, msg)
- log('Connect to '..host..' '..port)
- local ip, err = self.toip(host)
- if not ip then
- error(err)
- end
- local o = self:newConnect(ip, port)
- self.connection[o.id] = setmetatable(o, Connect)
- o:send(msg or '', 'connect')
- return o
- end
- function channel:newConnect(ip, port)
- local o = Connect:new(self, ip, port)
- self.connection[o.id] = o
- return o
- end
- function channel:custom(ip, port) -- "custom" connects, without linking
- local o = Connect:new(self, ip, port)
- self.connectionCustom[o.id] = o
- return o
- end
- function channel:setCallback(name, f)
- if type(name) ~= 'string' then
- error('arg#1 error: string expected. got '..type(name))
- end
- if type(f) ~= 'function' then
- error('arg#2 error: function expected. got '..type(f))
- end
- self.callback[name] = f
- end
- function channel:hasCallback(name)
- return self.callback[name] and true
- end
- function channel:getConnect(ip, port)
- return port and self.connection[ip..":"..port] or self.connection[ip]
- or self.connectionCustom[ip..":"..port] or self.connectionCustom[ip]
- end
- function channel:updateTime()
- local t = self.time()
- self.delta = t - self.timer
- self.timer = t
- return self.delta
- end
- -- send any tabled data, less then 8kb (udp limit)
- function channel:send(data, type, ip, port)
- local s = self.json.encode{data = data, type = type or 'msg'}
- log(('Self send %s:%s: %s'):format(ip, port, s))
- self.sock:sendto(s, ip, port)
- end
- -- main update loop and message receiveing iterator
- function channel:receive()
- -- update everything
- self:updateTime()
- if not self.sock then log('no sock') return function() end end
- for k, v in pairs(self.connection) do v:update(self.delta) end
- -- return iterator for new messages
- return function()
- local data, ip, port = self.sock:receivefrom()
- while data do
- if not self:handler(ip, port, data) then
- return data, ip, port
- end
- data, ip, port = self.sock:receivefrom()
- end
- end
- end
- -- internal function, if channel has callback for this type - consume it and return true
- function channel:handler(ip, port, data)
- local conn = self:getConnect(ip, port)
- local succ, data = pcall(self.json.decode, data)
- local consumed, type = false, type(data) == 'table' and data.type
- if type == 'connect' then
- conn = self:newConnect(ip, port)
- end
- if not conn or not type then return end
- if self.service[type] then
- log('start service callback "%s"', type)
- self.service[type](self, conn, data.data)
- consumed = true
- end
- if self.callback[type] then
- log('start user callback "%s"', type)
- self.callback[type](self, conn, data.data)
- consumed = true
- end
- return consumed
- end
- -- optimisation for sending one message for all clients without encode/decode
- function channel:sendList(clients, msg, type)
- local s = self.json.encode{type = type, data = msg}
- for k, v in pairs(clients) do
- self.sock:sendto(s, v.ip, v.port)
- end
- end
- -- close all conenctions
- function channel:close(msg)
- self:sendList(self.connection, (msg or 'closed'), 'disconnect')
- self.connection = {}
- self.connectionCustom = {}
- end
- function channel:debug(v)
- log.debug = not not v
- end
- -- called if any client was connected
- function service:connect(ip, port, msg)
- log('Client %s:%s connected', ip, port)
- end
- -- called if any client disconnected
- function service:disconnect(conn, msg)
- log('Client %s disconnected by "%s" reason', conn.id, tostring(msg))
- conn:close()
- end
- -- called if any client send us ping request
- function service:ping(conn, msg)
- conn:send('1', 'pong')
- end
- -- called if any client responds our ping request
- function service:pong(conn, msg)
- conn:pingAccept()
- end
- return channel
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement