全部產品
Search
文件中心

Server Load Balancer:CLB使用WebSocket協議實現資訊即時推送

更新時間:Jul 25, 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,用戶端和伺服器之間得以建立一種持久、低延遲的串連,提升了資料交換效率。

WebSocket通過資料幀進行通訊,它有自己的幀協議格式,頭資訊更簡潔,資料可以作為文本或二進位傳輸。這種方式減少了持久串連上額外的協議開銷,允許更高效的網路互動,能夠在節省伺服器資源和頻寬的同時,提供更流暢的即時互動體驗。

關於WebSocket協議的更多資訊,可參考官方文檔The WebSocket Protocol

WebSocket應用情境

WebSocket主要適用於需要快速、即時的雙向通訊的應用情境,例如AI應用、線上聊天室、即時通知系統、多人線上遊戲、即時市場資訊推送等。

情境樣本

某公司需要在阿里雲上部署Web線上聊天應用,使用者可以通過訪問網域名稱來接入後端服務進行即時交流。該應用由於其即時通訊的特性,要求使用者之間的資訊傳遞必須具備低延遲、高效率和雙向即時的特點。

該公司的網站服務面臨的挑戰是高並發與長串連管理。隨著使用者數量的增長,傳統的HTTP模式無法滿足大量使用者同時線上並保持即時通訊的需求,因為每次通訊都需要重建立立串連,這會導致伺服器壓力劇增且效能低下。

在這個情境下,選用CLB結合WebSocket協議,能夠有效解決高並發下的長串連管理問題。通過後端伺服器組多伺服器部署WebSocket應用程式,並使用Redis進行訊息同步,確保服務的高可用性,從而為線上聊天室應用提供了一個可靠、高效的即時資訊推送解決方案。

注意事項

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個Elastic Compute Service01、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執行個體為私網類型,需先為其綁定Elastic IP Address(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)如何快速入門?