Topic 19: Introduction to WebSocket

Real-time Updates

Really interactive, real-time web applications feature real-time updates. For instance, real-time chat applications update with messages contributed by any user in the chat, or a real-time train map needs to be updated which each train's current position.

We can implement these real-time applications using the technologies we already know through the use of polling, in which we schedule an AJAX request to be sent to the server every so often (every 5 seconds, every minute, etc). However this has the disadvantage that if we want frequent updates, we have to either send a message to the server very frequently, potentially overloading the server (and this would be wasteful if the data is not changing) or send it less frequently, meaning updates would not truly be real-time.

Push Technology

What we need instead is push technology. Normal HTTP requests are pull requests: they pull data from the server. Push requests, however, involve the server sending messages to the client without receiving a request. WebSocket is a protocol which allows servers to send push requests to clients.

How does WebSocket work?

WebSocket has a detailed specification available here which explains it in detail; this Mozilla article also goes into some detail and is easier to understand than the full specification. But to summarise:

Writing a WebSocket client

Writing a WebSocket client is quite easy because JavaScript provides native support. Here is an example:

function init() {
    var ws = new WebSocket('ws://example.com:8080');
    
    ws.onopen = e => {
        //Handle the web socket being opened...
        alert('Websocket was opened!');
        ws.send('ephp001');
    }

    ws.onmessage = e => {
        console.log('Websocket sent back... ' + e.data);
    }

    ws.onerror = e => {
        alert('Error with websocket communication: is the server running?');
    }
}
Note how we create a WebSocket object and specify the server and port (example.com and 8080 respectively, here). We then specify three different event handlers:

Writing a WebSocket server

Various server side languages can deal with websockets using third party libraries. Here is an example with the ws module in Node.

const wsock = require('ws');
const server = new wsock.Server({port:8080});

// When the server receives a new connection...
server.on('connection', clientConn=> {
    console.log('New connection');

    // Listen for messages from this connection...
    clientConn.on('message', msg=> {
        console.log(`Received a message: ${msg}`);

        server.clients.forEach ( curClient=> {
            curClient.send(msg);
        });
    });
});
Note how we:

Exercise

Write a simple chat application, similar to the one demonstrated in class. To do this:

  1. Create a web page with a text field to add a message, and a div to show all messages so far. The message should be sent to the web socket server when a button is clicked. Upload this to your public_html folder on Neptune.
  2. Write a corresponding websocket server and upload this to your personal directory. A div should update each time anyone sends a new message, as above.
  3. Run your websocket server from the Neptune console.
  4. Enhance your application so that it works like a real chat system; each time a new message is sent back from the server, the user who wrote that message should be displayed. To do this you will need a user ID field in the front end and send the user ID with the chat message. Send a JSON message with two fields, one for the user ID and one for the chat message; you can use JSON.stringify() to encode a JavaScript object as JSON.
  5. ADVANCED: you can avoid having to send the user ID each time as follows. The clientConn object is just a plain JavaScript object, so you can add properties to it.
    1. Users should have to login first, by specifying a user ID and clicking a button. The user ID will then be sent to the server. Note that you should send it as a piece of JSON data including a message type field, so that the server can tell that this is a user ID and not a chat message. When the server receives this message, attach the username to the current clientConn as a property, e.g.
      clientConn.user = msgData.user;
    2. When the user sends a chat message, this should be again sent as JSON so that the server can tell that it is a chat message and not a user ID.
    3. Work out which user sent the chat message. This can be done using the property of the clientConn object (current client connection) that you set in step 1.