Dynamically drop TCP/IP connection between 2 nodes

Continuation for topic raised: Dynamically Disable individual nodes - #21 by KarolisL

NR is communicating with PLC via TCP request.
I want to create test scenarios, which simulate TCP connection drop when PLC TCP side is misconfigured and drops connection.

Best representation of that I found is to create a temp listener like TCP-out node. Then connection is established
image
tcp request node change status - I pick it up and do following actions

when I disable tcp-out node, tcp-request node changes status once again
image

I pick this status and do further actions.

@gregorius msg.reset doesn't work because it drops connection but with another request it will be restored, where in reality after tcp listener is not available anymore it will generate a connection timeout error

As I understand you really just want to be able to deactivate the tcp listener not the tcp out. So being able to dynamically disable the tcp in node would be what you're looking for.

The msg.reset is a feature of the tcp out node to dynamically close a connected tcp client.

What I understand you need is to prevent tcp clients from connecting to the tcp in node which means dynamically turning off the tcp in node, i.e., the underlying tcp listener.

That is not possible because the tcp in node does not take any input messages that control its behaviour, therefore there is no way to send it a message to the tcp in node to stop listening for new connections.

1 Like

TCP-out represents TCP server (PLC) which nodered is connecting to receive messages.

When configuration is misaligned: TCP-request node goes into fault because it cannot connect to PLC.

Sometimes PLC goes down, and TCP-request is supposed to try and reconnect to it. I want to emulate this behavior without real PLC. First to establish connection with TCP server, then disconnect/disable simulated server so it TCP-request would throw connection timeout error.

You gave me idea to try and implement TCP-server in nodered and control it myself instead of disabling it

Thanks for your help. Along with ChatGPT something was born

[{"id":"5942d5d9f9468519","type":"tcp request","z":"b5b01e12c2d27ec0","name":"","server":"localhost","port":"4001","out":"sit","ret":"string","splitc":" ","newline":"\\n","trim":false,"tls":"","x":330,"y":1380,"wires":[["8d5b268c8bcce8eb"]]},{"id":"f7c9cfd991f83d37","type":"inject","z":"b5b01e12c2d27ec0","name":"Init Connection","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":130,"y":1380,"wires":[["5942d5d9f9468519"]]},{"id":"0a01b357d102210f","type":"function","z":"b5b01e12c2d27ec0","name":"tcp server","func":"const server = global.get('tcpServer');  // Get the server from global context\nconst connections = global.get('connections') || [];  // Retrieve active connections\n\n// Check if the message has the send flag, and if so, send a message to all connected clients\nif (msg?.send) {\n    const message = 'Hello from server\\n';  // The message you want to send\n\n    // Broadcast the message to all connected clients\n    connections.forEach((socket) => {\n        socket.write(message);  // Send the message to each connected client\n    });\n    // node.warn('Sent \"Hello from server\" to all clients');\n}\n\nif (server && msg?.disconnect) {\n    // Close all active client connections\n    connections.forEach((socket) => {\n        socket.destroy();  // Forcefully close each connection\n    });\n    global.set('connections', []);  // Clear the connections array\n\n    // Close the server and stop listening for new connections\n    server.close(() => {\n        node.warn('Server stopped successfully and all connections dropped');\n    });\n\n    // Nullify the global reference\n    global.set('tcpServer', undefined);  // Update the global context\n    return;  // Return immediately after stopping the server\n}\n\nif (!server && msg?.connect) {\n\n    // Create an array to store client connections\n    const connections = [];\n\n    // Create a server instance\n    const newServer = net.createServer((socket) => {\n        node.warn('Client connected');\n\n        // Store the new connection in the global list\n        connections.push(socket);\n        global.set('connections', connections);\n\n        // Handle incoming data\n        socket.on('data', (data) => {\n            node.warn('Received: ' + data.toString());\n            node.send({\n                payload: data.toString()  // Wrap the data in the 'payload' field\n            });\n            // node.send(data.toString())\n        });\n\n        // Handle client disconnection\n        socket.on('end', () => {\n            node.warn('Client disconnected');\n\n            // Remove the disconnected socket from the active connections list\n            const index = connections.indexOf(socket);\n            if (index > -1) {\n                connections.splice(index, 1);\n            }\n            global.set('connections', connections);\n        });\n\n        // Handle connection errors\n        socket.on('error', (err) => {\n            node.warn('Connection error: ' + err.message);\n        });\n    });\n\n    newServer.listen(4001, () => {\n        node.warn('Server listening on port 4001');\n    });\n\n    // Error handling for the server\n    newServer.on('error', (err) => {\n        node.error('Server error: ' + err.message);\n    });\n\n    // Store the new server instance and connections array in the global context\n    global.set('tcpServer', newServer);\n    global.set('connections', connections);  // Store the connections array\n}\n\n// Pass the message object\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[{"var":"net","module":"net"}],"x":340,"y":1480,"wires":[["4a4c761d3a1c8e4f"]]},{"id":"6a58edda9b720c8f","type":"inject","z":"b5b01e12c2d27ec0","name":"","props":[{"p":"connect","v":"true","vt":"bool"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":150,"y":1480,"wires":[["0a01b357d102210f"]]},{"id":"4a4c761d3a1c8e4f","type":"debug","z":"b5b01e12c2d27ec0","name":"Received from client","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":590,"y":1440,"wires":[]},{"id":"f1966c830e6d3440","type":"inject","z":"b5b01e12c2d27ec0","name":"DropServer","props":[{"p":"disconnect","v":"true","vt":"bool"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":150,"y":1520,"wires":[["0a01b357d102210f"]]},{"id":"8d5b268c8bcce8eb","type":"debug","z":"b5b01e12c2d27ec0","name":"Received from server","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":580,"y":1370,"wires":[]},{"id":"a310e017a209f172","type":"inject","z":"b5b01e12c2d27ec0","name":"Send from Server","props":[{"p":"send","v":"true","vt":"bool"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":160,"y":1560,"wires":[["0a01b357d102210f"]]}]
const server = global.get('tcpServer');  // Get the server from global context
const connections = global.get('connections') || [];  // Retrieve active connections

// Check if the message has the send flag, and if so, send a message to all connected clients
if (msg?.send) {
    const message = 'Hello from server\n';  // The message you want to send

    // Broadcast the message to all connected clients
    connections.forEach((socket) => {
        socket.write(message);  // Send the message to each connected client
    });
    // node.warn('Sent "Hello from server" to all clients');
}

if (server && msg?.disconnect) {
    // Close all active client connections
    connections.forEach((socket) => {
        socket.destroy();  // Forcefully close each connection
    });
    global.set('connections', []);  // Clear the connections array

    // Close the server and stop listening for new connections
    server.close(() => {
        node.warn('Server stopped successfully and all connections dropped');
    });

    // Nullify the global reference
    global.set('tcpServer', undefined);  // Update the global context
    return;  // Return immediately after stopping the server
}

if (!server && msg?.connect) {

    // Create an array to store client connections
    const connections = [];

    // Create a server instance
    const newServer = net.createServer((socket) => {
        node.warn('Client connected');

        // Store the new connection in the global list
        connections.push(socket);
        global.set('connections', connections);

        // Handle incoming data
        socket.on('data', (data) => {
            node.warn('Received: ' + data.toString());
            node.send({
                payload: data.toString()  // Wrap the data in the 'payload' field
            });
            // node.send(data.toString())
        });

        // Handle client disconnection
        socket.on('end', () => {
            node.warn('Client disconnected');

            // Remove the disconnected socket from the active connections list
            const index = connections.indexOf(socket);
            if (index > -1) {
                connections.splice(index, 1);
            }
            global.set('connections', connections);
        });

        // Handle connection errors
        socket.on('error', (err) => {
            node.warn('Connection error: ' + err.message);
        });
    });

    newServer.listen(4001, () => {
        node.warn('Server listening on port 4001');
    });

    // Error handling for the server
    newServer.on('error', (err) => {
        node.error('Server error: ' + err.message);
    });

    // Store the new server instance and connections array in the global context
    global.set('tcpServer', newServer);
    global.set('connections', connections);  // Store the connections array
}

// Pass the message object
return msg;

it works for my use case, but I would like to receive feedback how can it be improved