All Products
Search
Document Center

Server Load Balancer:Use CLB and WebSocket for real-time messaging

Last Updated:Jun 21, 2026

WebSocket is a network protocol that provides a full-duplex communication channel over a single TCP connection. It enables a persistent connection between a client and a server, allowing both to proactively send and receive data. This design reduces the overhead and latency of frequently establishing new connections, making it more efficient than the traditional HTTP request-response model. WebSocket is primarily used for applications that require real-time communication. Classic Load Balancer (CLB) supports the WebSocket protocol by default.

Introduction to WebSocket

Why use WebSocket

As web technologies evolve, applications increasingly require servers to push data in real time for features like live chat rooms and real-time comments. The traditional polling method, where a client's browser repeatedly sends HTTP requests to a server to fetch the latest data, has significant drawbacks. Frequent requests with large HTTP headers and small data payloads increase server load and waste bandwidth.

To address these issues, HTML5 introduced the WebSocket protocol, which offers a more efficient solution for client-server communication. WebSocket supports full-duplex communication, meaning the server and client can send and receive data simultaneously. This allows the server to proactively push new data to the client without waiting for a polling request. This bi-directional, real-time communication mechanism improves data transfer efficiency, reduces unnecessary network requests, and saves server resources and bandwidth, providing a smoother and more responsive user experience.

Key features of WebSocket

Communication begins with a standard three-way TCP handshake. The client then sends a special HTTP request, known as a protocol upgrade handshake. Once this handshake is successful, the connection switches from HTTP to WebSocket. All subsequent communication between the client and server uses the WebSocket protocol, allowing for bi-directional data exchange over the same connection.

Once a WebSocket connection is established, it remains active. This persistent, low-latency connection allows for continuous, bi-directional data transfer, which improves data exchange efficiency.

image

WebSocket communicates using data frames, which have their own frame protocol format with concise headers. Data can be transmitted as text or binary. This method reduces protocol overhead on persistent connections, making network interaction more efficient. It saves server resources and bandwidth while providing a smoother real-time interactive experience.

For more information about the WebSocket protocol, see the official documentation The WebSocket Protocol.

WebSocket use cases

WebSocket is ideal for applications that require fast, real-time, bi-directional communication, such as AI applications, online chat rooms, real-time notification systems, multiplayer online games, and real-time market data feeds.

Example scenario

A company wants to deploy a web-based online chat application on Alibaba Cloud. Users can access the backend service through a domain name for real-time communication. As an instant messaging application, it requires low latency and efficient, real-time, bi-directional communication.

The company's website service faces challenges with high concurrency and persistent connection management. As the number of users grows, the traditional HTTP model cannot support many concurrent users in real-time communication because each interaction requires a new connection. This leads to a sharp increase in server load and poor performance.

In this scenario, using CLB with the WebSocket protocol effectively manages persistent connections under high concurrency. By deploying the WebSocket application on multiple backend servers in a vServer group and using Redis for message synchronization, the service achieves high availability. This provides a reliable and efficient real-time messaging solution for the online chat application.

image

Usage notes

HTTP listeners on CLB support the WebSocket protocol by default. CLB supports hot updates, which means that configuration changes do not affect existing persistent connections.

Note the following:

  • If the connection between CLB and the backend servers uses a specific HTTP version, such as HTTP/1.1, a web server that supports the same HTTP version should be used on the backend servers.

  • The default connection request timeout for an HTTP listener is 60 seconds. If no messages are exchanged between CLB and a backend server for more than 60 seconds, CLB proactively closes the connection.

    • If the default 60-second timeout is insufficient for your needs, you can modify the listener's Connection Request Timeout.

    • To keep the connection open, you must implement a keepalive mechanism to exchange packets at least once every 60 seconds.

Prerequisites

  • You have created a Classic Load Balancer (CLB) instance.

  • Three ECS instances are required: ECS01, ECS02, and ECS03.

    • ECS01 and ECS02 are used to deploy the WebSocket application, and ECS03 is used to deploy Redis.

    • In this tutorial, all servers run CentOS 7.9.

    • We recommend that you place ECS01, ECS02, and ECS03 in the same security group. If they are in different security groups, make sure to configure rules that allow traffic on the required communication ports between the servers.

  • A registered domain name with a completed ICP filing is required. For more information, see Register an Alibaba Cloud domain name and ICP filing.

Procedure

Step 1: Deploy services

You need to deploy Redis on your ECS03 instance and the WebSocket application on your ECS01 and ECS02 instances.

This topic uses a simple Python-based online chat room on CentOS 7.9 for demonstration purposes. This example is for reference only. In a production environment, use your own application.

Deploy Redis on ECS03

  1. Log on to the ECS03 instance.

  2. Copy and run the following commands to install and configure Redis.

    # Install EPEL (Extra Packages for Enterprise Linux)
    sudo yum install epel-release -y
    # Install Redis
    sudo yum install redis -y
    # Start and enable the Redis service
    sudo systemctl start redis
    sudo systemctl enable redis
    # Edit the Redis configuration file to allow remote connections
    sudo sed -i 's/^bind 127.0.0.1$/bind 0.0.0.0/' /etc/redis.conf
    sudo sed -i 's/^protected-mode yes/protected-mode no/' /etc/redis.conf
    # Restart the Redis service for the changes to take effect
    sudo systemctl restart redis
    # Check the Redis status
    sudo systemctl status redis
    
  3. If the commands run without errors and the output shows the Redis service is in the active (running) state, the deployment and configuration are successful.

    ● redis.service - Redis persistent key-value database
       Loaded: loaded (/usr/lib/systemd/system/redis.service; enabled; vendor preset: disabled)
       Active: active (running) since ...
     Main PID: 12345 (redis-server)
       CGroup: /system.slice/redis.service
               └─12345 /usr/bin/redis-server 0.0.0.0:6379
    

Deploy WebSocket on ECS01

  1. Log on to the ECS01 instance.

  2. Run sudo pip3 install flask flask-socketio flask-cors redis to install the dependencies.

  3. Run vi ECS01_ws.py and press the i key to enter edit mode.

  4. Copy and paste the following code:

    Sample code for the test service

    Note

    On line 13, replace the IP address in redis_url with the IP address of your Redis server (ECS03).

    import os
    import redis
    from flask import Flask, render_template, request
    from flask_cors import CORS
    from flask_socketio import SocketIO, emit, disconnect
    app = Flask(__name__)
    app.config['SECRET_KEY'] = 'secret!'
    # Enable Cross-Origin Resource Sharing (CORS)
    CORS(app)
    # Configure Redis as the message queue and state store
    redis_url = "redis://192.168.*.*:6379/0"  # Replace with the IP address of your Redis server
    redis_client = redis.StrictRedis.from_url(redis_url)
    # Set the log level to DEBUG for easier debugging
    socketio = SocketIO(app, message_queue=redis_url, manage_session=True, logger=True, engineio_logger=True, cors_allowed_origins="*")
    SESSION_PREFIX = "session:"
    def set_session_data(session_id, key, value):
        redis_client.hset(f"{SESSION_PREFIX}{session_id}", key, value)
    def get_session_data(session_id, key):
        return redis_client.hget(f"{SESSION_PREFIX}{session_id}", key)
    def delete_session_data(session_id):
        redis_client.delete(f"{SESSION_PREFIX}{session_id}")
    @app.route('/')
    def index():
        return render_template('index.html')
    @socketio.on('connect')
    def handle_connect():
        try:
            session_id = request.sid  # Get the client's session ID
            print(f"Session {session_id} connected.")
            welcome_message = "You have entered the chat room!"
            emit('message', welcome_message)
            set_session_data(session_id, "username", '')  # Initialize the username as empty
        except Exception as e:
            print(f"Error during connection: {str(e)}")
    @socketio.on('disconnect')
    def handle_disconnect():
        try:
            session_id = request.sid
            username = get_session_data(session_id, "username")
            if username:
                username = username.decode()
                leave_message = f"{username} has left the chat room."
                emit('message', leave_message, broadcast=True)
                print(leave_message)
            delete_session_data(session_id)
            print(f"Session {session_id} disconnected.")
        except Exception as e:
            print(f"Error during disconnection: {str(e)}")
    @socketio.on('set_username')
    def handle_set_username(username):
        session_id = request.sid
        set_session_data(session_id, "username", username)
        print(f"Username for client {session_id} set to {username}")
        emit('message', f"Your username has been set to: {username}")
    @socketio.on('message')
    def handle_message(msg):
        session_id = request.sid
        username = get_session_data(session_id, "username")
        if username:
            username = username.decode()
            formatted_message = f"{username}: {msg}"
            emit('message', formatted_message, broadcast=True)
            print(formatted_message)
        else:
            warning_message = "Failed to send message: Please set a username first."
            emit('message', warning_message)
            print(warning_message)
    if __name__ == '__main__':
        # Make sure the templates directory exists
        if not os.path.exists('templates'):
            os.makedirs('templates')
        # Use the Flask template (index.html)
        html_code = '''<!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Chat Room</title>
        <style>
            body {
                font-family: Arial, sans-serif;
                display: flex;
                flex-direction: column;
                align-items: center;
                margin: 0;
                padding: 0;
                background-color: #f0f0f0;
            }
            h1 {
                color: #333;
            }
            .chat-container {
                width: 90%;
                max-width: 600px;
                background: white;
                padding: 20px;
                border-radius: 8px;
                box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
            }
            .user-container, .message-container {
                display: flex;
                margin-bottom: 10px;
            }
            .user-container input, .message-container input {
                flex: 1;
                padding: 10px;
                margin-right: 10px;
                border: 1px solid #ccc;
                border-radius: 4px;
            }
            .message-container {
                margin-top: 10px;
            }
            button {
                padding: 10px;
                background-color: #0056b3;
                color: white;
                border: none;
                border-radius: 4px;
                cursor: pointer;
            }
            button:hover {
                background-color: #004099;
            }
            #messages {
                border: 1px solid #ccc;
                padding: 10px;
                height: 300px;
                overflow-y: scroll;
                margin-bottom: 10px;
                border-radius: 4px;
                background-color: #f9f9f9;
            }
        </style>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.0.1/socket.io.js"></script>
        <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
    </head>
    <body>
        <h1>Online Chat Room</h1>
        <div class="chat-container">
            <div class="user-container">
                <input type="text" id="username" autocomplete="off" placeholder="Enter a username">
                <button onclick="setUsername()">Set Username</button>
            </div>
            <div id="messages"></div>
            <div class="message-container">
                <input type="text" id="myMessage" autocomplete="off" placeholder="Enter a message...">
                <button onclick="sendMessage()">Send</button>
            </div>
        </div>
        <script>
            var socket = io({ transports: ['websocket', 'polling', 'flashsocket'] });
            var usernameSet = false;
            socket.on('connect', function() {
                console.log("Connected to the server!");
                socket.on('message', function(msg){
                    $('#messages').append($('<div>').text(msg));
                    $('#messages').scrollTop($('#messages')[0].scrollHeight);
                });
            });
            function setUsername() {
                var username = $('#username').val();
                if (username) {
                    socket.emit('set_username', username);
                    usernameSet = true;  // Mark that username is set.
                } else {
                    alert("Username cannot be empty!");
                }
            }
            function sendMessage() {
                if (usernameSet) {
                    var message = $('#myMessage').val();
                    if (message) {
                        socket.send(message);
                        $('#myMessage').val('');
                    } else {
                        alert("Message cannot be empty!");
                    }
                } else {
                    alert("Please set a username first!");
                }
            }
        </script>
    </body>
    </html>
    '''
        # Save the template to a file
        with open('templates/index.html', 'w') as file:
            file.write(html_code)
        socketio.run(app, host='0.0.0.0', port=5000)
    
  5. Press the Esc key, and then enter :wq to save the changes.

  6. Run the sudo python3 ECS01_ws.py command to run the script.

  7. When the following output is displayed, the WebSocket application has started on port 5000.

    Server initialized for threading.
     * Serving Flask app 'ECS01_ws' (lazy loading)
     * Environment: production
       WARNING: This is a development server. Do not use it in a production deployment.
       Use a production WSGI server instead.
     * Debug mode: off
     * Running on all addresses.
       WARNING: This is a development server. Do not use it in a production deployment.
     * Running on http://192.168.*.*:5000/ (Press CTRL+C to quit)
    

    If the application fails to start, check if the port is already in use or if you copied the commands or code incorrectly.

● redis.service – Redis persistent key-value database
   Loaded: loaded (/usr/lib/systemd/system/redis.service; enabled; vendor preset: disabled)
  Drop-In: /etc/systemd/redis.service.d
           └─limit.conf
   Active: active (running) since Thu 2xxx xxx xxx xxx CST; 6s ago
  Process: 14715 ExecStop=/usr/libexec/redis-shutdown (code=exited, status=0/SUCCESS)
 Main PID: 14730 (redis-server)
   CGroup: /system.slice/redis.service
           └─14730 /usr/bin/redis-server 0.0.0.0:6379

Deploy WebSocket on ECS02

  1. Log on to the ECS02 instance.

  2. Run sudo pip3 install flask flask-socketio flask-cors redis to install the dependencies.

  3. Run vi ECS02_ws.py and press the i key to enter edit mode.

  4. Copy and paste the following code:

    Sample code for the test service

    Note

    On line 13, replace the IP address in redis_url with the IP address of your Redis server (ECS03).

    import os
    import redis
    from flask import Flask, render_template, request
    from flask_cors import CORS
    from flask_socketio import SocketIO, emit, disconnect
    app = Flask(__name__)
    app.config['SECRET_KEY'] = 'secret!'
    # Enable Cross-Origin Resource Sharing (CORS)
    CORS(app)
    # Configure Redis as the message queue and state store
    redis_url = "redis://192.168.*.*:6379/0"  # Replace with the IP address of your Redis server
    redis_client = redis.StrictRedis.from_url(redis_url)
    # Set the log level to DEBUG for easier debugging
    socketio = SocketIO(app, message_queue=redis_url, manage_session=True, logger=True, engineio_logger=True, cors_allowed_origins="*")
    SESSION_PREFIX = "session:"
    def set_session_data(session_id, key, value):
        redis_client.hset(f"{SESSION_PREFIX}{session_id}", key, value)
    def get_session_data(session_id, key):
        return redis_client.hget(f"{SESSION_PREFIX}{session_id}", key)
    def delete_session_data(session_id):
        redis_client.delete(f"{SESSION_PREFIX}{session_id}")
    @app.route('/')
    def index():
        return render_template('index.html')
    @socketio.on('connect')
    def handle_connect():
        try:
            session_id = request.sid  # Get the client's session ID
            print(f"Session {session_id} connected.")
            welcome_message = "You have entered the chat room!"
            emit('message', welcome_message)
            set_session_data(session_id, "username", '')  # Initialize the username as empty
        except Exception as e:
            print(f"Error during connection: {str(e)}")
    @socketio.on('disconnect')
    def handle_disconnect():
        try:
            session_id = request.sid
            username = get_session_data(session_id, "username")
            if username:
                username = username.decode()
                leave_message = f"{username} has left the chat room."
                emit('message', leave_message, broadcast=True)
                print(leave_message)
            delete_session_data(session_id)
            print(f"Session {session_id} disconnected.")
        except Exception as e:
            print(f"Error during disconnection: {str(e)}")
    @socketio.on('set_username')
    def handle_set_username(username):
        session_id = request.sid
        set_session_data(session_id, "username", username)
        print(f"Username for client {session_id} set to {username}")
        emit('message', f"Your username has been set to: {username}")
    @socketio.on('message')
    def handle_message(msg):
        session_id = request.sid
        username = get_session_data(session_id, "username")
        if username:
            username = username.decode()
            formatted_message = f"{username}: {msg}"
            emit('message', formatted_message, broadcast=True)
            print(formatted_message)
        else:
            warning_message = "Failed to send message: Please set a username first."
            emit('message', warning_message)
            print(warning_message)
    if __name__ == '__main__':
        # Make sure the templates directory exists
        if not os.path.exists('templates'):
            os.makedirs('templates')
        # Use the Flask template (index.html)
        html_code = '''<!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Chat Room</title>
        <style>
            body {
                font-family: Arial, sans-serif;
                display: flex;
                flex-direction: column;
                align-items: center;
                margin: 0;
                padding: 0;
                background-color: #f0f0f0;
            }
            h1 {
                color: #333;
            }
            .chat-container {
                width: 90%;
                max-width: 600px;
                background: white;
                padding: 20px;
                border-radius: 8px;
                box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
            }
            .user-container, .message-container {
                display: flex;
                margin-bottom: 10px;
            }
            .user-container input, .message-container input {
                flex: 1;
                padding: 10px;
                margin-right: 10px;
                border: 1px solid #ccc;
                border-radius: 4px;
            }
            .message-container {
                margin-top: 10px;
            }
            button {
                padding: 10px;
                background-color: #0056b3;
                color: white;
                border: none;
                border-radius: 4px;
                cursor: pointer;
            }
            button:hover {
                background-color: #004099;
            }
            #messages {
                border: 1px solid #ccc;
                padding: 10px;
                height: 300px;
                overflow-y: scroll;
                margin-bottom: 10px;
                border-radius: 4px;
                background-color: #f9f9f9;
            }
        </style>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.0.1/socket.io.js"></script>
        <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
    </head>
    <body>
        <h1>Online Chat Room</h1>
        <div class="chat-container">
            <div class="user-container">
                <input type="text" id="username" autocomplete="off" placeholder="Enter a username">
                <button onclick="setUsername()">Set Username</button>
            </div>
            <div id="messages"></div>
            <div class="message-container">
                <input type="text" id="myMessage" autocomplete="off" placeholder="Enter a message...">
                <button onclick="sendMessage()">Send</button>
            </div>
        </div>
        <script>
            var socket = io({ transports: ['websocket', 'polling', 'flashsocket'] });
            var usernameSet = false;
            socket.on('connect', function() {
                console.log("Connected to the server!");
                socket.on('message', function(msg){
                    $('#messages').append($('<div>').text(msg));
                    $('#messages').scrollTop($('#messages')[0].scrollHeight);
                });
            });
            function setUsername() {
                var username = $('#username').val();
                if (username) {
                    socket.emit('set_username', username);
                    usernameSet = true;  // Mark that username is set.
                } else {
                    alert("Username cannot be empty!");
                }
            }
            function sendMessage() {
                if (usernameSet) {
                    var message = $('#myMessage').val();
                    if (message) {
                        socket.send(message);
                        $('#myMessage').val('');
                    } else {
                        alert("Message cannot be empty!");
                    }
                } else {
                    alert("Please set a username first!");
                }
            }
        </script>
    </body>
    </html>
    '''
        # Save the template to a file
        with open('templates/index.html', 'w') as file:
            file.write(html_code)
        socketio.run(app, host='0.0.0.0', port=5000)
                      
  5. Press the Esc key, and then enter :wq to save the changes.

  6. Run the sudo python3 ECS02_ws.py command to run the script.

  7. When the following output is displayed, the WebSocket application has started on port 5000.

    Server initialized for threading.
     * Serving Flask app 'ECS02_ws' (lazy loading)
     * Environment: production
       WARNING: This is a development server. Do not use it in a production deployment.
       Use a production WSGI server instead.
     * Debug mode: off
     * Running on all addresses.
       WARNING: This is a development server. Do not use it in a production deployment.
     * Running on http://192.168.*.*:5000/ (Press CTRL+C to quit)
    

    If the application fails to start, check if the port is already in use or if you copied the commands or code incorrectly.

Step 2: Configure a vServer group

  1. Log on to the Classic Load Balancer (CLB) console.

  2. In the top navigation bar, select the region where the CLB instance is deployed.

  3. In the left-side navigation pane, choose Instances. On the Instances page, find the target instance and click its ID.

  4. On the vServer groups tab, click Create vServer Group. On the Create vServer Group page, configure the following parameter. You can use the default values for other parameters or modify them as needed. After you complete the configuration, click Create and follow the on-screen instructions.

    Parameter

    Description

    vServer Group Name

    Enter RS1 as the vServer group name.

  5. On the vServer groups tab, find the vServer group that you created and click Modify in the Actions column.

  6. On the Modify vServer Group page, click Add. On the Servers page, add the backend servers ECS01 and ECS02. Set the port for both servers to 5000, the port used by the WebSocket application.

  7. On the Modify vServer Group page, select the added servers and click Save.

Step 3: Configure an HTTP listener

  1. Log on to the Classic Load Balancer (CLB) console.

  2. In the top navigation bar, select the region where the CLB instance is deployed.

  3. In the left-side navigation pane, choose Instances.

  4. On the Instances page, find the target instance and click Configure Listener in the Actions column.

  5. On the Protocol & Listener page, configure the following parameters. You can use the default values for other parameters or modify them as needed. After you complete the configuration, click Next.

    Parameter

    Description

    Select Listener Protocol

    Select HTTP.

    Listener Port

    In this example, the port is set to 5000.

  6. On the Backend Servers page, configure the following parameter. You can use the default values for other parameters or modify them as needed. After you complete the configuration, click Next.

    Parameter

    Description

    Server Group

    Select the vServer group that you created.

  7. On the Health Check page, you can use the default values for the parameters or modify them as needed. Then, click Next.

  8. On the Confirm page, review the configuration and click Submit to create the listener.

Step 4: Configure DNS resolution

Note
  • For domains not registered with Alibaba Cloud, you must first add the domain to the Alibaba Cloud DNS console before you can configure DNS records.

  • If your CLB instance is an internal-facing instance, you must first associate an Elastic IP address (EIP) with it and then create an A record that maps the domain name to the EIP to enable public access.

  1. In the left-side navigation pane, choose CLB > Instances.

  2. On the Instances page, select the target instance and copy its IP Address.

  3. Perform the following steps to add an A record:

    1. Log on to the Alibaba Cloud DNS console.

    2. On the Public Zone page, find the target domain name and click Settings in the Actions column.

    3. On the Settings page, click Add Record.

    4. In the Add Record panel, configure the following parameters. You can leave other parameters with their default values or modify them as needed. Then, click OK.

      Parameter

      Description

      Record Type

      Select A from the drop-down list.

      Hostname

      The prefix of your domain name.

      Note

      For a root domain, set the hostname to @.

      Record Value

      Enter the copied IP address of the CLB instance.

Step 5: Verify the result

Prepare two computers with different public IP addresses. On each computer, use a browser to send and view chat messages to verify that CLB delivers messages in real time over WebSocket.

  1. In a browser, go to http://<your-domain-name>:5000 to access the online chat room application.

    The Online Chat Room interface loads. The message "You have entered the chat room!" appears in the chat message area, which indicates that the WebSocket connection is established. The page contains a username setting area with a Set Username button and a message sending area with a Send button.

    If you open your browser's developer tools, the Network tab shows that the browser is communicating using the WebSocket protocol.

    In the Network panel, set the filter to websocket. A WebSocket request with a status code of 101 indicates that the protocol upgrade was successful. The connection status is Pending, which means that the persistent WebSocket connection is established and active.

  2. Enter a username for the chat and click Set Username.

  3. On each computer, enter multiple chat messages and click Send to test the application.

    Both browsers receive the messages in real time.

    The Online Chat Room application is successfully deployed. The page contains a Username input box and a Set Username button, a chat message display area, and a message input box with a Send button at the bottom. Multiple users, such as User1 and User2, can have a real-time conversation in the chat room.

  4. This verifies that using CLB with WebSocket enables real-time, highly available messaging.

FAQ

How do I use the WebSocket Secure protocol?

WebSocket Secure is the encrypted version of the WebSocket protocol.

HTTPS listeners support the WebSocket Secure protocol by default. To use the WebSocket Secure protocol, select HTTPS when you configure the listener.

Is there a charge for using WebSocket?

There are no extra fees for using the WebSocket and WebSocket Secure protocols.

Which regions support WebSocket?

All regions that support CLB also support WebSocket and WebSocket Secure.

References

This tutorial demonstrates deploying Redis on an ECS instance for testing. However, a single Redis server is a potential single point of failure. For production environments, we recommend that you use Tair to ensure high availability. For more information, see Quick start for Tair.