Skip to content

Instantly share code, notes, and snippets.

@dawsontoth
Created January 14, 2014 03:02
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save dawsontoth/8412318 to your computer and use it in GitHub Desktop.
Save dawsontoth/8412318 to your computer and use it in GitHub Desktop.
NodeJS server side simple JSON based socket communications. The Titanium client side counterpart to this is here: https://gist.github.com/dawsontoth/8412167
// We'll use cluster to split work across all of our CPUs.
var cluster = require('cluster');
// Are we the master process ...
if (cluster.isMaster) {
// Monitor the workers for failures.
cluster.on('exit', function(worker) {
console.log('Worker ' + worker.process.pid + ' died. Forking another...');
cluster.fork();
});
// Fork one worker for each CPU.
var numCPUs = require('os').cpus().length;
for (var i = 0; i < numCPUs; i++) {
cluster.fork();
}
console.log('Forked ' + numCPUs + ' workers.');
}
// ... or are we the child process?
else {
require('./server').boot([ 3001 ]);
}
/*
Public API.
*/
exports.foo = foo;
exports.bar = bar;
exports.server;
/*
Implementation
*/
function foo(socket, data) {
console.log('foo: Got ' + data + ' from the client.');
}
function bar(socket, data) {
console.log('bar: Got ' + data + ' from the client.');
exports.server.respond(socket, 'bar', {
success: true,
description: 'The server received ' + data + ' from the client!'
});
}
/**
* This is the protocol that Lox uses to communicate.
*
* Payloads follow the following format:
* command{new line}payloadLength{new line}JSON payload
* For example:
* echo\n30\n{ "message": "Hello, world!" }
*
* Version: 1.0.0
*/
var socketData = {};
/*
Public API.
*/
exports.toPacket = toPacket;
exports.fromPacket = fromPacket;
exports.safeParse = safeParse;
/*
Implementation.
*/
function safeParse(txt) {
try {
return JSON.parse(txt);
}
catch (err) {
console.error('[LOX] Parse failed: ' + err);
console.error(txt);
return false;
}
}
function toPacket(command, data) {
var str = '' + JSON.stringify(data);
return command + '\n' + str.length + '\n' + str;
}
function fromPacket(str, localData) {
if (!localData) {
localData = socketData;
}
if (localData.buffer === undefined) {
localData.remaining = 0;
localData.buffer = '';
localData.result = [];
}
str !== undefined && (localData.buffer += str);
var nextNewLine;
if (!localData.command) {
if (!localData.buffer.length) {
return false;
}
nextNewLine = localData.buffer.indexOf('\n');
if (nextNewLine <= 0) {
return false;
}
localData.command = localData.buffer.substr(0, nextNewLine);
localData.buffer = localData.buffer.substr(nextNewLine + 1);
}
if (!localData.remaining) {
nextNewLine = localData.buffer.indexOf('\n');
if (nextNewLine <= 0) {
return false;
}
localData.remaining = +localData.buffer.substr(0, nextNewLine);
localData.buffer = localData.buffer.substr(nextNewLine + 1);
}
var took = Math.min(localData.remaining, localData.buffer.length);
localData.result.push(localData.buffer.substr(0, took));
localData.buffer = localData.buffer.substr(took);
localData.remaining -= took;
if (localData.remaining > 0) {
return false;
}
if (localData.remaining < 0) {
throw 'Assertion failure: Negative buffered data.';
}
var retVal = {
command: localData.command,
result: safeParse(localData.result.join(''))
};
localData.command = false;
localData.result = [];
return retVal;
}
var net = require('net'),
commands = require('./commands'),
loxProtocol = require('./lox.protocol.js');
/*
Public API.
*/
exports.boot = boot;
exports.respond = respond;
/*
Tell "commands" who we are, to avoid a cyclic require.
*/
commands.server = exports;
/**
* Boot starts us listening for connections.
*/
function boot(ports) {
ports.forEach(function(port) {
var devServer = net.createServer();
devServer.on('connection', handleConnection);
devServer.listen(port);
console.log('[SOCKET] Listening on ' + port + '.');
});
}
/**
* Handles an incoming connection.
*/
function handleConnection(socket) {
socket.on('data', handleData);
socket.on('error', handleError);
socket.on('close', handleClose);
}
/**
* Handles data coming in from a client.
*/
function handleData(obj) {
if (!this.socketState) {
this.socketState = {};
}
var data = loxProtocol.fromPacket(obj, this.socketState);
if (!data) {
return
}
var method = commands[data.command];
if (!method) {
return;
}
console.log('[SERVER] Read ' + data.command + '.');
method(this, data.result);
// Handle coalesced packets.
handleData.apply(this);
}
/**
* Sends a response to one socket.
*/
function respond(socket, command, data) {
if (!socket.writable) {
return false;
}
try {
socket.write(loxProtocol.toPacket(command, data));
return true;
}
catch (err) {
console.error(err);
return false;
}
}
/**
* Handles an error from the socket.
*/
function handleError(err) {
console.error(err);
}
/**
* Handles a connection closing.
*/
function handleClose() {
// For You TODO: Clean up whatever you want.
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment