Noobhub - Free Opensource Multiplayer And Network Messaging For Coronasdk

How do I pair basic http/s authentication and paths with socket.connect? I would think this is the common case for websockets, but I could be wrong. I’ve tried to play with Noobhub to get it to work but I’ve not made much progress.

I have a url that includes the basic auth:

  myuser:mypass@someuri.com/path 

and I really don’t understand how to make a good connection from that. Noobhub is happy to make a socket to someuri.com but after that it’s not clear how I’m supposed to negotiate the rest of it.

Anyone have any ideas?

Ok, I fixed my problem by simply sending a message to Corona from noobhub server. Not the most elegant way, but it works and it allmost never happens.

if (socket.channel.substr(0, 6) == 'Match\_') { console.log('\*\*\* Match channel number of player check. Connections in the same channel:'); var numberOfConnections = 0 for (var prop in sockets[socket.channel]) { numberOfConnections++; console.log(numberOfConnections + '. channel: ' + socket.channel + ', conn\_id (key) is: ' + prop + '. Value is: ' + sockets[socket.channel][prop]); } // if to many players, notify each client to cancel the match if (numberOfConnections \> 2) { var message = { "action":"ToManyPlayers\_in\_the\_channel" }; message = JSON.stringify(message); for (var prop in sockets[socket.channel]) { if (sockets[socket.channel].hasOwnProperty(prop)) { sockets[socket.channel][prop].write("\_\_JSON\_\_START\_\_" + message + "\_\_JSON\_\_END\_\_"); } } } } }

Thank you overtorment for this reply. Yes its UDP and we are using the same php script that come with noob hub. Please advise.

Thanks in advance :slight_smile:

Well, UDP wont work. Noobhub is TCP/IP library only.

Thats impossible. Youre trying HTTP auth, and HTTP is a protocol on top of TCP/IP. Noobhub is TCP/IP library only.

Think of authorisation INSIDE of Noobhub interaction.

hey overtorment,

Is it possible to get the server time from node server.

thanks in advance!

Thats impossible. Youre trying HTTP auth, and HTTP is a protocol on top of TCP/IP. Noobhub is TCP/IP library only.

Think of authorisation INSIDE of Noobhub interaction.

I don’t see how it’s possible to do auth inside of the default noobhub. Noobhub starts with a __SUBSCRIBE__ , there’s no chance for authentication. Doesn’t your basic authentication header have to come before your are authorized to subscribe to a channel? Then after that it’s doing __JSON ?

You can make auth service on php. 

Say, client connects to auth channel where he interacts with auth service, and gets information crutial to proceed.

That’s…not going to work with vendors.

I’m looking at

http://en.wikipedia.org/wiki/WebSocket and

http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-13#page-21  (newest Websocket protocol, used by Chrome)

I’ve mostly finished for implementing RFC6455 and integrated with pusher.com -

-------------------- -- PusherHub -- opensource websocket messaging for CoronaSDK and Pusher.com -- -- Authors: Jack9 -- -- License: WTFPL -------------------- local socket = require("socket") local mime = require("mime") local crypto = require("crypto") local self = { readyCallback = nil, server = nil, port = nil, headers = '', key = nil, secret = nil, app\_id = nil, sock = nil, buffer = '', channels = {}, readyState = 3 --0 (connecting), 1 (open), 2 (closing), and 3 (closed). } self.lpad = function(str, len, char) str = tostring(str) if char == nil then char = ' ' end return str .. string.rep(char, len - #str) end self.tobits = function(num) -- returns a table of bits, least significant first. local t={} -- will contain the bits while num\>0 do rest=math.fmod(num,2) t[#t+1]=rest num=(num-rest)/2 end return string.reverse(table.concat(t)) end self.handleBytes = function(byes) --print("some binary msg",byes) local chrs = {} local chr for i=1,string.len(byes) do chr = byes:byte(i) chrs[#chrs+1] = chr end --print(util.xmp(chrs)) return chrs end self.makeFrame = function(str) -- UGLY HARD PART - Assemble Websocket Frame Header -- see http://tools.ietf.org/html/rfc6455#section-5 (5.2) local bitGroups = {} local binStr = "1" -- FIN, why not? 1 binStr = binStr.."000" -- RSV1,RSV2,RESV3, -- the 'Sec-WebSocket-Extensions: x-webkit-deflate-frame' + RSV1 doesn't have an effect binStr = binStr.."0001" -- %x1 denotes a text frame (I guess this means 0001) - confirmed bitGroups[#bitGroups+1] = binStr -- we dump the value and have a byte binStr = "0" -- Not using a mask and starting over on the binStr local strLen = string.len(str) -- message length in bytes --print("strLen",strLen) local pad = 7 -- Spec says default of 7 -- This is a little confusing. spec says ... determine how many bits you need BEFORE assembling. -- If our strLen is over 125, we have to IGNORE the initial 7 because they become a placeholder value. -- If strLen is 126-65536 we can use 16 bits and leave the initial 7 at a placeholder of 126 -- If strLen is more, we leave the initial 7 at 127 and use 64 bits -- use those NEW bits for sizing. if strLen \> 125 then if strLen \<= 65536 then binStr = binStr.."1111110" -- 16 bit time pad = 16 else binStr = binStr.."1111111" -- 64 bit time! pad = 64 end end --print(strLen,self.tobits(strLen)) binStr = binStr..self.lpad(self.tobits(strLen),pad,"0") -- 7 or 7+16 or 7+64 bits to represent message byte length local s = 1 local e = 8 while e \< pad+9 do bitGroups[#bitGroups+1] = string.sub(binStr,s,e) -- we dump the value and have another byte s = s+8 e = e+8 end -- There are many ways to go from here. I chose a way that works. Can probably be improved. local ret = '' for i=1,#bitGroups do -- Now that we've assembled a delicate set of bits, move to bytes ret = ret..string.char(tonumber(bitGroups[i],2)) end --print(table.concat(bitGroups)) -- Leading bytes are the header for the frame. Ta-da return ret..str end self.makePongFrame = function() local char1bits = "10001010" -- this is a pong frame local char2bits = "00000000" -- this is empty in a pong frame local char1byte = tonumber(char1bits,2) -- clean! local char2byte = tonumber(char2bits,2) -- clean! local ascii1 = string.char(char1byte) -- tricky! local ascii2 = string.char(char2byte) -- tricky! -- Leading bytes are the header for the frame. Ta-da return ascii1..ascii2 -- this is a pong frame \*shrug\* end self.websocketresponse6 = function(key) -- This string is hardcoded per websocket specification local magic = key .. "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" return (mime.b64(crypto.digest(crypto.sha1, magic, true))) end self.new = function (params) -- constructor method params = params or {} params.port = params.port or 80 if not params.server or not params.key or not params.secret or not params.app\_id or not params.readyCallback or type(params.readyCallback) ~= "function" then print("PusherHub requires server, key, secret, app\_id, readyCallback function and defaults to port 80") self.err = "Invalid configuration" return self end -- Headers are legitimate, since Chrome uses them params.headers = params.headers or { 'GET /app/'..params.key..'?protocol=6&client=lua&version=1.0&flash=false HTTP/1.1', 'Host: '..params.server, 'Sec-WebSocket-Key: '..self.websocketresponse6(params.app\_id), -- anything is fine, might as well use the app\_id for something 'Upgrade: websocket', 'Connection: Upgrade', 'Sec-WebSocket-Version: 13', 'Pragma: no-cache', --'Authentication: Basic '..(mime.b64(params.key..":"..params.secret)), 'Origin: http://jsonur.com', --'User-Agent: Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.116 Safari/537.36', -- headers based on Chrome 'Sec-WebSocket-Extensions: x-webkit-deflate-frame', 'Cache-Control: no-cache', } self.readyCallback = params.readyCallback self.server = params.server self.port = params.port if params.headers ~= nil then for i=1,#params.headers do self.headers = self.headers..params.headers[i].."\n" end end self.headers = self.headers.."\n" self.key = params.key self.secret = params.secret self.app\_id = params.app\_id self.sock = socket.tcp() if( self.sock == "nil" or self.sock == nil) then self.err = "no connection" return self end self.sock:settimeout(3) -- minimum 1 !!! self.sock:setoption( 'tcp-nodelay', true ) -- Doesn't seem to hurt self.sock:setoption( 'keepalive', true ) -- Doesn't seem to hurt self.readyState = 0 local \_,erro = self.sock:connect(self.server, self.port) if erro then self.err = "Problem resolving servername, contacting server, or accessing port:"..erro self.sock:close() return self end -- Check if headers are good --print(self.headers.."\r\n") local bytesSent = self.sock:send(self.headers) --print("sent bytes:",bytesSent) self.origParams = params return self end self.subscribe = function(params) params.channel = params.channel or 'test\_channel' params.private = false if params.channel\_data and (string.sub(params.channel,1,8) == "private-" or string.sub(params.channel,1,9) == "presence-") then params.private = true end self.channels[params.channel] = { -- this could get ugly if you resubscribe with new events events = params.bindings } local msg = { ["event"] = "pusher:subscribe", ["data"] = { ["channel"] = params.channel, }, } if params.private then --pusher.com docs say HMAC::SHA256.hexdigest(secret, string\_to\_sign) local string\_to\_sign = self.id..":"..params.channel local auth = self.key..":"..crypto.hmac( crypto.sha256, string\_to\_sign, self.secret ) msg["data"]["auth"] = auth msg["data"]["channel\_data"] = json.encode(params.channel\_data) end msg = json.encode(msg) --print(msg) self.publish(msg) return true end self.unsubscribe = function(chan) -- Wipe out bindings, you won't get to see your own parting for i=1,#self.channels[chan]["events"] do self.channels[chan]["events"][i] = nil end self.channels[chan] = nil local msg = { ["event"] = "pusher:unsubscribe", ["data"] = { ["channel"] = chan, }, } msg = json.encode(msg) self.publish(msg) end -- Untested self.reconnect = function() print("PusherHub: attempt to reconnect...") self = self.new(self.origParams) Runtime:addEventListener('enterFrame', self) return self end self.publish = function(msg) print("publishing",msg) local num\_byes = self.sock:send(self.makeFrame(msg)) --print("bytes published",num\_byes) return true end self.enterFrame = function(evt) local chr, str local chrs = {} local got\_something\_new = false local skt, e, p local input,output = socket.select({ self.sock },nil, 0) -- this is a way not to block runtime while reading socket. zero timeout does the trick for i,v in ipairs(input) do ------------- while true do skt, e, p = v:receive() print("buf",skt,e,p) if e == "closed" then self.readyState = 3 self.valid = false Runtime:removeEventListener('enterFrame',self) self.readyState = 4 break end if (p) then --print("p",p) -- Websocket dead due to error local f,l = string.find(p,"Unknown opcode 11") if f ~= nil then self.readyState = 3 print("frame erro",p) Runtime:removeEventListener('enterFrame',self) self.valid = false self.readyState = 4 break end if p ~= '' then got\_something\_new = true self.buffer = self.buffer..p end break elseif (e) then --print("e",e) self.readyState = 3 print("websocket closing",e) Runtime:removeEventListener('enterFrame',self) self.valid = false self.readyState = 4 break elseif (skt) then --print("skt",skt) end end -- /while-do -- now, checking if a message is present in buffer... if got\_something\_new then -- this is for a case of several messages stocker in the buffer --print("somethingnew",self.buffer) local msg -- Startup Connection parsing if not self.valid then local startIdx,endIdx = string.find(self.buffer, '{"event":"pusher:connection\_established","data":"{\\"socket\_id\\":') if startIdx ~= nil and startIdx \> 0 then msg = string.sub(self.buffer,startIdx) msg = json.decode(msg) msg = json.decode(msg["data"]) self.id = msg.socket\_id self.valid = true self.readyState = 1 self.readyCallback() -- should call subscribe, I hope! end end -- Standard message (some bytes header, then json) local b,e = string.find(self.buffer, "{") if b ~= nil then bins = string.sub(self.buffer,1,b-1) chrs = self.handleBytes(bins) msg = string.sub(self.buffer,b)-- have to remember to strip off those frame header bytes! --print(msg) msg = json.decode(msg) if msg.event == "pusher\_internal:subscription\_succeeded" then if type(self.channels[msg.channel]["events"][msg.event]) == "function" then self.channels[msg.channel]["events"][msg.event](msg) end elseif msg.event == "pusher:subscription\_error" then print("sub err") if self.channels[msg.channel] ~= nil and type(self.channels[msg.channel]["events"][msg.event]) == "function" then self.channels[msg.channel]["events"][msg.event](msg) end elseif msg.event == "pusher:error" then print("Nonfatal Err:",msg.data.message) elseif self.channels[msg.channel] ~= nil and type(self.channels[msg.channel]["events"][msg.event]) == "function" then self.channels[msg.channel]["events"][msg.event](msg) end else chrs = self.handleBytes(self.buffer) if chrs[1] == 137 and chrs[2] == 0 then -- this is a ping print("heard ping...sending pong as keepalive") local byes = self.sock:send(self.makePongFrame()) --print("bytes sent:",byes) end end got\_something\_new = false self.buffer = '' end end end -- /enterFrame Runtime:addEventListener('enterFrame', self) return self --[[-- Example Usage print("connecting to chat server...") mychathub = nil -- global mychathub = pusherhub.new({ app\_id = '12345', -- Example key = '278d425bdf160c739803', -- Example http://pusher.com/docs/auth\_signatures secret = '7ad3773142a6692b25b8', -- Example http://pusher.com/docs/auth\_signatures server = "ws.pusherapp.com", port = 80, readyCallback = function() print("Connected to chat server.") print("Attempting to join Gen Chat...") mychathub.subscribe({ channel = "test\_channel", bindings = { ["client-message"] = function(msg1) print("test client-message",msg1) end, ["pusher\_internal:subscription\_succeeded"] = function(msg2) -- Msg2 is a table print("test pusher\_internal:subscription\_succeeded",msg2.event) print("Joined Gen Chat.") end } }) end }) ]]--

Caveats: Using a sandbox (free) account, you don’t want to try a private- or presence- channel. It wont respond to a ping correctly and will close out the connection. You cannot use a secure connection in a sandbox (free) account. The pusher docs are… hard to get through http://pusher.com/docs because the API/Websocket information is mixed together.

Available @ https://github.com/jack9/pusherhub

My question is not related to noobhub directly, but to node.js.

I did some modifications to the original noobhub server and noticed that the memory increased after more users connected to the server.

I downgraded my version to the original one from github, and still the same problem.

I finally tested with this minimal code, and the memory usage is still increasing with the number of connection (doesn’t matter if they are active or closed).

var server = require('net').createServer() , sockets = {} // this is where we store all current client socket connections , cfg = { port: 1337, buffer\_size: 1024\*8, // buffer is allocated per each socket client verbose: false } , \_log = function(){ if (cfg.verbose) console.log.apply(console, arguments); }; // black magic process.on('uncaughtException', function(err){ \_log('Exception: ' + err); }); server.on('connection', function(socket) { socket.destroy(); socket = null; if(true) return; }); // end of server.on 'connection' server.on('listening', function(){ console.log('NoobHub on ' + server.address().address +':'+ server.address().port); }); server.listen(cfg.port);

Is there some other reason that I miss out why the memory increases?

Since there’s been so much talk about HTTP Authentication lately, can anyone explain to me what it is(I have a limited understanding atm) and what it’s purpose would be, especially in noobhub? I’m just curious to learn more about it.

@beckslash Have you looked at the log files(if any) that are being stored or recorded on your server? There could be some message there you are missing. I’ve been using Noobhub for 9-10 months, since @overtorment became the online hero and posted a link about it in the failing Autolan forum thread :P, and i’ve never had any issue at all with memory, my memory usage is always very low. I have two live apps using Noobhub running on two separate Linux 11.04 installations being kept alive using forever, and I haven’t ran into any issues at all. I would take a look at those logs if any exist and also see if there’s any error with the way you have node installed or something along those lines, because the problem most likely isn’t coming from noobhub itself. That’s my 2 cents.

> Since there’s been so much talk about HTTP Authentication lately

Mostly from me, I think.

Overloading a server with sockets, flooding a server with messages, etc is common abuse when you don’t pair raw sockets with authentication.

Noobhub uses a psuedo-websocket handshake.

"\_\_SUBSCRIBE\_\_"..self.channel.."\_\_ENDSUBSCRIBE\_\_"

Websockets are essentially an HTTP connection and are authenticated similarly. This means you usually pass headers as part of the HTTP handshake (common case, see http://tools.ietf.org/html/rfc6455#section-10.5)

Hence, I wondered if there was an easy way to push headers via noobhub and there is. Pass them as a table in params.headers in new() assemble them during connection, pass them in via send()

Anyone hosted Noobhub on VPS but couldn’t connect to it?

In one instance, I was getting the “connection error” messages in Corona SDK, but after I opened all the IP ports in iptables it seemed to work, yet what is supposed to happen normally (where I can open two emulators and connect through the sample Noobhub server) does not occur anymore. 

EDIT:  Nevermind, it didn’t work when I tried forever start node.js -v but it works with just plain old node node.js with all the ports open.

Hi

overtorment ,

We are going to develop a multiplayer card game using Unity3D and want use to  NOOBHUB

Do you have in C# code ( Client Side ) for the NoobHub instead of JS ??

Regards,

Gokul

I was going to code it, but Im too rusty with Unity3D.

Anyway, client lib is extremely simple, check out lua client. Just make your own client for Unity!

And dont forget to share it, its opensource after all :slight_smile:

Trying to host this on Heroku.

Td3VXVO.png

But I get the following log output.

I have already changed the last line in node.js to server.listen(cfg.port || process.env.PORT);

Anyone have luck with this?

I dont think heroku provides you dedicated public IP.

Better use simple VDS. I recommend https://www.digitalocean.com/ this is where I host all my instances.

I have VPS set up at DigitalOcean as well (with two other servers, see below). One thing to keep in mind with them is they only offer KVM and it requires more memory overhead than OpenVZ in general. So make sure you get a plan with more RAM (IMHO at least 1GB for KVM). I’ve seen certain tasks fail with 512MB.

Other VPS providers I use who offer both OpenVZ and KVM are:

RamNode: https://clientarea.ramnode.com/aff.php?aff=552 (affiliate link, use LEB35 code for 35% recurring off)

Prometeus.nethttp://www.prometeus.net (in Italy)

They all provide great service so far.

Cheers,

Dave

I’m actually currently hosting it on a cheap $15/year VPS at https://liquid-solutions.biz/, it works, but was wondering if Heroku servers would be faster latency wise. Thanks for the suggestions

In order to decrease latency even more, I would recommend renting dedicated hardware server, instead of virtual one.

I’m new to this, do you mind telling me the differences between these options and what the jargon means? How does the RAM get used for my game in general?