How to properly close a Node.js TCP server? - javascript

I couldn't find a clear answer on Google or SO.
I know a net.Server instance has a close method that doesn't allow any more clients in. But it doesn't disconnect clients already connected. How can I achieve that?
I know how this can be done with Http, I guess I'm asking if it's the same with Tcp or if it's different.
With Http, I'd do something like this:
var http = require("http");
var clients = [];
var server = http.createServer(function(request, response) {
response.writeHead(200, {"Content-Type": "text/plain"});
response.end("You sent a request.");
});
server.on("connection", function(socket) {
socket.write("You connected.");
clients.push(socket);
});
// .. later when I want to close
server.close();
clients.forEach(function(client) {
client.destroy();
});
Is it the same for Tcp? Or should I do anything differently?

Since no answer was provided, here is an example of how to open and (hard) close a server in node.js:
Create the server:
var net = require('net');
var clients = [];
var server = net.createServer();
server.on('connection', function (socket) {
clients.push(socket);
console.log('client connect, count: ', clients.length);
socket.on('close', function () {
clients.splice(clients.indexOf(socket), 1);
});
});
server.listen(8194);
Close the server:
// destroy all clients (this will emit the 'close' event above)
for (var i in clients) {
clients[i].destroy();
}
server.close(function () {
console.log('server closed.');
server.unref();
});
Update: Since using the above code, I've ran into an issue that close will leave the port open (TIME_WAIT in Windows). Since I'm intentionally closing the connection, I'm using unref as it appears to fully close the tcp server, though I'm not 100% if this is the correct way of closing the connection.

I am using NodeJS v16.13.2 ... When the process containing the server code exits, all clients connection are closed/destroyed by default.
I came here to find out how I could listen for a server.("exit", myTaskCallback), since I wanted to delete some files while exiting the server. But the answer I have found is that such event does not exists. I had to listen to process.on('exit', myTaskCallback) to do the job.

sock.end(); //to correctly send the end of the connection in both sides
sock.on("close", fn) //add event listeners to destory all related sockets and clients
sock.on("close", () => { sock.destroy() }) //to destroy your side socket wrapper
Example:
const closeConn = async (sock, cb) => {
sock.ev.on("close", async ()=>{
await sock?.destroy();
if (typeof cb === "function") cb();
});
await sock?.end();
}
closeConn(sock, openSock);
You can check more here

Related

Node.js - Distinguish between server connect and disconnect on 'connection' event

I'm creating a server in my Node.js application with net.createServer.
I add an event listener to the "connection"-event to be able to determine when a connection has been established. All well so far.
The problem is that when I disconnect it seems to trigger the "connection"-event again! And there is no "disconnect" event, so I don't know how to know if it is connected or disconnected!
const server = createServer({ allowHalfOpen: true }, (socket) => {
this._socket.on("data", (buffer) => {
// ...
});
this._socket.on("end", () => {
// ...
});
server.on("connection", () => {
console.log("Connected or Disconnected!")
});
How can I determine if the connection is established or ended?
I would like to avoid having a flag keeping track of the state if possible.
Thanks!
server.on("close", () => {
console.log("Disconnected!")
});
https://nodejs.org/api/http.html#http_event_close
Within the connection listening event, there is a listening event for disconnections:
var clients = [];
server.on('connection', (socket) => {
// add client
clients.push({id:15, name:'client15'});
socket.on('disconnect', () => {
// first search client and remove
});
});
Now the next question would be: how do I know which person disconnected?
The answer to this question is more than putting logic to the process and you can create a global variable for save client, when the person starts session or connects to the socket, it is registered in that variable and when it is disconnected it must be taken out of said variables, this is not ma than an example but there may be better ways.
So my naive approach to this issue that seems to work is to use getConnections, which will increase on every connection event, and see if it is even or uneven to be able to determine if it is connected or disconnected:
server.on("connection", () => {
server.getConnections((e, c) => {
if (c % 2 === 0) {
// Disconnected
} else {
// Connected
}
});
It does not feel like a good solution, but maybe good enough until someone can show me a better way.

How to terminate a WebSocket connection?

Is it possible to terminate a websocket connection from server without closing the entire server? If it is then, how can I achieve it?
Note: I'm using NodeJS as back-end and 'ws' websocket module.
So because of some sort of omission in the documentation regarding ws.close() and ws.terminate() I think the solutions in provided answers won't close the sockets gracefully in some cases, thus keeping them hanging in the Event Loop.
Compare the next two methods of ws package:
ws.close():
Initializes close handshake, sending close frame to the peer and awaiting to receive close frame from the peer, after that sending FIN packet in attempt to perform a clean socket close. When answer received, the socket is destroyed. However, there is a closeTimeout that will destroy socket only as a worst case scenario, and it potentially could keep socket for additional 30 seconds, preventing the graceful exit with your custom timeout:
// ws/lib/WebSocket.js:21
const closeTimeout = 30 * 1000; // Allow 30 seconds to terminate the connection cleanly.
ws.terminate():
Forcibly destroys the socket without closing frames or fin packets exchange, and does it instantly, without any timeout.
Hard shutdown
Considering all of the above, the "hard landing" scenario would be as follows:
wss.clients.forEach((socket) => {
// Soft close
socket.close();
process.nextTick(() => {
if ([socket.OPEN, socket.CLOSING].includes(socket.readyState)) {
// Socket still hangs, hard close
socket.terminate();
}
});
});
Soft shutdown
You can give your clients some time to respond, if you could allow yourself to wait for a while (but not 30 seconds):
// First sweep, soft close
wss.clients.forEach((socket) => {
socket.close();
});
setTimeout(() => {
// Second sweep, hard close
// for everyone who's left
wss.clients.forEach((socket) => {
if ([socket.OPEN, socket.CLOSING].includes(socket.readyState)) {
socket.terminate();
}
});
}, 10000);
Important: proper execution of close() method will emit 1000 close code for close event, while terminate() will signal abnormal close with 1006 (MDN WebSocket Close event).
If you want to kick ALL clients without closing the server you can do this:
for(const client of wss.clients)
{
client.close();
}
you can also filter wss.clients too if you want to look for one in particular. If you want to kick a client as part of the connection logic (i.e. it sends bad data etc), you can do this:
let WebSocketServer = require("ws").Server;
let wss = new WebSocketServer ({ port: 8080 });
wss.on('connection', function connection(ws) {
ws.send('something');
ws.close(); // <- this closes the connection from the server
});
and with a basic client
"use strict";
const WebSocket = require("ws");
let ws = new WebSocket("ws://localhost:8080");
ws.onopen = () => {
console.log("opened");
};
ws.onmessage = (m) => {
console.log(m.data);
};
ws.onclose = () => {
console.log("closed");
};
you'll get:
d:/example/node client
opened
something
closed
According to the ws documentation, you need to call websocket.close() to terminate a connection.
let server = new WebSocketServer(options);
server.on('connection', ws => {
ws.close(); //terminate this connection
});
Just use ws.close() in this way.
var socketServer = new WebSocketServer();
socketServer.on('connection', function (ws) {
ws.close(); //Close connecton for connected client ws
});
If you use var client = net.createConnection() to create the socket you can use client.destroy() to destroy it.
With ws it should be:
var server = new WebSocketServer();
server.on('connection', function (socket) {
// Do something and then
socket.close(); //quit this connection
});

Can't close server (nodeJS)

Why I can't close the server by requesting localhost:13777/close in browser (it continues to accept new requests), but it will gracefully close on timeout 15000? Node version is 0.10.18. I fell into this problem, trying to use code example from docs on exceptions handling by domains (it was giving me 'Not running' error every time I secondly tried to request error page) and finally came to this code.
var server
server = require("http").createServer(function(req,res){
if(req.url == "/close")
{
console.log("Closing server (no timeout)")
setTimeout(function(){
console.log("I'm the timeout")
}, 5000);
server.close(function(){
console.log("Server closed (no timeout)")
})
res.end('closed');
}
else
{
res.end('ok');
}
});
server.listen(13777,function(){console.log("Server listening")});
setTimeout(function(){
console.log("Closing server (timeout 15000)")
server.close(function(){console.log("Server closed (timeout 15000)")})
}, 15000);
The server is still waiting on requests from the client. The client is utilizing HTTP keep-alive.
I think you will find that while the existing client can make new requests (as the connection is already established), other clients won't be able to.
Nodejs doesn't implement a complex service layer on top of http.Server. By calling server.close() you are instructing the server to no longer accept any "new" connections. When a HTTP Connection:keep-alive is issued the server will keep the socket open until the client terminates or the timeout is reached. Additional clients will not be able to issue requests
The timeout can be changed using server.setTimeout() https://nodejs.org/api/http.html#http_server_settimeout_msecs_callback
Remember if a client has created a connection before the close event that connection can continually be used.
It seems that a lot of people do not like this current functionality but this issue has been open for quite a while:
https://github.com/nodejs/node/issues/2642
As the other answers point out, connections may persist indefinitely and the call to server.close() will not truly terminate the server if any such connections exist.
We can write a simple wrapper function which attaches a destroy method to a given server that terminates all connections, and closes the server (thereby ensuring that the server ends nearly immediately!)
Given code like this:
let server = http.createServer((req, res) => {
// ...
});
later(() => server.close()); // Fails to reliably close the server!
We can define destroyableServer and use the following:
let destroyableServer = server => {
// Track all connections so that we can end them if we want to destroy `server`
let sockets = new Set();
server.on('connection', socket => {
sockets.add(socket);
socket.once('close', () => sockets.delete(socket)); // Stop tracking closed sockets
});
server.destroy = () => {
for (let socket of sockets) socket.destroy();
sockets.clear();
return new Promise((rsv, rjc) => server.close(err => err ? rjc(err) : rsv()));
};
return server;
};
let server = destroyableServer(http.createServer((req, res) => {
// ...
}));
later(() => server.destroy()); // Reliably closes the server almost immediately!
Note the overhead of entering every unique socket object into a Set

Socket IO Server to Server

Is it possible for a server to connect to another using Socket.IO and be treated like a client?
And have it join rooms, recieve io.sockets.in('lobby').emit(). And more?
The first server is also listening for connections/messages as well.
Hey Brad, here's my full .js app below for reference:
var io = require("socket.io").listen(8099);
io.set('log level', 1);
io.sockets.on("connection", function (socket) {
console.log('A Client has Connected to this Server');
//Let Everyone Know I just Joined
socket.broadcast.to('lobby').emit("message",'UC,' + socket.id); // Send to everyone in Room but NOT me
socket.on("message", function (data) {
//Missing code
socket2.send('message,' + data); //Forward Message to Second Server
});
socket.on("disconnect", function (data) {
//Send Notification to Second Server
//Need to figure out later
//Send Notification to Everyone
socket.broadcast.emit("message",'UD,' + socket.id ); //Send to Everyone but NOT me
//Remove user from Session ID
arSessionIDs.removeByValue(socket.id);
//Send Notification to Console
console.log("disconnecting " + arRoster[socket.id][1]);
});
});
var io_client = require( 'socket.io-client' );
var socket2 = io_client.connect('http://192.168.0.104:8090');
socket2.on('connect', function () {
socket2.emit('C3434M,Test');
});
Yes, absolutely. Just use the Socket.IO client in your server application directly.
https://github.com/LearnBoost/socket.io-client
You can install it with npm install socket.io-client. Then to use:
var socket = io.connect('http://example.com');
socket.on('connect', function () {
// socket connected
socket.emit('server custom event', { my: 'data' });
});
I realize this is an old post, but I was working on something similar and decided to come back and contribute something as it got me thinking.
Here's a basic Client -> Server 1 -> Server 2 setup
Server #1
// Server 1
var io = require("socket.io").listen(8099); // This is the Server for SERVER 1
var other_server = require("socket.io-client")('http://example.com:8100'); // This is a client connecting to the SERVER 2
other_server.on("connect",function(){
other_server.on('message',function(data){
// We received a message from Server 2
// We are going to forward/broadcast that message to the "Lobby" room
io.to('lobby').emit('message',data);
});
});
io.sockets.on("connection",function(socket){
// Display a connected message
console.log("User-Client Connected!");
// Lets force this connection into the lobby room.
socket.join('lobby');
// Some roster/user management logic to track them
// This would be upto you to add :)
// When we receive a message...
socket.on("message",function(data){
// We need to just forward this message to our other guy
// We are literally just forwarding the whole data packet
other_server.emit("message",data);
});
socket.on("disconnect",function(data){
// We need to notify Server 2 that the client has disconnected
other_server.emit("message","UD,"+socket.id);
// Other logic you may or may not want
// Your other disconnect code here
});
});
And here's Server #2
// Server 2
var io = require("socket.io").listen(8100);
io.sockets.on("connection",function(socket){
// Display a connected message
console.log("Server-Client Connected!");
// When we receive a message...
socket.on("message",function(data){
// We got a message. I don't know, what we should do with this
});
});
This is our Client, who sends the original message.
// Client
var socket = io('http://localhost');
socket.on('connect', function(){
socket.emit("message","This is my message");
socket.on('message',function(data){
console.log("We got a message: ",data);
});
});
I am making this post a Community Wiki so that someone can improve this if they feel like it.
The code has not been tested, use at your own risk.
I had the same problem, but instead to use socket.io-client I decided to use a more simple approach (at least for me) using redis pub/sub, the result is pretty simple.
You can take a look at my solution here: https://github.com/alissonperez/scalable-socket-io-server
With this solution you can have how much process/servers you want (using auto-scaling solution), you just use redis as a way to forward your messages between your servers.

Disconnect from Redis cleanly when Node exits?

Currently I have the following code, working with Node.js, socket.io and Redis:
var io = require('socket.io'), redis = require("redis"), client = redis.createClient();
io.sockets.on('connection', function (socket) {
var socket_id = socket.id;
socket.on('chat', function(data) {
client.set('user:' + socket_id, data['colour']);
// The user left the page. Remove them from Redis.
socket.on('disconnect', function () {
client.del('user:' + socket_id);
client.quit();
});
});
});
This works fine for normal socket connections and disconnections, but there seems to be a problem if Node goes down, or if I just restart Node as part of normal development.
The key is never deleted from Redis. So the number of entries stored in the Redis database gradually grows and grows. I'm also not sure whether the Redis client exists cleanly.
How can I clean up Redis entries and quit the Redis client when Node exits, as well as when the socket disconnects?
You could handle this when node exits, but e.g. in case the power goes down, there's no way you can clean it up at shutdown time. I'd wipe old stuff from the DB at startup time instead.
I've run in to this problem too. My solution was to just use a specific Redis database on the server for Socket.io data. A simple FLUSHDB to that database on start up will clean out stuck keys.
var socketIoDatabase = 4;
var client = redis.createClient();
client.select(socketIoDatabase);
client.flushdb();
Of course if you have multiple node processes using this same database clearing it will cause problems. In this case you can do it during a maintenance window or something while all node processes are terminated.
check this out:
http://nodejs.org/docs/v0.4.12/api/process.html#event_uncaughtException_
var io = require('socket.io'), redis = require("redis"), client = redis.createClient();
io.sockets.on('connection', function (socket) {
var socket_id = socket.id;
socket.on('chat', function(data) {
client.set('user:' + socket_id, data['colour']);
// The user left the page. Remove them from Redis.
socket.on('disconnect', function () {
client.del('user:' + socket_id);
client.quit();
});
});
});
// this will be activated on any error without try catch :)
process.on('uncaughtException', function (err) {
console.log('Caught exception: ' + err);
});

Categories

Resources