--[[ UPCOMMING ADDITIONS AUDP - advance udp/ Ensures packets arrive and handles late packets. P2P - peer to peer (Server to set up initial connection) Relay - offput server load (locally) Threading - Simple threading (UDP/AUDP Only) Priority handling ]] --[[ TODO: Finish stuff for Priority handling ]] function table.merge(t1, t2) for k,v in pairs(t2) do if type(v) == 'table' then if type(t1[k] or false) == 'table' then table.merge(t1[k] or {}, t2[k] or {}) else t1[k] = v end else t1[k] = v end end return t1 end function string.trim(s) local from = s:match"^%s*()" return from > #s and "" or s:match(".*%S", from) end socket=require("socket") http=require("socket.http") mime=require("mime") net={} net.Version={2,0,0} -- This will probably stay this version for quite a while... The modules on the otherhand will be more inconsistant net._VERSION="2.0.0" net.OnServerCreated=multi:newConnection() net.OnClientCreated=multi:newConnection() net.loadedModules={} net.autoInit=true function net.normalize(input) local enc=mime.b64(input) return enc end function net.denormalize(input) local unenc=mime.unb64(input) return unenc end function net.getLocalIP() local someRandomIP = "192.168.1.122" local someRandomPort = "3102" local mySocket = socket.udp() mySocket:setpeername(someRandomIP,someRandomPort) local dat = (mySocket:getsockname()) mySocket:close() return dat end function net.getExternalIP() local data=http.request("http://whatismyip.org/") return data:match("600;\">(%d-.%d-.%d-.%d-)") end function net:registerModule(mod,version) if net[mod] then error("Module by the name: "..mod.." has already been registered! Remember some modules are internal and use certain names!") end table.insert(self.loadedModules,mod) net[mod]={} if version then net[mod].Version=version net[mod]._VERSION=version[1].."."..version[2].."."..version[3] else net[mod].Version={1,0,0} net[mod]._VERSION={1,0,0} end return {Version=version,_VERSION=version[1].."."..version[2].."."..version[3]} end function net.getModuleVersion(ext) if not ext then return string.format("%d.%d.%d",net.Version[1],net.Version[2],net.Version[3]) end return string.format("%d.%d.%d",net[ext].Version[1],net[ext].Version[2],net[ext].Version[3]) end function net.resolveID(obj) local num=math.random(10000000,99999999) if obj[tostring(num)] then return net.resolveID(obj) end obj.ids[tostring(num)]=true return tostring(num) end function net.inList(list,dat) for i,v in pairs(list) do if v==dat then return true end end return false end function net.setTrigger(funcW,funcE) multi:newTrigger(func) end net:registerModule("net",net.Version) -- Client broadcast function net:newCastedClient(name) -- connects to the broadcasted server local listen = socket.udp() -- make a new socket listen:setsockname(net.getLocalIP(), 11111) listen:settimeout(0) local timer=multi:newTimer() while true do local data, ip, port = listen:receivefrom() if timer:Get()>3 then error("Timeout! Server by the name: "..name.." has not been found!") end if data then local n,tp,ip,port=data:match("(%S-)|(%S-)|(%S-):(%d+)") if n:match(name) then print("Found Server!",n,tp,ip,port) if tp=="tcp" then return net:newTCPClient(ip,tonumber(port)) else return net:newClient(ip,tonumber(port)) end end end end end -- UDP Stuff function net:newServer(port,servercode) local c={} c.udp=assert(socket.udp()) c.udp:settimeout(0) c.udp:setsockname("*", port) c.ips={} c.Type="udp" c.port=port c.ids={} c.servercode=servercode c.bannedIPs={} c.bannedCIDs={} c.autoNormalization=false function c:setUpdateRate(n) print("Not needed in a udp server!") end function c:banCID(cid) table.insert(self.bannedCIDs,cid) end function c:banIP(ip) table.insert(self.bannedIPs,cid) end c.broad=socket.udp() c.hostip=net.getLocalIP() function c:broadcast(name) local loop=multi:newTLoop(function(dt,loop) self.broad:setoption('broadcast',true) self.broad:sendto(name.."|"..self.Type.."|"..self.hostip..":"..self.port, "255.255.255.255", 11111) self.broad:setoption('broadcast',false) end,1) end function c:send(ip,data,port,cid) if self.autoNormalization then data=net.normalize(data) end if self.servercode then cid=cid or self:CIDFrom(ip,port) if not self.ips[cid] then print("Can't determine cid from client... sending the client a new one!") local cid=net.resolveID(self) print("Sending unique cid to client: "..cid) self.ips[cid]={ip,port,0,self.servercode==nil} print(ip) self.udp:sendto("I!"..cid,ip,port) if self.servercode then self.udp:sendto("S!",ip,port) end return end if net.inList(self.bannedIPs,ip) or net.inList(self.bannedCIDs,cid) then self.udp:sendto("BANNED CLIENT", ip, port or self.port) elseif self.ips[cid][4] then self.udp:sendto(data, ip, port or self.port) elseif self.ips[cid][4]==false then self.udp:sendto("Make sure your server code is correct!", ip, port) end else self.udp:sendto(data, ip, port or self.port) end end function c:pollClientModules(ip,port) self:send(ip,"L!",port) end function c:CIDFrom(ip,port) for i,v in pairs(self.ips) do if(ip==v[1] and v[2]==port) then return i end end end function c:sendAll(data) for i,v in pairs(self.ips) do self:send(v[1],data,v[2],i) end end function c:sendAllBut(data,cid) for i,v in pairs(self.ips) do if i~=cid then self:send(v[1],data,v[2],i) end end end function c:clientRegistered(cid) return self.ips[cid] end function c:clientLoggedIn(cid) if not self.clientRegistered(cid) then return nil end return self.ips[cid][4] end function c:update() local data,ip,port=self.udp:receivefrom() if net.inList(self.bannedIPs,ip) or net.inList(self.bannedCIDs,cid) then print("We will ingore data from a banned client!") return end if data then if self.autoNormalization then data=net.denormalize(data) end if data:sub(1,4)=="pong" then --print("Recieved pong from: "..data:sub(5,-1)) self.ips[data:sub(5,-1)][3]=os.clock() elseif data:sub(1,2)=="S!" then local cid=self:CIDFrom(ip,port) if data:sub(3,-1)==self.servercode then print("Servercode Accepted: "..self.servercode) if self.ips[cid] then self.ips[cid][4]=true else print("Server can't keep up! CID: "..cid.." has been skipped! Sending new CID to the client!") local cid=net.resolveID(self) print("Sending unique cid to client: "..cid) self.ips[cid]={ip,port,0,self.servercode==nil} print(ip) self.udp:sendto("I!"..cid,ip,port) if self.servercode then self.udp:sendto("S!",ip,port) end end else self.udp:sendto("Make sure your server code is correct!", ip, port) end elseif data:sub(1,2)=="C!" then local hook=(data:sub(11,-1)):match("!(.-)!") self.OnDataRecieved:getConnection(hook):Fire(self,data:sub(11,-1),data:sub(3,10),ip,port) elseif data:sub(1,2)=="E!" then self.ips[data:sub(3,10)]=nil obj.ids[data:sub(3,10)]=false self.OnClientClosed:Fire(self,"Client Closed Connection!",data:sub(3,10),ip,port) elseif data=="I!" then local cid=net.resolveID(self) print("Sending unique cid to client: "..cid) self.ips[cid]={ip,port,os.clock(),self.servercode==nil} print(ip) self.udp:sendto("I!"..cid,ip,port) if self.servercode then self.udp:sendto("S!",ip,port) end self.OnClientConnected:Fire(self,cid,ip,port) elseif data:sub(1,2)=="L!" then cid,cList=data:sub(3,10),data:sub(11,-1) local list={} for m,v in cList:gmatch("(%S-):(%S-)|") do list[m]=v end self.OnClientsModulesList:Fire(list,cid,ip,port) end end for cid,dat in pairs(self.ips) do if not((os.clock()-dat[3])<65) then self.ips[cid]=nil self.OnClientClosed:Fire(self,"Client lost Connection: ping timeout",cid,ip,port) end end end c.OnClientsModulesList=multi:newConnection() c.OnDataRecieved=multi:newConnection() c.OnClientClosed=multi:newConnection() c.OnClientConnected=multi:newConnection() c.connectiontest=multi:newAlarm(30) c.connectiontest.link=c c.connectiontest:OnRing(function(alarm) --print("pinging clients!") alarm.link:sendAll("ping") alarm:Reset() end) multi:newLoop(function() c:update() end) net.OnServerCreated:Fire(c) return c end function net:newClient(host,port,servercode,nonluaServer) local c={} c.ip=assert(socket.dns.toip(host)) c.udp=assert(socket.udp()) c.udp:settimeout(0) c.udp:setpeername(c.ip, port) c.cid="NIL" c.lastPing=0 c.Type="udp" c.servercode=servercode c.autoReconnect=true c.autoNormalization=false function c:pollPing(n) return not((os.clock()-self.lastPing)<(n or 60)) end function c:send(data) if self.autoNormalization then data=net.normalize(data) end self.udp:send("C!"..self.cid..data) end function c:sendRaw(data) if self.autoNormalization then data=net.normalize(data) end self.udp:send(data) end function c:getCID() if self:IDAssigned() then return self.cid end end function c:close() self:send("E!") end function c:IDAssigned() return self.cid~="NIL" end function c:update() local data=self.udp:receive() if data then if self.autoNormalization then data=net.denormalize(data) end if data:sub(1,2)=="I!" then self.cid=data:sub(3,-1) self.OnClientReady:Fire(self) elseif data=="S!" then self.udp:send("S!"..(self.servercode or "")) elseif data=="L!" then local mods="" local m="" for i=1,#net.loadedModules do m=net.loadedModules[i] mods=mods..m..":"..net.getModuleVersion(m).."|" end self.udp:send("L!"..self.cid..mods) elseif data=="ping" then self.lastPing=os.clock() self.OnPingRecieved:Fire(self) self.udp:send("pong"..self.cid) else local hook=data:match("!(.-)!") self.OnDataRecieved:getConnection(hook):Fire(self,data) end end end function c:reconnect() if not nonluaServer then self.cid="NIL" c.udp:send("I!") end self.OnConnectionRegained:Fire(self) end c.pingEvent=multi:newEvent(function(self) return self.link:pollPing() end) c.pingEvent:OnEvent(function(self) if self.link.autoReconnect then self.link.OnServerNotAvailable:Fire("Connection to server lost: ping timeout! Attempting to reconnect...") self.link.OnClientDisconnected:Fire(self,"closed") self.link:reconnect() else self.link.OnServerNotAvailable:Fire("Connection to server lost: ping timeout!") self.link.OnClientDisconnected:Fire(self,"closed") end end) c.pingEvent.link=c c.OnPingRecieved=multi:newConnection() c.OnDataRecieved=multi:newConnection() c.OnServerNotAvailable=multi:newConnection() c.OnClientReady=multi:newConnection() c.OnClientDisconnected=multi:newConnection() c.OnConnectionRegained=multi:newConnection() c.notConnected=multi:newFunction(function(self) self:hold(3) if self.link:IDAssigned()==false then self.link.OnServerNotAvailable:Fire("Can't connect to the server: no response from server") end end) c.notConnected.link=c if not nonluaServer then c.udp:send("I!") end multi:newLoop(function() c:update() end) multi:newJob(function() c.notConnected() end) net.OnClientCreated:Fire(c) return c end --TCP Stuff function net:newTCPServer(port) local c={} c.tcp=assert(socket.bind("*", port)) c.tcp:settimeout(0) c.ip,c.port=c.tcp:getsockname() c.ips={} c.port=port c.ids={} c.bannedIPs={} c.Type="tcp" c.rMode="*l" c.sMode="*l" c.updaterRate=1 c.autoNormalization=false c.updates={} c.links={} c.broad=socket.udp() c.hostip=net.getLocalIP() function c:broadcast(name) local loop=multi:newTLoop(function(dt,loop) self.broad:setoption('broadcast',true) self.broad:sendto(name.."|"..self.Type.."|"..self.hostip..":"..self.port, "255.255.255.255", 11111) self.broad:setoption('broadcast',false) end,1) end function c:setUpdateRate(n) self.updaterRate=n end function c:setReceiveMode(mode) self.rMode=mode end function c:setSendMode(mode) self.rMode=mode end function c:banCID(cid) print("Function not supported on a tcp server!") end function c:banIP(ip) table.insert(self.bannedIPs,cid) end function c:send(handle,data) if self.autoNormalization then data=net.normalize(data) end if self.sMode=="*l" then handle:send(data.."\n") else handle:send(data) end end function c:sendAllData(handle,data) if self.autoNormalization then data=net.normalize(data) end handle:send(data) end function c:pollClientModules(ip,port) self:send(ip,"L!",port) end function c:CIDFrom(ip,port) print("Method not supported when using a TCP Server!") return "CIDs in TCP work differently!" end function c:sendAll(data) for i,v in pairs(self.ips) do self:send(v,data) end end function c:sendAllBut(data,cid) for i,v in pairs(self.ips) do if not(cid==i) then self:send(v,data) end end end function c:clientRegistered(cid) return self.ips[cid] end function c:clientLoggedIn(cid) return self.ips[cid] end function c:getUpdater(cid) return self.updates[cid] end function c:update() local client = self.tcp:accept(self.rMode) if not client then return end table.insert(self.ips,client) client:settimeout(0) --client:setoption('tcp-nodelay', true) client:setoption('keepalive', true) ip,port=client:getpeername() if ip and port then print("Got connection from: ",ip,port) local updater=multi:newUpdater(skip) self.updates[client]=updater self.OnClientConnected:Fire(self,self.client,self.client,ip) updater:OnUpdate(function(self) local data, err = self.client:receive(self.rMode or self.Link.rMode) if err=="closed" then for i=1,#self.Link.ips do if self.Link.ips[i]==self.client then table.remove(self.Link.ips,i) end end self.Link.OnClientClosed:Fire(self.Link,"Client Closed Connection!",self.client,self.client,ip) self.Link.links[self.client]=nil -- lets clean up self:Destroy() end if data then if self.autoNormalization then data=net.denormalize(data) end if net.inList(self.Link.bannedIPs,ip) then print("We will ingore data from a banned client!") return end local hook=data:match("!(.-)!") self.Link.OnDataRecieved:getConnection(hook):Fire(self.Link,data,self.client,self.client,ip,self) if data:sub(1,2)=="L!" then cList=data local list={} for m,v in cList:gmatch("(%S-):(%S-)|") do list[m]=v end self.Link.OnClientsModulesList:Fire(list,self.client,self.client,ip) end end end) updater:setSkip(self.updaterRate) updater.client=client updater.Link=self function updater:setReceiveMode(mode) self.rMode=mode end self.links[client]=updater end end c.OnClientsModulesList=multi:newConnection() c.OnDataRecieved=multi:newConnection() c.OnClientClosed=multi:newConnection() c.OnClientConnected=multi:newConnection() multi:newLoop(function() c:update() end) net.OnServerCreated:Fire(c) return c end function net:newTCPClient(host,port) local c={} c.ip=assert(socket.dns.toip(host)) c.port=port c.tcp=socket.connect(c.ip,port) if not c.tcp then print("Can't connect to the server: no response from server") return false end c.tcp:settimeout(0) --c.tcp:setoption('tcp-nodelay', true) c.tcp:setoption('keepalive', true) c.Type="tcp" c.autoReconnect=true c.rMode="*l" c.sMode="*l" c.autoNormalization=false function c:setReceiveMode(mode) self.rMode=mode end function c:setSendMode(mode) self.sMode=mode end function c:send(data) if self.autoNormalization then data=net.normalize(data) end if self.sMode=="*l" then ind,err=self.tcp:send(data.."\n") else ind,err=self.tcp:send(data) end if err=="closed" then self.OnClientDisconnected:Fire(self,err) elseif err=="timeout" then self.OnClientDisconnected:Fire(self,err) elseif err then print(err) end end function c:sendRaw(data) if self.autoNormalization then data=net.normalize(data) end self.tcp:send(data) end function c:getCID() return "No Cid on a tcp client!" end function c:close() self.tcp:close() end function c:IDAssigned() return true end function c:update() if not self.tcp then return end local data,err=self.tcp:receive() if err=="closed" then self.OnClientDisconnected:Fire(self,err) elseif err=="timeout" then self.OnClientDisconnected:Fire(self,err) elseif err then print(err) end if data then if self.autoNormalization then data=net.denormalize(data) end local hook=data:match("!(.-)!") self.OnDataRecieved:getConnection(hook):Fire(self,data) end end function c:reconnect() multi:newFunction(function(func) self.tcp=socket.connect(self.ip,self.port) if self.tcp==nil then print("Can't connect to the server: No response from server!") func:hold(3) self:reconnect() return end self.OnConnectionRegained:Fire(self) self.tcp:settimeout(0) --self.tcp:setoption('tcp-nodelay', true) self.tcp:setoption('keepalive', true) end) end c.event=multi:newEvent(function(event) return event.link:IDAssigned() end) c.event:OnEvent(function(event) event.link.OnClientReady:Fire(event.link) end) c.event.link=c c.OnClientReady=multi:newConnection() c.OnClientDisconnected=multi:newConnection() c.OnDataRecieved=multi:newConnection() c.OnConnectionRegained=multi:newConnection() multi:newLoop(function() c:update() end) net.OnClientCreated:Fire(c) return c end