本文將示範如何利用 PHP 的 Swoole 擴展,快速構建一個基於 WebSocket 的多人線上聊天室,核心功能為訊息廣播。雖然未實現完整的聊天邏輯細節(如私聊、房間管理、用戶列表等),但作為入門範例足以展示 Swoole 處理高併發長連接的強大能力。
我們建立一個 WebSocket 伺服器,監聽 127.0.0.1:3999,並配置多進程模型以提升效能。
$serv = new swoole_websocket_server("127.0.0.1", 3999);
// 伺服器基礎配置
$serv->set(array(
'worker_num' => 2, // Worker 進程數
'reactor_num' => 8, // Reactor 線程數
'task_worker_num' => 1, // Task 工作進程數
'dispatch_mode' => 2, // 固定模式分發
'debug_mode' => 1, // 除錯模式
'daemonize' => true, // 守護進程模式
'log_file' => __DIR__.'/log/webs_swoole.log',
'heartbeat_check_interval' => 60, // 心跳檢測間隔
'heartbeat_idle_time' => 600 // 心跳最大閒置時間
));
// 連接建立事件
$serv->on('connect', function ($serv, $fd) {
// echo "client:$fd Connect.\n";
});
// 接收資料事件 (WebSocket 通常使用 onMessage)
$serv->on("receive", function(swoole_server $serv, $fd, $from_id, $data) {
// echo "receive#{$from_id}: receive $data \n";
});
// WebSocket 握手成功事件
$serv->on('open', function($server, $req) {
// echo "server#{$server->worker_pid}: handshake success with fd#{$req->fd}\n";
});
// 接收客戶端訊息事件
$serv->on('message', function($server, $frame) {
// echo "message: ".$frame->data."\n";
$msg = json_decode($frame->data, true);
switch ($msg['type']) {
case 'login':
$server->push($frame->fd, "歡迎歡迎~");
break;
default:
break;
}
// 將發送者 fd 附加到訊息中,交由 Task 工作進程處理廣播邏輯
$msg['fd'] = $frame->fd;
$server->task($msg);
});
// Worker 進程啟動事件
$serv->on("workerstart", function($server, $workerid) {
// echo "workerstart: ".$workerid."\n";
});
// 註冊 Task 回調
$serv->on("task", "on_task");
$serv->on("finish", function($serv, $task_id, $data) {
return;
});
// 連接關閉事件
$serv->on('close', function($server, $fd, $from_id) {
// echo "connection close: ".$fd."\n";
});
$serv->start();
// Task 任務處理函數:負責廣播訊息給所有連接
function on_task($serv, $task_id, $from_id, $data) {
switch ($data['type']) {
case 'login':
$send_msg = "說:我來了~";
break;
default:
$send_msg = "說:{$data['msg']['speak']}";
break;
}
// 遍歷所有連接進行廣播
foreach ($serv->connections as $conn) {
if ($conn != $data['fd']) {
// 遊客名稱加上 fd 區分
if (strpos($data['msg']['name'], "遊客") === 0) {
$name = $data['msg']['name'] . "_" . $data['fd'];
} else {
$name = $data['msg']['name'];
}
} else {
$name = "我";
}
$serv->push($conn, $name . $send_msg);
}
return;
}
function on_finish($serv, $task_id, $data) {
return true;
}
簡單的 WebSocket 客戶端頁面,包含暱稱輸入、訊息發送、聊天記錄顯示區域。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>WebSocket 聊天測試</title>
<script src="jquery-1.12.0.min.js"></script>
</head>
<body>
<h2>WebSocket Test</h2>
暱稱:<input type="text" id="name" size="5" value="遊客"/>
<input type="text" id="content" placeholder="輸入訊息">
<button onclick="speak_to_all()">發送</button>
<br/><br/>
<textarea id="message" rows="10" cols="50" readonly></textarea>
<div id="output"></div>
</body>
<script>
var wsUri = "ws://127.0.0.1:3999/";
var output;
var websocket;
function init() {
output = document.getElementById("output");
testWebSocket();
}
function testWebSocket() {
websocket = new WebSocket(wsUri);
websocket.onopen = function(evt) { onOpen(evt) };
websocket.onclose = function(evt) { onClose(evt) };
websocket.onmessage = function(evt) { onMessage(evt) };
websocket.onerror = function(evt) { onError(evt) };
}
function get_speak_msg() {
var name = document.getElementById("name").value;
var speak = document.getElementById("content").value;
return JSON.stringify({name: name, speak: speak});
}
function pack_msg(type, msg) {
return JSON.stringify({type: type, msg: JSON.parse(msg)});
}
function onOpen(evt) {
append_speak("已連接到伺服器.........");
var speak_msg = get_speak_msg();
var send_msg = pack_msg("login", speak_msg);
doSend(send_msg);
}
function onClose(evt) {
append_speak("連線已斷開!");
}
function onMessage(evt) {
append_speak(evt.data);
}
function onError(evt) {
console.error(evt);
alert("發生錯誤: " + evt.data);
}
function doSend(message) {
websocket.send(message);
}
function append_speak(new_msg) {
var textarea = document.getElementById("message");
textarea.value += new_msg + "\n";
textarea.scrollTop = textarea.scrollHeight;
}
function speak_to_all() {
var content = document.getElementById("content").value;
if (content === "") return;
var send_msg = pack_msg("speak", get_speak_msg());
doSend(send_msg);
document.getElementById("content").value = "";
}
init();
</script>
</html>
server.php,前端代碼保存為 index.html。php server.php 啟動服務。index.html (建議通過本地 Web 服務器如 Nginx/Apache 訪問,避免跨域問題)。這個簡單的 Demo 為進一步開發功能完善的即時通訊系統奠定了基礎,後續可擴展用戶認證、房間分組、離線訊息、歷史記錄等功能。