DEV Community

Cover image for What are Websockets: The detailed Guide.
alakkadshaw
alakkadshaw

Posted on

What are Websockets: The detailed Guide.

What are Websockets

Websockets provide a way for web browsers and servers to communicate in real-time without the need to continuously reload the page.

Unlike traditional HTTP requests that follow a request-response pattern, Websockets establish a full-duplex communication channel.

This means once a connection is opened, data can flow freely in both directions for the duration of the connection.

This technology is crucial for applications that require immediate data updates, such as live chat systems, real-time trading platforms, or gaming applications.

Definition of Websockets

What Websockets Are: Websockets are a communication protocol that provides a full-duplex communication channel over a single, long-held connection. They are designed to operate over the same ports as HTTP (80) and HTTPS (443), facilitating easier integration into existing network infrastructures

Websockets Vs HTTP Connections

  • Connection Lifetime: Unlike HTTP, which opens a new connection for each request/response cycle, Websockets establish a persistent connection that remains open for the duration of the interaction. This allows continuous data flow without repeated handshakes.

  • Data Transfer: Websockets allow data to flow bidirectionally without the overhead of HTTP headers with each message, reducing latency and bandwidth consumption.

  • Protocols: HTTP is a stateless protocol, whereas Websockets maintain state over a connected session, allowing for context and state to be preserved.

Brief Overview of How WebSockets work

WebSocket Handshake:

  1. Client Request (Opening Handshake): The WebSocket connection begins with an HTTP handshake, where the client sends a request to the server to upgrade the connection from HTTP to Websockets. This request includes a key that the server will use to form a response.

Example request headers might look like this:

GET /chat HTTP/1.1
Host: example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Version: 13
Enter fullscreen mode Exit fullscreen mode
  1. Server Response: The server processes this request and if it supports Websockets, responds with an acceptance of the upgrade request, confirming the establishment of a WebSocket connection.

Example response headers might look like this:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
Enter fullscreen mode Exit fullscreen mode

Data Transfer:

  • Frame-based Data Handling: Once the handshake is complete, data is transmitted in "frames", which can be of varying lengths and contain different types of data (text, binary, etc.).

  • Continuity: The connection remains open, allowing ongoing and instantaneous data transfer until either the client or server initiates a close.

  • Control Frames: WebSocket protocol defines several control frames for managing the connection, such as close, ping, and pong frames, which are used to check the connection's liveliness and to gracefully close the connection.

Advantages of Using Websockets

Real-Time Data Transfer with Low Latency:

  • Websockets provide a continuous connection between the client and the server, enabling data to be sent the instant it's available.

  • This eliminates the delay caused by the client having to wait for a scheduled time to send a request, as in traditional HTTP polling.

  • The direct and constant connection significantly reduces latency, making Websockets ideal for applications like online gaming, live sports updates, and financial trading where timing is crucial.

Bi-Directional Communication Between Client and Server:

  • Unlike HTTP where communication is predominantly initiated by the client, Websockets allow both the client and the server to send data independently once the connection is established.

  • This two-way communication channel is particularly beneficial for chat applications, real-time collaboration tools, and interactive dashboards, where server and client continuously exchange information.

Reduced Server Load and Network Traffic Compared to HTTP Polling:

  • HTTP polling involves the client repeatedly making HTTP requests at regular intervals, which results in a high number of overheard-laden HTTP headers being sent and processed, even if there is no data to return.

  • Websockets eliminate the need for constant requests and responses. After the initial handshake, only data frames are exchanged, and these frames do not carry the same overhead as HTTP headers.

  • This reduction in the number of transactions and data overhead leads to less bandwidth usage and lower server load, which is advantageous for both server performance and operational costs.

Practical Implementation of Websockets

Setting Up a Basic WebSocket Server

To set up a WebSocket server in Node.js, you will first need the ws library, a simple and powerful WebSocket library for Node.js. Below is a detailed, step-by-step guide and code examples on how to set up and manage a basic WebSocket server.

Step 1 Create a new Project directory

Create a new directory for the project then

mkdir websocket-server
cd websocket-server
Enter fullscreen mode Exit fullscreen mode

Step 2: Initialize a New Node.js Project

Initialize a new Node.js project by running:

npm init -y
Enter fullscreen mode Exit fullscreen mode

This command creates a package.json file that will manage your project's dependencies.

Step 3: Install the ws Library

Install the ws WebSocket library via NPM:

npm install ws
Enter fullscreen mode Exit fullscreen mode

Step 4: Create the WebSocket Server

Create a new file named server.js and open it in your preferred text editor. Add the following code to set up a basic WebSocket server:

// Import the WebSocket library
const WebSocket = require('ws');

// Create a new WebSocket server on port 8080
const wss = new WebSocket.Server({ port: 8080 });

// Set up connection event
wss.on('connection', function connection(ws) {
    console.log('A new client connected.');

    // Send a message to the newly connected client
    ws.send('Welcome to the WebSocket server!');

    // Set up event for receiving messages from the client
    ws.on('message', function incoming(message) {
        console.log('received: %s', message);

        // Echo the received message back to the client
        ws.send(`Server received your message: ${message}`);
    });

    // Set up event for client disconnection
    ws.on('close', () => {
        console.log('Client has disconnected.');
    });
});

console.log('WebSocket server is running on ws://localhost:8080');
Enter fullscreen mode Exit fullscreen mode

Explanation of the Code

  1. Import the ws Library: This line brings the WebSocket functionality into your Node.js script.

  2. Create the WebSocket Server: Initializes a WebSocket server listening on port 8080.

  3. Handle Connection Events: Adds an event listener for new client connections. Each client connection (ws) triggers the callback function.

  4. Send a Welcome Message: Sends a message to the client upon connection.

  5. Receive Messages from the Client: Adds an event listener for messages sent by the client. It prints received messages to the console and echoes them back to the client.

  6. Handle Client Disconnection: Logs a message when a client disconnects.

Running the WebSocket Server

To run your WebSocket server, execute:

node server.js
Enter fullscreen mode Exit fullscreen mode

Building a Websocket client and integrating it with the server we already buil

Creating a simple WebSocket client involves using HTML5 and JavaScript to establish a connection to a WebSocket server, send messages, and handle incoming messages. Here's a step-by-step guide and a detailed code example:

Step 1: Create an HTML File

Start by creating an HTML file, for example, index.html. This file will include both the HTML to provide a basic user interface and JavaScript for WebSocket interaction.

Step 2: Set Up HTML Structure

In your HTML file, define a simple user interface that includes a text input for sending messages and a button to trigger the send action. Also, include a section to display incoming messages.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>WebSocket Client</title>
</head>
<body>
    <h2>WebSocket Test Client</h2>
    <input id="messageInput" type="text" placeholder="Type your message here...">
    <button onclick="sendMessage()">Send Message</button>
    <div id="messages"></div>

    <script src="client.js"></script>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

Step 3: Write the JavaScript Code by creating the client.js file

Create a separate JavaScript file, e.g., client.js, and link it in your HTML file as shown above. The JavaScript will handle the WebSocket connection, sending messages, and receiving messages.

// Connect to WebSocket server
const ws = new WebSocket('ws://localhost:8080');

// Connection opened
ws.onopen = function(event) {
    console.log('Connection is open');
    displayMessage('Connected to the server');
};

// Listen for messages
ws.onmessage = function(event) {
    console.log('Message from server ', event.data);
    displayMessage(event.data);
};

// Connection closed
ws.onclose = function(event) {
    console.log('Connection is closed');
    displayMessage('Disconnected from the server');
};

// Send message function
function sendMessage() {
    var message = document.getElementById('messageInput').value;
    ws.send(message);
    displayMessage('You: ' + message);
    document.getElementById('messageInput').value = ''; // clear input field after send
}

// Display messages in the HTML page
function displayMessage(message) {
    const messagesDiv = document.getElementById('messages');
    messagesDiv.innerHTML += `<div>${message}</div>`;
}
Enter fullscreen mode Exit fullscreen mode

Explanation of the Code

  1. Connect to the WebSocket Server: Establishes a WebSocket connection to the server. The URL (ws://localhost:8080) should match your server's address.

  2. Handle Open Connection: The onopen event triggers when the connection to the server is successfully opened, confirming connectivity.

  3. Receive Messages: The onmessage event handles incoming messages from the server. It displays these messages in the HTML page.

  4. Handle Closed Connection: The onclose event triggers when the WebSocket connection is closed, either by the client or server.

  5. Send Messages: The sendMessage function gets the message from the text input and sends it to the server using ws.send(). It also displays the sent message in the HTML page.

  6. Display Messages: Updates the HTML to include each new message.

Running the WebSocket Client

Open the index.html file in a web browser to run your WebSocket client. It should connect to your WebSocket server, and you'll be able to send and receive messages in real-time.

Scaling Websocket Connections

When scaling WebSocket connections, the challenge is to handle a large number of concurrent connections efficiently while maintaining performance and reliability. Here are some strategies and technologies that can help:

Load Balancing

  • Use Load Balancers: Distribute incoming WebSocket connections across multiple servers. This spreads the load and reduces the risk of any single server becoming a bottleneck.

  • Sticky Sessions: Implement sticky sessions in your load balancer to ensure that WebSocket connections from the same client are routed to the same server. This is important because WebSocket connections are stateful.

Horizontal Scaling

  • Add More Servers: As demand increases, add more servers to your WebSocket server pool. This is horizontal scaling and helps manage more connections by increasing the number of servers that handle the workload.

  • Cloud-Based Scaling: Use cloud services that can dynamically add or remove servers based on demand, often automatically.

Managing State

  • Externalize State: Use an external store for session data, so connection state can be shared across multiple servers. This helps in maintaining continuity even if the client’s connection switches between different servers in a load-balanced environment.

Use of Redis or Other Message Brokers

  • Redis: Employ Redis as a fast, in-memory data store to manage session states or message queues. Redis can publish and subscribe to messages, making it a good choice for propagating messages across different instances of your application.

  • Message Brokers: Utilize message brokers like RabbitMQ or Kafka to handle messaging between clients and servers. These tools can effectively distribute messages across a system with many connections and maintain the performance of message delivery.

Example of Scaling Using Redis

Here’s how you might use Redis to manage scaling WebSocket connections:

  1. Setup Redis: Deploy Redis and connect it to your WebSocket servers. Each server will communicate with Redis to fetch and store state information.

  2. Pub/Sub Model: Implement a publish/subscribe model in Redis where:

* Each WebSocket server publishes messages to a channel.

* All other WebSocket servers subscribe to that channel and receive updates.
Enter fullscreen mode Exit fullscreen mode
  1. Session Storage: Use Redis to store session-related data. When a WebSocket connection is established, the server checks Redis for any existing session data related to that client and retrieves it. This is useful when a client reconnects and might connect to a different server due to load balancing.

  2. Handling Connection Failures: On a server failure, other servers can take over the connections using the session data stored in Redis, ensuring minimal disruption to the client.

Example Code Snippet for Using Redis with WebSockets

Here's a basic example of how you might configure a WebSocket server to use Redis for storing session data and broadcasting messages:

const WebSocket = require('ws');
const Redis = require('ioredis');

const redisSubscriber = new Redis();
const redisPublisher = new Redis();

const wss = new WebSocket.Server({ port: 8080 });

redisSubscriber.subscribe('websocketMessages');
redisSubscriber.on('message', (channel, message) => {
    // Broadcast message to all connected clients
    wss.clients.forEach((client) => {
        if (client.readyState === WebSocket.OPEN) {
            client.send(message);
        }
    });
});

wss.on('connection', (ws) => {
    ws.on('message', (message) => {
        // Publish received message to Redis
        redisPublisher.publish('websocketMessages', message);
    });
});
Enter fullscreen mode Exit fullscreen mode

In this setup, every message received by a WebSocket server is published to a Redis channel, and all WebSocket servers subscribed to that channel receive the message and broadcast it to their connected clients.

This ensures that messages can be distributed across a scaled environment efficiently.

Security Considerations for WebSocket Connections

Securing WebSocket connections is crucial due to their persistent nature and the sensitivity of data that may be transmitted. Here are key best practices and methods to mitigate common security threats.

Authentication

  • Secure Initial Handshake: Ensure the initial HTTP handshake that upgrades the connection to WebSocket is secure. Use HTTP authentication mechanisms like Basic or Bearer token authentication to verify user credentials before upgrading.

  • Session Tokens: Implement session tokens that are verified at the WebSocket connection level. Ensure tokens are transported securely and validated before establishing or continuing any WebSocket communication.

Encryption

  • Use TLS/SSL: Always use WebSockets over TLS (WSS://) to encrypt the data transmitted between the client and the server. This prevents interception and eavesdropping on the data exchanged.

  • Certificate Validation: Ensure proper SSL/TLS certificate validation practices are followed by your server to prevent man-in-the-middle (MITM) attacks.

Handling Common Security Threats

  • Cross-Site WebSocket Hijacking (CSWSH): Verify the origin of WebSocket connection requests to prevent unauthorized access from other domains. This can be done by checking the Origin header in the WebSocket handshake request.

  • Denial of Service (DoS): Implement rate limiting and connection throttling to mitigate the risk of denial-of-service attacks, which aim to overwhelm your server by creating a massive number of connections.

  • Input Validation: Always validate and sanitize any data received through WebSocket connections to prevent injection attacks and other malicious exploits.

  • Secure Cookie Use: If cookies are used for managing sessions or authentication, ensure they are secure and HttpOnly to prevent access from client-side scripts.

Example Code for Securing WebSocket Connections

Here's an example showing how to implement some of these security measures in Node.js using the ws library:

const WebSocket = require('ws');
const https = require('https');
const fs = require('fs');

// Load SSL certificate
const server = https.createServer({
  cert: fs.readFileSync('path/to/cert.pem'),
  key: fs.readFileSync('path/to/key.pem')
});

const wss = new WebSocket.Server({ server });

wss.on('connection', function connection(ws, request) {
  // Validate session token or cookies
  const token = request.headers['sec-websocket-protocol'];

  if (!isValidToken(token)) {
    ws.terminate(); // Terminate connection if token is invalid
    return;
  }

  // Handle WebSocket messages
  ws.on('message', function incoming(message) {
    if (!validateInput(message)) {
      ws.terminate(); // Terminate connection if message is not valid
      return;
    }
    console.log('received: %s', message);
  });
});

// Start the server
server.listen(8080);

function isValidToken(token) {
  // Token validation logic here
  return true; // Simplified for example
}

function validateInput(input) {
  // Input validation logic here
  return true; // Simplified for example
}
Enter fullscreen mode Exit fullscreen mode

In this setup:

  • SSL/TLS encryption is implemented using HTTPS with a certificate.

  • Token validation checks if the WebSocket connection request includes a valid token.

  • Input validation ensures all received messages are checked for potentially harmful content before processing.

Websockets vs. HTTP Long Polling vs. Server-Sent Events

These three technologies serve similar purposes but offer different capabilities and trade-offs. Below is a detailed comparison of their pros and cons along with practical examples and decision matrices to help determine the best use cases for each.

Websockets

Pros:

  • Bi-directional Communication: Allows both client and server to send data actively without waiting for a request. Ideal for chat applications, real-time gaming, or trading platforms where immediate response is critical.

  • Low Latency: Maintains an open connection, minimizing the delay in data transmission. Suitable for applications requiring quick updates.

  • Efficient Performance: Reduces overhead by eliminating the need for frequent connections and disconnections.

Cons:

  • Complexity in Handling: More complex to implement and manage compared to HTTP requests due to the persistent connection.

  • Scalability Concerns: Can be more demanding on server resources when scaling because each connection consumes memory and CPU.

Practical Example:

  • Use in a financial trading platform where traders need to see price updates in real-time and execute trades instantly.

HTTP Long Polling

Pros:

  • Compatibility: Works on any platform or browser that supports HTTP. No special protocols or servers are necessary beyond a standard HTTP server.

  • Simplicity: Easier to implement and debug than Websockets as it uses standard HTTP requests.

Cons:

  • Higher Latency: Each request may have a delay as the server waits to send a response until there's data available or a timeout occurs.

  • Increased Server Load: More HTTP requests can lead to higher server load, particularly if many users are constantly polling.

Practical Example:

  • Use in a news feed application where it’s acceptable to have a slight delay between when new content is available and when it appears to the user.

Server-Sent Events (SSE)

Pros:

  • Simple Server-Side Logic: Easier to implement on the server than Websockets, as the server only needs to handle outgoing messages.

  • Built-in Reconnection: Automatically tries to reconnect if the connection is lost, which is handled by the browser.

  • Efficiency in Unidirectional Tasks: Efficient for scenarios where only the server sends updates to the client.

Cons:

  • Limited Browser Support: Not supported in all browsers, especially older versions.

  • Unidirectional: Only supports server-to-client communication, which limits its use in interactive applications.

Practical Example:

  • Use in a live blog or event streaming where the server needs to push updates to clients but doesn’t need to receive data back from them.

Decision Matrix for Choosing Technology

Requirement Websockets HTTP Long Polling Server-Sent Events
Bi-directional Communication Yes No No
Real-time Interaction Best Moderate Good
Browser Compatibility Good Best Moderate
Implementation Complexity High Moderate Low
Server Resource Use High High Moderate
Communication Flow Both Server to Client Server to Client

When to Use Each Technology:

  • Websockets: Choose when real-time, two-way communication is necessary, and when the application demands high interactivity and low latency.

  • HTTP Long Polling: Opt for this when updates are required in near real-time but the system can tolerate slight delays, or when using legacy systems that must support older technologies.

  • Server-Sent Events: Best used for scenarios where the server continuously updates the data to the client, like a stock ticker or live sports scores, and where the client doesn’t need to send data to the server.

Top comments (0)