5 min read

(For more resources related to this topic, see here.)

Step 1 – fresh start

  1. In a new folder called, for example, 1_PubSub_Chat, let’s open our editor of choice and create here a file called pubsub_chat.js.
  2. Also, make sure that you have a terminal window open and you moved into the newly created project directory.

Step 2 – creating the TCP server

TCP servers are called net servers in Vert.x. Creating and using a net server is really similar to HTTP servers:

  1. Obtain the vertx bridge object to access the framework features:

    var vertx = require('vertx'); /* 1 */
    var netServer = vertx.createNetServer(); /* 2 */
    netServer.listen(1234); /* 3 */

  2. Ask Vert.x to create a TCP server (called NetServer in Vert.x).
  3. Actually, start the server by telling it to listen on TCP port 1234.

    Let’s test whether this works. This time we need another terminal to run the telnet command:

    $ telnet localhost 1234

    The terminal should now be connected and waiting to send/receive characters. If you have “connection refused” errors, make sure the server is running.

Step 3 – adding a connect handler

Now, we need to place a block of code to be executed as soon as a client connects:

  1. Define a handler function. This function will be called every time a client connects to the server:

    var vertx = require('vertx')
    var server = vertx.createNetServer().connectHandler(
    function(socket) {
    // Composing a client address string
    addr = socket.remoteAddress();
    addr = addr.ipaddress + addr.port;
    socket.write('Welcome to the chat ' + addr + '!');
    }).listen(1234)

    A NetServer connect handler accepts the socket object as a parameter; this object is our gateway to reading, writing, or closing the connection to a client.

  2. Use the socket object to write a greeting to the newly connected clients.
  3. If we test this one as in step 2 (Step 2 – creating the TCP server), we see that the server now welcomes us with a message containing an identifier of the client as its origin host and origin port.

Step 4 – adding a data handler

We just learned how to execute a block of code at the moment in which the client connects. However now we are interested in doing something else at the time when we receive new data from a client connection.

The socket object we used in the previous step for writing data back to the client, accepts a handler function too: the data handler. Let’s add one:

  1. Add a data handler function to the socket object. This is going to be called every time the client sends a new string of data:

    var vertx = require('vertx') var server = vertx.createNetServer().connectHandler( function(socket) { // Composing a client address string addr = socket.remoteAddress(); addr = addr.ipaddress + addr.port; socket.write('Welcome to the chat ' + addr + '!'); socket.dataHandler(function(data) { var now = new Date(); now = now.getHours() + ':' + now.getMinutes() + ':' + now.getSeconds(); var msg = now + ' <' + addr + '> ' + data; socket.write(msg); }) }).listen(1234)

  2. React to the new data event by writing the same data back to the socket (plus a prefix).

    What we have now is a sort of an echo server, which returns back to the sender the same message with a prefix string.

Step 5 – adding the event bus magic

The base requirement of a chat server is that every time a client sends a message, the rest of the connected clients should receive it. We will use event bus, the messaging service provided by the framework, to send (publish) received messages to a broadcast address. Each client will subscribe to the address upon connection and receive other clients’ messages from there:

  1. Add a data handler function to the socket object:

    var vertx = require('vertx') var server = vertx.createNetServer().connectHandler( function(socket) { // Composing a client address string addr = socket.remoteAddress(); addr = addr.ipaddress + addr.port; socket.write('Welcome to the chat ' + addr + '!'); vertx.eventBus.registerHandler('broadcast_address', function(event){ socket.write(event); }); socket.dataHandler(function(data) { var now = new Date(); now = now.getHours() + ':' + now.getMinutes() + ':' + now.getSeconds(); var msg = now + ' <' + addr + '> ' + data; vertx.eventBus.publish('broadcast_address', msg); }) }).listen(1234)

  2. As soon as a client connects, they listen to the event bus for new data to be published on the address broadcast_address.
  3. When a client sends a string of characters to the server, this data is published to the broadcast address, triggering a handler function that writes the string to all the clients’ sockets.
  4. The chat server is now complete! To try it out, just open three terminals:
    • Terminal 1: $ vertx run pubsub_chat.js
    • Terminal 2: $ telnet localhost 1234
    • Terminal 3: $ telnet localhost 1234
  5. Now, we have a server and two clients running and connected. Type something in terminal 2 or 3 and see the message being broadcast to both the other windows:

    $ telnet localhost 1234
    Trying ::1...
    Connected to localhost.
    Escape character is '^]'.
    Hello from terminal two!
    13:6:56 <0:0:0:0:0:0:0:155991> Hello from terminal two!
    13:7:24 <0:0:0:0:0:0:0:155992> Hi there, here's terminal three!
    13:7:56 <0:0:0:0:0:0:0:155992> Great weather today!

Step 6 – Organizing a more complex project

Since Vert.x is a polyglot platform, we can choose to write an application (or a part of it) in either of the many supported languages. The granularity of the language choice is at verticle level. It’s important to give a good architecture to a non-trivial project from the beginning.

Follow this list of generic principles to avoid performance bottlenecks or the need for massive refactoring in the future:

  1. Wrap synchronous libraries or legacy code inside a worker verticle (or a module). This will keep the blocking code away from the event loop threads.
  2. Divide the problem in isolated domains and write a verticle to handle each of them (for example, database persistor verticle, web server verticle, authenticator verticle, and cache manager verticle).
  3. Use a startup verticle. This will be the single entry point to the application. Its responsibilities will be to:
    • Validate the configuration file
    • Programmatically deploy other verticles in the correct order
    • Decide how many instances of a verticle to create (the decision might depend on the environment: for example, the amount of available processors)
    • Register periodic tasks

Summary:

In this article, we learned in a step-wise procedure how we can create an Internet Relay Chat using the TCP server, and interconnect the server with the clients using an event bus, and enable different types of communication between them.

Resources for Article:


Further resources on this subject:


LEAVE A REPLY

Please enter your comment!
Please enter your name here