You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
220 lines
5.7 KiB
220 lines
5.7 KiB
8 years ago
|
-- see copyright notice in grldc.h
|
||
|
|
||
|
local socket = require( "grldc.socket" )
|
||
|
|
||
|
local assert = assert
|
||
|
local setmetatable = setmetatable
|
||
|
local print = print
|
||
|
local error = error
|
||
|
local type = type
|
||
|
local tonumber = tonumber
|
||
|
local tostring = tostring
|
||
|
local loadstring = loadstring
|
||
|
local string = string
|
||
|
local table = table
|
||
|
local pairs = pairs
|
||
|
local globals = _G
|
||
|
|
||
|
module( "grldc.net" )
|
||
|
|
||
|
local listenerMeta = { __index = {} }
|
||
|
local connectionMeta = { __index = {} }
|
||
|
|
||
|
local function debugPrint( ... )
|
||
|
--print( ... )
|
||
|
end
|
||
|
|
||
|
function serialize( value )
|
||
|
local t = type( value )
|
||
|
if t == "number" then
|
||
|
local res = tostring( value )
|
||
|
if string.find( value, "[^%.,%-0-9]" ) ~= nil then
|
||
|
res = "function() return 0/0 end"
|
||
|
end
|
||
|
return res
|
||
|
elseif t == "boolean" then
|
||
|
if value then return "true" end
|
||
|
return "false"
|
||
|
elseif t == "string" then
|
||
|
return string.format( "%q", value )
|
||
|
elseif t == "table" then
|
||
|
local res = "{ "
|
||
|
for k, v in pairs( value ) do
|
||
|
res = res.."["..serialize( k ).."] = "..serialize( v )..", "
|
||
|
end
|
||
|
res = res.." }"
|
||
|
return res
|
||
|
else
|
||
|
error( "Can't serialize a value of type "..t )
|
||
|
end
|
||
|
end
|
||
|
|
||
|
local function fixUp( value )
|
||
|
local t = type( value )
|
||
|
if t == "table" then
|
||
|
local res = {}
|
||
|
for k, v in pairs( value ) do
|
||
|
res[fixUp(k)] = fixUp(v)
|
||
|
end
|
||
|
return res
|
||
|
elseif t == "function" then
|
||
|
return value()
|
||
|
else
|
||
|
return value
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function deserialize( str )
|
||
|
local f = loadstring( "return "..str )
|
||
|
if f then
|
||
|
local res = f()
|
||
|
return fixUp( res )
|
||
|
else
|
||
|
error( "Unable to parse serialized value: "..tostring(str) )
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function bind( address, port )
|
||
|
local self = { listener = socket.bind( address, port ) }
|
||
|
setmetatable( self, listenerMeta )
|
||
|
return self
|
||
|
end
|
||
|
|
||
|
function listenerMeta.__index:accept()
|
||
|
local res = self.listener:accept()
|
||
|
if res ~= nil then
|
||
|
res = { connection = res }
|
||
|
setmetatable( res, connectionMeta )
|
||
|
res:init_()
|
||
|
end
|
||
|
return res
|
||
|
end
|
||
|
|
||
|
function connect( address, port )
|
||
|
local self = { connection = socket.connect( address, port ) }
|
||
|
print( "Connected to "..address..":"..port )
|
||
|
setmetatable( self, connectionMeta )
|
||
|
self:init_()
|
||
|
return self
|
||
|
end
|
||
|
|
||
|
function connectionMeta.__index:init_()
|
||
|
self.received_ = {}
|
||
|
end
|
||
|
|
||
|
function connectionMeta.__index:getpeername()
|
||
|
return self.connection:getpeername()
|
||
|
end
|
||
|
|
||
|
function connectionMeta.__index:send( data, channel )
|
||
|
channel = channel or "default"
|
||
|
debugPrint( "Sending "..tostring( data ).." on channel "..channel )
|
||
|
local sdata = serialize( data )
|
||
|
local packet = channel.."\n"..(#sdata).."\n"..sdata
|
||
|
local res, msg = self.connection:send( packet )
|
||
|
if res == nil then
|
||
|
error( msg, 0 )
|
||
|
else
|
||
|
return res
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function connectionMeta.__index:waitData()
|
||
|
local needResumeHook = false
|
||
|
if globals.grldc.suspendHook ~= nil then
|
||
|
-- the grldc module is loaded, we need to avoid the debug hook to be called from the receive function (because the hook internally uses receive too)
|
||
|
globals.grldc.suspendHook()
|
||
|
needResumeHook = true
|
||
|
end
|
||
|
local hasData = false
|
||
|
for _, received in pairs( self.received_ ) do
|
||
|
if #received > 0 then
|
||
|
hasData = true
|
||
|
break
|
||
|
end
|
||
|
end
|
||
|
if hasData then
|
||
|
if needResumeHook then globals.grldc.resumeHook() end
|
||
|
return
|
||
|
end
|
||
|
debugPrint( "updating channels..." )
|
||
|
self:updateChannels_( nil )
|
||
|
if needResumeHook then globals.grldc.resumeHook() end
|
||
|
end
|
||
|
|
||
|
function connectionMeta.__index:receive( channel )
|
||
|
channel = channel or "default"
|
||
|
debugPrint( "Blocking receive on channel "..channel )
|
||
|
local data = self:popReceived_( channel )
|
||
|
while data == nil do
|
||
|
self:updateChannels_( nil )
|
||
|
data = self:popReceived_( channel )
|
||
|
end
|
||
|
debugPrint( "received "..tostring(data).." on channel "..channel )
|
||
|
return data
|
||
|
end
|
||
|
|
||
|
function connectionMeta.__index:tryReceive( channel )
|
||
|
channel = channel or "default"
|
||
|
local data = self:popReceived_( channel )
|
||
|
if data == nil then
|
||
|
self:updateChannels_( 0 )
|
||
|
data = self:popReceived_( channel )
|
||
|
end
|
||
|
if data ~= nil then
|
||
|
debugPrint( "received "..tostring(data).." on channel "..channel )
|
||
|
end
|
||
|
return data
|
||
|
end
|
||
|
|
||
|
function connectionMeta.__index:updateChannels_( timeout )
|
||
|
local needResumeHook = false
|
||
|
if globals.grldc.suspendHook ~= nil then
|
||
|
-- the grldc module is loaded, we need to avoid the debug hook to be called from the receive function (because the hook internally uses receive too)
|
||
|
globals.grldc.suspendHook()
|
||
|
needResumeHook = true
|
||
|
end
|
||
|
self.connection:settimeout( timeout )
|
||
|
local channel, msg = self.connection:receive()
|
||
|
self.connection:settimeout( nil )
|
||
|
if channel == nil and msg == "timeout" then
|
||
|
if needResumeHook then globals.grldc.resumeHook() end
|
||
|
return
|
||
|
end
|
||
|
local size
|
||
|
if channel ~= nil then
|
||
|
debugPrint( "receiving..." )
|
||
|
size, msg = self.connection:receive()
|
||
|
end
|
||
|
local data
|
||
|
if size ~= nil then
|
||
|
size = assert( tonumber( size ) )
|
||
|
data, msg = self.connection:receive( size )
|
||
|
end
|
||
|
if data == nil then
|
||
|
if needResumeHook then globals.grldc.resumeHook() end
|
||
|
error( msg, 0 )
|
||
|
end
|
||
|
if channel == "ka" then
|
||
|
-- special channel keepalive, we simply ignore data received on this channel
|
||
|
else
|
||
|
local received = self.received_[channel]
|
||
|
if received == nil then
|
||
|
received = {}
|
||
|
self.received_[channel] = received
|
||
|
end
|
||
|
table.insert( received, deserialize( data ) )
|
||
|
debugPrint( "queued "..tostring(data).." on channel "..channel )
|
||
|
end
|
||
|
if needResumeHook then globals.grldc.resumeHook() end
|
||
|
end
|
||
|
|
||
|
function connectionMeta.__index:popReceived_( channel )
|
||
|
local received = self.received_[channel]
|
||
|
if received == nil then return nil end
|
||
|
local res = received[1]
|
||
|
if res == nil then return nil end
|
||
|
table.remove( received, 1 )
|
||
|
return res
|
||
|
end
|