Let’s say we want a very simple NodeJS server that listens on a specified port and writes the data it receives from TCP connections to a file. How easy can we code this up? Turns out this is fairly simple with NodeJS.
Require the necessary modules and instantiate a server.
var server = require('net').createServer();
var fs = require('fs');
server.listen(14300, serverReady);
Start listening on a port, call function ‘serverReady’ when the server’s bound and ready to start receiving incoming connections.
function serverReady(){
server.on('connection', handleConnection);
}
Assign an event handler on the server to call ‘handleConnection’ whenever a client connects to the bound server. The handler will receive the socket object for an argument.
function handleConnection(socket){
socket.setEncoding('utf8');
socket.on('data', writeData);
}
When we get a connection, set the socket to utf8 encoding so that we simply receive strings for data rather than Buffer objects. When data is received, call the ‘writeData’ function. The argument sent to ‘writeData’ is a simple string containing the data, let’s write it to the end of a file.
function writeData(data){
fs.appendFile('/var/log/file.txt', data, function (err) {
if (err) throw err;
});
}
And that's all we need for the simplest of TCP servers. We can also test this very simply with a telnet client. On the MS Windows telent client, we run 'telent host port' from the command line, so 'telnet example.com 14300' for our example code. At the same time, we can run 'tail -f' on the log file on the server and see the characters show up in the file as we type them.
I don't have local echo enabled on my telnet client so the characters I type don't show there, however as I hit the keyboard, the characters show up at the end of the log file I'm writing to on the server.
Pretty cool for under 20 lines of code. However it's very simplistic. There is no error detection for the connection, so terminating the telnet client with ctrl+c causes a bad closing of the TCP connection. This bad TCP close fires an error which is uncaught in NodeJS and thus the whole server process ends. We can add some simple error handling with a simple function to mitigate this.
Then add the handler to the socket when created.Pretty cool for under 20 lines of code. However it's very simplistic. There is no error detection for the connection, so terminating the telnet client with ctrl+c causes a bad closing of the TCP connection. This bad TCP close fires an error which is uncaught in NodeJS and thus the whole server process ends. We can add some simple error handling with a simple function to mitigate this.
function err(error){
if (error){
console.log(error.message);
writeData(error.message);
}
}
function handleConnection(socket){
socket.setEncoding('utf8');
socket.on('data', writeData);
socket.on('error', err);
}
Now the server will remain running after a socket abruptly disconnects.Talking to the server via telnet is a nice quick hack, but we generally want to do a bit more. What's the minimal client we can write to connect to our server?
var net = require('net');
var client = net.connect(14300, 'example.com', handleConnection);
function handleConnection(){
setInterval(function(){
client.write('Time: ' + (new Date()).toString() + '\n');
}, 3000);
}
That's it! Simply connect, run a callback when connected, and then I added an interval to send data over the connection every 3 seconds. Output in file looks like this:Time: Thu Dec 12 2013 21:49:31 GMT-0500 (Eastern Standard Time) Time: Thu Dec 12 2013 21:49:34 GMT-0500 (Eastern Standard Time) Time: Thu Dec 12 2013 21:49:37 GMT-0500 (Eastern Standard Time) Time: Thu Dec 12 2013 21:49:40 GMT-0500 (Eastern Standard Time) Time: Thu Dec 12 2013 21:49:43 GMT-0500 (Eastern Standard Time) Time: Thu Dec 12 2013 21:49:46 GMT-0500 (Eastern Standard Time) Time: Thu Dec 12 2013 21:49:49 GMT-0500 (Eastern Standard Time)
Nothing too fancy but that's what we wanted. Now, again, this is super simplistic, no error handling and there's actually no way to gracefully close the connection. I have to forcefully end the process with ctrl+c which then causes the connection error for the server that I talked about above. Granted, we now have error handling on the server but I still would like a clean close if not too difficult. Took me a little bit to figure out, here's how to do it.
The NodeJS documentation didn't seem to provide a direct answer on how to close the connection properly. I first tried just running client.end() but a subsequent process.exit() would still result in an abrupt termination and 'read ECONNRESET' being logged on the server. I eventually looked up how TCP termination is supposed to work. I'm not a TCP expert, but apparently the closing initiator (the client in our case) should wait for acknowledgement from the other side of the connection before dying off, see Wikipedia link above. So, I set process.exit() to run as a callback on the close event of the client object.
client.on('close', function(){
process.exit();
});
The close callback will then run after receiving a close signal from the server, which we can initiate from the client with client.end(). Now, slightly off topic, but how can we trigger client.end() by user input when the client is running? We could listen to standard input with process.stdin, then parse the input string for a particular character or string. I did this at first but I didn't like having to type a character and then hit enter. I prefer the old short-cut of ctrl+c.
Now, by default the ctrl+c hotkey will just kill the process, so we have to catch it. On unix-y systems, we would use process.on('SIGINT', callback) to run the callback when ctrl+c is pressed. Windows however doesn't work with the Signal Interrupt, but the NodeJS coding community has added a workaround that looks somewhat similar.
var readline = require('readline');
var rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
rl.on('SIGINT', function(){
client.end('Closing@' + (new Date()).toString() + '\n');
});
The readline module basically just watches the keyboard for the ctrl+c keys for us here. Thus, pressing ctrl+c writes a final data string to the server, initiates a close of the connection, then exits when the connection is truly closed. Everything ends gracefully and no errors are thrown. I'm not sure if you would need to also listen to process.on('SIGINT') for Unix-y (POSIX) OS's or if readline would also work there. So the client ends up slightly bigger than the server but still under 25 lines, extra mostly for handling the user closing of the program. There's plenty that can be added here for any project you may need network communications for. Be sure to browse the Net docs for NodeJS for more info.
Comments