Multiple Clients connected to the same Server using UDP in NodeJS - javascript

Is it possible to have multiple clients to the same UDP server ?
I'd like to broadcast the same data to all connected clients.
Here would be a starting sample, if it helps somehow ...
// Server
var news = [
"Borussia Dortmund wins German championship",
"Tornado warning for the Bay Area",
"More rain for the weekend"
];
var dgram = require('dgram');
var server = dgram.createSocket("udp4");
server.bind(function() {
server.setBroadcast(true)
server.setMulticastTTL(128);
setInterval(broadcastNew, 3000);
});
function broadcastNew() {
var message = new Buffer(news[Math.floor(Math.random() * news.length)]);
server.send(message, 0, message.length, 5007, "224.1.1.1");
console.log("Sent " + message + " to the wire...");
}
// Client 1
var PORT = 5007;
var dgram = require('dgram');
var client = dgram.createSocket('udp4');
client.on('listening', function() {
var address = client.address();
console.log('UDP Client listening on ' + address.address + ":" + address.port);
client.setBroadcast(true)
client.setMulticastTTL(128);
client.addMembership('224.1.1.1');
});
client.on('message', function(message, remote) {
console.log('A: Epic Command Received. Preparing Relay.');
console.log('B: From: ' + remote.address + ':' + remote.port + ' - ' + message);
});
client.bind(PORT);
// Client 2
// Here would go another client, it is possible ?

Yes, it is possible.
I won't go on a speech about how you should use TCP before UDP and only use UDP when absolutely necessary.
For your problem, the fact is that UDP doesn't have any "connection". You receive messages, you send messages, but there is no "connection".
So what you should do is:
When receiving a message from an incoming client, store the IP/Port used by the client
When wanting to send messages to clients, send to all the IP/Port combinations stored
Periodically remove old clients (for example who didn't send a message in the last 5 minutes)
You can detect when a message is received on a bound socket after the "message" event. Your code would look something like that (helped myself):
// Server
var news = [
"Borussia Dortmund wins German championship",
"Tornado warning for the Bay Area",
"More rain for the weekend"
];
var clients = {};
const dgram = require('dgram');
const server = dgram.createSocket('udp4');
server.on('error', (err) => {
console.log(`server error:\n${err.stack}`);
server.close();
});
server.on('message', (msg, rinfo) => {
console.log(`server got: ${msg} from ${rinfo.address}:${rinfo.port}`);
clients[JSON.stringify([rinfo.address, rinfo.port])] = true;
//use delete clients[client] to remove from the list of clients
});
function broadCastNew() {
var message = new Buffer(news[Math.floor(Math.random() * news.length)]);
for (var client in clients) {
client = JSON.parse(client);
var port = client[1];
var address = client[0];
server.send(message, 0, message.length, port, address);
}
console.log("Sent " + message + " to the wire...");
}
server.on('listening', () => {
var address = server.address();
console.log(`server listening ${address.address}:${address.port}`);
setInterval(broadcastNew, 3000);
});
server.bind(5007);
Now whenever your server gets an UDP message on port 5007, it will add the sender to the list of clients, and every 3 seconds it will send a message to all the clients stored. How to make the sender receive that piece of news is another story, but you can use a tool such as WireShark to confirm yourself that it was correctly sent back.
Here I didn't delete old clients but you probably should include a mechanism to store the last time they contacted you (instead of using = true you can for example store current time, then periodically remove old clients)
Broadcast and Multicast are probably different from what you imagine, broadcast is used for example to send a message to everyone on the local network.

Related

Socket.io client disconnects due to ping timeout / transport closed

Here's my setup:
Raspberry Pi 2 (192.168.1.101):
Sensor recording temperature, pressure and humidity.
Python3 script connected to a Raspberry Pi 3, reading sensor data and sending to Pi 3, in JSON format, every 5 seconds.
Raspberry Pi 3 (192.168.1.100):
Node.js server listening for python client on port 8888.
Socket.io listening for web clients on port 3000 (port 3000 and 80 have been opened on my router).
Web server (on port 80) with a website displaying sensor data.
JavaScript connecting to node server, using socket.io, via foobar.ddns.net:3000.
Misc:
I'm using noip.com to have a domain serving my dynamic IP address, my router lets noip know when my public IP changes. I have a URL that looks like foobar.ddns.net.
This setup seems to be working. The Python script is sending data to the node server, which is forwarding it on to any web client connected, which is displayed correctly on the website.
My issue is that the web client disconnects after 1 round of ping/pong between the client and node server.
Here's the chrome console log when connected to the server and receiving data:
The web client connects, receives some data, does a ping/pong with the server, receives some more data, then when it's supposed to ping/pong again it disconnects, then after a while it tries reconnecting and the cycle continues.
And here's the node.js log:
The first New Connection is the Python client (I'm not sure why the IP is the Pi3 address), the rest are the same web client connecting, being disconnected for ping time out, then reconnecting. The client appears to be disconnecting based on on the servers pingInterval + pingTimeout values.
Changing the pingTimeout and pingInterval values just delays the disconnect.
Here's my code:
Python Client:
import json
import socket
import bme280_sensor
import time
import os
class Connection():
def __init__(self, host, port):
self.host = host
self.port = port
def connect(self):
print('Creating socket')
try:
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
except socket.error as msg:
print('Failed to create socket: %s' % msg)
raise
print('Socket created')
server_address = (self.host, self.port)
print('Connecting to %s:%s' % server_address)
try:
self.sock.connect(server_address)
except socket.error as msg:
print('Failed to connect: %s' % msg)
raise
print('Connected')
def shutdown(self):
print('Shutting down')
self.sock.shutdown(socket.SHUT_RDWR)
self.sock.close()
def measure_temp():
bme_data = bme280_sensor.read_all()
obj = {}
obj['temp'] = round(bme_data[2], 2)
obj['humidity'] = round(bme_data[0], 2)
obj['pressure'] = round(bme_data[1], 2)
return json.dumps(obj)
def sendData(sock):
print('Sending data')
while True:
try:
data = 'data,' + measure_temp()
sock.sendall(data.encode())
except socket.error as msg:
print("Cannot send to server: %s" % msg)
break
time.sleep(5)
connection = Connection('192.168.1.100', 8888)
while True:
try:
connection.connect()
sendData(connection.sock)
connection.shutdown()
break
except socket.error as msg:
print('Connection failed, retrying in 3 seconds.')
time.sleep(3)
print('Done')
Node.js Server:
var net = require('net');
var port = 8888;
var server = net.createServer();
// socket io listens for clients on port 3000
var io = require('socket.io')(3000,{
pingInterval: 10000,
pingTimeout: 5000,
});
// server listens for python client on port 8888
server.listen(port);
console.log('Server started');
// store the last data recorded, so when a socket.io client connects, they can get the last reading instead of waiting for the next one
global.last;
server.on('connection', function(socket){
console.log('New server connection ' + socket.address().address);
// when the server recieves data, send it to the connected socket clients
socket.on('data', function(data){
// strip the first 5 characters from the input string, parse json from the result
var actual = generateJSON(data.toString().substring(5));
// store the dta
global.last = actual;
//send the data
io.sockets.emit('data', actual);
});
});
io.on('connection', function(socket){
console.log('New io connection ' + socket.id);
// if the server has data previously recorded, send it to the new client
if(global.last){
io.emit('data', global.last);
}
socket.on('disconnect', function(reason){
console.log('io disconnect: ' + reason);
});
});
function generateJSON(data){
var dataJSON = JSON.parse(data);
var obj = new Object();
obj.temperature = dataJSON.temp;
obj.humidity = dataJSON.humidity;
obj.pressure = dataJSON.pressure;
obj.datetime = new Date().toString();
return JSON.stringify(obj);
}
Website Javascript:
var socket;
var connected = false;
function connect(){
console.log('connecting...')
if(socket){
socket.destroy()
delete socket;
socket = null;
}
socket = io.connect("http://foobar.ddns.net:3000", {
forceNew: true,
reconnection: true,
reconnectionDelay: 3000,
reconnectionDelayMax: 5000,
reconnectionAttempts: Infinity
});
console.log(socket);
socket.on("data", function(data){
var obj = JSON.parse(data);
console.log(data);
$('#temperature-value').text(obj.temperature);
$('#humidity-value').text(obj.humidity);
$('#pressure-value').text(obj.pressure);
lastUpdate = new Date();
});
socket.on('connect_error', function(error){
console.log('connection error: ' + error);
});
socket.on('connect_timeout', function(){
console.log('connection timeout');
});
socket.on('reconnect', function(){
console.log('reconnect');
});
socket.on('reconnect_attempt', function(){
console.log('reconnect attempt');
});
socket.on('reconnect_failed', function(){
console.log('reconnect_failed');
});
socket.on('reconnect_error', function(){
console.log('reconnect_error');
});
socket.on('reconnecting', function(){
console.log('reconnecting');
});
socket.on('ping', function(){
console.log('ping');
});
socket.on('pong', function(ms){
console.log('pong ' + ms + "ms");
});
socket.on('connect', function(){
console.log('connected to server');
connected = true;
});
socket.on('disconnect', function(reason){
console.log('disconnected from server: ' + reason);
connected = false;
});
}
$(document).ready(function(){
connect();
});
I'm accessing the socket.io.js script with this in my index.html:
<script src="http://foobar.ddns.net:3000/socket.io/socket.io.js"></script>
This is functional but the disconnects are rather annoying, I'd rather the client stays connected. I have a feeling that my node.js server is not setup correctly, but I can't figure out what the issue is. If there's a better way to feed data from the python script > node.js server > web clients then please let me know.
Thanks
I've solved the issue! It had nothing to do with node.js or socket.io.
The issue was on the web page I have displaying the data, I had this method to update a span showing the seconds since the last update:
function updateLastUpdateTimer(){
var seconds = (new Date() - lastUpdate) / 1000;
$('#time-since-last-update').text(formatTime(seconds) + " ago");
$('#last-updated-time').text(lastUpdate);
setInterval(updateLastUpdateTimer, 1000);
}
The issue was setInterval when it should have been setTimeout. I realised that my web page was eating up RAM, which was causing the client socket to hang and not send any data to the server, which was causing the time out!
The setInterval method runs a function every x milliseconds. DO NOT put it in the method you want to call! Call it once instead.
To anyone reading this who has the same issue with ping timeout and transport closed disconnects, check your client!

Send Video between servers using Node

i am working in a project using node. My goal is to get a video from one server to the other. I am using udp protocol, so, i just want to know, how i must work with the video and how to do that. I mean, must i convert the video to a string and sent it by the socket udp? I know how to send strings, but i don't know how to apply this using video. Cpuld you help me please?
This is the code i use for the listening server:
var PORT = 33333;
var HOST = '127.0.0.1';
var dgram = require('dgram');
var server = dgram.createSocket('udp4');
server.on('listening', function () {
var address = server.address();
console.log('UDP Server Running on ' + address.address + ":" + address.port);
});
server.on('message', function (message, remote) {
console.log(remote.address + ':' + remote.port +' - ' + message);
});
server.bind(PORT, HOST);
Regards

NodeJS simple UDP client server application using broker

I am writing a simple client server NodeJS application using UDP protocol. The main point of this application is that it requires the use of a broker, whose function, in the given case, is to link the sender with the receiver. The requirements tell me that the sender doesn't need to be aware of the receiver's IP address and port number - it only needs to know the broker's corresponding IP and PORT. Afterwards, the broker will send the client's message to the server, based on the server's IP and PORT.
To clarify the previous(?confusing) paragraph, below you will find a illustration of what I've done so far:
sender.js
var PORT1 = XXXXX;
var HOST = '127.0.0.1';
var fs = require('fs');
var dgram = require('dgram');
var client = dgram.createSocket('udp4');
fs.readFile('Path/to/the/file','utf8', function (err, data) {
if (err) throw err;
var message = new Buffer(data);
client.send(data, 0, message.length, PORT1, HOST, function(err, bytes) {
if (err) throw err;
console.log('UDP message sent to ' + HOST +':'+ PORT1);
client.close();
});
});
The code above reads from a file, stores its contents in a buffer and sends it to the broker's port(the broker listens to the same port) and host(which, in my case is the localhost).
broker.js
var PORT1 = XXXXX;
var PORT2 = YYYYY;
var HOST = '127.0.0.1';
var dgram = require('dgram');
var server = dgram.createSocket('udp4');
var client = dgram.createSocket('udp4');
server.on('listening', function () {
var address = server.address();
console.log('UDP broker listening on ' + address.address + ":" + address.port);
});
server.on('message', function (message, remote) {
client.send(message, 0, message.length, PORT2, HOST, function(err, bytes) {
if (err) throw err;
console.log('UDP message sent to ' + HOST +':'+ PORT2);
client.close();
});
console.log(remote.address + ':' + remote.port +' - ' + message);
});
server.bind(PORT1, HOST);
Here, PORT1 is the port that the broker listens to(waiting for incoming messages from sender) and PORT2 is the port which transmits the message to the receiver(and correspondingly, the receiver listens to this port).
receiver.js
var PORT2 = YYYYY;
var HOST = '127.0.0.1';
var fs = require('fs');
var dgram = require('dgram');
var server = dgram.createSocket('udp4');
var parser = require('xml2json');
server.on('listening', function () {
var address = server.address();
console.log('UDP receiver listening on ' + address.address + ":" + address.port);
});
server.on('message', function (message, remote) {
console.log(remote.address + ':' + remote.port +' - ' + message);
var contents = fs.writeFile("/Path/To/Written/File", parser.toJson(message),
function(error){
if (error) {
console.log("error writing");
}
console.log("File was saved");
});
});
server.bind(PORT2, HOST);
The receiver gets the message from the broker and writes it to a file in the JSON format.
Here are the results:
Sender
UserName's-MacBook-Pro:UDP server UserName$ node sender.js
UDP message sent to 127.0.0.1:XXXXX
Broker
UserName's-MacBook-Pro:UDP server UserName$ node broker.js
UDP broker listening on 127.0.0.1:XXXXX
127.0.0.1:60009 - <?xml version="1.0"?>
<Some XML content here>
</XML content ends here>
UDP message sent to 127.0.0.1:YYYYY
Receiver
UserName's-MacBook-Pro:UDP server UserName$ node receiver.js
UDP receiver listening on 127.0.0.1:YYYYY
127.0.0.1:63407 - <?xml version="1.0"?>
<XML contents here>
</XML content ends here>
File was saved
I am sorry for the long post, but I want to specify all the details to eliminate(hopefully) any ambiguities. Now, to the matter,
HERE is my question
What changes should I make for the broker in order to solve the following problem:
In case of multiple senders and receivers, the broker should manage the ports to link the sender to the receiver(with any specified criteria).
Thank you in advance!
Take a look in this book "Node.js Design Patterns,Publisher:Packt Publishing By: Mario Casciaro ISBN: 978-1-78328-731-4 Year: 2014" at page 361. There is the exact thing you want to do with very good explanation.
Hope it will help!

How to bind socket to more than one port, NodeJS

I have set up a node application which receives packets from a Port at an IP address and serves the contents to a web-page. This IP address has traffic going through two ports, 9999 and 10000. My application has the following code:
var PORT_ONE = 9999;
var PORT_TWO = 10000;
var SENSOR = '239.0.0.1';
var client = require('dgram').createSocket('udp4');
var dns = require('dns');
/* Client starts listening on IP */
client.on('listening', function () {
console.log('UDP Client listening on ' + SENSOR + ":" + PORT_TWO);
client.setMulticastTTL(1);
client.addMembership(SENSOR);
});
/* Client receives a message */
client.on('message', function (message, remote) {
var tempMessage = message.toString(); //cast Buffer var to String
var delimiter = "\n";
var tempData = tempMessage.split(delimiter);
console.log('From: ' + remote.address + ':' + remote.port +' - \n' + message);
var data = {
ip: [SENSOR],
info: [tempData]
};
sendMessage(data);
});
client.bind(PORT_ONE);
client.bind(PORT_TWO); //error here
When I run my node application in the terminal, I receive the error
dgram.js:163
throw new Error('Socket is already bound');
Where dgram.js is part of the Nodejs libraries. The error comes when binding to PORT_TWO. Is there a way to bind the socket to more than one port?
I want to add my 2 cents here, what I normally do is create array of ports and Ips and put in for loop binding on them one by one
udpclients = [5550,5551];
udpsockets=[]
var dgram = require('dgram');
udpclients.forEach(function(port){
var udpServer = dgram.createSocket('udp4');
udpServer.bind(port,'127.0.0.1')
udpServer.on('listening', function() {
var address = udpServer.address()
console.log("listening"+address.address+" port::"+address.port)
});
udpServer.on('message', function(msg, rinfo) {
console.log("message received :: "+ msg +" address::"+rinfo.address+ "port = "+ rinfo.port )
});
});
Or you can bind multiple sockets, wrap them into streams and then combine streams to get feeling of one single socket.

Send Broadcast datagram

I need to send a broadcast datagram to all machine (servers) connected to my network.
I'm using NodeJS Multicast
Client
var dgram = require('dgram');
var message = new Buffer("Some bytes");
var client = dgram.createSocket("udp4");
client.send(message, 0, message.length, 41234, "localhost");
// If I'm in the same machine 'localhost' works
// I need to do something 192.168.0.255 or 255.255.255
client.close();
Servers
var dgram = require("dgram");
var server = dgram.createSocket("udp4");
server.on("message", function (msg, rinfo) {
console.log("server got: " + msg + " from " +
rinfo.address + ":" + rinfo.port);
});
server.on("listening", function () {
var address = server.address();
console.log("server listening " + address.address + ":" + address.port);
});
server.bind(41234);
Thanks.
I spent a lot of time trying to be able to do UDP broadcasting and multicasting between computers. Hopefully this makes it easier for others since this topic is quite difficult to find answers for on the web. These solutions work in Node versions 6.x-12.x:
UDP Broadcasting
Calculate the broadcast address
Broadcast address = (~subnet mask) | (host's IP address) - see Wikipedia. Use ipconfig(Windows) or ifconfig(Linux), or checkout the netmask module.
Server (remember to change BROADCAST_ADDR to the correct broadcast address)
var PORT = 6024;
var BROADCAST_ADDR = "58.65.67.255";
var dgram = require('dgram');
var server = dgram.createSocket("udp4");
server.bind(function() {
server.setBroadcast(true);
setInterval(broadcastNew, 3000);
});
function broadcastNew() {
var message = Buffer.from("Broadcast message!");
server.send(message, 0, message.length, PORT, BROADCAST_ADDR, function() {
console.log("Sent '" + message + "'");
});
}
Client
var PORT = 6024;
var dgram = require('dgram');
var client = dgram.createSocket('udp4');
client.on('listening', function () {
var address = client.address();
console.log('UDP Client listening on ' + address.address + ":" + address.port);
client.setBroadcast(true);
});
client.on('message', function (message, rinfo) {
console.log('Message from: ' + rinfo.address + ':' + rinfo.port +' - ' + message);
});
client.bind(PORT);
UDP Multicasting
Multicast addresses
Looking at the IPv4 Multicast Address Space Registry and more in-depth clarification in the RFC 2365 manual section 6, we find the appropriate local scope multicast addresses are 239.255.0.0/16 and 239.192.0.0/14 (that is, unless you obtain permission to use other ones).
The multicast code below works just fine on Linux (and many other platforms) with these addresses.
Most operating systems send and listen for multicasts via specific interfaces, and by default they will often choose the wrong interface if multiple interfaces are available, so you never receive multicasts on another machine (you only receive them on localhost). Read more in the Node.js docs. For the code to work reliably, change the code so you specify the host's IP address for the interface you wish to use, as follows:
Server - server.bind(SRC_PORT, HOST_IP_ADDRESS, function() ...
Client - client.addMembership(MULTICAST_ADDR, HOST_IP_ADDRESS);
Take a look at these supporting sources: NodeJS, Java, C#, and a helpful command to see which multicast addresses you are subscribed to - netsh interface ipv4 show joins.
Server
var SRC_PORT = 6025;
var PORT = 6024;
var MULTICAST_ADDR = '239.255.255.250';
var dgram = require('dgram');
var server = dgram.createSocket("udp4");
server.bind(SRC_PORT, function () { // Add the HOST_IP_ADDRESS for reliability
setInterval(multicastNew, 4000);
});
function multicastNew() {
var message = Buffer.from("Multicast message!");
server.send(message, 0, message.length, PORT, MULTICAST_ADDR, function () {
console.log("Sent '" + message + "'");
});
}
Client
var PORT = 6024;
var MULTICAST_ADDR = '239.255.255.250';
var dgram = require('dgram');
var client = dgram.createSocket('udp4');
client.on('listening', function () {
var address = client.address();
console.log('UDP Client listening on ' + address.address + ":" + address.port);
});
client.on('message', function (message, rinfo) {
console.log('Message from: ' + rinfo.address + ':' + rinfo.port + ' - ' + message);
});
client.bind(PORT, function () {
client.addMembership(MULTICAST_ADDR); // Add the HOST_IP_ADDRESS for reliability
});
UPDATE: There are additional options for server.send (named socket.send in the docs). You can use a string for the msg instead of a Buffer, and depending on your version, several parameters are optional. You can also check whether an error has occurred in the callback function.
UPDATE: Since Node.js v6, new Buffer(str) is deprecated in favor of Buffer.from(str). The code above has been updated to reflect this change. If you are using an earlier version of Node, use the former syntax.
I never used Node.js, but I do recall that with Berkely sockets (which seem to be the most widely used implementation of sockets) you need to enable the SO_BROADCAST socket option to be able to send datagrams to the broadcast address. Looking up the dgram documentation, there seems to be a function for it.
var client = dgram.createSocket("udp4");
client.setBroadcast(true);
client.send(message, 0, message.length, 41234, "192.168.0.255");
You might want to find out the broadcast address programmatically, but I can't help you with that.
I think since node 0.10.0 some things has changed this works for me now:
//var broadcastAddress = "127.255.255.255";
var broadcastAddress = "192.168.0.255";
var message = new Buffer("Some bytes");
var client = dgram.createSocket("udp4");
client.bind();
client.on("listening", function () {
client.setBroadcast(true);
client.send(message, 0, message.length, 6623, broadcastAddress, function(err, bytes) {
client.close();
});
});
Hope this helps somebody ;)
If you want a AUTOMATIC BROADCAST ADDRESS yo can do:
const broadcastAddress = require('broadcast-address');
const os = require("os")
var PORT = 1234;
var dgram = require('dgram');
var server = dgram.createSocket("udp4");
server.bind(function() {
server.setBroadcast(true);
setInterval(broadcastNew, 5000);
});
function broadcastNew() {
var message = Buffer.from("Broadcast message!");
Object.keys(os.networkInterfaces()).forEach(it=>{
console.log(broadcastAddress(it));
server.send(message, 0, message.length, PORT, broadcastAddress(it), function() {
console.log("Sent '" + message + "'");
});
})
}
This code will get a broadcast address for each interface on you server and send a message.
;) reguards
NOTE: dont forget install broadcast-address = "npm i broadcast-address"

Categories