使用 Swoole 開發 WebSocket 長連線服務(如即時聊天、推播系統)時,onConnect 與 onClose 回調是實現上線廣播、下線清理、連線統計的關鍵。但許多開發者發現:明明註冊了回調,卻完全不被觸發。本文深入剖析根本原因——dispatch_mode 工作進程分發模式,並提供解決方案與最佳實踐。
swoole_websocket_server 建立服務。on('open', ...)、on('close', ...) 回調。on('message') 或 on('request') 正常運作。Swoole 伺服器提供多種 dispatch_mode(資料分發模式)決定連線由哪個 Worker 處理:
| 模式值 | 名稱 | 特性 | onConnect/onClose |
|---|---|---|---|
| 1 | 輪詢 | 新連線平均分發給各 Worker | 不觸發 |
| 2 | 固定模式 | 根據 FD 取模固定分配 | 觸發 |
| 3 | 搶佔模式 | 主進程投遞,Worker 搶佔處理 | 不觸發 |
| 4 | IP 分配 | 同一 IP 固定分配給同一 Worker | 觸發 |
| 5 | UID 分配 | 綁定 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 乾脆禁用回調。
$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();
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/onReceive,請使用 on('open')/on('message')。$server->exist($fd)。Swoole WebSocket 伺服器的 onConnect/onClose 回調僅在固定分發模式(dispatch_mode=2/4/5)下生效。開發長連線服務時,務必在 set() 中明確設定 'dispatch_mode' => 2,並善用 swoole_table 管理連線狀態,構建穩定高效的即時通訊系統。