Skip to main content

A Very Simple Network Connection with Node

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.
function err(error){
  if (error){
    console.log(error.message);
    writeData(error.message);
  }
}
Then add the handler to the socket when created.
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

Popular posts from this blog

Fancy FTP Deployment with Grunt

I recently dove into Grunt.js at work for automating our build process and I haven’t looked back. It’s an awesome tool with a plug-in for just about anything. I expected the usual would be there like JavaScript minification and concatenation, but I was surprised at a few others that I found, one being for FTP file deployment. Just shows how popular and community supported Grunt is.

There are a few FTP plug-ins available for Grunt. I didn’t do an analysis of all of them but ran across grunt-ftp-push which seemed to do what I needed so I decided to try it out. A simple ftp-push setup to upload an entire project via FTP could look like this:

grunt.initConfig({ftp_push:{all:{options:{host:'example.com',port:21,dest:'/project/path/',username:'user',password:'pass'},expand:true,cwd:'dist',src:['**/*','!**/*.zip']}}});
Some details here: I opted to put the username and password in the main config rather than using an .ftpauth file. The …

Accessing other HTTP servers on Cloud 9 IDE

If you're using Cloud 9 to do development, you'll quickly realize that only ports 8080 through 8082 are available to the outside world from your development box. This is generally not an issue as you can set your application to bind to the $PORT environment variable when in development mode. However, there are sometimes other servers that we want to make use of that host on different default ports.

I recently had to setup a Neo4j server which defaults the admin interface of port 7474. Unfortunately, I could not access the admin interface even through the IDE based web browser window. So, what to do? I could change the default server settings so that it runs on a different port. However, the app I'm working on with a team has 7474 hard-coded and I currently don't feel like writing a local only work-around.

After some searching, I ran across a neat Linux tool called socat. This allows us to easily forward one port to another. After a quick install via apt-get, I ran the …

Moving to Babel 6 on the Server

Cross post from my employer's development blog: http://rootinc.github.io/2016/03/14/babel-6-server/

Decided it was time to upgrade my server-side code to run on Babel 6. Below is a synopsis of all the issues I ran into and resolved while upgrading my 0.11.3 SailsJS server to run with Babel 6 transpilation.

The upgrade to Babel 6 itself is easily achieved in Sails by upgrading the `sails-hook-babel` package.

### Missing preset
`couldn't find preset "stage-0" relative to directory`
Just because a preset is on the official Babel preset page, doesn’t mean that Babel comes with it. Simple fix by installing the [package][1] from npm. Read more [here][2].
[1]: https://www.npmjs.com/package/babel-preset-stage-0
[2]: http://jamesknelson.com/the-six-things-you-need-to-know-about-babel-6/

### Need strict mode everywhere
`Block-scoped declarations (let, const, function, class) not yet supported outside strict mode`
I didn’t have to worry about this before, and I don’t feel like …