TCP socket handling for multiplayer game on NodeJS - node.js

I have a multiplayer game where my server uses nodejs and TCPSocket (net.createServer) to communicate with a client.
I have thousands of clients connecting to the server and many people are complaining that they are constantly being disconnected from the server.
Here is how my server handles the connections now:
Init:
var net = require("net");
server = net.createServer(function(socket) {
socket.setTimeout(15000);
socket.setKeepAlive(true);
socket.myBuffer = "";
socket.msgsQue = [];
socket.on("data", onData);
socket.on("error", onError);
socket.on("end", onClientDisconnect);
socket.on("timeout", onClientDisconnect);
});
server.listen(port);
Sending to client:
var stringData = JSON.stringify({name:event, message:data});
if (!this.socket.msgsQue || typeof this.socket.msgsQue == "undefined")
this.socket.msgsQue = [];
this.socket.msgsQue.unshift(stringData);
var i = this.socket.msgsQue.length;
while(i--) {
if (this.socket.writable) {
var elem = this.socket.msgsQue[i];
this.socket.write(elem+"\0");
this.socket.msgsQue.splice(i, 1);
} else {
//Unable to write at index "i"
break;//will send the rest on the next attempt
}
}
When disconnected
var onClientDisconnect = function() {
this.myBuffer = "";
delete this.myBuffer;
this.msgsQue = [];
delete this.msgsQue;
this.destroy();
}
Receiving from client
var onData = function(data) {
if (!data || !data.toString()) return;
var raw = data.toString();
this.myBuffer += raw;
var d_index = this.myBuffer.indexOf('\0'); // Find the delimiter
// While loop to keep going until no delimiter can be found
var string;
while (d_index > -1) {
// Create string up until the delimiter
string = this.myBuffer.substring(0,d_index);
// Cuts off the processed chunk
this.myBuffer = this.myBuffer.substring(d_index+1);
onMessage(string, this);//handle
// Find the new delimiter
d_index = this.myBuffer.indexOf('\0');
}
}
A problem I notice is that msgsQue becomes huge because the socket is not writable, but disconnect handler is not fired (or hired later..)
Can you give me some advises on how to optimize this ?
I noticed that sometimes I get disconnected, but I can ping the server, so it is definitely a server-related problem. Can it be because of high load on the server itself?
(I do not want to use socket.io because the last year I had many problems with it like memory leaking, freezing the server app, no support, etc..)

Related

Simple Access-Control-Allow-Origin with net module

I have two servers one on port 80 which is a normal web server and than one on port 8080 that is a broadcast server that sends a text string with all the updates to show. However the browser gives me a CORS header 'Access-Control-Allow-Origin' missing. And is it any simple way for me to add the header with the net package? The server code looks as following:
Code to create the server:
var net = require('net');
var fs = require('fs');
var buffer = require('buffer');
var serverHost = "0.0.0.0";
var serverPort = 8080;
var splitter = require('./splitter.js');
var server = net.createServer(function(target) {
// Socket errors
target.on('error', function(error) {
console.log('Socket error: ', error.message);
});
target.on('data', function(data){
// TODO: Verify the data
var number = parseInt(data);
if(!isNaN(number)){
// Adds the client to the list
splitter.addClient(target, number);
}
else{
target.write("Match ID is not correctly formatted");
}
});
});
// Listening for any problems with the server
server.on('error', function(error) {
console.log("Error listening to the server!", error.message);
});
server.listen(serverPort, serverHost);
splitter code (Add client and send data):
var gameids = {1337:[]}
function addClient(client, gameID){
if(matchExists(gameID)){
console.log("Client", client.remoteAddress, "begun watching gameID", gameID,"\n");
var id = gameids[gameID].push(client);
}
else{
client.write("A match with the given ID does not exists");
}
}
function sendData(data, gameID){
if(gameids[gameID].length > 0){
for(i = 0; i < gameids[gameID].length;i++){
console.log(gameids[gameID][i]);
if(gameids[gameID][i].writable){
console.log("Sent data");
gameids[gameID][i].write(data);
}
}
}
}
The function send data is called from my main script which just takes the input from another server and then sends that out to all clients watching.

node.js server not always clearing data from previous run

I'm brand new to node.js (or javascript in general) and not sure what's going on here. Sometimes when I run my node.js server, right when it starts up it begins printing data from the last time it ran. Is there something that could be keeping everything from resetting on a new run? I have arduino XBee nodes connecting to this server. Here's my node.js code:
var SerialPort = require("serialport");
var app = require('express')();
var http = require('http').Server(app);
var io = require('socket.io')(http);
var ON_DEATH = require('death');
// Set up serial port
var portName = process.argv[2],
portConfig = {
baudRate: 9600,
parser: SerialPort.parsers.readline("\n")
};
var sp = new SerialPort(portName, portConfig); // got rid of deprecation issue with sp4
// Respond with file when a GET request is made to the homepage: app.METHOD(PATH, HANDLER) '/'=homepage
app.get('/', function(req, res){
res.sendfile('index.html');
});
// Listen on the connection event for incoming sockets, and log it to the console.
io.on('connection', function(socket){
console.log('a user connected');
socket.on('disconnect', function(){
});
socket.on('chat message', function(msg){
io.emit('chat message', msg);
sp.write(msg + "\n");
});
});
// Make the http server listen on port 3000
http.listen(3000, function(){
console.log('listening on *:3000');
});
/////////////////////////////////////////////////////////////////////////////////////////////////
sp.on("open", function (err) {
if (err)
return console.log('Error opening port: ', err.message);
console.log('open');
// INFO VARIABLES
//var nodeCount = 0;
var pendingNodes = [];
var connectedNodes = [];
var buffer0;
var nodeInfo;
// Cleanup when termination signals are sent to process
ON_DEATH(function(signal, err) {
var death_msg = "Q";
sp.write(death_msg);
sp.close();
// zero out all variables
pendingNodes = [];
connectedNodes = [];
buffer0 = [];
nodeInfo = [];
console.log("\n\nSending reset signal to nodes.\n\n")
sp.flush(function(err,results){});
process.exit();
})
// listen for data, grab time, delimit
sp.on('data', function(data) {
var time = new Date();
buffer0 = data.split('\n'); // array of data till newline
// Split again into either "#ID" into [#ID] (if new broadcast), or "#ID Data" into [#ID, Data] (preconnected node)
nodeInfo = buffer0[0].split(' ');
//console.log(" nodeInfo: " + nodeInfo);
// DEBUGGING PRINT -- DELETE LATER
//console.log(" pendingNodes: " + pendingNodes + " connectedNodes: " + connectedNodes);
////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Receiving ID or [ID, Data] -- Testing all possible cases
// if first char
if (nodeInfo[0][0] == "#") { // could check up here for && no temp data too
// Brand new node (ID not in pendingNodes or connectedNodes)
if ((pendingNodes.indexOf(nodeInfo[0]) == -1) && (connectedNodes.indexOf(nodeInfo[0]) == -1)) {
console.log("Brand new node: " + nodeInfo[0])
pendingNodes.push(nodeInfo[0]);
}
// Pending node (ID in pendingNodes, but not in connectedNodes)
else if ((pendingNodes.indexOf(nodeInfo[0]) != -1) && ((connectedNodes.indexOf(nodeInfo[0]) == -1))) {
console.log(" Pending node: " + nodeInfo[0])
// send back ID to confirm handshake
sp.write(nodeInfo[0]);
// Remove from pendingNodes
var index = pendingNodes.indexOf(nodeInfo[0]);
if (index > -1) {
pendingNodes.splice(index, 1);
}
// Add to connectedNodes
//var
connectedNodes.push(nodeInfo[0]);
}
// Connected node (ID in connectedNodes, but not in pendingNodes)
else if ((pendingNodes.indexOf(nodeInfo[0]) == -1) && (connectedNodes.indexOf(nodeInfo[0]) != -1)) {
console.log(" Connected node: " + nodeInfo[0] + " " + nodeInfo[1]);
// Disconnect nodes with undefined temperatures to force restart
if (typeof nodeInfo[1] == 'undefined') {
console.log("Undefined data for node: " + nodeInfo[0] + ". Dropping connection to this node.");
var index = connectedNodes.indexOf(nodeInfo[0]);
if (index > -1) {
connectedNodes.splice(index, 1);
}
}
//var t = time.getTime();
// FIRST: fix bug -- sometimes temp showing as undefined
// Then:
// Update data properly
// Average data
// add 3rd element to nodes for time stamp, check that all timestamps are recent periodically
// Detect drop-offs and remove nodes
}
// Error (ID in both pendingNodes and connectedNodes [should not happen])
else {
console.log("Error: node exists in both pending and connected.")
}
}
else if (nodeInfo[0][0] != "#") {
// FOR DEBUGGING PURPOSES, DELETE LATER
console.log("Error 2: incoming data does not start with '#'.")
}
/* // placeholders for functionality to add
var CalcAverage = function() {
console.log("CalcAverage: * every 1 second(s) *");
}
setInterval(function(){ CalcAverage() },2000);
var CheckNodes = function() {
console.log("CheckNodes: * every 6 second(s) *");
}
setInterval(function(){ CheckNodes() },6000);
*/
}); // end data
}); // end open
And here's an example of old node data showing up somehow:
Some possibilities:
Old (or new data that you didn't know it sent) data from clients has been buffered somehow on the serial port, or socket.io auto-reconnects on the client browsers and starts sending data even though you didn't interact with the client browser web page.

ZeroMQ: How to notify a Publisher from a Subscriber

I use PUB/SUB ZeroMQ pattern.
System consists from Web Server ( Publisher ), clustered TCP servers ( Subscribers ) and external applications ( clients, which connect to TCP servers ).
Huge amount of external clients connect to every TCP server. Every external client has unique peerId which I use as topic in Publisher. For some management purposes I send messages to TCP servers ( like remove peer, change, etc. ). But also I need to send messages from TCP server to Web Server ( connect, disconnect, error ). I didn't find right way how to do it. Can anybody suggest how to do it correctly?
Update 1
It looks like using ROUTER/DEALER pattern is the most convenient for that.
Some comments about scripts.
External clients connect to tcp servers ( cluster ) and send unique peerId, on tcp server side tcp socket cached by unique peerId. Then tcp server sends peerId message by ZeroMQ socket to Web Server. Web Server caches envelope by peerId. Every n milliseconds Web Server sends messages to random peer ( generate 'peerId' ). TCP Server receives these messages, gets correct tcp socket from cache and sends theirs to clients. Clients calculate count of messages and every n milliseconds send their to TCP server, which sends count to WEB Server by ZeroMQ socket. On Web Server every n milliseconds count of sended and received messages are printed on console.
Test js script of server part:
var cluster = require('cluster'),
zmq = require('zmq'),
net = require('net'),
zmqport = 'tcp://127.0.0.1:12345';
var count = 10;
var countPeers = 10000;
var interval = 1;
if (cluster.isMaster) {
for (var i = 0; i < count; i++) cluster.fork({
TCP_SERVER: 1
});
cluster.fork({
WEB_SERVER: 1
});
cluster.on('death', function (worker) {
console.log('worker ' + worker.pid + ' died');
});
} else {
if (process.env.TCP_SERVER) {
var sockets = Object.create(null);
var socket = zmq.socket('dealer');
socket.identity = 'process-' + process.pid;
socket.connect(zmqport);
socket.on('message', function (peerIdBuffer) {
var peerId = peerIdBuffer.toString();
if (typeof sockets[peerId] !== 'undefined') {
var buffer = new Buffer(4);
buffer.writeUInt32BE(1, 0);
sockets[peerId].write(buffer);
}
});
var server = net.createServer(function (tcpsocket) {
tcpsocket.on('data', function (data) {
if (!tcpsocket.peerId) {
var peerId = data.toString();
sockets[peerId] = tcpsocket;
tcpsocket.peerId = peerId;
return socket.send(['id', data]);
}
return socket.send(['count', data]);
});
});
server.listen('13333', '0.0.0.0');
} else {
var countMessagesSended = 0;
var countMessagesReceived = 0;
var socket = zmq.socket('router');
var clients = Object.create(null);
socket.bind(zmqport, function (err) {
if (err) throw err;
setInterval(function () {
for (var i = 0; i < countPeers; i++) {
var topic = Math.floor(Math.random() * countPeers) + '-peer';
if (typeof clients[topic] !== 'undefined') {
countMessagesSended++;
socket.send([clients[topic], topic]);
}
}
}, interval);
});
socket.on('message', function (envelope, messageId, data) {
switch (messageId.toString()) {
case "id":
clients[data.toString()] = envelope.toString();
break;
case "count":
countMessagesReceived += data.readUInt32BE(0);
break;
}
});
setInterval(function () {
console.log('%s messages have been sended, %s - received', countMessagesSended, countMessagesReceived);
countMessagesSended = 0;
countMessagesReceived = 0;
}, 5000);
}
}
Test js script for clients:
var cluster = require('cluster'),
net = require('net');
var count = 10;
if (cluster.isMaster) {
for (var i = 0; i < count; i++) cluster.fork({
CLUSTER: i
});
cluster.on('death', function (worker) {
console.log('worker ' + worker.pid + ' died');
});
} else {
var clientspernode = 1000;
var offset = parseInt(process.env.CLUSTER, 10);
for (var j = (offset) * clientspernode; j < (offset + 1) * clientspernode; j++) {
(function (j) {
var countMessages = 0;
var client = net.connect({
port: 13333,
host: '127.0.0.1'
}, function () {
client.write(j + '-peer');
});
client.on('data', function (buffer) {
countMessages += Math.ceil(buffer.length / 8);
});
client.on('error', function () {
});
setInterval(function () {
var buf = new Buffer(4);
buf.writeUInt32BE(countMessages, 0);
client.write(buf);
countMessages = 0;
}, 5000);
})(j);
}
}

AS3 XMLSocket sends data from all clients started, but data recieved only by last client connected

AS3 XMLSocket sends data from all clients started, but data recieved only by last client connected.
I have the flash web client, and if you open for example 2 or more tabs with the app, every client will send the data to socket server, but only THE LAST client connected gets all the data. Here is the link http://151.248.124.213/. It has chat alike interface for now and green button is the SEND button. App gets connected when you hit stage with the mouse. App is connected when the message Connected appears in the screen. To test http://151.248.124.213/ just open 2 or more tabs.
Here is the AS3 code:
Security.loadPolicyFile("xmlsocket://151.248.124.213:3843");
var socket:XMLSocket;
stage.addEventListener(MouseEvent.CLICK, doConnect);
function doConnect(evt:Event):void
{
stage.removeEventListener(MouseEvent.CLICK, doConnect);
socket = new XMLSocket("151.248.124.213", 3000);
socket.addEventListener(Event.CONNECT, onConnect);
socket.addEventListener(IOErrorEvent.IO_ERROR, onError);
}
function onConnect(evt:Event):void
{
trace("Connected");
output_txt.text = "Connected\n";
socket.removeEventListener(Event.CONNECT, onConnect);
socket.removeEventListener(IOErrorEvent.IO_ERROR, onError);
socket.addEventListener(DataEvent.DATA, onDataReceived);
socket.addEventListener(Event.CLOSE, onSocketClose);
}
function onSocketClose(evt:Event):void
{
trace("Connection Closed");
socket.removeEventListener(Event.CLOSE, onSocketClose);
socket.removeEventListener(DataEvent.DATA, onDataReceived);
}
function onError(evt:IOErrorEvent):void
{
trace("Connect failed");
socket.removeEventListener(Event.CONNECT, onConnect);
socket.removeEventListener(IOErrorEvent.IO_ERROR, onError);
}
function onDataReceived(evt:DataEvent):void
{
try {
trace( "From Server:", evt.data );
var msg = evt.data;
output_txt.text += msg + "\n";
}
catch (e:Error) {
trace('error');
}
}
send_btn.addEventListener(MouseEvent.CLICK, send_btn_clicked);
function send_btn_clicked(evt:MouseEvent):void
{
var msg = input_txt.text;
socket.send(msg);
input_txt.text = "";
}
And here is the server code:
var express = require('express');
var app = express.createServer();
app.configure(function () {
app.use(express.static(__dirname));
})
app.listen(80);
var net = require('net');
var mySocket;
var server = net.createServer(function(socket) {
mySocket = socket;
mySocket.on("connect", onConnect);
mySocket.on("data", onData);
});
server.listen(3000);
function onConnect()
{
console.log("Connected to Flash");
}
function onData(d)
{
if(d == "exit\0")
{
console.log("exit");
mySocket.end();
server.close();
}
else
{
console.log("From Flash = " + d);
mySocket.write(d, 'utf8');
}
}
You have to create one socket per client on server side.
Each time a new client is connected, create a new socket. look here for an example.

event handling not working as expected nodejs

writing this little domain search app, it should sequentially search the .com of each item in an array, but it keeps searching for test1. even if I do a console log within the search function it tells me the value of x is test2, and test 3. do I need to remove the listener or something?
I get the following output
domain test1.com
Domain Name: TEST1.COM
domain test2.com
Domain Name: TEST1.COM
domain test3.com
Domain Name: TEST1.COM
app.js
var port = 43;
var net = require('net');
var host = 'whois.internic.net';
var dotCom = new net.Socket();
var c = 0;
var connections = 0;
var dotComStatus;
dotCom.setEncoding('ascii');
var searches = ['test1', 'test2', 'test3'];
search(searches.shift());
function chkconnections(z) {
if (connections <= 0) {
if (searches.length >= 1) {
process.nextTick(function() {
search(searches.shift());
});
}
}
}
function search(x) {
var q = "domain " + x + ".com\r\n";
dotCom.connect(port, host, function() {
dotCom.write(q);
console.log(q);
connections++;
});
dotCom.on('data', function(data) {
c++;
if (c == 2) {
dotComStatus = data.split('\n')[1];
dotCom.on('close', function() {
console.log(dotComStatus);
connections--;
chkconnections();
});
}
});
}
There are several obvious problems with this code. Firstly putting the close event inside the data event is a bad idea. If the connection closed before data was received that section of code would never be reached.
Next is there is a big problem with the section with
c++;
if (c == 2)
Since you never reset c to 0 the next line dotComStatus = data.split('\n')[1]; is never executed. But then the socket closes and the event closed is triggered. And this is executed again.
console.log(dotComStatus);
connections--;
chkconnections();
But the value of dotComStatus has not changed since c was equal to 0. There are many examples of how to do this connect/data/end flow that is common in NodeJS.
var port = 43;
var net = require('net');
var host = 'whois.internic.net';
var searches = ['test1', 'test2', 'test3'];
search(searches.shift());
function chkconnections(z) {
if(searches.length > 0)
search(searches.shift());
}
function search(x) {
var dotCom = new net.Socket();
dotCom.setEncoding('ascii');
var q = "domain " + x + ".com\r\n";
dotCom.connect(port, host, function() {
dotCom.write(q);
});
var data = ""; // holding place until socket closes
dotCom.on('data', function(chunk) {
data += chunk; // add chunk to data
});
dotCom.on("end", function() {
// socket closed
dotComStatus = data.split('\n')[7]; // Should be 'Domain Name: blah'
console.log(dotComStatus);
chkconnections(); // move on to next
});
};

Resources