Noobhub - Free Opensource Multiplayer And Network Messaging For Coronasdk

they fixed it in latest versions, since sometimes people get confused a s “node” refers to other absolutely not related package. 

Which OS do you get nodejs (as the binary name) working? It doesn’t work in CentOS…

burn@gaia:~$ uname -a

Linux gaia 3.5.0-17-generic #28-Ubuntu SMP Tue Oct 9 19:31:23 UTC 2012 x86_64 x86_64 x86_64 GNU/Linux

burn@gaia:~$ nodejs -v

v0.10.9

Did you build from source? I’m running v0.10.10 on three different servers (CentOS 6.4 on OpenVZ and KVM), all were built from source and only “node -v” works.

I am not sure if this is the right place for my issue.

I am not new to corona. But I am new to noobhub as well as socket handing.

We have been developing a card game and we have a script in the server to open the ports. When we pass a request from the device it checks if the port is open. The port opening script is called using a Cron Job every half an hour since the port closes automatically.

This script returns an error every time the port is already open. Due to this function being called every half an hour the server gets stuck due to the pilling up of these errors

<?php

$address = “108.xxx.xx.xxx”;            //server address//

$port = 9875;                                 //port to which client connects//

$max_clients = 100;                       //max clients at a time//

  $socket = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);        //creating a socket//

  socket_bind($socket, ‘0.0.0.0’, $port);                                                //binding the socket with port//

  $sock_data = socket_set_option($socket, SOL_SOCKET, SO_BROADCAST, 1);     

$from = ‘’;

$port = 0;

//recive the incomng data through the port until data is not received//

do {
$pkt=socket_recvfrom($socket, $input, 65535, 0, $from, $port); //$sockect = socket, $input = the data that is sent from the device, $from = Sender’s IP, $port = Sender’s port//
              
            if ($input == null)
            {
               
            }
            else
            {
            echo $input;

           socket_sendto($socket, $input, strlen($input), 0, $from, $port);  //reply to the client(device)//
   
        }
        }
    while ($pkt !== false);

?>

This is the error that is returned:

Warning: socket_bind() [function.socke

t-bind]: unable to bind address [98]: Address already in use in /home/xxx/public_html/portlisten/servertest.php on line 10
could not bind socket

Can anyone suggest a way to keep the port open so that this script does not need to be used. Or can anyone suggest a way to avoid the error that comes up if the connection is already set.

youre doing something with UDP, not TCP/IP.

check out sample php script which comes with noobhub. it works fine.

also, there are card games on appstore which successfully run on noobhub.

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.

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()

What kind of pings are you guys getting when you use Noobhub’s default server to test? I’m easily getting pings of 300-500 and after doing some research found out that nothing other than UDP should be used for real-time multiplayer games like FPS or fighting. I’m making a fighting game.

Is there an inherent limitation to Noobhub’s latencies and nothing I can do about it?

Thanks

Noobhub’s client is using a straightforward TCP socket connection. You can switch to udp by calling socket.udp() before socket.connect. This may have additional consequences as I have not tested noobhub’s client after doing this.

When I reworked noobhub’s client for my pusherhub client, my pings against pusher.com’s server(s) are consistent to my pings against google.com (about 25ms). There’s nothing in noobhub’s client (I am extremely familiar with it) to cause increased latency other than using rather large messages in the form of text and using TCP, which are negligible.

Since Noobhub routes connections through server (the noobhub server),  of course it increases latency from client to client.

That’s the price you pay to easily interact through any NAT (average wifi network for example) and all the advantages of publish/subscribe paradigm.

So it really depends on where your server is, and how far all clients are from it. Ideally, you should have a server in each important region, like, West Europe/ US West / US East / Asia, and before going into multiplayer game should decide where geographically user is and use corresponding server. And server better be with enough CPU because the one I provided for demo is really cheap and slow, it adds ~100 msec latency (dont remember exact value; a lot!) compared to the production one I use in the same region.

My average ping from client-to-client-and-back is about 100-150 msec, which is 50-75 msec of actual latency on message  delivery from one end to another. For me, its an appropriate value to do prediction and lag-compensation.

In case of UDP that’s a lot more difficult. You have to do a workaround to make it work through NAT, add layers of packets synchronize/acknowledge/resend on failure. TCP/IP does that for you.

And to reduce latency, also get rid of relay server, which means code really complicated p2p communication, and implement game logic on top of it. 

When I was making Noobhub, I kept speed 1st priority (as much as TCP and relay server can), and simplicity - 2nd.

Hope this helps.

Cheers!

PS. Related reading http://gafferongames.com/networking-for-game-programmers/what-every-programmer-needs-to-know-about-game-networking/

Another poster mentioned trying to use UDP earlier, but overtorment replied that you can’t use UDP with Noobhub, that’s just the way it is, but was wondering about the trade-offs. Also, random question but I’ve never heard of Pusher and just checked it out, what sort of ways could it help to use it for my Corona SDK game?

How much is it to rent a server for small-scale, say up to 1000 connections? I read about $2/month for VPS hosting, was wondering if you had any recommendations (my first time doing this).

I am getting around 160-180 ms of latency from using the ping command in my command line, and the same latency as well when launching two instances of my game on my simulator using the default server. I’m sitting at a Starbucks that has slower-than-average WiFi, so I guess this is normal? 

I opted for Noobhub since there seems to be more discussion and support going on as opposed to AutoLAN though it uses UDP.

Right now I’m trying to get the synchronizing between player positions to smooth out - player 2 moves his character using buttons on screen, but if I repeatedly send messages about his new x,y positions it ends up looking really laggy on player 1’s screen since the latency isn’t consistent. Do you have any tips for how to fix this, especially if the latency is high? Do I slow down the game itself based on the slowest player? 

P.S It was reading that article that led me to post here :P. I assume I’m currently faced with the problem of the  original Quake (Client-Server but with medium-high latency) that needs client-side prediction and lag compensation to fix?

> overtorment replied that you can’t use UDP with Noobhub, that’s just the way it is,

It’s the server that would be difficult (total rewrite?) to change to UDP. But if you use a server or service that supports UDP, you could rework the noobhub client.

Pusher.com is a message publish/subscribe relaying service that uses an HTTP(s) API or websocket. It’s ideal for chat services and probably more.

Hi, Overtorment

I am having troubles to keep a channel limited to 2 players. This would only happen if 2 players at the same time click to join another (third) player. What I do is I set in MongoDB a flag that a player is not anymore in his lobby channel. After a player clicks on connect I noobhub publish via Corona a check to see if the other player is still available. A callback gets sent back to Corona whether the other player is available.

Is it possible to close a socket connection on the server? Cause I can check how many players are in the same channel with the following code. But I am not sure how to close the connection on that player.

 // list all connections in the same Match channel, check if not more than 2 players are connected by accident 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 (sockets[socket.channel].hasOwnProperty(prop) && numberOfConnections \> 2) { // delete sockets[socket.channel][prop] - doesnt work console.log('\*\*\* To many players for a Match\_, need to remove prop: ' + prop + ' from channel: ' + socket.channel) } } }

…or what would be another way to make sure only 2 people can max connect to eachother out of a list of friends.

thx a lot