I would like to get a multi-process node. Workers are listening clients connections. I need pass sockets to master process because master process emit message to clients. Workers also need socket to emit message to clients.
Socket is a circular object and can't pass to a master process.
My code:
const cluster = require('cluster');
const http = require('http');
var io = require('socket.io');
var users;
var clients = {};
if (cluster.isMaster) {
function messageHandler(msg) {
if (msg.usersarray) {
usersarray = msg.usersarray;
console.log(usersarray);
}else if(msg.socket){
clients[usersarray["manu"][0]] = msg.socket;
clients[usersarray["manu"][0]].emit("hola","hola");
}
}
// Start workers and listen for messages containing notifyRequest
const numCPUs = require('os').cpus().length;
for (var i = 0; i < numCPUs; i++) {
cluster.fork();
}
Object.keys(cluster.workers).forEach((id) => {
cluster.workers[id].on('message', messageHandler);
});
}else {
// Create server & socket
var server = http.createServer(function(req, res){
// Send HTML headers and message
res.writeHead(404, {'Content-Type': 'text/html'});
res.end('<h1>Aw, snap! 404</h1>');
});
server.listen(3000);
io = io.listen(server);
// Add a connect listener
io.sockets.on('connection', function(socket) {
var hs = socket.handshake;
console.log("socket connected");
if(users == undefined){
users = {};
}
if(hs.query.usuario != undefined){
if(users[hs.query.usuario] == undefined){
users[hs.query.usuario] = new Array();
}
users[hs.query.usuario].push(socket.id); // connected user with its socket.id
clients[socket.id] = socket; // add the client data to the hash
process.send({ usersarray: users});
process.send({ socket: socket});
}
// Disconnect listener
socket.on('disconnect', function() {
console.log('Client disconnected.');
});
});
}
in line process.send({ socket: socket}); Node js get error "TypeError: Converting circular structure to JSON"
-I used some module to transform circular object but don't working.
-I tried to pass socket id and then in master process, created new socket with this id but I didn't know to use it.
There is any posibility to pass socket from worker to master process?
Node js version: v5.5.0
Hm, I don't think it is possible what you are trying to do. When you create a cluster it means that you create separate processes (master + workers) which can only talk over the pipe.
Talking over the pipe means they can only send strings to each other. process.send tries to serialize a Javascript object as JSON (--> making a string out of it) using JSON.stringify. JSON for example cannot have functions, circles, etc. I just checked the socket object, it is very complex and contains functions (such as socket.emit()), so you cannot just serialize it and send it over the pipe.
Maybe you can check this or this on how to use clustered WebSockets.
It doesn't seem very trivial.. Maybe you can just pass CPU intensive tasks to some worker processes (via cluster or just spawning them yourself), send the results back to the master and let him do all the communication with the client?
I understand your purpose of broadcasting to all the node worker processes in a cluster, although you can not send socket component as such but there is a workaround for the purpose to be served. I will try an explain with an example :
Step 1: When a client action requires a broadcast :
Child.js (Process that has been forked) :
socket.on("BROADCAST_TO_ALL_WORKERS", function (data)
{
process.send({cmd : 'BROADCAST_TO_ALL_WORKERS', message :data.message});
})
Step 2: On the cluster creation side
Server.js (Place where cluster forking happens):
if (cluster.isMaster) {
for (var i = 0; i < numCPUs; i++) {
var worker = cluster.fork();
worker.on('message', function (data) {
if (data.cmd === "BROADCAST_TO_ALL_WORKERS") {
console.log(server_debug_prefix() + "Server Broadcast To All, Message : " + data.message + " , Reload : " + data.reload + " Player Id : " + data.player_id);
Object.keys(cluster.workers).forEach(function(id) {
cluster.workers[id].send({cmd : "BROADCAST_TO_WORKER", message : data.message});
});
}
});
}
cluster.on('exit', function (worker, code, signal) {
var newWorker = cluster.fork();
newWorker.on('message', function (data) {
console.log(data);
if (data.cmd === "BROADCAST_TO_ALL_WORKERS") {
console.log(data.cmd,data);
Object.keys(cluster.workers).forEach(function(id) {
cluster.workers[id].send({cmd : "BROADCAST_TO_WORKER", message : data.message});
});
}
});
});
}
else {
//Node Js App Entry
require("./Child.js");
}
Step 3: To Broadcast in the child process
-> Put this before io.on("connection") in Child.js
process.on("message", function(data){
if(data.cmd === "BROADCAST_TO_WORKER"){
io.sockets.emit("SERVER_MESSAGE", { message: data.message, reload: data.reload, player_id : data.player_id });
}
});
I hope its clear. Please comment if its confusing ... I will try and make it clear.
Related
i have an experimental app that is pretty simple, you click a display button and an image appears in real time to all clients, utilizing socket.io. now it does work for the most part. however i had an issue (my original question here: socket.io emit on connect) with an event not happening when the client connected, but i have resolved it with the help of this thread: socket.emit on sever side is ignored after connection?.
basically, my 'new-client-append event' retrieves data (html in the form of a string) so that when a new client connects, it shows the same data that current clients see (similar to connecting to a chat room and being able to see all chat history). i had to reorganize my code so my 'new-client-append' event would take place, and i can get it to work if i put data in manually. my new issue is now that i have had to reorganize my code, my 'new-client-append' event is dependent on a variable i set within the class, so it is no longer recognizable. i'm relatively new to JS, how can i get my variable to be recognized and why is this happening? i had tried moving the event in different places of my code with no luck. it is the this.mainContainer variable.
CLIENT
import $ from 'jquery';
import SaveInput from './SaveInput';
import io from 'socket.io-client';
// make connection
const socket = io.connect('localhost:3000');
**socket.on('new-client-append', (data) => {
console.log('NEW CLIENT ENTERED');
console.log('on new-client-clone ' + JSON.stringify(data));
this.mainContainer.append(data);
});**
socket.on('connect_error', function(){
console.log('fail');
});
class Display extends SaveInput {
constructor(){
this.mainContainer = $('.main-container');
this.pGrid = $('.pic-grid-container');
this.display = $('#btn-display');
this.buttons();
}
buttons (){
// click buttons
this.display.click(this.displayEls.bind(this));
//display images
displayEls() {
let img = 'https://secure.gravatar.com/avatar/22f38e0216f57af53a1776fb2a72c436?s=60&d=wavatar&r=g';
let $picContainer = $('<div class="picture-frame"></div>');
let $newImg = $('<img>');
// clone pic-grid-container
let htmlClone = this.pGrid.clone();
let stringClone = htmlClone.html();
// EMIT
//send image url
socket.emit('client-image', {
image: img
});
// send dom clone to server
socket.emit('new-client-append', {
clone: stringClone
});
// LISTEN
// append image in real time
socket.on('client-image', (data) => {
let foo = data.image.toString();
$newImg.attr('src', foo);
// console.log(data);
// console.log(foo);
$newImg.appendTo($picContainer);
this.pGrid.append($picContainer);
// console.log('html clone ' + JSON.stringify(htmlClone));
// console.log('string clone ' + stringClone);
});
}
export default Display;
SERVER
const express = require('express');
const socketIO = require('socket.io');
const http = require('http');
// app set up
const app = express();
const server = http.Server(app);
// const = new socket(server);
let port = process.env.PORT || 3000;
// static files
app.use(express.static('app'));
// socket setup & pass SERVER
const io = new socketIO(server);
let jqueryImage;
// on client connect
io.on('connection', (socket) => {
console.log('client has entered...');
socket.emit('new-client-append', jqueryImage);
// events
socket.on('client-image', function(data){
console.log('SERVER ' + data.image);
io.sockets.emit('client-image', data);
});
socket.on('new-client-append', function(data){
jqueryImage = data.clone;
console.log('jqueryImage ' + JSON.stringify(jqueryImage));
});
// errors
io.on('error', function (err) {
console.log(err);
});
io.on('connect_error', function(){
console.log('fail');
});
});
server.listen(port, () => {
console.log('server running....');
});
UPDATE
added suggested code, calling the maincontainer from the display object. but it is saying that it is not defined.
socket.on('new-client-append', (data) => {
console.log('NEW CLIENT ENTERED');
console.log('on new-client-clone ' + JSON.stringify(data));
**display.mainContainer.append(data);**
});
file where i create the objects
import SaveInput from './modules/SaveInput';
import Display from './modules/Display';
const saveInput = new SaveInput();
const display = new Display ();
Your socket.on function has
this.mainContainer.append(data);
This implies that you've attached a mainContainer property to the socket object. Because you've not done this, but rather declared it inside your Display (sub)class, it's attached to the Display object you created.. I can't seem to find it, but somewhere, wherever this script is required there is a
variable = new Display();
You must call this mainContainer object by it's name. Find that code that initiates the Display object and then use
theVarNameYouFound.mainContainer.append(thatThing);
I am trying to overcome a limitation placed on how many connections TCP protocol can have opened on a single port. So i thought of a way to create multiple instances of my server running on different ports for example:
instance 1 (3001) server_i1.js
instance 2 (3002) server_i2.js
instance 3 (3003) server_i3.js
instance 4 (3004) server_i4.js
then i could have one additional file server_route.js that would check how many connections are established on each instance and forward user to less populated instance. I tried build something using cluster but it only seems to create new processes on the same port. How it can be done to have all users connect for example to http://exmaple.com:3000 and then forward them to one of four possible ports [3001, 3002, 3003, 3004]?
current server approach:
var cluster = require('cluster');
var numCPUs = require('os').cpus().length;
if(cluster.isMaster) {
for (var i = 0; i < numCPUs; i++) {
cluster.fork();
}
cluster.on('exit', function(worker, code, signal) {
console.log('worker ' + worker.process.pid + ' died');
});
} else {
var http = require('http'),
_io = require('socket.io'),
server = http.createServer();
server.listen('3000', 'example.com');
var io = _io.listen(server);
var connections = {},
msg_sent = 0;
io.on('connection', function(socket) {
connections[socket.id] = new Date().getTime();
socket.on('client-request', function(msg) {
msg_sent++;
});
socket.on('disconnect', function() {
delete connections[socket.id];
});
});
setInterval(function() {
console.log( 'Active connections: ', Object.keys(connections).length, 'Messages sent: ', msg_sent );
}, 1000);
}
Maybe use round robin? A sample implementation would look like this:
const ports = ["3001"," 3002", "3003"];
var current = 0;
io.on('connection', function(socket) {
socket.emit("redirect",ports[current]);
current = (current + 1) % ports.length;
});
On clientside one would do
(function start(port){
const socket = io("http://localhost:"+port);
socket.on("redirect", port => (socket.close(), start(port)));
//whatever
})(3000);
Using socket.io 1.4.5.
It seems no matter what I do, I cannot prevent socket.io from firing the reconnection event or destroying the client socket when there are interruptions with the internet connection.
On the client side, I have:
reconnectionDelay: 99999999,
timeout: 99999999999,
reconnection: false,
And yet, if the internet disconnects, the socket will become undefined (after 20 seconds or so) and the reconnection event fires when the internet goes back on.
My ultimate goal is to use the same exact socket on the server and on the client (regardless of how long it's been since they've communicated) unless that socket is explicitly disconnected on the server. I cannot have the socket reconnecting at will, because I store data on the socket and use the socket.id extensively in my application. IF the socket and socket id were to suddenly change, the application breaks.
I think You've to pass some unique variable on handshake and keep data by that variable.
Regeneration of socket.id is normal behavior of socket.io.
In my practice I'm doing initial request to server to create slot variable and keep it slots collection in mongodb and after I'm creating connection by defining that slot variable as handshake.
or let's just simplify answer to Your question and use: https://www.npmjs.com/package/express-socket.io-session
also a little "hack" use namespaces logic: https://socket.io/docs/rooms-and-namespaces/#custom-namespaces
BONUS:
here is quick solution:
clientside:
function genUUID() {
var d = new Date().getTime();
var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r = (d + Math.random()*16)%16 | 0;
d = Math.floor(d/16);
return (c=='x' ? r : (r&0x3|0x8)).toString(16);
});
return uuid;
};
var namespace = localStorage.getItem("namespace");
if(!namespace) {
namespace = genUUID();
localStorage.setItem("namespace", namespace);
}
var connect = function (ns) {
return io.connect(ns, {
query: 'ns='+ns // this is handshake variable `ns`
});
}
var socket = connect('/'+namespace);
backend:
const
url = require('url'),
sharedData = {};
io.sockets.on('connection', (socket) => {
const handshake = url.parse(socket.handshake.url, true);
const ns = handshake.query ? handshake.query.ns : 'default';
console.log('GOT CONNECTION TO NS: '+ns);
io.of(ns).on('connection', (socket) => {
if(!sharedData[ns]) sharedData[ns] = {};
// put socket code here
socket.on('some-event', (data) => {
sharedData[ns]['some-event'] = data.someData;
});
});
});
My server is running NodeJS and uses the amqplib api to request data from another application. The NodeJS server is receiving the information successfully but there's a noticable delay and I'm trying to determine whether I am doing this in the most efficient manner. Specifically I'm concerned with the way that I open and close connections.
Project Layout
I have two controller files that handle receiving and requesting the data, request.img.server.controller.js and receive.img.server.controller.js. Finally the routes handle the controller methods when a button on the front end is pushed, oct.server.routes.js.
request.img.server.controller.js
'use strict';
var amqp = require('amqplib/callback_api');
var connReady = false;
var conn, ch;
amqp.connect('amqp://localhost:5672', function(err, connection) {
conn = connection;
connReady = true;
conn.createChannel(function(err, channel) {
ch = channel;
});
});
exports.sendRequest = function(message) {
console.log('sending request');
if(connReady) {
var ex = '';
var key = 'utils';
ch.publish(ex, key, new Buffer(message));
console.log(" [x] Sent %s: '%s'", key, message);
}
};
receive.img.server.controller.js
var amqp = require('amqplib/callback_api');
var fs = require('fs');
var wstream = fs.createWriteStream('C:\\Users\\yako\\desktop\\binarytest.txt');
var image, rows, cols;
exports.getResponse = function(resCallback) {
amqp.connect('amqp://localhost:5672', function(err, conn) {
conn.createChannel(function(err, ch) {
var ex = '';
ch.assertQueue('server', {}, function(err, q) {
console.log('waiting for images');
var d = new Date();
var n = d.getTime();
ch.consume(q.queue, function(msg) {
console.log(" [x] %s: '%s'", msg.fields.routingKey, msg.content.toJSON());
rows = msg.content.readInt16LE(0);
cols = msg.content.readInt16LE(2);
console.log("rows = %s", msg.content.readInt16LE(0));
console.log("cols = %s", msg.content.readInt16LE(2));
image = msg.content;
var currMax = 0;
for (var i = 4; i < image.length; i+=2) {
if (image.readInt16LE(i) > currMax) {
currMax = image.readInt16LE(i);
}
wstream.write(image.readInt16LE(i) + ',');
}
console.log('done writing max is', currMax);
//console.log(image);
resCallback(rows, cols, image);
}, {
noAck: true
});
});
});
});
};
oct.server.routes.js
'use strict';
module.exports = function(app) {
var request_img = require('../../app/controllers/image-tools/request.img.server.controller.js');
var receive_img = require('../../app/controllers/image-tools/receive.img.server.controller.js');
// oct routes
app.get('/load_slice', function(req, res) {
console.log('load slice hit');
receive_img.getResponse(function (rows, cols, image) {
res.end(image);
});
request_img.sendRequest('123:C:\\Users\\yako\\Documents\\Developer\\medicaldiag\\test_files\\RUS-01-035-09M-21.oct');
});
};
The way you're opening connections is bad, and is at least part of the performance problem.
Connections are expensive to open. They open a new TCP/IP connection on a TCP/IP port between the client and rabbitmq server. This takes time, and uses up a limited resource on both the client and server.
Because of this, a single connection to RabbitMQ should be created and used within each of your node.js processes. This one connection should be shared by all of the code in that process.
Whenever you need to do something with RabbitMQ, open a new channel on the shared connection and do your work. Channels are cheap and are meant to be opened and closed as needed, within a connection.
More specifically in your code, the receive.img.server.controller.js file is the major problem. This opens a new connection to RabbitMQ every time you call the getResponse method.
If you have 10 users hitting the site, you'll have 10 open RabbitMQ connections when 1 would be sufficient. If you have thousands of users hitting the site, you'll have thousands of open RabbitMQ connections when 1 would be sufficient. You also run the risk of exhausting your available TCP/IP connections on the RabbitMQ server or client.
Your receive.img.server.controller.js should look more like your request.img.server.controller.js - one connection open, and re-used all the time.
Also, FWIW - I recommend using the wascally library for RabbitMQ w/ node.js. This library sits on top of amqplib, but makes things significantly easier. It will manage your one connection for you, and make it easier for you to send and receive messages.
I also have some training material available for RabbitMQ and node.js that covers the basics of amqplib and then moves in to using wascally for real application development.
I am trying to emit to a particular socket ID:
socket(user[playID]).emit('correct', data);
But I'm getting:
TypeError: object is not a function
if I log out user[playID] I do get a valid socket ID.
Appreciated!
Here is my setup in case I'm missing something:.
// Tell Socket.io to pay attention
servio = io.listen(server);
// Tell HTTP Server to begin listening for connections on port 3250
sock = server.listen(3250);
This should do it
servio.sockets.socket(id).emit('hello');
This answer covers the same/similar topic. In short, consider keeping a reference to the connected clients yourself and emit to them as desired, rather than relying on socket.io's internals, which could change.
Per http://socket.io/docs/rooms-and-namespaces/,
Each Socket in Socket.IO is identified by a random, unguessable,
unique identifier Socket#id. For your convenience, each socket
automatically joins a room identified by this id.
You emit to a room like:
io.to('some room').emit('some event')
If you want to emit to just a specific socket.id, just replace 'some room' with the associated id.
Another way to do this is:
var players = [];
io.sockets.on('connection', function(socket){
socket.on('skt_init', function (data) {
var player = new Object();
player.id = data.id;
player.socket = socket.id;
players.push(player);
});
socket.on('disconnect', function() {
var len = 0;
for(var i=0, len=players.length; i<len; ++i ) {
var p = players[i];
if(p.socket == socket.id){
players.splice(i,1);
break;
}
}
});
socket.on('skt_event', function(data, id_player){
var len = 0;
for(var i=0, len=players.length; i<len; ++i ) {
var p = players[i];
if(p.id == id_player){
io.sockets.socket(p.socket).emit('correct', data);
break;
}
}
});
Hope that helps somewhat.
UPDATE: in socket.io-1.4 you have to prepend "/#" to socket id ( very frustrating that it doesnt work now). you can also view all connected sockets from backend as io.sockets.connected
io.to( "/#" + socket_id).emit("event_name",{data:true})
This should work:
servio.sockets.sockets[playId].emit(...)
Since socket.io doesn't provide a stable API to get the socket from a socket's ID, you can simply (and easily) keep track of them with an object keyed by socket IDs.
sockets_by_id = {}
io.on "connection", (socket)->
sockets_by_id[socket.id] = socket
sockets_by_id[socket_id].emit event, data...
(^CoffeeScript^)