全部产品
Search
文档中心

负载均衡:CLB使用WebSocket协议实现信息实时推送

更新时间:Jul 24, 2026

WebSocket是一种在单个TCP连接上提供全双工通信渠道的网络协议。WebSocket的设计使得客户端和服务器之间可以实现持久连接,都能够主动地向对方发送数据或接收数据,减少了频繁建立连接的开销和延迟,这通常比传统的HTTP请求和响应模式更高效。WebSocket主要应用于需要实时通信功能的场景,CLB默认支持WebSocket协议。

WebSocket简介

为什么使用WebSocket

随着互联网技术的迅速发展,Web应用的多样化趋势日益显著,其中不少应用场景,例如直播间聊天室、实时弹幕等,都需要服务器具备实时推送数据的能力。传统的实现方式是通过轮询技术,即客户端浏览器在固定的时间间隔(例如每1秒)向服务器发起HTTP请求,服务器随后将最新数据返回给客户端。然而,这种模式存在显著的不足,客户端需要频繁地发起请求,而HTTP请求的头部信息通常较长,有效数据却相对较少,这不仅增加了服务器的负担,也造成了带宽资源的浪费。

为了解决这些问题,HTML5引入了WebSocket协议,它为服务器与客户端之间的通信提供了更高效的解决方案。WebSocket协议支持全双工通信,这意味着服务器和客户端可以同时进行数据的发送和接收,从而允许服务器在有新数据时主动推送给客户端,无需客户端不断轮询。这种双向实时通信机制显著提高了数据传输的效率,减少不必要的网络请求,有效节省服务器资源和带宽,同时为用户带来更流畅和实时的交互体验。

WebSocket协议的特性

WebSocket通信前,首先客户端与服务器要进行TCP三次握手连接,然后进行一次叫做“握手”的特殊HTTP请求进行协议升级,完成协议升级后原始的HTTP连接被升级到WebSocket连接。在协议升级后,客户端和服务器之间的通信将使用WebSocket协议进行而不再是HTTP,可以在同一个WebSocket连接上进行双向通信。

WebSocket连接一旦经过握手协商成功建立,便能维持活跃状态,使得双方能进行连续不断的双向数据传输,而不必为每个通信回合重新发起连接或等待确认。通过WebSocket,客户端和服务器之间得以建立一种持久、低延迟的连接,提升了数据交换效率。

image

WebSocket通过数据帧进行通信,它有自己的帧协议格式,头信息更简洁,数据可以作为文本或二进制传输。这种方式减少了持久连接上额外的协议开销,允许更高效的网络交互,能够在节省服务器资源和带宽的同时,提供更流畅的实时互动体验。

关于WebSocket协议的更多信息,可参考官方文档The WebSocket Protocol。

WebSocket应用场景

WebSocket主要适用于需要快速、实时的双向通信的应用场景,例如AI应用、在线聊天室、实时通知系统、多人在线游戏、实时市场信息推送等。

场景示例

某公司需要在阿里云上部署Web在线聊天应用,用户可以通过访问域名来接入后端服务进行实时交流。该应用由于其即时通讯的特性,要求用户之间的信息传递必须具备低延迟、高效率和双向实时的特点。

该公司的网站服务面临的挑战是高并发与长连接管理。随着用户数量的增长,传统的HTTP模式无法满足大量用户同时在线并保持实时通信的需求,因为每次通信都需要重新建立连接,这会导致服务器压力剧增且性能低下。

在这个场景下,选用CLB结合WebSocket协议,能够有效解决高并发下的长连接管理问题。通过后端服务器组多服务器部署WebSocket应用程序,并使用Redis进行消息同步,确保服务的高可用性,从而为在线聊天室应用提供了一个可靠、高效的实时信息推送解决方案。

image

注意事项

CLB的HTTP监听默认支持WebSocket协议。CLB默认支持热更新,即配置变更时不会影响已有长连接。

使用时需要注意如下事项:

  • 若CLB与后端服务器的连接采用某个版本的HTTP协议(例如HTTP/1.1),建议后端服务器采用支持同样HTTP协议版本的Web Server。

  • HTTP监听的默认连接请求超时时间为60秒,即如果CLB与后端服务超过60秒无消息交互,会主动断开连接。

    • 如果60秒无法满足您的需求,您可以通过修改监听的连接请求超时时间字段,调整该时间值。

    • 如果需要维持连接一直不中断,需要主动实现保活机制,每60秒内进行一次报文交互。

  • CLB 性能保障型实例的 HTTPS 监听默认支持 WebSocket Secure(WSS)协议,无需额外配置。

  • 当使用 TCP 监听时,CLB 不提供内置的 WebSocket 支持,需要在后端服务器上自行配置 WebSocket 服务。

  • 配置监听端口时,请避免使用高危端口(如 25、135、139、444、445、5800、5900 等)。该限制来自运营商网络,非 CLB 系统限制,CLB 控制台本身不阻止这些端口的配置,但运营商网络可能会屏蔽这些端口的流量。

前提条件

  • 已创建CLB实例。

  • 已准备3个云服务器ECS01、ECS02、ECS03。

    • ECS01、ECS02用于部署WebSocket应用程序,ECS03用于部署Redis。

    • 本文示例中服务器操作系统统一为CentOS7.9。

    • ECS01、ECS02、ECS03建议处于同一安全组中。如果分配了不同的安全组,注意互相放行服务器需要通信的端口。

  • 已经注册域名并完成备案。具体操作,请参见注册阿里云域名、ICP备案。

操作步骤

步骤一:部署服务

您需要在您的ECS03服务器中部署Redis,在ECS01、ECS02服务器中部署WebSocket应用程序。

本文以CentOS 7.9为示例,演示使用Python快速部署一个简易的在线聊天室测试服务。示例仅供参考,实际使用过程中以您自己开发的程序和服务为准。

在ECS03部署Redis服务

  1. 登录ECS03服务器后台。

  2. 复制粘贴如下命令,并执行,完成Redis部署与配置。

    # 安装 EPEL (Extra Packages for Enterprise Linux)
    sudo yum install epel-release -y
    # 安装 Redis
    sudo yum install redis -y
    # 启动并启用 Redis 服务
    sudo systemctl start redis
    sudo systemctl enable redis
    # 检查并编辑 Redis 配置文件,允许远程连接
    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
    # 重启 Redis 服务以使更改生效
    sudo systemctl restart redis
    # 检查 Redis 是否运行
    sudo systemctl status redis
    
  3. 命令执行无报错,并且命令运行完成后,返回如下信息并显示Redis服务为active(running)状态,表示部署与配置成功。

    ● 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
    

在ECS01中部署WebSocket应用程序

  1. 登录ECS01服务器后台。

  2. 执行sudo pip3 install flask flask-socketio flask-cors redis,安装依赖库。

  3. 执行vi ECS01_ws.py,按i键进入编辑模式。

  4. 复制并粘贴如下代码:

    部署测试服务代码参考

    说明

    注意第13行的redis_url中的IP地址,需要修改为Redis服务器的IP地址,即ECS03的IP地址。

    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!'
    # 启用跨域资源共享(CORS)
    CORS(app)
    # 配置 Redis 作为消息队列和状态存储
    redis_url = "redis://192.168.*.*:6379/0"  # 替换为你的 Redis 服务器 IP
    redis_client = redis.StrictRedis.from_url(redis_url)
    # 日志级别增加为 DEBUG 以便于调试
    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  # 获取客户端的 session ID
            print(f"Session {session_id} connected.")
            welcome_message = "您已进入聊天室!"
            emit('message', welcome_message)
            set_session_data(session_id, "username", '')  # 初始化用户名为空
        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} 已离开聊天室。"
                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"客户端 {session_id} 的用户名设置为 {username}")
        emit('message', f"您的用户名已设置为:{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 = "发送消息失败:请先设置用户名。"
            emit('message', warning_message)
            print(warning_message)
    if __name__ == '__main__':
        # 确保存在 templates 目录
        if not os.path.exists('templates'):
            os.makedirs('templates')
        # 使用 Flask 模板(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>聊天室</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>在线聊天室</h1>
        <div class="chat-container">
            <div class="user-container">
                <input type="text" id="username" autocomplete="off" placeholder="请输入用户名">
                <button onclick="setUsername()">设置用户名</button>
            </div>
            <div id="messages"></div>
            <div class="message-container">
                <input type="text" id="myMessage" autocomplete="off" placeholder="请输入消息...">
                <button onclick="sendMessage()">发送</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;  // 设置用户名标识
                } else {
                    alert("用户名不能为空!");
                }
            }
            function sendMessage() {
                if (usernameSet) {
                    var message = $('#myMessage').val();
                    if (message) {
                        socket.send(message);
                        $('#myMessage').val('');
                    } else {
                        alert("消息不能为空!");
                    }
                } else {
                    alert("请先设置用户名!");
                }
            }
        </script>
    </body>
    </html>
    '''
        # 将模板保存到文件
        with open('templates/index.html', 'w') as file:
            file.write(html_code)
        socketio.run(app, host='0.0.0.0', port=5000)
    

  5. 按Esc键,输入:wq保存修改。

  6. 执行sudo python3 ECS01_ws.py命令,运行脚本。

  7. 当最后显示如下执行结果时,表示WebSocket应用程序已启动,端口为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)
    

    如果出现启动失败,需要排查下端口是否已被占用,或者命令及代码是否复制粘贴错误。

● 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

在ECS02中部署WebSocket应用程序

  1. 登录ECS02服务器后台。

  2. 执行sudo pip3 install flask flask-socketio flask-cors redis,安装依赖库。

  3. 执行vi ECS02_ws.py,按i键进入编辑模式。

  4. 复制并粘贴如下代码:

    部署测试服务代码参考

    说明

    注意第13行的redis_url中的IP地址,需要修改为Redis服务器的IP地址,即ECS03的IP地址。

    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!'
    # 启用跨域资源共享(CORS)
    CORS(app)
    # 配置 Redis 作为消息队列和状态存储
    redis_url = "redis://192.168.*.*:6379/0"  # 替换为你的 Redis 服务器 IP
    redis_client = redis.StrictRedis.from_url(redis_url)
    # 日志级别增加为 DEBUG 以便于调试
    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  # 获取客户端的 session ID
            print(f"Session {session_id} connected.")
            welcome_message = "您已进入聊天室!"
            emit('message', welcome_message)
            set_session_data(session_id, "username", '')  # 初始化用户名为空
        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} 已离开聊天室。"
                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"客户端 {session_id} 的用户名设置为 {username}")
        emit('message', f"您的用户名已设置为:{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 = "发送消息失败:请先设置用户名。"
            emit('message', warning_message)
            print(warning_message)
    if __name__ == '__main__':
        # 确保存在 templates 目录
        if not os.path.exists('templates'):
            os.makedirs('templates')
        # 使用 Flask 模板(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>聊天室</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>在线聊天室</h1>
        <div class="chat-container">
            <div class="user-container">
                <input type="text" id="username" autocomplete="off" placeholder="请输入用户名">
                <button onclick="setUsername()">设置用户名</button>
            </div>
            <div id="messages"></div>
            <div class="message-container">
                <input type="text" id="myMessage" autocomplete="off" placeholder="请输入消息...">
                <button onclick="sendMessage()">发送</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;  // 设置用户名标识
                } else {
                    alert("用户名不能为空!");
                }
            }
            function sendMessage() {
                if (usernameSet) {
                    var message = $('#myMessage').val();
                    if (message) {
                        socket.send(message);
                        $('#myMessage').val('');
                    } else {
                        alert("消息不能为空!");
                    }
                } else {
                    alert("请先设置用户名!");
                }
            }
        </script>
    </body>
    </html>
    '''
        # 将模板保存到文件
        with open('templates/index.html', 'w') as file:
            file.write(html_code)
        socketio.run(app, host='0.0.0.0', port=5000)
                      

  5. 按Esc键,输入:wq保存修改。

  6. 执行sudo python3 ECS02_ws.py命令,运行脚本。

  7. 当最后显示如下执行结果时,表示WebSocket应用程序已启动,端口为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)
    

    如果出现启动失败,需要排查下端口是否已被占用,或者命令及代码是否复制粘贴错误。

步骤二:配置服务器组

  1. 登录传统型负载均衡CLB控制台。

  2. 在顶部菜单栏,选择实例所属的地域。

  3. 在左侧导航栏,选择实例管理,并在实例管理页面找到目标实例,单击实例ID。

  4. 在虚拟服务器组页签单击创建虚拟服务器组。并在创建虚拟服务器组页面配置以下信息,其他参数可保持默认值或根据实际情况修改。完成后单击创建并根据控制台指导完成操作。

    配置

    说明

    虚拟服务器组名称

    输入虚拟服务器组名称RS1。

  5. 在虚拟服务器组页签中,找到已创建的目标虚拟服务器组的操作列单击编辑。

  6. 在编辑虚拟服务器组页面,单击添加,并在我的服务器页面根据控制台指导添加后端服务器ECS01与ECS02,注意端口需要配置为WebSocket应用程序端口。本文代码示例中WebSocket应用程序端口为5000。

  7. 在编辑虚拟服务器组页面选中已添加的服务器,单击保存。

步骤三:配置HTTP监听

  1. 登录传统型负载均衡CLB控制台。

  2. 在顶部菜单栏,选择实例所属的地域。

  3. 在左侧导航栏,选择实例管理。

  4. 在实例管理页面,找到目标实例,在操作列单击监听配置向导。

  5. 在协议&监听页面配置以下信息,其他参数可保持默认值或根据实际情况修改。完成后单击下一步。

    配置

    说明

    选择监听协议

    选择HTTP。

    监听端口

    本文配置端口5000。

  6. 在后端服务器页面配置以下信息,其他参数可保持默认值或根据实际情况修改。完成后单击下一步。

    配置

    说明

    选择服务器组

    选择此前已创建好的虚拟服务器组。

  7. 在健康检查页面,单击下一步。参数可保持默认值或根据实际情况修改。

  8. 在配置审核页面,检查配置参数是否有误,无误的话单击提交,等待监听创建完成。

步骤四:配置域名解析

说明
  • 对于非阿里云注册域名,需先将域名添加到云解析控制台,才可以进行域名解析设置。

  • 如果您的CLB实例为私网类型,需先为其绑定弹性公网IP(EIP),随后配置A记录将域名解析指向该EIP实现公网访问。

  1. 在左侧导航栏,选择传统型负载均衡 CLB > 实例管理。

  2. 在实例管理页面,选择目标实例,并复制目标实例的服务地址。

  3. 执行以下步骤添加A解析记录。

    1. 登录域名解析控制台。

    2. 在公网权威解析页面,找到目标域名,在操作列单击解析设置。

    3. 在解析设置页面,单击添加记录。

    4. 在添加记录面板配置以下信息,其他参数可保持默认值或根据实际情况修改,完成后单击确定。

      配置

      说明

      记录类型

      在下拉列表中选择A。

      主机记录

      您的域名的前缀。

      说明

      创建域名为根域名时,主机记录为@。

      记录值

      输入域名对应的A地址,即您复制的CLB实例的服务地址。

步骤五:结果验证

准备2个不同IP地址的能够访问公网的终端电脑,通过在浏览器输入聊天消息并查看效果,验证CLB使用WebSocket协议实现信息实时推送。

  1. 在浏览器中输入http://域名:5000,访问在线聊天室应用。

    页面成功显示在线聊天室应用界面,聊天消息区域提示"您已进入聊天室!",表明 WebSocket 连接已建立。页面包含用户名设置区域(含设置用户名按钮)和消息发送区域(含发送按钮)。

    如果您打开了浏览器开发者工具,您可以在网络或Network页签看到,浏览器已经在使用WebSocket协议进行通信。

    在 Network 面板中将过滤条件设为 websocket,可以看到一条状态码为 101 的 WebSocket 请求(表示协议升级成功),连接状态为 Pending,说明 WebSocket 长连接已建立并保持通信中。

  2. 输入用户名用于后续聊天交互,完成后单击设置用户名。

  3. 在不同终端电脑中,分别输入多条聊天消息并单击发送,进行测试。

    所有浏览器中,均可以实时收到消息。

    在线聊天室应用部署成功,页面包含用户名输入框与设置用户名按钮、聊天消息展示区域以及底部消息输入框与发送按钮,多个用户(如小白、小黑)可在聊天室中完成实时对话。

  4. 如上验证过程表明,通过CLB使用WebSocket协议实现了信息实时推送,并且实现了高可用。

TCP 监听场景下 WebSocket 配置

CLB 使用 TCP 监听时,CLB 作为四层代理仅负责透传 TCP 流量,不参与七层协议处理,不内置 WebSocket 协议升级功能。HTTP Upgrade 握手需由后端服务自行实现,CLB 直接转发 TCP 连接至后端,由后端完成 WebSocket 协议升级。

配置步骤

  1. 登录传统型负载均衡 CLB 控制台,在左侧导航栏选择实例列表,找到目标 CLB 实例,单击监听配置向导。

  2. 在监听配置向导中,前端协议选择 TCP,填写监听端口,单击下一步。

  3. 完成服务器组配置和健康检查配置,单击提交,等待 TCP 监听创建完成。

  4. 在后端服务器上部署 WebSocket 服务,实现 HTTP Upgrade 握手流程:

    • 接收客户端发起的 HTTP Upgrade 请求(请求头含 Upgrade: websocket)

    • 返回 HTTP 101 Switching Protocols 响应,完成 WebSocket 握手

    • 握手完成后,WebSocket 数据帧通过 CLB TCP 通道透传

CLB 将 TCP 连接直接转发至后端,后端完成 WebSocket 握手后即可正常进行 WebSocket 通信,无需在 CLB 侧进行任何 WebSocket 相关配置。

TCP 监听与 HTTP/HTTPS 监听的 WebSocket 支持对比

监听类型

WebSocket 支持方式

是否需要额外配置

CLB 处理层级

说明

HTTP 监听

CLB 内置支持 WS

否

七层

CLB 自动处理 HTTP Upgrade,无需后端改造

HTTPS 监听

CLB 内置支持 WSS

否

七层

CLB 自动处理 HTTP Upgrade,无需后端改造

TCP 监听

后端服务自行实现

是(后端配置)

四层

CLB 仅透传 TCP 连接,后端需自行实现 WebSocket 握手

适用场景

  • HTTP/HTTPS 监听:适用于标准 Web 应用 WebSocket 通信场景,无需对后端服务进行特殊改造。

  • TCP 监听:适用于后端服务已具备 WebSocket 握手能力、需要自定义连接控制逻辑,或对连接管理有特殊要求的场景。

常见问题

如何使用WebSocket Secure协议?

WebSocket Secure是WebSocket协议的加密版本。

HTTPS监听默认支持WebSocket Secure协议。您在配置监听时,选择使用HTTPS监听,即可使用WebSocket Secure协议。

使用WebSocket收费吗?

WebSocket和WebSocket Secure协议不额外收取费用。

哪些地域支持WebSocket?

CLB支持的地域,都已支持WebSocket和WebSocket Secure。

相关文档

本文的示例中使用了在ECS部署Redis的简单方式,便于您做业务测试,如果Redis服务器出现问题可能造成系统单点故障。在实际生产环境中,建议您使用云数据库 Tair(兼容 Redis),提升应用系统整体高可用性。云数据库Tair(兼容Redis)如何快速入门?