首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

Swoole WebSocket 伺服器 onConnect/onClose 回調失效?dispatch_mode 陷阱詳解

     編輯:PHP 相關知識

Swoole WebSocket 伺服器 onConnect/onClose 回調失效?dispatch_mode 陷阱詳解

使用 Swoole 開發 WebSocket 長連線服務(如即時聊天、推播系統)時,onConnect 與 onClose 回調是實現上線廣播、下線清理、連線統計的關鍵。但許多開發者發現:明明註冊了回調,卻完全不被觸發。本文深入剖析根本原因——dispatch_mode 工作進程分發模式,並提供解決方案與最佳實踐。

現象回顧

  • 使用 swoole_websocket_server 建立服務。
  • 註冊 on('open', ...)、on('close', ...) 回調。
  • 客戶端連線/斷線,伺服器日誌無任何輸出,回調函式未執行。
  • on('message') 或 on('request') 正常運作。

根本原因:dispatch_mode 設定

Swoole 伺服器提供多種 dispatch_mode(資料分發模式)決定連線由哪個 Worker 處理:

模式值名稱特性onConnect/onClose
1輪詢新連線平均分發給各 Worker不觸發
2固定模式根據 FD 取模固定分配觸發
3搶佔模式主進程投遞,Worker 搶佔處理不觸發
4IP 分配同一 IP 固定分配給同一 Worker觸發
5UID 分配綁定 UID 固定分配觸發

關鍵結論: 僅當 dispatch_mode = 2, 4, 5 時,onConnect 與 onClose 事件會在對應的 Worker 進程中觸發。模式 1(輪詢)與 3(搶佔)會禁用這兩個回調,這在 Swoole 1.7.16 版本更新公告中明確註記:設置dispatch_mode = 1, 3 後關閉onClose/onConnect事件回調。

為什麼固定模式才支援回調?

固定模式下,每個客戶端連線固定由同一 Worker 處理,Worker 可在記憶體中維護該連線的私有狀態(如用戶資料、狀態機、快取)。連線建立/斷開時,Worker 需執行初始化/清理邏輯,因此必須觸發 onConnect/onClose。反之,輪詢/搶佔模式下連線可能漂移至不同 Worker,無法保證狀態一致性,故 Swoole 乾脆禁用回調。

解決方案

  1. 明確設定 dispatch_mode = 2(或 4/5):
    $server = new Swoole\WebSocket\Server("0.0.0.0", 9501);
    $server->set([
        'worker_num'      => 4,
        'dispatch_mode'   => 2, // 關鍵:固定模式
    ]);
    $server->on('open', function ($server, $request) {
        echo "Client {$request->fd} connected.\n";
    });
    $server->on('close', function ($server, $fd) {
        echo "Client {$fd} disconnected.\n";
    });
    $server->start();
  2. 避免在 onConnect/onClose 執行耗時操作:這兩個回調在 Worker 進程中同步執行,阻塞會影響該 Worker 處理其他連線。耗時任務(如資料庫寫入、外部 API 呼叫)應投遞至 Task Worker 或使用非同步客戶端。
  3. WebSocket 協議特殊性:swoole_websocket_server 繼承自 swoole_http_server,onReceive 同樣不會觸發,請改用 on('message') 處理資料幀。

進階應用:上線/下線廣播實作

$server->on('open', function ($server, $request) {
    $fd = $request->fd;
    // 1. 綁定 UID(可選,便於後續精準推播)
    $server->bind($fd, $fd); // 簡單綁定 fd 為 uid
    
    // 2. 記錄線上用戶(使用 Table 共享記憶體)
    $server->onlineUsers->set($fd, ['fd' => $fd, 'login_time' => time()]);
    
    // 3. 廣播上線通知
    $msg = json_encode(['type' => 'join', 'fd' => $fd, 'count' => $server->onlineUsers->count()]);
    foreach ($server->connections as $conn_fd) {
        if ($conn_fd !== $fd) {
            $server->push($conn_fd, $msg);
        }
    }
});

$server->on('close', function ($server, $fd) {
    // 1. 移除線上記錄
    $server->onlineUsers->del($fd);
    
    // 2. 廣播下線通知
    $msg = json_encode(['type' => 'leave', 'fd' => $fd, 'count' => $server->onlineUsers->count()]);
    foreach ($server->connections as $conn_fd) {
        $server->push($conn_fd, $msg);
    }
});

搭配 swoole_table 實現跨進程共享線上用戶列表,實現高效廣播。

常見誤區與避坑指南

  • 誤以為 onConnect 不觸發是 Bug:實為架構設計決策,請查閱版本更新日誌。
  • 混淆 swoole_server 與 swoole_websocket_server 回調:WebSocket 伺服器不觸發 onConnect/onReceive,請使用 on('open')/on('message')。
  • 忽略 dispatch_mode 預設值:預設為 2(固定模式),但若显式設為 1 或 3 即會失效。
  • 在 onClose 中使用 $server->push() 向已斷開的 FD 發送:會報錯,請先檢查 $server->exist($fd)。

總結

Swoole WebSocket 伺服器的 onConnect/onClose 回調僅在固定分發模式(dispatch_mode=2/4/5)下生效。開發長連線服務時,務必在 set() 中明確設定 'dispatch_mode' => 2,並善用 swoole_table 管理連線狀態,構建穩定高效的即時通訊系統。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved