Web Socket Applications and their Construction

To get the most out of this post an understanding and a basic knowledge of Node will really help.

Websockets work in conjunction with server-side implementations of JavaScript for example Node. They work by establishing a tunnel between the server and the client. This tunnel remains open until it is closed by the application. Once the tunnel has been established any data can be passed through it, strings as well as binary data. This makes the web socket protocol incredibly flexible, not only for instant messaging but for scenarios such as online interactive white boards, gaming apps etc.

We are going to be using socket.io for our demo app.

Why use socket.io?

This is a Node package that acts as the glue between the web server (Express in this case), and the client. This is in effect our tunnel. The HTML5 Websocket API at time of writing has mixed support across browsers. socket.io on the other hand has been developed to work across all leading HTML5 compliant web browsers. This means a lot of the pain and hard work has been done for us in providing a well built and robust tunnel to send and receive data through.

How do we build a Web Socket App?

  • Step 1: Create a specific application folder
  • Step 2: Install node
  • Step 3: Install NPM – the node package manager
  • Step 4: CD to your app folder and NPM init to initialize the package manager for this application
  • Step 5: The most important aspect to this is the root document for the NodeJS – suggest leave the default index.js

Installing Express Server

In order for the app to work we need to use a web server to process the HTTP requests and convert to TCP connections in order to transfer data between client and server.

When installing dependencies for node sometimes it is important to be specific on the version of the dependency you are installing.  Otherwise it could have a knock on effect for other aspects of the other application. In this case the latest version of Express should be fine.

 npm install express --save

How data is matched

You have a main.js file that interacts with the DOM and a second JS file which is the index.js. The main.js has event listeners for various aspects of the DOM such as classes and or IDs. That data is then passed to the Socket listener. So – without wishing to confuse things here the CLIENT – in this case main.js EMITS data input by the user to the index.js file. The index.js has various ON methods that capture the data EMITED.

A basic example:

 main.js

 // Tell the server your username
 socket.emit('add user', username);

 index.js
 // Server receives username
 // when the client emits 'add user', this listens and executes
  socket.on('add user', function (username) {
     //Do stuff - validating username such as matching details on a database
  });

Note: A quick alert on the use of emit and on. In the above example I demonstrated how the client emits data and how the server might receive it using emit and on respectively. The context of these keywords is interchangeable and can be used either on client and server as long as the socket.io API is visible.

The key point to remember here is that emit pushes out and on receives or listens for what is being emitted by matching the variable names.

What is the directory structure of a socket.io app?

For this post we will build a IT Helpdesk app – you can post requests and have them resolved by the community who are online at the time.

Our aim is to create a helpdesk app that enables the user to do the following:

  1. Login
  2. Select category from dropdown (email, shared hosting, FTP, dedicated server)
  3. Check against rooms available in rooms array in index.js
  4. Find relevant room and alert to those online in that group that the user has joined
  5. Alert user they are logged in and allow them to send message

How would we structure it and similar apps like it? Anything goes but a good structure for our theatre ticket app is:

  helpdeskapp
    public
      index.html
      main.js
      style.css
    index.js
    package.json

Let’s create our assets

Once the relevant packages have been installed let’s builr out our index.js

//Require express server package
var express = require('express');
//Create an express object
var app = express();
//Create an http request based on express object
var server = require('http').createServer(app);
//Create a global socket variable "io"
var io = require('socket.io')(server);
//Identify port number either processed by the environment or stipulated 
var port = process.env.PORT || 3000;

// Route to public folder to render default view (index.html) and dependencies (css/js etc)
app.use(express.static(__dirname + '/public'));


io.on('connection', function(socket){
  console.log('a user connected');
  socket.on('disconnect', function(){
    console.log('user disconnected');
  });
});


server.listen(port, function () {
  console.log('Server listening at port %d', port);
});

What’s happening?

We load up our express server and create an express object in order for us to exchange data with the server. What we then need is an http request to initiate a handshake between the client and server thus creating the tunnel.

Our next objective is to create a global socket.io var (io) that will be also visible to the client. We then define the public path so we can send content to the client and tell Express where the default location is to load up our client side assets such as CSS, JS etc.

Finally, to get us started we have created call to the on method of the io object to listen for a connection established by the client.

Let’s take a look at our view (index.html)

<!doctype html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Helpdesk Requests</title>
    <link rel="stylesheet" type="text/css" href="style.css">
   <script src="https://code.jquery.com/jquery-1.10.2.min.js"></script>
  <script src="/socket.io/socket.io.js"></script>
	<script>
	  //declare variable to point to global io socket variable as defined in index.js
          //This will automatically emit connection signal either connected or disconnected
	  var socket = io();
	</script>
  </head>
  <body>
    <ul id="messages"></ul>
    <form action="">
      <input id="m" autocomplete="off" /><button>Send</button>
    </form>
  </body>
</html>

What’s happening?

The key elements of note here is the loading of the socket.io and our socket var. This is what is generating the emit whether or not a connection has been established. This is sent back down the tunnel to our index.js that listens for the call “connection“.

The key point to remember here is that emit pushes out and on receives or listens for what is being emitted by matching the variable names.

What have we achieved so far?

So far we have set up our server, created our core file structure and established a connection between the client and server. We have also written to the console that a connection has been established. Our next objective is to make greater use of our app. In the next section we will:

  • Create a login
  • Display who is online
  • Build a request form
  • Log requests and display them
  • Allow user to switch groups
  • Alert when a user has disconnected

Login

Let’s start with the index.js:

//Require express server package
var express = require('express');
//Create an express object
var app = express();
//Create an http request based on express object
var server = require('http').createServer(app);
//Create a global socket variable "io"
var io = require('socket.io')(server);
//Identify port number either processed by the environment or stipulated 
var port = process.env.PORT || 3000;

server.listen(port, function () {
  console.log('Server listening at port %d', port);
});

// Route to public folder to render default view (index.html) and dependencies (css/js etc)
app.use(express.static(__dirname + '/public'));

// Tech support room

//Initialize logged in Total
var loggedInTot = 0;
var usernames = {};

var rooms = ['General Enquiries', 'FTP', 'Email', 'Shared Hosting', 'Dedicated Servers'];
//Create new object
io.on('connection', function (socket) {
  
  io.sockets.on('connection', function(socket) {
    socket.on('adduser', function(username) {
        socket.username = username;
        //Add usernames to username property of usernames object
        usernames[username] = username;
        increment logged in total
        ++loggedInTot;
        //Add to default group
        socket.join('General enquiries');
        socket.emit('updatechat', 'HELPDESK', 'you have connected to General enquiries');
        socket.broadcast.to('General enquiries').emit('updatechat', 'HELPDESK', username + ' has connected to this room');
        //Send list of rooms available  and the active room (in this case the default 'General enquiries'
        socket.emit('updaterooms', rooms, 'General enquiries');
        //echo globally (all clients) that a person has connected
        //Display globally username and logged in total 
        socket.broadcast.emit('user joined', {
           username: socket.username,
           loggedInTot: loggedInTot
        });
    });

});

What’s happening?

This page we start by initializing the logged in total to zero. We then have an add user method that checks if addUser is greater than zero – if it is return true and stop processing the rest of the function. Then emit to client total number of users.

What we are also doing is assigning the user to the default room (General enquiries) and passing the list of the other rooms to the view.

We could easily scale this up to make a connection to a database to check for a valid login. If true then login is successful.

The client side JS

Login View

<li class="login page">
      <div class="form">
        <h3 class="title">Login with your username</h3>
        <input class="usernameInput" type="text" maxlength="14" />
      </div>
</li>

What’s happening?

Here we are creating a single page app. We have created a simple form element with a single form element.

Let’s take a look at the client js

main.js code snippet

var socket = io.connect('http://localhost:3000');

socket.on('connect', function(){
    socket.emit('adduser', prompt("Please enter your username: "));
});

socket.on('updatechat', function (username, data) {
    $('#conversation').append('<b>'+ username + ':</b> ' + data + '<br>');
});


socket.on('updaterooms', function (rooms, current_room) {
    $('#rooms').empty();
    $.each(rooms, function(key, value) {
        if(value == current_room){
            $('#rooms').append('<div>' + value + '</div>');
        }
        else {
            $('#rooms').append('<div><a href="#" onclick="switchRoom(\''+value+'\')">' + value + '</a></div>');
        }
    });
});

What’s happening?

First of all we are establishing a connection with the socket server which is listening on port 3000. Then we are emitting the username which will be picked up by the adduser event listener.

Note we have included the updatechat and updaterooms methods which will be the array of rooms we created in index.js. The conversation will be nothing to begin with but we still need to include it to kick things off.

Let’s take a look at the markup

index.html snippet

<body>
    <div style="float:left;width:100px;border-right:1px solid black;height:300px;padding:10px;overflow:scroll-y;">
        <b>ROOMS</b>
        <div id="rooms"></div>
    </div>

    <div style="float:left;width:300px;height:250px;overflow:scroll-y;padding:10px;">
        <div id="conversation"></div>
        <input id="data" style="width:200px;" />
        <input type="button" id="datasend" value="send" />
    </div>

   <div style="float:left;width:300px;height:250px;overflow:scroll-y;padding:10px;">
       <div id="room creation"></div>
       <input id="roomname" style="width:200px;" />
        <input type="button" id="roombutton" value="create room" />
   </div>
  
</body>

What’s happening?

This is fairly standard fair – what is happening is we have defined three columns:

  1. Left column will display active room and list of rooms available
  2. Middle column displays the chat
  3. Right column displays the option to create a new room

The important thing of note here is CSS ids. These act as injection points for the data emitted.

How do you switch between rooms?

To switch rooms we need to create a click event for each of the rooms emitted in the view. We then create a switchroom event listener in our index.js. This then invokes the inbuilt socket.io logic for assigning a user to a room. What you will also notice is that we apply some logic to inform the other users that this specific user has left the group and joined another. Similar logic in fact to what we did when we first started the application.

Server JS (index.js)

    socket.on('switchRoom', function(newroom) {
        var oldroom;
        oldroom = socket.room;
        socket.leave(socket.room);
        socket.join(newroom);
        socket.emit('updatechat', 'HELPDESK', 'you have connected to ' + newroom);
        socket.broadcast.to(oldroom).emit('updatechat', 'HELPDESK', socket.username + ' has left this room');
        socket.room = newroom;
        socket.broadcast.to(newroom).emit('updatechat', 'HELPDESK', socket.username + ' has joined this room');
        socket.emit('updaterooms', rooms, newroom);
    });

Notice how we swap out the old room with the new room. We then emit the changes back to the view.

The view logic (snippet)

 socket.on('updaterooms', function (rooms, current_room) {
    $('#rooms').empty();
    $.each(rooms, function(key, value) {
        if(value == current_room){
            $('#rooms').append('<div>' + value + '</div>');
        }
        else {
            $('#rooms').append('<div><a href="#" onclick="switchRoom(\''+value+'\')">' + value + '</a></div>');
        }
    });
 });

 function switchRoom(room){
    socket.emit('switchRoom', room);
 }

Notice the onclick event which has the switchRoom function and takes one argument – the value of the room that the user wants to join. We then invoke the switchRoom function which emits a callback to our index.js and the corresponding switchRoom listener.

What have we explored in this post?

We have explored the differences between AJAX and Websockets – principally that Websockets offer a two way always on connection which enables the server to push data to the client when required. We have also explored socket.io which provides us with a robust framework for creating tunnels or sockets. Using socket.io we have built a simple Helpdesk app that will have introduced you to some core concepts in emitting data, managing users and using the concept of rooms to keep data categorized and tied to specific groups.

Websockets are not a replacement for AJAX

It is important to stress that Websockets should not be seen as a replacement for AJAX. Instead they are a useful tool in specific situations where a two way, duplex method of communication between client and server is required. For example AJAX is ideal for a core login to an application – where you need to poll the server code to check the validity of login credentials. Once logged in there may be aspects of the application such as live chat where Websockets are ideal for that realtime communication.