How/should one remove old websocket connections? - node.js

I'm using 'ws' on Node.js and I'm not really sure how old connections are removed (old connections being disconnects or idle ones, this may be two different issues). Do I need to implement this manually for the two types?

For this you need to first maintain a list of live web socket connection objects in your server end. When the user disconnects from web socket by closing the browser or any other means you need to delete the user web socket connection object from the list.
Server side codes. You can refer to members array in the following code for handling this.
io.on("connection",function(client)
{
client.on("join",function(data)
{
members[membercount] = client;
membername[membercount] = data;
membercount++;
for(var i =0 ;i <members.length;i++)
{
members[i].emit("message",membername);
}
});
client.on("messages",function(data)
{
for(var i =0 ;i <members.length;i++)
{
if(members[i].id !== client.id)
{
members[i].emit("message",data);
}
}
});
client.on("disconnect",function(data)
{
for(var i =0;i<members.length;i++)
{
if(members[i].id === client.id )
{
membercount--;
members.splice(i,1);
membername.splice(i,1);
}
}
for(var i =0 ;i <members.length;i++)
{
members[i].emit("message",membername);
}
});
});
Hope this helps !!!!!

Related

react-beautiful-dnd and mysql state management

okay. I'm confused as to the best way to do this:
the following pieces are in play: a node js server, a client-side react(with redux), a MYSql DB.
in the client app I have lists (many but for this issue, assume one), that I want to be able to reorder by drag and drop.
in the mysql DB the times are stored to represent a linked list (with a nextKey, lastKey, and productionKey(primary), along with the data fields),
//mysql column [productionKey, lastKey,nextKey, ...(other data)]
the current issue I'm having is a render issue. it stutters after every change.
I'm using these two function to get the initial order and to reorder
function SortLinkedList(linkedList)
{
var sortedList = [];
var map = new Map();
var currentID = null;
for(var i = 0; i < linkedList.length; i++)
{
var item = linkedList[i];
if(item?.lastKey === null)
{
currentID = item?.productionKey;
sortedList.push(item);
}
else
{
map.set(item?.lastKey, i);
}
}
while(sortedList.length < linkedList.length)
{
var nextItem = linkedList[map.get(currentID)];
sortedList.push(nextItem);
currentID = nextItem?.productionKey;
}
const filteredSafe=sortedList.filter(x=>x!==undefined)
//undefined appear because server has not fully updated yet, so linked list is broken
//nothing will render without this
return filteredSafe
;
}
const reorder = (list, startIndex, endIndex) => {
const result = Array.from(list);
const [removed] = result.splice(startIndex, 1);
result.splice(endIndex, 0, removed);
const adjustedResult = result.map((x,i,arr)=>{
if(i==0){
x.lastKey=null;
}else{
x.lastKey=arr[i-1].productionKey;
}
if(i==arr.length-1){
x.nextKey=null;
}else{
x.nextKey=arr[i+1].productionKey;
}
return x;
})
return adjustedResult;
};
I've got this function to get the items
const getItems = (list,jobList) =>
{
return list.map((x,i)=>{
const jobName=jobList.find(y=>y.jobsessionkey==x.attachedJobKey)?.JobName;
return {
id:`ProductionCardM${x.machineID}ID${x.productionKey}`,
attachedJobKey: x.attachedJobKey,
lastKey: x.lastKey,
machineID: x.machineID,
nextKey: x.nextKey,
productionKey: x.productionKey,
content:jobName
}
})
}
my onDragEnd
const onDragEnd=(result)=> {
if (!result.destination) {
return;
}
// dropped outside the list
const items = reorder(
state.items,
result.source.index,
result.destination.index,
);
dispatch(sendAdjustments(items));
//sends update to server
//server updates mysql
//server sends back update events from mysql in packets
//props sent to DnD component are updated
}
so the actual bug looks like the graphics are glitching - as things get temporarily filtered in the sortLinkedList function - resulting in jumpy divs. is there a smoother way to handle this client->server->DB->server->client dataflow that results in a consistent handling in DnD?
UPDATE:
still trying to solve this. currently implemented a lock pattern.
useEffect(()=>{
if(productionLock){
setState({
items: SortLinkedList(getItems(data,jobList)),
droppables: [{ id: "Original: not Dynamic" }]
})
setLoading(false);
}else{
console.log("locking first");
setLoading(true);
}
},[productionLock])
where production lock is set to true and false from triggers on the server...
basically: the app sends the data to the server, the server processes the request, then sends new data back, when it's finished the server sends the unlock signal.
which should trigger this update happening once, but it does not, it still re-renders on each state update to the app from the server.
What’s the code for sendAdjustments()?
You should update locally first, otherwise DnD pulls it back to its original position while you wait for backend to finish. This makes it appear glitchy. E.g:
Set the newly reordered list locally as your state
Send network request
If it fails, reverse local list state back to the original list

room broadcast vs sockets emit - are they the same?

I'm using my own rooms implementation in Socket.io (got my own Room/Player classes), since I need to perform several filters the room. For now, I save all sockets inside "players" property in a Room, and implemented my own "emit" to the room, the loops players and emits to their sockets.
Is it considerably slower to the traditional broadcast.to('room')? or it basically does what I did to my own room implementation? I'm aiming on having thousands of rooms with 2-4 players in each...
Thanks :)
As you can see by looking at the code for the socket.io adapter .broadcast() on GitHub, all socket.io is doing is looping over a list of sockets and sending a packet to each one (see code below).
So, if your code is doing something similar, then performance would likely be something similar.
Where you might notice a feature difference is if you are using a custom adapter such as the redis adapter that is used with clustering, then the logic of broadcasting to users connected to different servers would be handled for you by the built-in adapter, but may be something you'd have to implement yourself if doing your own broadcast.
Here's the socket.io-adapter version of .broadcast():
Adapter.prototype.broadcast = function(packet, opts){
var rooms = opts.rooms || [];
var except = opts.except || [];
var flags = opts.flags || {};
var packetOpts = {
preEncoded: true,
volatile: flags.volatile,
compress: flags.compress
};
var ids = {};
var self = this;
var socket;
packet.nsp = this.nsp.name;
this.encoder.encode(packet, function(encodedPackets) {
if (rooms.length) {
for (var i = 0; i < rooms.length; i++) {
var room = self.rooms[rooms[i]];
if (!room) continue;
var sockets = room.sockets;
for (var id in sockets) {
if (sockets.hasOwnProperty(id)) {
if (ids[id] || ~except.indexOf(id)) continue;
socket = self.nsp.connected[id];
if (socket) {
socket.packet(encodedPackets, packetOpts);
ids[id] = true;
}
}
}
}
} else {
for (var id in self.sids) {
if (self.sids.hasOwnProperty(id)) {
if (~except.indexOf(id)) continue;
socket = self.nsp.connected[id];
if (socket) socket.packet(encodedPackets, packetOpts);
}
}
}
});
};

Express.js - while loop before sending response

I'm trying to implement and existing solution in node.js, specifically, using express.js framework. Now, the existing solution works as follows:
server exposes a GET service that clients can connect to
when a client calls the GET service, the client number increments (a global variable) and then the number of clients is checked;
if there are not at least 3 clients connected, the service is in endless loop, waiting for other clients to connect
if (or rather, when) the rest of the two clients connect, the service sends respond to everyone that enough clients are connected (a 'true' value).
So what basically happens is, the client connects and the connection is active (in a loop) until enough clients connect, then and only then there is a response (to all clients at the same time).
Now I'm not expert in these architectures, but from what I think, this is not a correct or good solution. My initial thought was: this must be solved with sockets. However, since the existing solution works like that (it's not written in node.js), I tried to emulate such behaviour:
var number = (function(){
var count = 0;
return {
increase: function() {
count++;
},
get: function(){
return count;
}
};
})();
app.get('/test', function(req, res){
number.increase();
while (number.get() < 3) {
//hold it here, until enough clients connect
}
res.json(number.get());
});
Now while I think that this is not a correct solution, I have a couple of questions:
Is there any alternative to solving this issue, besides using sockets?
Why does this "logic" work in C#, but not in express.js? The code above hangs, no other request is processed.
I know node.js is single-threaded, but what if we have a more conventional service that responds immediately, and there are 20 requests all at the same time?
I would probably use an event emitter for this:
var EventEmitter = require('events').EventEmitter;
var emitter = new EventEmitter();
app.get('/', function(req, res) {
// Increase the number
number.increase();
// Get the current value
var current = number.get();
// If it's less than 3, wait for the event emitter to trigger.
if (current < 3) {
return emitter.once('got3', function() {
return res.json(number.get());
});
}
// If it's exactly 3, emit the event so we wake up other listeners.
if (current === 3) {
emitter.emit('got3');
}
// Fall through.
return res.json(current);
});
I would like to stress that #Plato is correct in stating that browsers may timeout when a response takes too much time to complete.
EDIT: as an aside, some explanation on the return emitter.once(...).
The code above can be rewritten like so:
if (current < 3) {
emitter.once('got3', function() {
res.json(number.get());
});
} else if (current === 3) {
emitter.emit('got3');
res.json(number.get());
} else {
res.json(number.get());
}
But instead of using those if/else statements, I return from the request handler after creating the event listener. Since request handlers are asynchronous, their return value is discarded, so you can return anything (or nothing). As an alternative, I could also have used this:
if (current < 3) {
emitter.once(...);
return;
}
if (current === 3) {
...etc...
Also, even though you return from the request handler function, the event listener is still referencing the res variable, so the request handler scope is maintained by Node until res.json() in the event listener callback is called.
Your http approach should work
You are blocking the event loop so node refuses to do any other work while it is in the while loop
You're really close, you just need to check every now and then instead of constantly. I do this below with process.nextTick() but setTimeout() would also work:
var number = (function(){
var count = 0;
return {
increase: function() {
count++;
},
get: function(){
return count;
}
};
})();
function waitFor3(callback){
var n = number.get();
if(n < 3){
setImmediate(function(){
waitFor3(callback)
})
} else {
callback(n)
}
}
function bump(){
number.increase();
console.log('waiting');
waitFor3(function(){
console.log('done');
})
}
setInterval(bump, 2000);
/*
app.get('/test', function(req, res){
number.increase();
waitFor3(function(){
res.json(number.get());
})
});
*/

Should I define socket.on('... event outside the io.sockets.on('connection... scope?

I have various clients' sockets connect to my server and I need to define an event on each of them, but based on some logic, not unconditionally. Like:
for(i= 0; i<= this.users.length; i++) {
if (i=== actionUserOrdinal) {
io.sockets.socket(mySocketId).on('action', function(action) {...
but doing so it gets defined multiple times for some sockets during the course of running the app. And so, it gets invoked multiple times too (by the same trigger).
And if I define it the default way,
io.sockets.on('connection', function(socket) {
socket.on('action', function(data) {
...
I cannot access my main app logic's variables and such. Unless I make some things global.
One solution I thought of was to delete the event after it is triggered
for(i= 0; i<= this.users.length; i++) {
if (i=== actionUserOrdinal) {
thisocket = io.sockets.socket(mySocketId);
io.sockets.socket(mySocketId).on('action', function(action) {
delete thisocket._events.action;
(Thanks #ArdiVaba)
But I discover this is flaky and the event still gets fired twice sometimes.
Is there some other way to define the event such that it doesn't gets define more than once, by either defining it in my main app's logic itself, or by defining it in its default scope yet I still be able to access my main app's variables without going global?
Instead of defining different events possible for each user, listen for all events for all users and in the function verify if the user is allowed to use this event.
Use sessionSockets to store the type of user and his permissions
// checkPermission function check if user is allowed to do something
checkPermissions = function(function_name, session, callback) {
if(!session) {
callback(null);
return;
}
if(!session.user) {
callback(null);
return;
}
// If user is superuser, he has the right to do everything
if(!session.user.superuser) {
callback('allowed');
return;
}
// TODO Here check with user and function_name if user has the right
// to use this function
// use callback('allowed') if he is allowed
// use callback(null) if he is not allowed
}
sessionSockets.on('connection', function (err, socket, session) {
socket.on('event', function() {
checkPermission('event', session, function(r) {
if(r === 'allowed') {
// User is allowed
// Do your thing
} else {
// User is not allowed
// send error message to user ?
}
});
});
});

Storing data in Socket.io

I am now starting in this world of Node.js, I've done several tests, initially as the typical "Hello World", up to the creation of a simple chat, I have embarked on the creation of one a bit more complex, which I describe below:
A chat in which users already exist, which are housed in a MySQL database.
It should create multiple chat rooms, where every conversation is one to one.
Initially I did Mysql connection tests, achieving it efficiently, but coming across problems when closing the connection to it, so I found another "solution" which will ask if it is advisable to do it or the Otherwise it would be a big mistake.
To chat'm making use of Seller Socket.io known, which, reading the documentation I have found related to the ID of the session, you can associate more information, what I'm doing is the following:
I a room through socket.join option (room)
With the property "set" of socket.io, I am storing information related to the connected user, such as name, database id and url of the picture, through an object.
Using io.sockets.clients (room), get users who are in the room, given that a user can have more than one session (several windows / tabs), so do the validation client side with the user id, to show just one.
Server:
var io = require("socket.io").listen(8080, {log: false});
io.on("connection", function(socket)
{
socket.on("newUser", function(data)
{
var participants = [];
var room = data.ig;
socket.set('iduser', data.d, function ()
{
socket.join(room);
for (var ver in pas = io.sockets.clients(room))
{
participants.push({id: pas[ver].store.data.iduser[0].id, n: pas[ver].store.data.iduser[0].n, f: pas[ver].store.data.iduser[0].f});
}
io.sockets.in(room).emit("newConnection", {participants: participants});
});
});
socket.on("disconnect", function()
{
var rooms = io.sockets.manager.roomClients[socket.id];
for(var room in rooms)
{
if(room.length > 0)
{
room = room.substr(1);
}
}
var idusuario = 0;
for(var ver in pas = io.sockets.clients(room))
{
if(pas[ver].id == socket.id)
{
idusuario = pas[ver].store.data.iduser[0].id;
break;
}
}
if(idusuario != 0)
{
//El número de veces que tiene una sesión...
var num = 0;
for(var ver in pas = io.sockets.clients(room))
{
if(pas[ver].store.data.iduser[0].id == idusuario)
{
num++;
}
}
if(num <= 1)
{
io.sockets.in(room).emit("userDisconnected", {id: idusuario, sender:"system"});
}
}
});
});
Client
Appreciate their opinions or suggestions on how to deal with this difficulty.
Thanks in advance for their help.

Resources