• gm_glsock - Full async sockets
    54 replies, posted
First of all you all might wonder why another socket library that will eventually crash anyway, well I'm using the Boost.asio library here to help my self for cross platform compatibility. Also what I didn't like about the other modules is that they don't provide individual callbacks per asynchronous operation such as reading, sending and so on. I've been working quite a while on this and wanted to make sure its not that buggy on the initial release. I also kinda struggled at the beginning with the callbacks as I was using multiple threads and the locking didn't work that well then I took a look at OOSocks Src and found out that it uses a Think hook to do polling and such, thanks for the hint! [B]Examples[/B] [URL="http://code.google.com/p/mattmodules/source/browse/trunk/gm_glsock/examples/acceptor.lua"]Acceptor Socket[/URL] [URL="http://code.google.com/p/mattmodules/source/browse/trunk/gm_glsock/examples/tcp.lua"]TCP[/URL] [URL="http://code.google.com/p/mattmodules/source/browse/trunk/gm_glsock/examples/read_until.lua"]Read Until [/URL] As you can see each call has pretty much its own callback which makes your code more dynamic. You aren't bound to single callback doing all decisions in it. If you are using this on Linux and you get some error of glibc stuff missing then delete your libstdc++.6.so in your orangebox/bin directory, if your srcds fails after that then I can't/wont help :( I kinda hate Linux development so my apologies. You will find documentation on the SVN Link below and the latest binaries. Also please report all bugs with a snippet of the used lua code so I can debug the library on my VM. [URL="https://code.google.com/p/mattmodules/source/browse/trunk/gm_glsock/Release/"]https://code.google.com/p/mattmodules/source/browse/trunk/gm_glsock/Release/ [/URL] [B]GM13 Beta Builds[/B] Windows: [URL]https://dl.dropbox.com/u/29407915/gm_glsock_gm13.rar[/URL] Linux: [URL="http://g.metastruct.uk.to/%7Esrcds/modules/gm_glsock_linux.dll"]http://g.metastruct.uk.to/~srcds/modules/gm_glsock_linux.dll[/URL] Might break again and again and again, pretty much depends on garry.
Definitely an improvement. I will let you know if I have any problems.
Heres another example that is also being used and working. [lua] function EncodeLua(code) local buf = "" for i=1, #code do buf = buf .. string.format("%02X", string.byte(code[i])) end return buf end function DecodeLua(code) local buf = "" local i = 1 while i < #code do local hex = code[i] .. code[i + 1] local n = tonumber(hex, 16) buf = buf .. string.char(n) i = i + 2 end return buf end if SERVER then require 'glsock' function ParseHTTPField(header, field) field = field .. ": " local i = string.find(header, field) if i == 0 then return nil end local sub = string.sub(header, i + #field) i = string.find(sub, "\r\n") if i != 0 then sub = string.sub(sub, 1, i) end return sub end function ProcessHeader(header) --print(header) local authtoken = ParseHTTPField(header, "Token") local code = ParseHTTPField(header, "Code") local ply = ParseHTTPField(header, "Player") print("Player: "..ply) print("Auth Token: "..authtoken) if( !authtoken or !code or !ply ) then return end ply = player.GetByUniqueID(string.Trim(ply)) if !ply then print("Invalid Player") return end if( !ply.LuaAuthToken or !luadev.IsPlayerAllowed(ply) ) then print("No permissions") return end if( tostring(ply.LuaAuthToken) != tostring(string.Trim(authtoken)) ) then print("Auth Token mismatch (" .. tostring(ply.LuaAuthToken) .. " - " .. tostring(authtoken) .. ")") return end code = DecodeLua(string.Trim(code)) print("LuaWebServer("..tostring(ply).."): "..code) luadev.RunOnServer(code, ply) end function OnReadHeader(sck, data, errno) if( errno == GLSOCK_ERROR_SUCCESS ) then local read, headerdata = data:Read(data:Size()) ProcessHeader(headerdata) end end function ProcessClient(client) client:ReadUntil("\r\n\r\n", OnReadHeader) end function OnAccept(sck, client, errno) if( errno == GLSOCK_ERROR_SUCCESS ) then print("New Connection: "..tostring(client)) ProcessClient(client) sck:Accept(OnAccept) else print("Accept failed: "..errno) end end function OnListen(sck, errno) if( errno == GLSOCK_ERROR_SUCCESS ) then print("Socket Listening") sck:Accept(OnAccept) else print("Failed to listen: "..errno) end end function OnBind(sck, errno) if( errno == GLSOCK_ERROR_SUCCESS ) then print("Bound to port 6678") sck:Listen(10, OnListen) else print("Failed to bind port 6678: "..errno) end end local acceptor = GLSock(GLSOCK_TYPE_ACCEPTOR) acceptor:Bind("", 6678, OnBind) function _R.Player:SendLuaAuth() local host = GetConVarString("ip") local port = 6678 if( luadev.IsPlayerAllowed(self) ) then self.LuaAuthToken = util.CRC(math.random()..self:UniqueID()..CurTime()) umsg.Start("luaauthtoken", self); umsg.String(self.LuaAuthToken) umsg.String(host..":"..port) umsg.End() else self.LuaAuthToken = nil end end timer.Simple(1, function() for k,v in pairs(player.GetAll()) do v:SendLuaAuth() end end) else usermessage.Hook("luaauthtoken", function(data) local ply = LocalPlayer() ply.LuaAuthToken = data:ReadString() ply.LuaWebServer = data:ReadString() print("Received Lua Auth: "..ply.LuaAuthToken .. "(" .. ply.LuaWebServer .. ")") end); function _R.Player:SendLuaCode(code) local headers = "Accept: */*\r\n" headers = headers .. "Token: " .. self.LuaAuthToken .. "\r\n" headers = headers .. "Player: " .. self:UniqueID() .. "\r\n" headers = headers .. "Code: " .. EncodeLua(code) .."\r\n" if( self.LuaWebServer and self.LuaAuthToken ) then http.Get(self.LuaWebServer, headers, print) end end end [/lua] Well not really used but I did this to test how far I could go :) Edit: I just fixed a critical bug when calling Close()/Cancel() when the socket was already closed/cancelled it was crashing. Thats now fixed, also I added Destroy() for instant cleanup, when called the socket handle is closed and will be no longer valid.
Oh cool, you did what I wanted to eventually do +1
[lua]module("http2", package.seeall) require'glsock' function ParseURL(url) local i = 0 local urltype = "" i = string.find(url, "://") if( i ) then urltype = string.sub(url, 1, i - 1) url = string.sub(url, i + 3) end local host = "" local port = "" local path = "" local user = "" local pass = "" i = string.find(url, "/") if( i ) then host = string.sub(url, 1, i - 1) path = string.sub(url, i) else host = url path = "/" end i = string.find(url, "@") if( i ) then local login = string.sub(url, 1, i - 1) url = string.sub(url, i + 1) i = string.find(login, ":") if( i ) then user = string.sub(login, 1, i - 1) pass = string.sub(login, i + 1) else user = login end end i = string.find(host, ":") if( i ) then port = string.sub(host, i + 1) port = tonumber(port) host = string.sub(host, 1, i - 1) else port = 80 end local res = { protocol = urltype, user = user, pass = pass, host = host, port = port, path = path } return res end function Get(url, headers, cb, ...) local sock = GLSock(GLSOCK_TYPE_TCP) local urldata = ParseURL(url) local cbargs = {...} local buffer = "" PrintTable(urldata) function FinishCallback() cb(buffer, #buffer, cbargs) sock:Destroy() end local function OnDataRead(sck, data, errno) if( data and data:Size() > 0 ) then local len, buf = data:Read(data:Size()) //print(buf) buffer = buffer .. buf end if( errno != GLSOCK_ERROR_SUCCESS ) then FinishCallback() return end sck:Read(1024, OnDataRead) end local function OnHeaderRead(sck, data, errno) if( errno != GLSOCK_ERROR_SUCCESS ) then FinishCallback() return end //print("Received Header") local len, headerdata = data:Read(data:Size()) local headers = string.Explode("\r\n", headerdata) local statusline = headers[1] if( !statusline ) then FinishCallback() return end /* local statusversion = "" local statuscode = "" local statusmsg = "" local i = 0 i = string.find(statusline, " ") if( i ) then statusversion = string.sub(statusline, 1, i - 1) i = string.find(statusline, " ") if( i ) then statuscode = string.sub(statusline, i + 1) i = string.find(statuscode, " ") if( i ) then statusmsg = string.sub(statuscode, i + 1) statuscode = string.sub(statuscode, i - 1) else print("Status Msg parsing failed") FinishCallback() return end else print("Status Code parsing failed") FinishCallback() return end else print("Version parsing failed") FinishCallback() return end */ // TODO: If 302 Moved then follow redirect //print(statusversion.." "..statuscode.." "..statusmsg) // Read Data sck:Read(1024, OnDataRead) end local function OnSend(sck, size, errno) if( errno != GLSOCK_ERROR_SUCCESS ) then FinishCallback() return end sck:ReadUntil("\r\n\r\n", OnHeaderRead) end local function OnConnect(sck, errno) if( errno != GLSOCK_ERROR_SUCCESS ) then FinishCallback() return end print("Connected to Host") local header = GLSockBuffer() header:Write("GET ".. urldata.path .." HTTP/1.1\r\n") header:Write("Host: ".. urldata.host ..":".. tostring(urldata.port) .. "\r\n") header:Write("Connection: Close\r\n") if( #headers > 0 ) then header:Write(headers) end header:Write("\r\n") sck:Send(header, OnSend) end sock:Connect(urldata.host, urldata.port, OnConnect) end Get("http://google.com", "", function(content, size) print(size) end)[/lua] And haza55, maybe we can work together on this one, OOSocks was the initial reason why I started this.
Oh wait I haven't posted in this thread yet saying this is awesome cakes. It's awesome :D
[QUOTE=Zeh Matt;33268282] And haza55, maybe we can work together on this one, OOSocks was the initial reason why I started this.[/QUOTE] Yeah maybe. Was really disappointed with OOSocks, which I had to push it out in a few hours because my server depended on it at the time.
Can you post a example of UDP usage? Can't seem to get it to work.
Here you go, a damn messy but working server query script: [lua]-- helps -- require'glsock' local errd={} for k,v in pairs(_G) do if k:find("GLSOCK_") then errd[v]=k:match("GLSOCK_ERROR_(.+)") or k end end local mod = math.fmod local floor = math.floor local function rshift(x,n) return floor((x%4294967296)/2^n) end local function band(x,y) local z,i,j = 0,1 for j = 0,31 do if (mod(x,2)==1 and mod(y,2)==1) then z = z + i end x = rshift(x,1) y = rshift(y,1) i = i*2 end return z end local function err(id) local name=errd[id] name=name and name or "UNKNOWN???" return name..'('..tostring(id)..')' end local Err=function(...) Msg"[SrvQuery]"print(...) end local function TOIP(x) return x and x[5] and tonumber(x[5]) and x[1].."."..x[2].."."..x[3].."."..x[4]..":"..x[5] end local c=string.char local A2S_SERVERQUERY_GETCHALLENGE=c(0xFF)..c(0xFF)..c(0xFF)..c(0xFF)..c(0x57) local A2S_SERVERQUERY_GETCHALLENGE_HACK=c(0xFF)..c(0xFF)..c(0xFF)..c(0xFF)..c(0x55)..c(0xFF)..c(0xFF)..c(0xFF)..c(0xFF) local A2S_INFO=c(0xFF)..c(0xFF)..c(0xFF)..c(0xFF)..c(0x54).."Source Engine Query"..c(0x00) local A2A_PING=c(0x69) local A2S_PLAYER=c(0xFF)..c(0xFF)..c(0xFF)..c(0xFF)..c(0x55) local A2S_RULES=c(0xFF)..c(0xFF)..c(0xFF)..c(0xFF)..c(0x56) local valid={ ["A2S_SERVERQUERY_GETCHALLENGE"]=A2S_SERVERQUERY_GETCHALLENGE, ["A2S_INFO"]=A2S_INFO, ["A2A_PING"]=A2A_PING, ["A2S_PLAYER"]=A2S_PLAYER, ["A2S_RULES"]=A2S_RULES } ServerInfo = ServerInfo or {Queries=valid} local socket if ServerInfo.socket then print"Resetting ServerInfo" ServerInfo.socket:Cancel() socket=ServerInfo.socket end ------------------------------ ServerInfo.Servers=ServerInfo.Servers or {} ------------------------------ local srvinfo=ServerInfo.Servers local function SrvData(ip,port) if not port then ip,port = ip:match("(%d+.%d+.%d+.%d+)%:?(%d*)") end port = tonumber(port) or 27015 if not ip then error"invalid ip" end if port<=0 or port>=65535 then error"invalid port" end srvinfo[port]=srvinfo[port] or {} srvinfo[port][ip] = srvinfo[port][ip] or {ip,port,{},{}} local data = srvinfo[port][ip] return data end local function GetSrvData(ip,port) if not port then ip,port = ip:match("(%d+.%d+.%d+.%d+)%:?(%d*)") if not ip then error"invalid ip" end end port = tonumber(port) or 27015 if port<=0 or port>=65535 then error"invalid port" end return srvinfo[port] and srvinfo[port][ip] end ServerInfo.Find=GetSrvData ------------------------------ local function ProcessPing(buf,ip,port,data) print("ProcessPing for",ip,port) end local minusone=c(0xFF)..c(0xFF)..c(0xFF)..c(0xFF) local function GetSendBuffer(info,challenge) info = info == A2S_SERVERQUERY_GETCHALLENGE and A2S_SERVERQUERY_GETCHALLENGE_HACK or info local buffer = GLSockBuffer() local sanity = buffer:Write(info) assert((info):len()==sanity) if info==A2S_SERVERQUERY_GETCHALLENGE_HACK then return buffer end if ( info==A2S_RULES or info==A2S_PLAYER ) then print("\tAppending challenge ("..challenge..") to SendBuffer") if challenge then buffer:WriteLong(challenge) else buffer:Write(minusone) end end return buffer end local function ProcessChallenge(buf,ip,port,data) print("ProcessChallenge for",ip,port) local _,challenge=buf:ReadLong() print("\t","Challenge="..challenge) if data.challenge then print("\t","Old Challenge="..data.challenge) end data.challenge = challenge end local function ProcessInfo(buf,ip,port,data) print("ProcessInfo for",ip,port) local _,version = buf:ReadByte( ) local _,hostname = buf:ReadString( ) local _,map = buf:ReadString( ) local _,gamedirectory = buf:ReadString( ) local _,gamedescription = buf:ReadString( ) local _,shortappid = buf:ReadShort( ) local _,numplayers = buf:ReadByte( ) local _,maxplayers = buf:ReadByte( ) local _,numofbots = buf:ReadByte( ) local _,dedicated = buf:ReadByte( ) local _,os = buf:ReadByte( ) local _,password = buf:ReadByte( ) local _,secure = buf:ReadByte( ) local _,gameversion = buf:ReadString( ) local _,extra = buf:ReadByte( ) local _,gameport = band(extra,0x80)==0x80 if gameport then _,gameport = buf:ReadShort( ) end local steamid = band(extra,0x10)==0x10 if steamid then steamid = {select(2,buf:ReadLong( )),select(2,buf:ReadLong( ))} end local spectport = band(extra,0x40)==0x40 if spectport then _,spectport = buf:ReadShort( ) end local spectname = band(extra,0x40)==0x40 if spectname then _,spectname = buf:ReadString( ) end local gametag = band(extra,0x20)==0x20 if gametag then _,gametag = buf:ReadString( ) end local gameid = band(extra,0x01)==0x01 if gameid then gameid = {select(2,buf:ReadLong( )),select(2,buf:ReadLong( ))} end local PSSQ_INFO_REPLY = { ["version"] = version, ["hostname"] = hostname, ["map"] = map, ["gamedirectory"] = gamedirectory, ["gamedescription"] = gamedescription, ["shortappid"] = shortappid, ["numplayers"] = numplayers, ["maxplayers"] = maxplayers, ["numofbots"] = numofbots, ["dedicated"] = c(dedicated), ["os"] = c(os), ["password"] = password, ["secure"] = secure, ["gameversion"] = gameversion, ["extra"] = extra, ["gameport"] = gameport, ["steamid"] = steamid, ["spectport"] = spectport, ["spectname"] = spectname, ["gametag"] = gametag, ["gameid"] = gameid, } Msg"PSSQ_INFO_REPLY = " PrintTable(PSSQ_INFO_REPLY) end local function ProcessPlayers(buf,ip,port,data) print("ProcessPlayers for",ip,port) local _,NumPlayers=buf:ReadShort() print("\tNumPlayers="..NumPlayers) local i=1 local playerstbl={} while i<=NumPlayers do local _,id=buf:ReadByte() local _,name=buf:ReadString() local _,kills=buf:ReadLong() local _,playtime=buf:ReadFloat() if _<1 then Err"players parsing failed, too little data?" break end playerstbl[i]={ id=id, name=name, kills=kills, playtime=playtime } i=i+1 end print("\tplayers tbl="..table.Count(playerstbl)) Msg"players = " PrintTable(playerstbl) end local function ProcessRules(buf,ip,port,data) print("ProcessRules for",ip,port) local _,rules=buf:ReadShort() print("\tRules="..rules) local i=1 local rulestbl={} while i<=rules do local _,k=buf:ReadString() local _,v=buf:ReadString() if _<1 then Err"rules parsing failed, too little data?" break end rulestbl[k]=v i=i+1 end print("\tRules tbl="..table.Count(rulestbl)) MsgN"Rules: " for k,v in pairs(rulestbl) do if k:find("toolmode",1,true) then continue end MsgN(k.." = "..v) end end local PROCESS_RECEIVED_STUFF=PROCESS_RECEIVED_STUFF local function JumpTo(b5,...) if b5==0x41 then return ProcessChallenge(...) or true elseif b5==0x49 then return ProcessInfo(...) or true elseif b5==0x44 then return ProcessPlayers(...) or true elseif b5==0x45 then return ProcessRules(...) or true end return false end local function CombineSplit(split) local buf = GLSockBuffer() local lastk=0 for k,v in pairs(split) do Msg"." if k-1!=lastk then Err"CombineSplit received missing packets. Aborting!" return end local safe=0 while safe and safe<8192 do safe=safe+1 local read,byte=v:ReadByte() if read==0 then break end local wrote=buf:WriteByte(byte) assert(wrote>0) end lastk=k end buf:Seek(0,GLSOCKBUFFER_SEEK_SET) return buf end local function ProcessSplit(buf,ip,port,data) -- print("Split from",ip) local _,b2=buf:ReadByte() local _,b3=buf:ReadByte() local _,b4=buf:ReadByte() if b2!=0xFF or b3!=0xFF or b4!=0xFF then Err("Split packet weird data from",ip,port,"Bytes: ",0xFE,b2,b3,b4 ) end local _,ReqID=buf:ReadLong() if _<0 then Err"Split packet Unexpected EOF" return end local bz2=ReqID>=32768 if bz2 then --ReqID=ReqID-32768 end local _,NumPackets=buf:ReadByte() if _<0 then Err"Split packet Unexpected EOF" return end
My Android app will be a documented example of UDP in glsock. You will be able to read the client code (Android side) as well.
As promised: [thread=1144836]GLSock UDP example.[/thread]
I'm surprised no one else has chimed in this thread about how all the code posts are using globals when not necessary. This is bad and sloppy coding practice, ask any working professional. Python seems to be the only one with at least a concept of the idea of local versus global.
[QUOTE=agmike;33563087]I'm surprised no one else has chimed in this thread about how all the code posts are using globals when not necessary. This is bad and sloppy coding practice, ask any working professional. Python seems to be the only one with at least a concept of the idea of local versus global.[/QUOTE] Theres no disadvantage using global functions, just tells me you dont know anything about lua internals.
[QUOTE=Zeh Matt;33563522]Theres no disadvantage using global functions, just tells me you dont know anything about lua internals.[/QUOTE] maybe you should read up on what locals are. [b]It is good programming style to use local variables whenever possible. Local variables help you avoid cluttering the global environment with unnecessary names. Moreover, the access to local variables is faster than to global ones.[/b] [url]http://www.lua.org/pil/4.2.html[/url]
[QUOTE=_Chewgum;33563683]maybe you should read up on what locals are. [b]It is good programming style to use local variables whenever possible. Local variables help you avoid cluttering the global environment with unnecessary names. Moreover, the access to local variables is faster than to global ones.[/b] [url]http://www.lua.org/pil/4.2.html[/url][/QUOTE] I'm aware of that, I just found the statement funny: "This is bad and sloppy coding practice, ask any working professional." Also thanks for going totally off-topic, coding style wasnt really the discussion
[QUOTE=agmike;33563087]I'm surprised no one else has chimed in this thread about how all the code posts are using globals when not necessary. This is bad and sloppy coding practice, ask any working professional.[/quote] It's a simple example showing how the module is to be used. Coding conventions are not very important in an example. Besides, the difference in performance is minimal and name clashing is rare. On top of that, one can assume that one who knows C++ can easily grasp the difference between global and local variables. [QUOTE=agmike;33563087] Python seems to be the only one with at least a concept of the idea of local versus global.[/QUOTE] I don't think you know what you're talking about. Even if you do, you can not make such a bold statement over all Lua scripters just like that. [editline]5th December 2011[/editline] And the only thing he didn't localize was his functions. You're nitpicking.
[QUOTE=agmike;33563087]I'm surprised no one else has chimed in this thread about how all the code posts are using globals when not necessary. This is bad and sloppy coding practice, ask any working professional. Python seems to be the only one with at least a concept of the idea of local versus global.[/QUOTE] It's quite funny really, you post everywhere all the time about how X doesn't know Y or how Y is shit at doing X and sucks at coding and should kill himself. On top of that you treat yourself like a programming god. let me set something straight to you. THIS IS HOBBY PROGRAMMING, NOT PROFESSIONAL, NOBODY GIVES A FUCK ABOUT HOW GOOD YOU ARE AND HOW SHITTY EVERYONE ELSE IS.
For those that are using this, I updated it. I did quite a few updates on the SVN without posting here whats going on. If you want to know what exactly changed look up the SVN History. For now [url]https://code.google.com/p/mattmodules/source/detail?r=55[/url]
Are you still working on this? If you bind to different IPs and use the same port then it complains that the address is in use.
Will add a optional reuse flag
Guys, how is it possible to create a UDP socket that replies whenever the data is received? For example, I've sent something like 'status' and what shall I do next? A json-encoded array should be returned (however, that's not big deal).
RecvFrom will trigger the callback if theres anything incomming, and you keep calling RecvFrom as long you want to receive more data then. Just look at all the plenty examples here.
No no no, I got that working. I've made the server read queries, how do I make it reply to them?
I'm trying to send data from PHP to a GMod server. It's working well when I'm using double quotes. However, if I use single quotes I get a "GLSOCK_ERROR_ADDRESSFAMILYNOTSUPPORTED" error. So my question is: Is this intentional or just a bug?
What the hell. This breaks some epic way in beta. [code] local asd,dat=data:Read(data:Size()-1) print("WTF",asd,dat) [/code]asd and dat are both numbers and are the same number and seem to be the string length, but where is the string now???
Seemslike the last update ruined some things, see tmysql3.
Could be that garry changed the interfaces, however if that is the case he would need to share them or I would need to reverse them (again) which sucks ass
As Wizard of Ass said, see [url=http://www.facepunch.com/threads/1184875?p=36008857&viewfull=1#post36008857]tmysql3[/url] [url]http://code.google.com/p/gmodmodules/source/diff?spec=svn190&r=190&format=side&path=/trunk/secretheaders/ILuaObject.h[/url]
My glsock crashes in gmod 13. Does it have to do with Wizard of Ass' comment that some things are fucked up? [url]http://dumps.garrysmod.com/?view=2816875439[/url] The crash occurs when I send text through my Gmod Android app.
Recompile it with the given Interface above and it will work again, atm busy.
Sorry, you need to Log In to post a reply to this thread.