Advertisement
Snusmumriken

Love2d or Lua udp networking

Mar 17th, 2017
360
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 7.00 KB | None | 0 0
  1. local socket = require'socket'
  2.  
  3. -- link you'r favorite struct-encode lib (json/xml/bson/yaml etc)
  4. local json = require'json'
  5.  
  6. -- mapping is nice!
  7. local function map(t, func, ...)
  8.     for k, v in ipairs(t) do
  9.      t[k] = func(v, ...)
  10.     end
  11.     return t
  12. end
  13.  
  14. -- printf, if debug is active
  15. local log = {debug = false}
  16. setmetatable(log, log)
  17. function log:__call(s, ...)
  18.     if self.debug and type(s) == 'string' then
  19.         print(s:format(...))
  20.     end
  21. end
  22.  
  23. local Connect = {}
  24. Connect.__index = Connect
  25. Connect.json = json
  26.  
  27. function Connect:new(channel, ip, port, ...)
  28.     print(self, channel, ip, port, ...)
  29.     log('Create new connection to "%s:%s"', ip, port)
  30.     local o = {
  31.         ip = ip,
  32.         port = port,
  33.         id = ip..':'..port,
  34.         ping = {
  35.             lag = 0,
  36.             timer = 0,
  37.             delay = 10,
  38.             value = 0
  39.         },
  40.         state = 'unconnected',
  41.         delta = 0,
  42.         speed = 0,
  43.         bytes = 0,
  44.         channel = channel
  45.     }
  46.     return setmetatable(o, self)
  47. end
  48.  
  49. function Connect:send(data, type)
  50.     local s = self.json.encode{data = data, type = type or 'msg'}
  51.     self.channel.sock:sendto(s, self.ip, self.port)
  52. end
  53.  
  54. function Connect:status()
  55.     return self.state
  56. end
  57.  
  58. function Connect:pingAccept()
  59.     if self.ping.timer < 0 then
  60.         -- five is magic constant, seconds for high-delay pinging
  61.         self.ping.value = 5 - self.ping.timer
  62.     end
  63.     self.ping.lag = 0
  64.     self.ping.timer = self.ping.delay
  65.     self.state = 'connected'
  66. end
  67.  
  68. function Connect:update(dt)
  69.     self.ping.timer = self.ping.timer - dt
  70.     self.ping.lag = self.ping.lag + dt
  71.    
  72.     -- ping time!
  73.     if self.ping.timer < 0 then
  74.         self:send('1', 'ping')
  75.         self.ping.timer = 5
  76.     end
  77.    
  78.     if self.ping.lag > self.channel.ping.unconnected then
  79.         self.state = 'unconnected'
  80.     end
  81.    
  82.     if self.ping.lag > self.channel.ping.disconnect then
  83.         self:close('timeout')
  84.         return
  85.     end
  86.    
  87.     self.speed = self.bytes * dt
  88.     self.bytes = 0
  89. end
  90.  
  91. -- autosend disconnect
  92. function Connect:close(msg)
  93.     self:send(msg, 'disconnect')
  94.     self.channel.connection[self.id] = nil
  95.     collectgarbage()
  96. end
  97.  
  98. -- channel is main class
  99.  
  100. -- service - service-callbacks for msgs by types 'connect', 'disconnect', 'ping', 'pong'
  101. local channel, service = {}, {}
  102. channel.json = json
  103. channel.__index = channel
  104. channel.toip = socket.dns.toip
  105. channel.time = socket.gettime
  106. channel.sleep = socket.sleep
  107.  
  108. -- calling witn port create 'server', else ephemeral port
  109. setmetatable(channel, {
  110.     __call = function(self, port)
  111.         local o = {}
  112.         o.ping = {disconnect = 30, unconnected = 10}
  113.         o.sock = socket.udp()
  114.         o.sock:settimeout(0)
  115.         o.delta = 0
  116.         o.timer = 0
  117.         o.service = service
  118.         o.callback = {}                  -- user callbacks, can be named as service callbacks (two calls for one msg)
  119.         o.connection = {}                -- list of connects
  120.         o.connectionCustom = {}          -- connects to custom clients, callbacks worked, no updates
  121.         o = setmetatable(o, self)
  122.         if port then o:listen(port) end
  123.         return o
  124.     end
  125. })
  126.  
  127. function channel:listen(port)
  128.     log('Listen %s port', port)
  129.     self.sock = self.sock and self.sock:close()
  130.     self.sock = socket.udp()           -- reload sock anyway
  131.     self.sock:setsockname('*', port)
  132.     self.sock:settimeout(0)
  133. end
  134.  
  135.  
  136. -- return connection handler with methods like 'send',
  137. -- receives is going to callbacks or main loop.
  138. -- msg is message with any data, may be, for example, login data
  139. function channel:connect(host, port, msg)
  140.     log('Connect to '..host..' '..port)
  141.     local ip, err = self.toip(host)
  142.    
  143.     if not ip then
  144.         error(err)
  145.     end
  146.    
  147.     local o = self:newConnect(ip, port)
  148.     self.connection[o.id] = setmetatable(o, Connect)
  149.     o:send(msg or '', 'connect')
  150.    
  151.     return o
  152. end
  153.  
  154. function channel:newConnect(ip, port)
  155.     local o = Connect:new(self, ip, port)
  156.     self.connection[o.id] = o
  157.     return o
  158. end
  159.  
  160. function channel:custom(ip, port) -- "custom" connects, without linking
  161.     local o = Connect:new(self, ip, port)
  162.     self.connectionCustom[o.id] = o
  163.     return o
  164. end
  165.  
  166. function channel:setCallback(name, f)
  167.     if type(name) ~= 'string' then
  168.         error('arg#1 error: string expected. got '..type(name))
  169.     end
  170.    
  171.     if type(f) ~= 'function' then
  172.      error('arg#2 error: function expected. got '..type(f))
  173.     end
  174.    
  175.     self.callback[name] = f
  176. end
  177.  
  178. function channel:hasCallback(name)
  179.     return self.callback[name] and true
  180. end
  181.  
  182. function channel:getConnect(ip, port)
  183.     return port and self.connection[ip..":"..port] or self.connection[ip]
  184.         or self.connectionCustom[ip..":"..port] or self.connectionCustom[ip]
  185. end
  186.  
  187. function channel:updateTime()
  188.     local t = self.time()
  189.     self.delta = t - self.timer
  190.     self.timer = t
  191.     return self.delta
  192. end
  193.  
  194. -- send any tabled data, less then 8kb (udp limit)
  195. function channel:send(data, type, ip, port)
  196.     local s = self.json.encode{data = data, type = type or 'msg'}
  197.     log(('Self send %s:%s: %s'):format(ip, port, s))
  198.     self.sock:sendto(s, ip, port)
  199. end
  200.  
  201. -- main update loop and message receiveing iterator
  202. function channel:receive()
  203.     -- update everything
  204.     self:updateTime()
  205.     if not self.sock then log('no sock') return function() end end
  206.     for k, v in pairs(self.connection) do v:update(self.delta) end
  207.    
  208.     -- return iterator for new messages
  209.     return function()
  210.         local data, ip, port = self.sock:receivefrom()
  211.         while data do
  212.             if not self:handler(ip, port, data) then
  213.                 return data, ip, port
  214.             end
  215.             data, ip, port = self.sock:receivefrom()
  216.         end
  217.     end
  218. end
  219.  
  220. -- internal function, if channel has callback for this type - consume it and return true
  221. function channel:handler(ip, port, data)
  222.     local conn = self:getConnect(ip, port)
  223.     local succ, data = pcall(self.json.decode, data)
  224.    
  225.     local consumed, type = false, type(data) == 'table' and data.type
  226.    
  227.     if type == 'connect' then
  228.         conn = self:newConnect(ip, port)
  229.     end
  230.    
  231.     if not conn or not type then return end
  232.    
  233.     if self.service[type] then
  234.         log('start service callback "%s"', type)
  235.         self.service[type](self, conn, data.data)
  236.         consumed = true
  237.     end
  238.    
  239.     if self.callback[type] then
  240.         log('start user callback "%s"', type)
  241.         self.callback[type](self, conn, data.data)
  242.         consumed = true
  243.     end
  244.     return consumed
  245. end
  246.  
  247. -- optimisation for sending one message for all clients without encode/decode
  248. function channel:sendList(clients, msg, type)
  249.     local s = self.json.encode{type = type, data = msg}
  250.     for k, v in pairs(clients) do
  251.         self.sock:sendto(s, v.ip, v.port)
  252.     end
  253. end
  254.  
  255. -- close all conenctions
  256. function channel:close(msg)
  257.     self:sendList(self.connection, (msg or 'closed'), 'disconnect')
  258.     self.connection = {}
  259.     self.connectionCustom = {}
  260. end
  261.  
  262. function channel:debug(v)
  263.     log.debug = not not v
  264. end
  265.  
  266. -- called if any client was connected
  267. function service:connect(ip, port, msg)
  268.     log('Client %s:%s connected', ip, port)
  269. end
  270.  
  271. -- called if any client disconnected
  272. function service:disconnect(conn, msg)
  273.     log('Client %s disconnected by "%s" reason', conn.id, tostring(msg))
  274.     conn:close()
  275. end
  276.  
  277.  
  278. -- called if any client send us ping request
  279. function service:ping(conn, msg)
  280.     conn:send('1', 'pong')
  281. end
  282.  
  283. -- called if any client responds our ping request
  284. function service:pong(conn, msg)
  285.     conn:pingAccept()
  286. end
  287.  
  288. return channel
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement