Introduction to WebSockets: How To Create Responsive And Low-Latency Applications

Written by alexchao26 | Published 2020/02/01
Tech Story Tags: websockets | javascript | express | ws | socket.io | sock.js | websocket | programming

TLDR In this blog post we’ll be discussing WebSockets and how they can be used to create performant, multi-user web applications. We'll be going through the following steps to implement them in a small game of tic-tac-toe using HTML/JS/JS (no server sockets yet) We'll use WebSocket connections to connect multiple clients in a multi-player game. The following steps are the following: Set up a front-end to utilize the WebSocket socket connections in our game.via the TL;DR App

In this blog post we’ll be discussing WebSockets and how they can be used to create performant, multi-user web applications. We will be covering:
  • What WebSockets are and how they differ from HTTP connections
  • Why WebSockets are necessary for demanding applications
  • How WebSocket connections are created and used to send information
  • How to implement WebSockets in a multi-player game of tic-tac-toe
I have always wondered how instant messaging and notifications worked on my phone. Do phones constantly ping some server to ask if there is a new message? That could work, but it seems inefficient for both the server and my phone; although it would certainly explain my phone’s battery life... Alternatively, is my phone always listening for someone to send it some data? That raises some security concerns; can anyone send data to my phone? Before WebSockets were introduced in 2011, the primary approach for maintaining live data was long polling.

A PRIMER ON LONG-POLLING

Before we jump into WebSockets, we need to understand the issues surrounding long-polling. Pre-WebSockets, web applications were built around the premise that servers receive requests and send corresponding responses. Note that there is nothing in place for the server to initiate the sending of data to the client. Servers were meant to be purely responsive to requests coming from the client. With this client-server model in place, long-polling was used as a solution to maintaining up-to-date data on the client.Long-polling is the cycle of sending an HTTP request from the client to the server, and the server not sending a response until there is some new data. When the client receives that response from the server, it immediately sends another request to the server.This infinite loop works for maintaining live data, but also taxes the client/browser and the server, as each HTTP request and response also sends a non-negligible amount of metadata, headers, and cookies. For applications that require frequently sending updated data in very short intervals, such as online games or financial tickers, the overhead of HTTP requests is unacceptable. This is where WebSockets come in.

WHAT ARE WEBSOCKETS?

The WebSocket API, more commonly referred to as WebSockets, is a communication protocol that provides a mechanism for sending data between the client and server without the overhead of HTTP requests and long-polling.
WebSocket connections provide three major benefits over long-polling.
  • The server can now initialize sending data to the client, it does not need to wait for a request.
  • Headers, cookies, and other metadata do not need to be transferred along with every data transfer between the client and server.
  • The server can open WebSocket connections to multiple clients.
So how exactly do WebSockets pull all of that off? It starts with the client sending a HTTP request to the server called the WebSocket handshake request. The handshake request contains a header to upgrade the connection to a WebSocket connection. When the server accepts the handshake, a WebSocket connection is established between the client and server. The newly established WebSocket connection is bidirectional, so the client and server can send messages to each other directly. Remember that with HTTP requests, the server had to wait for an incoming request before sending a response, now either party can send data to the other!
Handshake request opens a bi-directional, WebSocket connection.
In addition to reducing overhead, WebSockets allow servers to connect to multiple clients. Now that the server can send messages without waiting for a client’s request, we can use the server to relay information to all connected clients. This opens up possibilities to creating responsive, multi-user applications that require each client to receive live data. For example, chat rooms, online games, financial tickers, and (for people obsessed like me) live sports scores and stats.
A WebSockets server relaying messages between clients

IMPLEMENTING WEBSOCKETS IN A MULTI-PLAYER GAME

Now that we have an understanding of how WebSocket connections are made and how they can be used, let’s implement them in a small application. We’ll be going through the following steps:
  1. Make a tic-tac-toe game using HTML/CSS/JS (no sockets yet!)
  2. Create a server that serves up our (front end) game
  3. Add WebSocket functionality to the server
  4. Refactor our front-end to utilize the WebSocket connections
  5. Play our game!
Setup
Before we start writing code, let’s set up our project’s folder and file structure. We’ll be using a couple Node.js packages in our server, so run the following terminal commands in this project’s folder:
npm init -y
npm install express ws
The final file structure will look like this:
project_folder/
    client/
        index.html
        index.js
        styles.css
    server/
        server.js
    package.json

Build the Front End

Start by using HTML, CSS and JavaScript to make a simple tic-tac-toe game in the browser. In the body of our index.html file, create three rows that have three buttons in each. Give the buttons the starting text of a dash:
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="UTF-8">
 <title>Tic-Tac-Toe</title>
</head>
<body>
   <div>
     <button id="11">-</button>
     <button id="12">-</button>
     <button id="13">-</button>
   </div>
   <div>
     <button id="21">-</button>
     <button id="22">-</button>
     <button id="23">-</button>
   </div>
   <div>
     <button id="31">-</button>
     <button id="32">-</button>
     <button id="33">-</button>
   </div>
</body>
</html>
If you open up your index.html file now, you’ll see our 3x3 board with some very small buttons. Later on, we will be using the id’s on these buttons to sync up our two players via WebSockets!
Let’s add some CSS to give our game some style. First import the stylesheet into the index.html file. In the head of the html file add:
 <link rel="stylesheet" href="./styles.css"></link>
Then we can add some styling to each of our buttons:
/* styles.css */
button {
 width: 100px;
 height: 100px;
 font-size: 25px;
}
Reload the index.html file and the buttons should be appearing larger and look more like a proper game of tic-tac-toe! Have fun with this part and make the game your own!
If you try to click a button, nothing happens! Let's change that now. Start by importing the index.js file into index.html after the body closing tag. The final index.html file should look like this:
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="UTF-8">
 <link rel="stylesheet" href="./styles.css"></link>
 <title>Tic-Tac-Toe</title>
</head>
<body>
   <div>
     <button id="11">-</button>
     <button id="12">-</button>
     <button id="13">-</button>
   </div>
   <div>
     <button id="21">-</button>
     <button id="22">-</button>
     <button id="23">-</button>
   </div>
   <div>
     <button id="31">-</button>
     <button id="32">-</button>
     <button id="33">-</button>
   </div>
</body>
<script src="./index.js"></script>
</html>
Now we can add some Javascript code to our game. Update the index.js file to set an event listener on every button that will change its text to “X” or “O”:
// index.js
// wait for the window to load
window.onload = () => {
 // grab all of the button elements off of the DOM
 const collectionOfButtons = document.querySelectorAll('button');
 
 // boolean that will track if it's the 'X' player's turn
 let xTurn = true;
 
 // add an on-click event listener for each button to update its text to X or O
 collectionOfButtons.forEach((buttonElement) => {
   buttonElement.addEventListener('click', (event) => {
     // when clicked, update the button's text to an 'X' or an 'O' & disable the button
     event.target.innerHTML = xTurn ? 'X' : 'O';
     event.target.disabled = true;
 
     // change the turn tracker boolean
     xTurn = !xTurn;
   });
 });
};
We’re grabbing an HTMLCollection of all of the buttons on our webpage, and applying an on-click event listener to each of them. When clicked, the button will change its text to either “X” or “O” and disable itself so it can’t be clicked more than once.
Let’s refresh the index.html page and play our game locally! You should be able to alternate between placing X’s and O’s on the board due to the xTurn boolean variable. Now we have some of the base functionality on our front-end, let’s start building out our backend.

Creating an Express Server

Before we add WebSockets, we need to create a server. This is where the express library as well as the native Node.js 
path
 and 
http
 modules will come in.
// server.js
const express = require('express');
const path = require('path');
const http = require('http');
 
const app = express();
const server = http.createServer(app);
 
// serve up static files (index.html, styles.css, & index.js)
app.use(express.static(path.resolve(__dirname, '../client')));
 
server.listen(3000, () => { console.log('listening on port 3000'); });
The start of our express server is just to “serve up” the front-end that we just built. The express.static method makes all of the files in our client folder available to the browser upon request.
Startup the server by running node server/server.js in the terminal, and then go to localhost:3000/ in your browser. You should see the exact same app as before when we opened the index.html file, but now a server is providing the HTML, CSS and JS files.

Adding in WebSockets

Now that we have a server and front-end, we need to add WebSocket functionality to both of them. In the server.js file:
  • Import the ws library and use its Server method to create a WebSockets server.
  • Add an event listener to the WebSocket server for connections. In the on-connection callback, the default parameter is an instance of a WebSocket connection. Add those connections to a Set of all open connections (setClients), and log to the console that there is a new connection.
  • Next, add an event listener for a message coming from a connected client. When this event is triggered, the same message will be emitted to each of the clients.
  • Finally, add an event listener for a disconnecting client. When a client ends their WebSockets connection, remove them from the set of clients and log how many active connections there are to the console.
The server.js file should now look like this:
// server.js
const express = require('express');
const path = require('path');
const http = require('http');
const WebSocket = require('ws');
 
const app = express();
const server = http.createServer(app);
const wsServer = new WebSocket.Server({ server });
 
// a set to hold all of our connected clients
const setClients = new Set();
 
wsServer.on('connection', (socketConnection) => {
 // When a connection opens, add it to the clients set and log the number of connections
 setClients.add(socketConnection);
 console.log('New client connected, total connections is: ', setClients.size);
 
 // When the client sends a message to the server, relay that message to all clients
 socketConnection.on('message', (message) => {
   setClients.forEach((oneClient) => {
     oneClient.send(message);
   });
 });
 
 // When a connection closes, remove it from the clients set and log the number of connections
 socketConnection.on('close', () => {
   setClients.delete(socketConnection);
   console.log('Client disconnected, total connections is: ', setClients.size);
 });
});
 
// serve up static files
app.use(express.static(path.resolve(__dirname, '../client')));
 
server.listen(3000, () => { console.log('listening on port 3000'); });
Our server is now configured to create WebSocket connections and relay messages between all the connected clients.
Last step! We have to make changes to the front end to connect to the WebSockets server, send messages (containing the id of a button that was clicked), and receive messages. Make the following changes to the index.js file:
  • First, establish a WebSocket connection using the built in WebSocket API.
  • Add a “onopen” event listener that logs when the WebSocket connection is made.
  • Now that there is a WebSocket connection to the server, update the buttons’ on-click event listeners to send a message to the server containing the button’s id.
  • Setup an “onmessage” event listener on the WebSocket connection. When the server sends a message to the client, the message will contain the id of a clicked button. Check if the corresponding button’s value is a dash and if so, change its text to “O”.
  • Finally, update the initial on-click event listener for each button to only change buttons’ innerHTML to “X.” Now, when the client client clicks on a button, it will change to "X" and send the id to the server on the WebSocket connection. When other clients receive the id on a message, it will change the corresponding button to "O."
The index.js file should now look like this:
// index.js
window.onload = () => {
 // connect to socket server through WebSocket API
 const socketConnection = new WebSocket('ws://www.localhost:3000/');
 // when connection opens, log it to the console
 socketConnection.onopen = (connectionEvent) => {
   console.log('websocket connection is open', connectionEvent);
 };
 
 // when a message is received from the socket connection,
 // the message will contain the id of a button that the other player clicked
 socketConnection.onmessage = (messageObject) => {
   // if the button is unclicked, changes its text to "O", and disable the button
   const buttonClicked = document.getElementById(messageObject.data);
   if (buttonClicked.innerHTML === '-') {
     buttonClicked.innerHTML = 'O';
     buttonClicked.disabled = true;
   }
 };
 
 // an event listener to log any errors with the socket connection.
 socketConnection.onerror = (error) => {
   console.log('socket error: ', error);
 };
 
 // put an event listener on every button that changes the text to "X",
 // disables the button, and sends a message through the socket connection
 // with the id of the clicked button
 const collectionOfButtons = document.querySelectorAll('button');
 collectionOfButtons.forEach((buttonElement) => {
   buttonElement.addEventListener('click', (event) => {
     // set the target's value to "X" and disable the button
     event.target.innerHTML = 'X';
     event.target.disabled = true;
 
     // send message (of the clicked button's id) through the socket connection
     socketConnection.send(event.target.id);
   });
 });
};
Restart the server by pressing Ctrl + C in the terminal, and then restart the server by running node
server/server.js
 again. Open up two browser windows to localhost:3000/ and watch how the WebSockets server relays messages between the two connected clients!
You may have noticed a couple things that are “off” with our tic-tac-toe game. Both players/browsers are playing as if they are player “X.” And an even bigger bug, they don’t have to wait for each other to make another move! I will leave the challenge of implementing some additional game logic to you!
Congratulations on using WebSockets to create a multiplayer tic-tac-toe game! I hope this opens your eyes to the power of WebSockets and how they can be used to create responsive, multi-user applications. Without WebSockets, our application would have to rely on client requests and long-polling to maintain updated data, leading to unnecessary strain on the browser and server. By implementing WebSockets, the server can push new information to each client exactly when it wants to, and without the overhead associated with HTTP requests!
Aside from
ws
, there are many other libraries for implementing WebSockets on both the client and server sides, including:
For further reading on the WebSockets protocol, and the browser’s native WebSockets API:
Previously published at https://www.codesmith.io/blog/introduction-to-websockets

Published by HackerNoon on 2020/02/01