You want to create a client that connects to a networked server by using Node.js.
Solution
Creating a functional Node.js client extends from the concepts you learned about in Section 2-2. That is to say that a client is simply a connection to a server endpoint. Earlier you saw how to initiate a connection; in this section you will learn how to take that connected socket and understand the events that can be associated with it.
Let us make an assumption that we will connect our client to a simple Node.js server, similar to that which you created in Section 2-1. However, this server will receive a message from the client and also write a message to the client. This message will be a simple text message that shows a count of the current connections to the server. This server is shown in Listing 2-17.
Listing 2-17. Simple Node.js Server to Echo Back to the Client var net = require('net');
var server = net.createServer(function(connectionListener) { //get connection count
this.getConnections(function(err, count) { if (err) {
console.log('Error getting connections'); } else {
// send out info for this socket
connectionListener.write('connections to server: ' + count + '\r\n'); }
});
connectionListener.on('end', function() { console.log('disconnected'); });
//Make sure there is something happening connectionListener.write('heyo\r\n');
connectionListener.on('data', function(data) { console.log('message for you sir: ' + data); });
// Handle connection errors
connectionListener.on('error', function(err) { console.log('server error: ' + err); });
});
server.on('error', function(err) {
Chapter 2 ■ NetworkiNg with Node.js server.on('data', function(data) { console.log(data.toString()); }); server.listen(8181, function() { console.log('server is listening'); });
First, you see that, when creating a connected client using the net module in Node.js, you need to register the underlying events that can be emitted via the Node.js event emitter. In the example client you will create, these events are set to listen for data, end, and error. These events take a callback, which can be used to process the data transmitted via these events. This takes the example of the server shown in Section 2-2 and turns it into something that looks like what you see in Listing 2-18.
Listing 2-18. Client with Socket Events var net = require('net');
// createConnection
var connection = net.createConnection({port: 8181, host:'127.0.0.1'}, // connectListener callback
function() {
console.log('connection successful'); this.write('hello');
});
connection.on('data', function(data) { console.log(data.toString()); });
connection.on('error', function(error) { console.log(error);
});
connection.on('end', function() { console.log('connection ended'); });
As you see, there are many options for registering event listeners on a client. These events are gateways to determining the state of a server or to processing a response buffer from a server. These can help you to determine the state and information sent from a networked client in your Node.js application.
Also present in the client (see Listing 2-18) is the simplest form of communication to the server that you can send: the write() method on a socket. The socket in this case is the one that you created when you instantiate your connection. This simply sends a string, “hello,” to the server once the connection is established. This is handled on the client via the connectionListener's data event binding.
connectionListener.on('data', function(data) { console.log('message for you sir: ' + data); });
If you have everything running properly, you will see the client interacting with your server in the console output as shown in Listings 2-19 and 2-20.
Chapter 2 ■ NetworkiNg with Node.js
Listing 2-19. Server Interaction on the Command Line $ node server.js
server is listening
message for you sir: hello
Listing 2-20. Client Communicating with the Server $ node client.js
Connection successful Heyo
How It Works
As you investigate how this client connects and communicates with your server, you again see that we have created a connection to a server using the net module that is native to Node.js. This module carries with it the ability to communicate smoothly between TCP servers and clients. In the example you created in Listing 2-17, you created a connection that listened on a port and a host as described in Section 2-2. Once you created this connection, which you set to the variable ‘client’, it takes three arguments. These are exposed because the client is actually a representation of a TCP socket.
Regardless, the socket is created when you implement the net.createConnection() method. This means that you now have access to the options and events that are passed between sockets. This can be demonstrated by looking at the Node.js source for these sockets. In Node.js, the net socket is a representation of a stream. This means that to understand the code that is executing when connection.end happens you can see it is really a representation of the socket.end method, as shown in Listing 2-21.
Listing 2-21. Socket.end Method
Socket.prototype.end = function(data, encoding) {
stream.Duplex.prototype.end.call(this, data, encoding); this.writable = false;
DTRACE_NET_STREAM_END(this);
// just in case we're waiting for an EOF.
if (this.readable && !this._readableState.endEmitted) this.read(0);
return; };
You can see from Listing 2-21 that you have access to the socket that is in reality a stream. The ‘end’ method calls the end to this stream and immediately sets the stream to not be writable. The end event is triggered when the other end of the stream sends the FIN packet, which you saw in the previous sections. There you examined the half-open socket connection; however, in this case, the socket is no longer writable. Then there is one last round of checks to see if there is still a readable entity in the stream, which it reads before it returns, finalizing the “end” of the socket.
In Listings 2-19 and 2-20 you saw the server was started with the command node server.js. This immediately produces the .listen() callback, which prints the message “server is listening” to your console. You then start the client (node client.js) and you invoke the connectListener callback that prints “Connection successful” in your console. This connection also initiates a Socket.write() from the server and a Socket.write() from the client. You will learn more about utilizing sockets to communicate in the next section, but for now you really need to understand that the end result of Socket.write is that each socket sends its data along the socket. This results in producing the “hello” message from the client on the server and the “heyo” message on the client via the server.
Chapter 2 ■ NetworkiNg with Node.js
If you examine the data event, the event that handles the receipt of the data for your client, you see it is emitted each time data are received. When you listen for this event, which you register with client.on('data'...), you will be able to see data that are transmitted from your server. Data, in Node.js, is transmitted as a buffer or a string. It is emitted as a buffer by default, but if you set the socket.setEncoding() function, you will see that the data are transmitted as a string. In this solution you sent this data via the Socket.write() method, which defaults to sending the data with UTF-8 encoding. The data event is triggered in the stream module of Node.js. The stream module is triggered from the socket.write() method in the net module within Node.js as in Listing 2-22.
Listing 2-22. Triggering the Streams Module from socket.write( ) if (typeof chunk !== 'string' && !Buffer.isBuffer(chunk)) throw new TypeError('invalid data');
return stream.Duplex.prototype.write.apply(this, arguments);
Once you have worked your data into the stream interface, you meander it through the module until you find yourself where the readable stream exists. Stream.Readable is an instance of a readable stream that has a function, emitDataEvents . This is the “data” that will be read into the server that you send from your client. This lets an event listener, which is registered on the data event, actually go through the readable event on the stream, emitting the stream.read() as the “data” returned .on('data'). This section of the source can be viewed in Listing 2-23. Listing 2-23. Emitting the Data Event from the Stream Module
stream.readable = true;
stream.pipe = Stream.prototype.pipe;
stream.on = stream.addListener = Stream.prototype.on; stream.on('readable', function() {
readable = true; var c;
while (!paused && (null !== (c = stream.read()))) stream.emit('data', c); if (c === null) { readable = false; stream._readableState.needReadable = true; } });
This section of the code highlights the main point about transferring data as streams. You can see that within the emitDataEvents() method the stream listens for its own readable event. Once the readable event registers, then the stream.read( ) event is called, passing the data to the variable “c.” Then the stream emits the data event, while passing the argument “c.”
The other event listener that was created for the Node.js client in this section was registered on the error event. This event is emitted when the socket encounters an error. A great example of this is that, if you have your client connected to the server, you will get an error when the connection to the server fails. If you shut down the server, the error you will receive is a connection reset. This will be an object that looks like Listing 2-24.
Listing 2-24. Error: Connection Reset
{ [Error: read ECONNRESET] code: 'ECONNRESET', errno: 'ECONNRESET', syscall: 'read' }
You now should be able to build a networked client in a Node.js environment. The process of communicating via sockets will be covered in more detail in Section 2-5.
Chapter 2 ■ NetworkiNg with Node.js