I am using node.js building a TCP server, just like the example in the doc. The server establishes persistent connections and handle client requests. But I also need to send data to any specified connection, which means this action is not client driven. How to do that?
Your server could maintain a data structure of active connections by adding on the server "connection" event and removing on the stream "close" event. Then you can pick the desired connection from that data structure and write data to it whenever you want.
Here is a simple example of a time server that sends the current time to all connected clients every second:
var net = require('net')
, clients = {}; // Contains all active clients at any time.
net.createServer().on('connection', function(sock) {
clients[sock.fd] = sock; // Add the client, keyed by fd.
sock.on('close', function() {
delete clients[sock.fd]; // Remove the client.
});
}).listen(5555, 'localhost');
setInterval(function() { // Write the time to all clients every second.
var i, sock;
for (i in clients) {
sock = clients[i];
if (sock.writable) { // In case it closed while we are iterating.
sock.write(new Date().toString() + "\n");
}
}
}, 1000);
Related
My project works as intended except that I have to refresh the browser every time my keyword list sends something to it to display. I assume it's my inexperience with Expressjs and not creating the route correctly within my websocket? Any help would be appreciated.
Browser
let socket = new WebSocket("ws://localhost:3000");
socket.addEventListener('open', function (event) {
console.log('Connected to WS server')
socket.send('Hello Server!');
});
socket.addEventListener('message', function (e) {
const keywordsList = JSON.parse(e.data);
console.log("Received: '" + e.data + "'");
document.getElementById("keywordsList").innerHTML = e.data;
});
socket.onclose = function(code, reason) {
console.log(code, reason, 'disconnected');
}
socket.onerror = error => {
console.error('failed to connect', error);
};
Server
const ws = require('ws');
const express = require('express');
const keywordsList = require('./app');
const app = express();
const port = 3000;
const wsServer = new ws.Server({ noServer: true });
wsServer.on('connection', function connection(socket) {
socket.send(JSON.stringify(keywordsList));
socket.on('message', message => console.log(message));
});
// `server` is a vanilla Node.js HTTP server, so use
// the same ws upgrade process described here:
// https://www.npmjs.com/package/ws#multiple-servers-sharing-a-single-https-server
const server = app.listen(3000);
server.on('upgrade', (request, socket, head) => {
wsServer.handleUpgrade(request, socket, head, socket => {
wsServer.emit('connection', socket, request);
});
});
In answer to "How to Send and/or Stream array data that is being continually updated to a client" as arrived at in comment.
A possible solution using WebSockets may be to
Create an interface on the server for array updates (if you haven't already) that isolates the array object from arbitrary outside modification and supports a callback when updates are made.
Determine the latency allowed for multiple updates to occur without being pushed. The latency should allow reasonable time for previous network traffic to complete without overloading bandwidth unnecessarily.
When an array update occurs, start a timer if not already running for the latency period .
On timer expiry JSON.stringify the array (to take a snapshot), clear the timer running status, and message the client with the JSON text.
A slightly more complicated method to avoid delaying all push operations would be to immediately push single updates unless they occur within a guard period after the most recent push operation. A timer could then push modifications made during the guard period at the end of the guard period.
Broadcasting
The WebSockets API does not directly support broadcasting the same data to multiple clients. Refer to Server Broadcast in ws documentation for an example of sending data to all connected clients using a forEach loop.
Client side listener
In the client-side message listener
document.getElementById("keywordsList").innerHTML = e.data;
would be better as
document.getElementById("keywordsList").textContent = keywordList;
to both present keywords after decoding from JSON and prevent them ever being treated as HTML.
So I finally figured out what I wanted to accomplish. It sounds straight forward after I learned enough and thought about how to structure the back end of my project.
If you have two websockets running and one needs information from the other, you cannot run them side by side. You need to have one encapsulate the other and then call the websocket INSIDE of the other websocket. This can easily cause problems down the road for other projects since now you have one websocket that won't fire until the other is run but for my project it makes perfect sense since it is locally run and needs all the parts working 100 percent in order to be effective. It took me a long time to understand how to structure the code as such.
My problem is that the current solution I have for sending a specific socket using the library "ws" with node.js is not good enough.
The reason is because if I connect with multiple tabs to the websocket server with the same userid which is defined on the client-side, it will only refer to the latest connection with the userid specified.
This is my code:
// Server libraries and configuration
var server = require("ws").Server;
var s = new server({ port: 5001});
// An array which I keep all websockets clients
var search = {};
s.on("connection", function(ws, req) {
ws.on("message", function(message){
// Here the server process the user information given from the client
message = JSON.parse(message);
if(message.type == "userinfo"){
ws.personName = message.data;
ws.id = message.id;
// Defining variable pointing to the unique socket
search[ws.id] = ws;
return;
}
})
})
As you can see, each time a socket with same id connects, it will refer to the latest one.
Example If you did not understand:
Client connect to server with ID: 1337
search[1337] defined as --> websocket 1
A new connection with same ID: 1337
search[1337] becomes instead a variable refering to websocket 2 instead
Websockets provide a means to create a low-latency network "socket" between a browser and a server.
Note that the client here is the browser, not a tab on a browser.
If you need to manage multiple user sessions between the browser and server, you'll need to write code to do it yourself.
I am trying to create a system where I have a desktop client created in VB, and a browser based client, that can send messages to each other. I am using a Node.js server to handle the connections and messages.
This is the code of my Node.js server:
net = require('net')
// Supports multiple client chat application
// Keep a pool of sockets ready for everyone
// Avoid dead sockets by responding to the 'end' event
var sockets = [];
// Create a TCP socket listener
var s = net.Server(function (socket) {
// Add the new client socket connection to the array of
// sockets
sockets.push(socket);
// 'data' is an event that means that a message was just sent by the
// client application
socket.on('data', function (msg_sent) {
// Loop through all of our sockets and send the data
for (var i = 0; i < sockets.length; i++) {
// Don't send the data back to the original sender
if (sockets[i] == socket) // don't send the message to yourself
continue;
// Write the msg sent by chat client
sockets[i].write(msg_sent);
}
});
// Use splice to get rid of the socket that is ending.
// The 'end' event means tcp client has disconnected.
socket.on('end', function () {
var i = sockets.indexOf(socket);
sockets.splice(i, 1);
});
});
s.listen(8000);
console.log('System waiting at http://localhost:8000');
With this sever, I am able to send messages between two desktop clients successfully.
However, because I am using net and not HTTP I cannot get the browser based client to connect.
How can I get both the clients to connect? I would really appreciate any help/suggestions/directions. I have been searching everywhere for about 4 days now! TIA!
You could use http or express for browser based client. could check socket.io also which works on http port.
I would try to help more if know type of the desktop client you are using.
I'm running Socket.io multi-threaded with the native cluster functionality provided by Node.js v0.6.0 and later (with RedisStore).
For every new change in state, the server iterates over each connection and sends a message if appropriate. Note: this isn't "broadcasting" to all connections, it's comparing server data with data the client sent on connection to decide whether to send the server data to that particular client. Consider this code sample:
io.sockets.clients().forEach(function (socket) {
socket.get('subscription', function (err, message) {
if(message.someProperty === someServerData) {
socket.emit('position', someServerData);
}
});
This worked fine when there was only one process, but now, the client receives a message for each Node process (ie. if there are 8 Node process running, all clients receive the messages 8 times).
I understand why the issue arises, but I'm not sure of a fix. How can I assign a 1-to-1 relation from one process to only on client. Perhaps something using NODE_WORKER_ID of Cluster?
This previous SO question seems somewhat related, although I'm not sure it's helpful.
This seems like a pretty common request. Surely, I must be missing something?
So if I get this straight you need to emit custom events from the server. You can do that by creating your own custom EventEmitter and triggering events on that emitter, for example:
var io = require('socket.io').listen(80);
events = require('events'),
customEventEmitter = new events.EventEmitter();
io.sockets.on('connection', function (socket) {
// here you handle what happens on the 'positionUpdate' event
// which will be triggered by the server later on
eventEmitter.on('positionUpdate', function (data) {
// here you have a function that checks if a condition between
// the socket connected and your data set as a param is met
if (condition(data,socket)) {
// send a message to each connected socket
// if the condition is met
socket.emit('the new position is...');
}
});
});
// sometime in the future the server will emit one or more positionUpdate events
customEventEmitter.emit('positionUpdate', data);
Another solution would be to have those users join the 'AWE150', so only they will receive updates for 'AWE150', like so:
var io = require('socket.io').listen(80);
io.sockets.on('connection', function (socket) {
if (client_is_interested_in_AWE) { socket.join('AWE150'); }
io.sockets.in('AWE150').emit('new position here');
});
Resources:
http://spiritconsulting.com.ar/fedex/2010/11/events-with-jquery-nodejs-and-socket-io/
I'm using the ws library for WebSockets in Node.js and
I'm trying this example from the library examples:
var sys = require("sys"),
ws = require("./ws");
ws.createServer(function (websocket) {
websocket.addListener("connect", function (resource) {
// emitted after handshake
sys.debug("connect: " + resource);
// server closes connection after 10s, will also get "close" event
setTimeout(websocket.end, 10 * 1000);
}).addListener("data", function (data) {
// handle incoming data
sys.debug(data);
// send data to client
websocket.write("Thanks!");
}).addListener("close", function () {
// emitted when server or client closes connection
sys.debug("close");
});
}).listen(8080);
All OK. It works, but running 3 clients, for instance, and sending "Hello!" from one will make the server only reply "Thanks!" to the client which sent the message, not to all.
How can I broadcast "Thanks!" to all connected clients when someone sends "Hello!"?
Thanks!
If you want to send out to all clients, you have to keep track of them. Here is a sample:
var sys = require("sys"),
ws = require("./ws");
// # Keep track of all our clients
var clients = [];
ws.createServer(function (websocket) {
websocket.addListener("connect", function (resource) {
// emitted after handshake
sys.debug("connect: " + resource);
// # Add to our list of clients
clients.push(websocket);
// server closes connection after 10s, will also get "close" event
// setTimeout(websocket.end, 10 * 1000);
}).addListener("data", function (data) {
// handle incoming data
sys.debug(data);
// send data to client
// # Write out to all our clients
for(var i = 0; i < clients.length; i++) {
clients[i].write("Thanks!");
}
}).addListener("close", function () {
// emitted when server or client closes connection
sys.debug("close");
for(var i = 0; i < clients.length; i++) {
// # Remove from our connections list so we don't send
// # to a dead socket
if(clients[i] == websocket) {
clients.splice(i);
break;
}
}
});
}).listen(8080);
I was able to get it to broadcast to all clients, but it's not heavily tested for all cases. The general concept should get you started though.
EDIT: By the way I'm not sure what the 10 second close is for so I've commented it out. It's rather useless if you're trying to broadcast to all clients since they'll just keep getting disconnected.
I would recommend you to use socket.io. It has example web-chat functionality out of the box and also provides abstraction layer from the socket technology on client (WebSockets are supported by Safari, Chrome, Opera and Firefox, but disabled in Firefox and Opera now due to security vulnerabilities in ws-protocol).