I am new to node.js and would like to connect to a TCP socket. For this I am using the net module.
My idea was to wrap the connect sequence into a function then on the 'close' event, attempt a reconnection. Not that easy apparently.
function conn() {
client.connect(HOST_PORT, HOST_IP, function() {
startSequence();
})
}
client.on('close', function(e) {
log('info','Connection closed! -> ' + e)
client.destroy();
setTimeout(conn(),1000);
});
So when the remote host is closed, I see my logs comming through, howere what seems to be happening is that as soons as the remote host comes online ALL the previous attempts start to get processed - if that makes sense. If you look at client.connect, there is a function called startSequence that sends some data that "iniates" the connection from the remote server side. When the server goes offline and I start reconnecting all the failed attempts from before seem to have been buffered and are all sent together when the server goes online.
I have tried the code from this Stackoverflow link as well to no avail (Nodejs - getting client socket to try again after 5 sec time out)
client.connect(HOST_PORT, HOST_IP, function() {
pmsStartSequence();
})
// Add a 'close' event handler for the client socket
client.on('close', function(e) {
log('debug','connection closed -> ' + e)
client.setTimeout(10000, function() {
log('debug', 'trying to reconnect')
client.connect(HOST_PORT, HOST_IP, function() {
pmsStartSequence();
})
})
});
Is there any advice on how I can reconnect a socket after failure?
Inspired from the other solutions, I wrote this, it's tested, it works !
It will keep on trying every 5 sec, until connection is made, works if it looses connection too.
/* Client connection */
/* --------------------------------------------------------------------------------- */
const client = new net.Socket()
var intervalConnect = false;
function connect() {
client.connect({
port: 1338,
host: '127.0.0.1'
})
}
function launchIntervalConnect() {
if(false != intervalConnect) return
intervalConnect = setInterval(connect, 5000)
}
function clearIntervalConnect() {
if(false == intervalConnect) return
clearInterval(intervalConnect)
intervalConnect = false
}
client.on('connect', () => {
clearIntervalConnect()
logger('connected to server', 'TCP')
client.write('CLIENT connected');
})
client.on('error', (err) => {
logger(err.code, 'TCP ERROR')
launchIntervalConnect()
})
client.on('close', launchIntervalConnect)
client.on('end', launchIntervalConnect)
connect()
The problem is where you set the on-connect callback.
The doc of socket.connect() says:
connectListener ... will be added as a listener for the 'connect' event once.
By setting it in socket.connect() calls, every time you try reconnecting, one more listener (a one-time one), which calls startSequence(), is attached to that socket. Those listeners will not be fired until reconnection successes, so you got all of them triggered at the same time on a single connect.
One possible solution is separating the connect listener from socket.connect() calls.
client.on('connect', function() {
pmsStartSequence();
});
client.on('close', function(e) {
client.setTimeout(10000, function() {
client.connect(HOST_PORT, HOST_IP);
})
});
client.connect(HOST_PORT, HOST_IP);
My solution:
var parentHOST = '192.168.2.66';
var parentPORT = 9735;
var net = require('net');
var S = require('string');
var parentClient = new net.Socket();
var parentActive = false;
var startParentClient = function () {
parentClient = new net.Socket();
parentActive = false;
parentClient.connect(parentPORT, parentHOST, function() {
console.log('Connected ' + cluster.worker.id + ' to parent server: ' + parentHOST + ':' + parentPORT);
parentActive = true;
});
parentClient.on('error', function() {
parentActive = false;
console.log('Parent connection error');
});
parentClient.on('close', function() {
parentActive = false;
console.log('parent connection closed');
setTimeout(startParentClient(), 4000);
});
}
If is necessary connect:
if (!S(parentHOST).isEmpty() && !S(parentPORT).isEmpty()) {
startParentClient();
}
As mentioned multiple times in the comments, you need to use .removeAllListeners() before trying to reconnect your client to the server in order to avoid having multiple listeners on the same event.
The code below should do the trick
Note that I try to reconnect the client after the close and end events because these two events can be fired in different orders after closing a connection
const net = require("net")
let client = new net.Socket()
function connect() {
console.log("new client")
client.connect(
1337,
"127.0.0.1",
() => {
console.log("Connected")
client.write("Hello, server! Love, Client.")
}
)
client.on("data", data => {
console.log("Received: " + data)
})
client.on("close", () => {
console.log("Connection closed")
reconnect()
})
client.on("end", () => {
console.log("Connection ended")
reconnect()
})
client.on("error", console.error)
}
// function that reconnect the client to the server
reconnect = () => {
setTimeout(() => {
client.removeAllListeners() // the important line that enables you to reopen a connection
connect()
}, 1000)
}
connect()
I use the following code to achieve reconnection with node.js. I am not a Javascript expert so I guess it can be improved but it nevertheless works fine for me.
I hope this could help.
Best.
//----------------------------------------------------------------//
// SocketClient //
//----------------------------------------------------------------//
var net = require('net');
var SocketClient = function(host, port, data_handler, attempt)
{
var node_client;
var attempt_index = (attempt ? attempt : 1);
this.m_node_client = new net.Socket();
node_client = this.m_node_client;
this.m_node_client.on('close', function()
{
var new_wrapper = new SocketClient(host, port, data_handler, attempt_index + 1);
node_client.destroy();
new_wrapper.start();
});
this.m_node_client.on('data', data_handler);
this.m_node_client.on('error', function(data)
{
console.log("Error");
});
this.start = function()
{
this.m_node_client.connect(port, host, function()
{
console.log('Connected ' + attempt_index);
});
};
};
//----------------------------------------------------------------//
// Test //
//----------------------------------------------------------------//
var test_handler = function(data)
{
console.log('TestHandler[' + data + ']');
};
var wrapper = new SocketClient('127.0.0.1', 4000, test_handler);
wrapper.start();
I have tried re-using the same socket connection, by using this:
const s = net.createConnection({port});
s.once('end', () => {
s.connect({port}, () => {
});
});
that didn't work, from the server-side's perspective. If the client connection closes, it seems like a best practice to create a new connection:
const s = net.createConnection({port});
s.once('end', () => {
// create a new connection here
s = net.createConnection(...);
});
sad but true lulz.
Following this:
//
// Simple example of using net.Socket but here we capture the
// right events and attempt to re-establish the connection when
// is is closed either because of an error establishing a
// connection or when the server closes the connection.
//
// Requires
const net = require('net');
// Create socket
const port = 5555;
const host = '127.0.0.1';
const timeout = 1000;
let retrying = false;
// Functions to handle socket events
function makeConnection () {
socket.connect(port, host);
}
function connectEventHandler() {
console.log('connected');
retrying = false;
}
function dataEventHandler() {
console.log('data');
}
function endEventHandler() {
// console.log('end');
}
function timeoutEventHandler() {
// console.log('timeout');
}
function drainEventHandler() {
// console.log('drain');
}
function errorEventHandler() {
// console.log('error');
}
function closeEventHandler () {
// console.log('close');
if (!retrying) {
retrying = true;
console.log('Reconnecting...');
}
setTimeout(makeConnection, timeout);
}
// Create socket and bind callbacks
let socket = new net.Socket();
socket.on('connect', connectEventHandler);
socket.on('data', dataEventHandler);
socket.on('end', endEventHandler);
socket.on('timeout', timeoutEventHandler);
socket.on('drain', drainEventHandler);
socket.on('error', errorEventHandler);
socket.on('close', closeEventHandler);
// Connect
console.log('Connecting to ' + host + ':' + port + '...');
makeConnection();
function createServer() {
const client = new net.Socket();
client.connect(HOST_PORT, HOST_IP, function() {
console.log("Connected");
state = 1 - state;
client.write(state.toString());
});
client.on("data", function(data) {
console.log("Received: " + data);
//client.destroy(); // kill client after server's response
});
client.on("close", function() {
console.log("Connection closed");
//client.connect()
setTimeout(createServer, 2000);
});
}
createServer();
Related
Here is an example of how to create a TCP client connection from the node net docs (https://nodejs.org/api/net.html#net_net_connect_options_connectlistener)
const client = net.createConnection({ port: 1905 }, () => {
// 'connect' listener
console.log('connected to server!');
client.write('world!\r\n');
});
client.on('data', (data) => {
console.log(data.toString());
client.end();
});
client.on('end', () => {
console.log('disconnected from server');
});
If the server is not available I get Error: connect ECONNREFUSED 127.0.0.1:1905.
What would be a good way to wait/reconnect until the server is available and connect when it is, instead of throwing an error?
EDIT: Here is an alternative approach I have tried, but here I get the problem
MaxListenersExceededWarning: Possible EventEmitter memory leak
detected. 11 connect listeners added. Use emitter.setMaxListeners() to
increase limit
I would like the latest listener to replace earlier listeners. They all listen for the same thing. I just want to retry.
function initTcpClient() {
console.log("Initiating TCP client...")
var tcpSocket = new net.Socket();
const client = net.createConnection({ port: 1905 }, () => {
tcpSocket.on('error', function onError(err) {
setTimeout(connect, 1000);
});
connect();
function connect() {
console.log("Looking for TCP server...");
tcpSocket.connect(argv.tcpport, argv.tcphost, function onConnected() {
console.log("Connecting to TCP server...");
tcpSocket.on('data', function onIncoming(data) {
if (connectedWebsocketClient) {
console.log('Forwarding to WebSocket: %s', data);
webSocketClient.send(data.toString());
} else {
console.log('Not connected to websocket client. Dropping incoming TCP message: %s', data);
}
});
tcpSocket.on('close', function onClose(hadError) {
console.log("Connection to TCP server was closed.");
connectedToTcpServer = false;
setTimeout(connect, 1000);
});
console.log("Connected to TCP server.");
connectedToTcpServer = true;
});
}
}
Here to elaborate on my comment. Is an example that will work. Try it with a simple tcp server. Start the client and then after a few seconds start the server. It is important to register you listeners after a reconnect happens in onError You may also want to have a limit of how many times you want to try to reconnect.
const net = require('net')
let client = connect()
client.on('data', onData);
client.on('error', onError);
client.on("close", onClose);
function onData(data) {
console.log(data)
}
function onError(err) {
if(err.message.indexOf('ECONNREFUSED') > -1) {
//do recconect
console.log("Attempting to reconnect shortly")
setTimeout(()=>{
client = connect();
client.on('data', onData);
client.on('error', onError);
client.on("close", onClose);
},1000)
}
}
function onClose() {
console.log("Removng all listeners")
client.removeAllListeners("data");
client.removeAllListeners("error")
}
function connect() {
const c = net.createConnection({
port: 3000
},
()=>{
console.log('connected')
});
return c
}
I have two commands to send to server, first move forward, get the acknowledgment and then send next command move backward. I have written two separate java script files do achieve this. Can it is possible to write in single function. I am trying below code but only move forward command is sent to server.
var net = require('net');
var HOST = '127.0.0.1';
var PORT = 1850;
var client = new net.Socket();
client.connect(PORT, HOST, function() {
console.log('CONNECTED TO: ' + HOST + ':' + PORT);
client.write('READER_FWD');
//client.end();
});
client.on('data', function(data) {
console.log('DATA: ' + data);
//client.destroy();
//
if (data == 'ACK')
{
console.log('DATA1: ' + data);
client.end();
console.log('DATA2: ' + data);
client.connect(PORT, HOST, function() {
console.log('CONNECTED TO: ' + HOST + ':' + PORT);
client.write('READER_BWD');
//client.end();
console.log('DATA3: ' + data);
});
}
client.end();
});
client.on('end', function() {
console.log('disconnected from server');
});
client.on('error', function(err) {
console.log(err)
});
I have updated the code, as you rightly pointed out connection is getting close while writing, i have added some delay.
var net = require('net');
var config = {
host: '127.0.0.1',
port: 1850
};
var move = {
forward: 'READER_FWD',
backward: 'READER_BWD'
};
var client = new net.Socket();
client.connect({
host: config.host,
port: config.port
}, function () {
console.log('connected to ' + config.host + ':' + config.port);
client.write(move.forward, function () {
console.log('move forward command sent');
});
});
client.on('data', function (data)
{
var str = data.toString();
if (str === 'ACK')
{
setTimeout(function()
{
console.log('ACK received');
client.write(move.backward, function ()
{
console.log('move backward sent');
client.end();
});
}, 3000);
}
});
client.on('error', function (err) {
console.log('Error : ', err);
});
client.on('close', function () {
console.log('socket closed');
});
You don't have to end your socket and re-open it again in your 'data' listener. You can keep the same socket.
Here is my client.js file which sends the commands:
var net = require('net');
var config = {
host: '127.0.0.1',
port: 1850
};
var move = {
forward: 'READER_FWD',
backward: 'READER_BWD'
};
var client = new net.Socket();
client.connect({
host: config.host,
port: config.port
}, function () {
console.log('connected to ' + config.host + ':' + config.port);
client.write(move.forward, function () {
console.log('move forward command sent');
});
});
client.on('data', function (data) {
var str = data.toString();
if (str === 'ACK') {
console.log('ACK received');
client.write(move.backward, function () {
console.log('move backward sent');
client.end();
});
}
});
client.on('error', function (err) {
console.log('Error : ', err);
});
client.on('close', function () {
console.log('socket closed');
});
The connect() method connects the socket to the server and send the forward command to it. It's exactly the same as yours.
Then, the problem comes from your 'data' listener. Your data listener must do the following things (as you mentionned in your description):
Get data from the server
If it's the ACK message: send the backward command
Then, close the connection (if needed; if not, keep it alive)
Be careful to the following point: the Socket nodejs documentation for the event 'data' says that we are receiving a Buffer. So you need to convert it to a String to compare with another String, using for this the .toString() method of the Buffer.
Thus, as is the Nodejs net.Socket is used with events, I don't think it is possible to send the forward command, listen to the 'data' event and send the backward command.
First, it is not a good idea, because you will put the on 'data' listener after the connection and it is possible that you will miss some data!
Secondly, as it is event based, you should create your architecture that follows the process :)
Below is my code for the server:
var net = require('net');
var port = 1850;
var move = {
forward: 'READER_FWD',
backward: 'READER_BWD'
};
var server = net.createServer(function (client) {
console.log('client connected');
client.on('end', function () {
console.log('client disconnected');
});
client.on('data', function (data) {
var str = data.toString();
if (str === move.forward) {
console.log('move forward command received');
client.write('ACK', function () {
console.log('ACK sent');
});
} else if (str === move.backward) {
console.log('move backward command received: do nothing...');
} else {
console.log('unknown received message: ', str);
}
});
});
server.listen(port, function () { //'listening' listener
console.log('server bound on port: ' + port);
});
Here are also the outputs if needed:
Server:
server bound on port: 1850
client connected
move forward command received
ACK sent
move backward command received: do nothing...
client disconnected
Client:
connected to 127.0.0.1:1850
move forward command sent
ACK received
move backward sent
socket closed
I hope it answers the question. Feel free to ask if there is anything.
I'm a new in nodejs, i have project about TCP server client , I init 2000 client and send to server 1KB/1s on terminal, after i click X button on terminal (disconnect client unusual).
On my server, only 1500-1600 client disconnect, the rest client still not disconnect ??
So how can i clean this garbage connection ??
How can i check status of socket ??
Client code :
var test = setInterval(function () {
var client = socks.connect(function (socket) {
lstClient.push(socket);
var message = '';
console.log('>> Connection successful');
socket.on('data', function (data) {
console.log(data.length);
});
var myTimer = setInterval(function () {
if (socket.writable) socket.write(datareal.toString());
else clearInterval(myTimer);
}, 5000);
socket.on('error', function (err) {
console.log(err);
});
socket.on('close', function () {
console.log('Disconnect socket...');
socket.destroy();
});
})
}, 15);
Thanks to advance !!
I'm trying my hand at writing an ftp client against Filezilla that supports active mode using node.js. I'm new to ftp and node.js. I thought I could get a good understanding of tcp socket communication and the ftp protocol by doing this exercise. Also, node-ftp an jsftp don't seem to support active mode, so I think this will be a nice (though rarely used) addition to npm.
I've got some proof of concept code that works at least sometimes, but not all the time. In the case where it works, the client uploads a file called file.txt with the text 'hi'.
When it works, I get this:
220-FileZilla Server version 0.9.41 beta
220-written by Tim Kosse (Tim.Kosse#gmx.de)
220 Please visit http://sourceforge.net/projects/filezilla/
331 Password required for testuser
230 Logged on
listening
200 Port command successful
150 Opening data channel for file transfer.
server close
226 Transfer OK
half closed
closed
Process finished with exit code 0
When it doesn't work, I get this:
220-FileZilla Server version 0.9.41 beta
220-written by Tim Kosse (Tim.Kosse#gmx.de)
220 Please visit http://sourceforge.net/projects/filezilla/
331 Password required for testuser
230 Logged on
listening
200 Port command successful
150 Opening data channel for file transfer.
server close
half closed
closed
Process finished with exit code 0
So, I'm not getting the 226, and I'm not sure why I'm getting the inconsistent results.
Forgive the poorly written code. I'll refactor once I'm confident I understand how this should work.:
var net = require('net'),
Socket = net.Socket;
var cmdSocket = new Socket();
cmdSocket.setEncoding('binary')
var server = undefined;
var port = 21;
var host = "localhost";
var user = "testuser";
var password = "Password1*"
var active = true;
var supplyUser = true;
var supplyPassword = true;
var supplyPassive = true;
var waitingForCommand = true;
var sendFile = true;
function onConnect(){
}
var str="";
function onData(chunk) {
console.log(chunk.toString('binary'));
//if ftp server return code = 220
if(supplyUser){
supplyUser = false;
_send('USER ' + user, function(){
});
}else if(supplyPassword){
supplyPassword = false;
_send('PASS ' + password, function(){
});
}
else if(supplyPassive){
supplyPassive = false;
if(active){
server = net.createServer(function(socket){
console.log('new connection');
socket.setKeepAlive(true, 5000);
socket.write('hi', function(){
console.log('write done');
})
socket.on('connect', function(){
console.log('socket connect');
});
socket.on('data', function(d){
console.log('socket data: ' + d);
});
socket.on('error', function(err){
console.log('socket error: ' + err);
});
socket.on('end', function() {
console.log('socket end');
});
socket.on('drain', function(){
console.log('socket drain');
});
socket.on('timeout', function(){
console.log('socket timeout');
});
socket.on('close', function(){
console.log('socket close');
});
});
server.on('error', function(e){
console.log(e);
});
server.on('close', function(){
console.log('server close');
});
server.listen(function(){
console.log('listening');
var address = server.address();
var port = address.port;
var p1 = Math.floor(port/256);
var p2 = port % 256;
_sendCommand('PORT 127,0,0,1,' + p1 + ',' + p2, function(){
});
});
}else{
_send('PASV', function(){
});
}
}
else if(sendFile){
sendFile = false;
_send('STOR file.txt', function(){
});
}
else if(waitingForCommand){
waitingForCommand = false;
cmdSocket.end(null, function(){
});
if(server)server.close(function(){});
}
}
function onEnd() {
console.log('half closed');
}
function onClose(){
console.log('closed');
}
cmdSocket.once('connect', onConnect);
cmdSocket.on('data', onData);
cmdSocket.on('end', onEnd);
cmdSocket.on('close', onClose);
cmdSocket.connect(port, host);
function _send(cmd, callback){
cmdSocket.write(cmd + '\r\n', 'binary', callback);
}
Also, is the server appropriate, or should I do it some other way?
EDIT:
I changed the callback in server.listen to use a random port. This has removed the 425 I was getting previously. However, I am still not getting consistent behavior with the file transfer.
The flow for Active mode FTP transfers goes roughly like this:
Connection preamble (USER/PASS)
Establish a client local socket for data
Inform the server of that socket (PORT)
Tell the server to open the remote file for writing (STOR)
Start writing the data from the data socket established above (socket.write())
Close the stream from the client side (socket.end()) to end the file transfer
Tell the server you are done (QUIT)
Clean up any open sockets and servers on the client
So once you've done this:
else if(sendFile){
sendFile = false;
_send('STOR file.txt', function(){
});
}
The server will respond with a 150 saying it has connected to the data socket you established and is ready to receive data.
One improvement to make it easier to reason about the execution at this point would be to change your control flow to operate on a parsed response code rather than pre-defined bools.
function onData(chunk) {
console.log(chunk);
var code = chunk.substring(0,3);
if(code == '220'){
instead of:
function onData(chunk) {
console.log(chunk.toString('binary'));
//if ftp server return code = 220
if(supplyUser){
Then you can add a section for sending the data:
//ready for data
else if (code == '150') {
dataSocket.write('some wonderful file contents\r\n', function(){});
dataSocket.end(null, function(){});
}
And a little more to clean up:
//transfer finished
else if ( code == '226') {
_send('QUIT', function(){ console.log("Saying Goodbye");});
}
//session end
else if ( code == '221') {
cmdSocket.end(null, function(){});
if(!!server){ server.close(); }
}
Obviously things will get more complicated if you are sending multiple files etc, but this should get your proof of concept running more reliably:
var net = require('net');
Socket = net.Socket;
var cmdSocket = new Socket();
cmdSocket.setEncoding('binary')
var server = undefined;
var dataSocket = undefined;
var port = 21;
var host = "localhost";
var user = "username";
var password = "password"
var active = true;
function onConnect(){
}
var str="";
function onData(chunk) {
console.log(chunk.toString('binary'));
var code = chunk.substring(0,3);
//if ftp server return code = 220
if(code == '220'){
_send('USER ' + user, function(){
});
}else if(code == '331'){
_send('PASS ' + password, function(){
});
}
else if(code == '230'){
if(active){
server = net.createServer(function(socket){
dataSocket = socket;
console.log('new connection');
socket.setKeepAlive(true, 5000);
socket.on('connect', function(){
console.log('socket connect');
});
socket.on('data', function(d){
console.log('socket data: ' + d);
});
socket.on('error', function(err){
console.log('socket error: ' + err);
});
socket.on('end', function() {
console.log('socket end');
});
socket.on('drain', function(){
console.log('socket drain');
});
socket.on('timeout', function(){
console.log('socket timeout');
});
socket.on('close', function(){
console.log('socket close');
});
});
server.on('error', function(e){
console.log(e);
});
server.on('close', function(){
console.log('server close');
});
server.listen(function(){
console.log('listening');
var address = server.address();
var port = address.port;
var p1 = Math.floor(port/256);
var p2 = port % 256;
_send('PORT 127,0,0,1,' + p1 + ',' + p2, function(){
});
});
}else{
_send('PASV', function(){
});
}
}
else if(code == '200'){
_send('STOR file.txt', function(){
});
}
//ready for data
else if (code == '150') {
dataSocket.write('some wonderful file contents\r\n', function(){});
dataSocket.end(null, function(){});
}
//transfer finished
else if ( code == '226') {
_send('QUIT', function(){ console.log("Saying Goodbye");});
}
//session end
else if ( code == '221') {
cmdSocket.end(null, function(){});
if(!!server){ server.close(); }
}
}
function onEnd() {
console.log('half closed');
}
function onClose(){
console.log('closed');
}
cmdSocket.once('connect', onConnect);
cmdSocket.on('data', onData);
cmdSocket.on('end', onEnd);
cmdSocket.on('close', onClose);
cmdSocket.connect(port, host);
function _send(cmd, callback){
cmdSocket.write(cmd + '\r\n', 'binary', callback);
}
Could anyone provide a rock-solid, dead-simple unit test for Node.js using WebSockets (Socket.io)?
I'm using socket.io for Node.js, and have looked at socket.io-client for establishing the client connection to a server in the test. However, I seem to be missing something.
In the example below, "worked..." never gets printed out.
var io = require('socket.io-client')
, assert = require('assert')
, expect = require('expect.js');
describe('Suite of unit tests', function() {
describe('First (hopefully useful) test', function() {
var socket = io.connect('http://localhost:3001');
socket.on('connect', function(done) {
console.log('worked...');
done();
});
it('Doing some things with indexOf()', function() {
expect([1, 2, 3].indexOf(5)).to.be.equal(-1);
expect([1, 2, 3].indexOf(0)).to.be.equal(-1);
});
});
});
Instead, I simply get:
Suite of unit tests
First (hopefully useful) test
✓ Doing some things with indexOf()
1 test complete (26 ms)
Any suggestions?
After further poking and prodding, I found some incredibly useful information. In the author's example, he points out the critical step of establishing socket listeners in the before hooks.
This example works:
Assuming a server is listening for socket connections at localhost:3001, of course
var io = require('socket.io-client')
, assert = require('assert')
, expect = require('expect.js');
describe('Suite of unit tests', function() {
var socket;
beforeEach(function(done) {
// Setup
socket = io.connect('http://localhost:3001', {
'reconnection delay' : 0
, 'reopen delay' : 0
, 'force new connection' : true
});
socket.on('connect', function() {
console.log('worked...');
done();
});
socket.on('disconnect', function() {
console.log('disconnected...');
})
});
afterEach(function(done) {
// Cleanup
if(socket.connected) {
console.log('disconnecting...');
socket.disconnect();
} else {
// There will not be a connection unless you have done() in beforeEach, socket.on('connect'...)
console.log('no connection to break...');
}
done();
});
describe('First (hopefully useful) test', function() {
it('Doing some things with indexOf()', function(done) {
expect([1, 2, 3].indexOf(5)).to.be.equal(-1);
expect([1, 2, 3].indexOf(0)).to.be.equal(-1);
done();
});
it('Doing something else with indexOf()', function(done) {
expect([1, 2, 3].indexOf(5)).to.be.equal(-1);
expect([1, 2, 3].indexOf(0)).to.be.equal(-1);
done();
});
});
});
I found that the placement of done() in the beforeEach, socket.on('connect'...) listener was crucial to having the connection get established. For example, if you comment out done() in the listener, then add it one scope out (just before exiting the beforeEach), you'll see the "no connection to break..." message instead of the "disconnecting..." message. Like so:
beforeEach(function(done) {
// Setup
socket = io.connect('http://localhost:3001', {
'reconnection delay' : 0
, 'reopen delay' : 0
, 'force new connection' : true
});
socket.on('connect', function() {
console.log('worked...');
//done();
});
socket.on('disconnect', function() {
console.log('disconnected...');
});
done();
});
I'm new to Mocha, so there's probably a very obvious reason to the initiated for placing done() within the socket scope itself. Hopefully that little detail will save others in my shoes from hair pulling.
For me, the above test (with correct scoping of done()) outputs:
Suite of unit tests
First (hopefully useful) test
◦ Doing some things with indexOf(): worked...
✓ Doing some things with indexOf()
disconnecting...
disconnected...
◦ Doing something else with indexOf(): worked...
✓ Doing something else with indexOf()
disconnecting...
disconnected...
2 tests complete (93 ms)
Offering an extension of the accepted answer here. Has basic client to server communication useful as boilerplate for other future tests. Using mocha, chai, and expect.
var io = require('socket.io-client')
, io_server = require('socket.io').listen(3001);
describe('basic socket.io example', function() {
var socket;
beforeEach(function(done) {
// Setup
socket = io.connect('http://localhost:3001', {
'reconnection delay' : 0
, 'reopen delay' : 0
, 'force new connection' : true
, transports: ['websocket']
});
socket.on('connect', () => {
done();
});
socket.on('disconnect', () => {
// console.log('disconnected...');
});
});
afterEach((done) => {
// Cleanup
if(socket.connected) {
socket.disconnect();
}
io_server.close();
done();
});
it('should communicate', (done) => {
// once connected, emit Hello World
io_server.emit('echo', 'Hello World');
socket.once('echo', (message) => {
// Check that the message matches
expect(message).to.equal('Hello World');
done();
});
io_server.on('connection', (socket) => {
expect(socket).to.not.be.null;
});
});
});
Dealing with callbacks and promises yourself can be difficult and non trivial examples quickly become very complex and hard to read.
There is a tool called socket.io-await-test available via NPM that allows you to suspend/wait in a test until events have been triggered using the await keyword.
describe("wait for tests", () => {
it("resolves when a number of events are received", async () => {
const tester = new SocketTester(client);
const pongs = tester.on('pong');
client.emit('ping', 1);
client.emit('ping', 2);
await pongs.waitForEvents(2) // Blocks until the server emits "pong" twice.
assert.equal(pongs.get(0), 2)
assert.equal(pongs.get(1), 3)
})
})
Check out this boilerplate solution that's based on promises and good practice.
You can test your servers entire io events with it, no sweat.
You just need to copy a boilerplate test and add your own code as needed.
Checkout the repo on GitHub for full source code.
https://github.com/PatMan10/testing_socketIO_server
const io = require("socket.io-client");
const ev = require("../utils/events");
const logger = require("../utils/logger");
// initSocket returns a promise
// success: resolve a new socket object
// fail: reject a error
const initSocket = () => {
return new Promise((resolve, reject) => {
// create socket for communication
const socket = io("localhost:5000", {
"reconnection delay": 0,
"reopen delay": 0,
"force new connection": true
});
// define event handler for sucessfull connection
socket.on(ev.CONNECT, () => {
logger.info("connected");
resolve(socket);
});
// if connection takes longer than 5 seconds throw error
setTimeout(() => {
reject(new Error("Failed to connect wihtin 5 seconds."));
}, 5000);
}
);
};
// destroySocket returns a promise
// success: resolve true
// fail: resolve false
const destroySocket = socket => {
return new Promise((resolve, reject) => {
// check if socket connected
if (socket.connected) {
// disconnect socket
logger.info("disconnecting...");
socket.disconnect();
resolve(true);
} else {
// not connected
logger.info("no connection to break...");
resolve(false);
}
});
};
describe("test suit: Echo & Bello", () => {
test("test: ECHO", async () => {
// create socket for communication
const socketClient = await initSocket();
// create new promise for server response
const serverResponse = new Promise((resolve, reject) => {
// define a handler for the test event
socketClient.on(ev.res_ECHO, data4Client => {
//process data received from server
const { message } = data4Client;
logger.info("Server says: " + message);
// destroy socket after server responds
destroySocket(socketClient);
// return data for testing
resolve(data4Client);
});
// if response takes longer than 5 seconds throw error
setTimeout(() => {
reject(new Error("Failed to get reponse, connection timed out..."));
}, 5000);
});
// define data 4 server
const data4Server = { message: "CLIENT ECHO" };
// emit event with data to server
logger.info("Emitting ECHO event");
socketClient.emit(ev.com_ECHO, data4Server);
// wait for server to respond
const { status, message } = await serverResponse;
// check the response data
expect(status).toBe(200);
expect(message).toBe("SERVER ECHO");
});
test("test BELLO", async () => {
const socketClient = await initSocket();
const serverResponse = new Promise((resolve, reject) => {
socketClient.on(ev.res_BELLO, data4Client => {
const { message } = data4Client;
logger.info("Server says: " + message);
destroySocket(socketClient);
resolve(data4Client);
});
setTimeout(() => {
reject(new Error("Failed to get reponse, connection timed out..."));
}, 5000);
});
const data4Server = { message: "CLIENT BELLO" };
logger.info("Emitting BELLO event");
socketClient.emit(ev.com_BELLO, data4Server);
const { status, message } = await serverResponse;
expect(status).toBe(200);
expect(message).toBe("SERVER BELLO");
});
});
---- Foot Note ----
Depending on how you setup your server environment, you may experience environmental conflict between socket.io and socket.io-client running from the same project simultaneously. In which case it would be better to separate the project into a "test client" and a server. Checkout below repo if you get this issue.
https://github.com/PatMan10/testing_socketIO_server_v2
In OP's code,
socket.on('connect', function(done) {
console.log('worked...');
done();
});
the done was applied to the wrong callback. It should be removed from the socket.on callback and added to Mocha's it block callback:
it('First (hopefully useful) test', function (done) {
var socket = io.connect('http://localhost:3001');
socket.on('connect', function () {
console.log('worked...');
done();
});
});
A complete example
Existing answers are great but don't show the server ultimately being tested. Here's a complete version with console.logs to illustrate what's going on. Explanation follows.
src/server.js:
const express = require("express");
const createServer = (port=3000) => {
const app = express();
const http = require("http").Server(app);
const io = require("socket.io")(http);
io.on("connection", socket => {
console.log("[server] user connected");
socket.on("message", msg => {
console.log(`[server] received '${msg}'`);
socket.emit("message", msg);
});
socket.on("disconnect", () => {
console.log("[server] user disconnected");
});
});
http.listen(port, () =>
console.log(`[server] listening on port ${port}`)
);
return {
close: () => http.close(() =>
console.log("[server] closed")
)
};
};
module.exports = {createServer};
test/server.test.js:
const {expect} = require("chai");
const io = require("socket.io-client");
const {createServer} = require("../src/server");
const socketUrl = "http://localhost:3000";
describe("server", function () {
this.timeout(3000);
let server;
let sockets;
beforeEach(() => {
sockets = [];
server = createServer();
});
afterEach(() => {
sockets.forEach(e => e.disconnect())
server.close();
});
const makeSocket = (id=0) => {
const socket = io.connect(socketUrl, {
"reconnection delay": 0,
"reopen delay": 0,
"force new connection": true,
transports: ["websocket"],
});
socket.on("connect", () => {
console.log(`[client ${id}] connected`);
});
socket.on("disconnect", () => {
console.log(`[client ${id}] disconnected`);
});
sockets.push(socket);
return socket;
};
it("should echo a message to a client", done => {
const socket = makeSocket();
socket.emit("message", "hello world");
socket.on("message", msg => {
console.log(`[client] received '${msg}'`);
expect(msg).to.equal("hello world");
done();
});
});
it("should echo messages to multiple clients", () => {
const sockets = [...Array(5)].map((_, i) => makeSocket(i));
return Promise.all(sockets.map((socket, id) =>
new Promise((resolve, reject) => {
const msgs = [..."abcd"].map(e => e + id);
msgs.slice().forEach(e => socket.emit("message", e));
socket.on("message", msg => {
console.log(`[client ${id}] received '${msg}'`);
expect(msg).to.equal(msgs.shift());
if (msgs.length === 0) {
resolve();
}
});
})
));
});
});
In summary, the server exports a function that lets a server app be created from scratch, allowing each it block to be idempotent and avoid server state from carrying between tests (assuming no persistence on the server otherwise). Creating an app returns an object with a close function. socket.disconnect() must be called per socket in each test to avoid timeouts.
Given these requirements, the testing suite follows this per-test setup/teardown workflow:
let server;
let sockets;
beforeEach(() => {
sockets = [];
server = createServer();
});
afterEach(() => {
sockets.forEach(e => e.disconnect())
server.close();
});
makeSocket is an optional helper to reduce the repeated boilerplate of connecting and disconnecting a socket client. It does produce a side effect on the sockets array for cleanup later, but this is an implementation detail from the it block's perspective. Test blocks shoudn't touch server or sockets variables, although other workflows are likely depending on need. The critical takeaways are test case idempotency and closing all connections after each test case.
Options on the socket.connect object on the client let you choose transport and behavior of the socket. "force new connection": true creates a new Manager per socket instead of reusing an existing one and transports: ["websocket"] upgrades to WS protocol from long polling immediately.
Use it("should ... ", done => { /* tests */ }); and invoke done() after all work is completed in callbacks or return a promise (and omit the done parameter to the it callback). The example above shows both approaches.
Used in this post:
node: 12.19.0
chai: 4.2.0
express: 4.16.4
mocha: 5.2.0
socket.io: 2.2.0
socket.io-client: 2.2.0
I had this problem: How to do unit test with a "socket.io-client" if you don't know how long the server take to respond?.
I've solved so using mocha and chai:
var os = require('os');
var should = require("chai").should();
var socketio_client = require('socket.io-client');
var end_point = 'http://' + os.hostname() + ':8081';
var opts = {forceNew: true};
describe("async test with socket.io", function () {
this.timeout(10000);
it('Response should be an object', function (done) {
setTimeout(function () {
var socket_client = socketio_client(end_point, opts);
socket_client.emit('event', 'ABCDEF');
socket_client.on('event response', function (data) {
data.should.be.an('object');
socket_client.disconnect();
done();
});
socket_client.on('event response error', function (data) {
console.error(data);
socket_client.disconnect();
done();
});
}, 4000);
});
});