Created
May 6, 2015 16:00
-
-
Save xeoncross/85feef5806647cfc9483 to your computer and use it in GitHub Desktop.
Simple Socket.io (v1.3+) Multi-room server + benchmark client
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var io = require('socket.io-client'); | |
var i, clients = 0, sent = 0, received = 0; | |
var TOTAL_CLIENTS = process.argv[2] || 500; | |
var CLIENTS_PER_ROOM = process.argv[3] || 3; | |
console.log("Total clients: " + TOTAL_CLIENTS); | |
console.log("Clients per room: " + CLIENTS_PER_ROOM); | |
console.log("\nclients\tsent\treceived\tsent/c\treceived/c"); | |
setInterval(function() { | |
console.log([clients, sent, received, '', | |
Math.round(sent/clients), Math.round(received/clients)].join("\t")); | |
sent = 0; | |
received = 0; | |
}, 2000); | |
function sendMessage(socket, room) { | |
var message = { msg: 'hello from ' + socket.id, room: room }; | |
socket.emit('message', message, function(error, target) { | |
if(error) die(arguments); | |
++sent; | |
setTimeout(function() { | |
sendMessage(socket, room); | |
}, 1000); | |
}); | |
}; | |
function die(err) { | |
console.log(err); | |
process.exit(); | |
} | |
function pickUsername(socket) { | |
var name = 1; | |
do { | |
} while($taken); | |
} | |
function connectClient(i) { | |
var channel = 'test' + Math.floor(i / CLIENTS_PER_ROOM); | |
setTimeout(function() { | |
var socket = io.connect('http://localhost:3000', { 'force new connection': true }); | |
// On any of the standard connection errors | |
for(var type in ['disconnect', 'connect_error', 'connect_timeout', 'reconnect_failed']) { | |
function(type) { | |
socket.on(type, function() { | |
console.log(socket.id, type); | |
process.exit(1); | |
}); | |
}(type); | |
} | |
socket.on('connect_timeout', function() { | |
console.log(socket.id, 'connect_timeout'); | |
process.exit(1); | |
}); | |
socket.emit('join', channel, function(err) { | |
if(err) die(err); | |
++clients; | |
socket.on('message', function(msg) { | |
// die(msg); | |
++received; | |
}); | |
sendMessage(socket, channel); | |
}); | |
socket.on('clients', function(room)) | |
}, i*10); | |
}; | |
for (i=0; i<TOTAL_CLIENTS; ++i) { | |
connectClient(i); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"name": "socket.io-rooms", | |
"description": "A simple example of multiple rooms with Socket.IO, benchmark included", | |
"version": "0.0.1", | |
"dependencies": { | |
"bower": "^1.4.1", | |
"ejs": "^2.3.1", | |
"express": "^4.12.3", | |
"socket.io": "^1.3.5", | |
"socket.io-client": "^1.3.5", | |
"underscore": "^1.8.3" | |
}, | |
"scripts": { | |
"postinstall": "./node_modules/bower/bin/bower install", | |
"start": "node server.js" | |
}, | |
"engines": { | |
"node": ">= 0.4.10" | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var express = require('express') | |
, app = express() | |
, server = require('http').createServer(app) | |
, io = require("socket.io").listen(server) | |
// , npid = require("npid") | |
// , uuid = require('node-uuid') | |
// , Room = require('./room.js') | |
, _ = require('underscore')._; | |
app.set('port', process.env.OPENSHIFT_NODEJS_PORT || 3000); | |
app.set('ipaddr', process.env.OPENSHIFT_NODEJS_IP || "127.0.0.1"); | |
// app.use(express.bodyParser()); | |
// app.use(express.methodOverride()); | |
app.use(express.static(__dirname + '/public')); | |
app.use('/', express.static(__dirname + '/public')); | |
// app.use('/components', express.static(__dirname + '/components')); | |
// app.use('/js', express.static(__dirname + '/js')); | |
// app.use('/icons', express.static(__dirname + '/icons')); | |
// app.set('views', __dirname + '/views'); | |
app.engine('html', require('ejs').renderFile); | |
/* Store process-id (as priviledged user) */ | |
// try { | |
// npid.create('/var/run/advanced-chat.pid', true); | |
// } catch (err) { | |
// console.log(err); | |
// process.exit(1); | |
// } | |
app.get('/', function(req, res) { | |
res.render('index.html'); | |
}); | |
/* You can send messages to chat rooms via AJAX | |
app.post('/send/:room/', function(req, res) { | |
var room = req.params.room | |
message = req.body; | |
io.sockets.in(room).emit('message', { room: room, message: message }); | |
res.end('message sent'); | |
}); | |
*/ | |
server.listen(app.get('port'), app.get('ipaddr'), function(){ | |
console.log('Express server listening on IP: ' + app.get('ipaddr') + ' and port ' + app.get('port')); | |
}); | |
var exec = require('child_process').exec; | |
exec('node benchmark.js 4 2', function (error, stdout, stderr) { | |
console.log('benchmark running'); | |
}); | |
var messageCount = 0; | |
var debugLog = setInterval(function() { | |
//console.log("\nclients\tsent\treceived\tsent/c\treceived/c"); | |
console.log('Messages: ' + messageCount); | |
console.log(_.map(io.sockets.sockets, function(currentObject) { | |
return _.pick(currentObject, "id", "username"); | |
})); | |
console.log(io.sockets.adapter.rooms); | |
// console.log(io.nsps["/"].adapter.rooms); | |
// console.log(io.sockets.sockets); | |
console.log(); | |
}, 3000); | |
function die(err) { | |
console.log(err); | |
process.exit(); | |
} | |
// io.set("log level", 1); | |
var MAX_SOCKETS_IN_ROOM = 100; | |
var MAX_ROOMS_IN_SERVER = 1000000; | |
var MAX_ROOMS_PER_SOCKET = 2; // Including the default socket.id room! | |
var ALLOW_GLOBAL_MESSAGES = false; // Messages must be to a room or specific client | |
// io.sockets.clients('room').length = number of clients in room | |
// socket.rooms.length = number of rooms client is in | |
// Is the string/array empty? | |
var isEmpty = function(str) { | |
return (str.length === 0 || !str.trim()); | |
}; | |
// https://github.com/chriso/validator.js/blob/master/validator.js#L621 | |
var escape = function (str) { | |
return (str.replace(/&/g, '&') | |
.replace(/"/g, '"') | |
.replace(/'/g, ''') | |
.replace(/</g, '<') | |
.replace(/>/g, '>') | |
.replace(/\//g, '/') | |
.replace(/\`/g, '`')); | |
}; | |
// Inform all clients in the room about the other clients | |
var sendClientList = function(room) { | |
var clients = io.nsps["/"].adapter.rooms[room]; | |
// var clients = io.sockets.clients(room); | |
io.sockets.in(room).emit('clients', { | |
room: room, | |
clients: clients | |
}); | |
}; | |
var usernames = {}; | |
// On username change or disconnect free our usernames | |
var removeSocketUsername = function(socket) { | |
if(socket.username) { | |
delete usernames[socket.username]; | |
delete socket.username; | |
} | |
} | |
// @todo expand to support all Unicode letters and numbers | |
// var isSafeString = function(str) { | |
// if(typeof str === "string" || str instanceof String) { | |
// return str.length !== 0 && ! str.match(/[^\w\-]/)); // [a-zA-Z0-9_] | |
// } | |
// }; | |
/* | |
* 1. All ack() functions take an ERROR code as their first argument | |
* 2. All "message" events must be objects with either a _.room or _.to property | |
* if global messaging is disabled. | |
* 3. All usernames must be unqiue | |
*/ | |
io.sockets.on('connection', function(socket) { | |
// FYI, Socket.io auto-joins the socket to a room by the same name | |
// socket.join(socket.id); | |
// @todo this is unique, so it's safe for us | |
// (do we actually want to share this with others though?) | |
socket.username = socket.id; | |
usernames[socket.id] = socket.id; | |
// One of the most common requests is being able to set a unique | |
// username on the clientside. Something like this could be built | |
// for OAuth, session cookies, or a username + password combo. | |
socket.on('username', function(username, ack) { | |
if(isEmpty(username)) { | |
ack('USERNAME_EMPTY'); | |
return; | |
} | |
if(usernames[username]) { | |
if(usernames[username] !== socket.id) { | |
ack("USERNAME_TAKEN"); | |
} else { | |
ack(); | |
} | |
return; | |
} | |
removeSocketUsername(socket); | |
username = escape(username); | |
usernames[username] = socket.id; | |
socket.username = username; | |
ack(null); | |
}); | |
// Returns the user's current connected rooms on success | |
socket.on('join', function(room, ack) { | |
// console.log(socket.id, "joining", room); | |
if(MAX_ROOMS_PER_SOCKET && socket.rooms.length >= MAX_ROOMS_PER_SOCKET) { | |
ack("MAX_ROOMS_PER_SOCKET", socket.rooms); | |
return; | |
} | |
socket.join(room); | |
ack(null, socket.rooms); | |
sendClientList(room); | |
}); | |
// Returns the user's current connected rooms on success | |
socket.on('leave', function(room, ack) { | |
// console.log(socket.id, "leaving", room); | |
if(room === socket.id || ! socket.rooms[room]) { | |
ack("INVALID_ROOM", socket.rooms); | |
return; | |
} | |
socket.leave(room); | |
ack(true, socket.rooms); | |
sendClientList(room); | |
}); | |
// On client message to server | |
socket.on('message', function(packet, ack) { | |
packet.from = socket.username; | |
messageCount++; | |
// console.log('message', socket.id, packet); | |
if( ! ALLOW_GLOBAL_MESSAGES) { | |
if( ! packet.room && ! packet.to) { | |
ack("INVALID_TARGET"); | |
return; | |
} | |
} | |
// Sending a message to a room/channel | |
if(packet.room) { | |
if(socket.rooms.indexOf(packet.room) === -1) { | |
// die(socket.rooms); | |
ack("INVALID_ROOM", packet.room); | |
return; | |
} | |
// console.log('message', socket.id, packet); | |
// process.exit(); | |
// Leave trace of the room this packet belongs too | |
//var room = packet.room; | |
//delete packet.room; | |
socket.broadcast.to(packet.room).emit('message', packet); | |
ack(); | |
return; | |
} | |
// Sending a message to specific client | |
if(packet.to) { | |
if( ! usernames[packet.to]) { | |
ack("INVALID_USER"); | |
return; | |
} | |
socket.broadcast.to(packet.to).emit('message', packet); | |
ack(); | |
return; | |
} | |
// Only possible if global messages are allowed | |
io.sockets.emit('message', packet); | |
}); | |
// Free username if taken | |
socket.on('disconnect', function() { | |
console.log('disconnect', socket.id, socket.username, socket.rooms); | |
// Other than the global room and the client's own room | |
for(var room in socket.rooms) { | |
// if(room !== '/' && room !== socket.id) { | |
// socket.broadcast.do(room).emit('clients', socket.username); | |
if(room !== socket.id) { | |
// Let the disconnect finish before we inform everyone | |
// setTimeout(function() { | |
// sendClientList(room); | |
// }, 500); | |
sendClientList(room); | |
} | |
} | |
removeSocketUsername(socket); | |
}); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment