Zion National Park

What are WebSockets and Why we use them?

Luc Borris

--

Have you ever wondered how instant messaging, or tweets, or tracking your uber driver for pickup is even possible?! The list goes on and as you will see, it is not a difficult concept to understand.

After reading this you should have a fundamental understanding of:

  • What WebSockets are and what they are used for?
  • How WebSockets differ from standard HTTP requests? What the benefits of this are?
  • What popular WS libraries are out there?

Before continuing on, it is assumed that you have some knowledge of the basic usage of Node.js and npm libraries. If this is not you, I highly recommend you look into Codecademy’s quick tutorial on Node.js.

Little history lesson

Before the wide range of support browsers now have for WebSockets, we had to use what is called Long-Polling. Before the advent of WebSockets, we were still using the HTTP request-response model. The main difference between long-polling and WebSockets has a lot to do with server resources. When the client made a request to the server, and the server did not have any state updates to share, it would simply hold the response until it did or if it timed out, whichever came first. Now we can see how this can have a lot of strain on the server, as it holds connections with each client that is waiting for a response. This is why we opt for WebSockets at present, it does not limit server resources as well as sends only the data that is relevant to the change. In other words, only sends part of the state and not the whole state.

Long-Polling

WebSockets in action

In the following examples, we will build a very simple instant messaging app using only Node.js. You can fork/clone the repo here or you can just copy and paste the code below. However, you will need to npm install ws.

Server-Side Websocket

In the above code, we have a very simple WebSocket server listening on port 8181.

wss.on("connection", function (ws) {
...
}

The server is specifically listening for a connection, once a connection has been made it triggers this event.

ws.on("message", function (message) {    
console.log(message);
wss.clients.forEach((client) => {
if (client.readyState === WebSocketServer.OPEN) {
client.send(message)
}
});
}

Once a connection has been made, it listens to another event listener, “message”. If the server receives a message from any of its connected clients, it is triggered. Inside this event listener, if it is triggered, the server looks up its array of client objects that attempted a connection with the server. If the connection is still OPEN, it will send the message to each one of the clients, that it itself has received. So if the server knows everyone knows.

This is what is so powerful about WebSockets, it allows for a real-time update of state changes, something that allows for a great UX application, and something that is expected from the masses in today's standard.

Image of response-request from Chrome DevTools.

Here is an image of chrome DevTools, Command+Option+J (Mac) or Control+Shift+J (Windows/Linux), then click the Network Tab.

First, let’s take a look at the general section of the DevTools network results. The request has been made to ws://localhost:8181, this piece of information is important. WebSocket connections are made via the WS scheme and not through the HTTP protocol because it does not follow this convention. However, the initial request is still an HTTP GET request when a client attempts to establish a connection with the server. This leads us to our Status Code: 101 Switching Protocols, from the Response Header’s Connection: Upgrade and Upgrade: WebSocket. Once the server receives a WebSocket GET Request, the server Switches the Protocol to WS, responds to the client with a connection upgrade and the nature of the upgrade, in our case WebSocket.

The result of this process allows for an event-driven, full-duplex asynchronous communications channel for your web applications. What this means is a traditional HTTP request-response is initiated only by the client, however, a WS request-response allows for a client and server to initiate any subsequent request. The other keyword “asynchronous communications”, means that any event triggered will be added to the callback queue on either side of the communication, be it server or client. In other words, it will not slow down the application. Don’t worry, if you don’t know what a callback queue is it’s not within the scope of this article, but if you would like to know, there is a great article about it by Rahul Sagore.

Now let’s get to the client-side of WebSockets.

Client-Side Websocket
const readline = require("readline");
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
rl.on("line", (input) => {
sendMessage(input);
});

This code has nothing to do with WebSockets, it is a built-in feature of Node that allows us to use the command line to input strings. In our case, to add messages to our instant messaging app.

const WebSocket = require("ws");
const ws = new WebSocket("ws://localhost:8181");

Another thing to note here is the address “ws://localhost:8181”. If it were written like “wss://localhost:8181”, the wss implies a secure connection. It is the equivalent of http vs https.

ws.onopen = function (e) {  
console.log("Connection to server opened");
};

This acts just like the on(“connection”) event listener from the server. If a connection is made with the server, the WebSocket connection is then open.

Now that we have seen both sides of the connection being made, we now need to talk about the close() method. This connection will remain open unless either party closes the connection. This is done like so:

ws.onclose();

It is omitted for this simple example, the connection simply closes when you exit out of the terminal. Next, we have:

ws.onmessage = function (e) {  
console.log(e.data);
};

This event listener just like on the server, will listen for messages sent by the server and will print it out on the console once it is received.

function sendMessage(message) {  
ws.send(message);
}
rl.on("line", (input) => {
sendMessage(input);
});

Now, these two functions are what send the messages to the server.

Let’s talk real-world applications, the server receives the new message, updates the state inside the database, and then will send the updated portion of the state to whoever is still connected to the server via a socket connection. It’s also important to understand that it sends only a portion and not the whole state. This is the benefit of a ws:// connection over an http:// one. By sending very little data, it makes the latency of the WebSocket much lower than that of a regular http:// request-response or in other words a faster data transfer. This is the ‘instant’ in instant messager at play.

What Libraries are out there?

On Mozilla, it lists a few we can choose from. Just to name a few of the popular libraries, however, there are many more.

  1. WS
  2. Socket.IO
  3. WebSocket-Node
  4. sockjs

For our example, we used WS and you can see on npm that WS is the most popular.

Summary

  • WebSockets are a way for client/server connections to remain open and provide low latency data transfers.
  • WebSockets are a predecessor of long-polling to optimize server loading.
  • HTTP response-request is a one-and-done connection whereas Websockets remain open.
  • We saw that there are many libraries to choose from most notably WS has become one of the most popular WebSocket libraries for Node.

Resources

  • WebSocket,” Andrew Lombardi, O’Reilly Media

--

--

Luc Borris

Aviator / Photographer / Developer