PHP Socket 程式設計是理解網絡通訊本質、構建高效能長連線服務 (WebSocket、即時推送、遊戲伺服器) 的基石。本文提供標準阻塞式 Server/Client 完整代碼、關鍵 API 解析、常見坑點 (CLI 模式、超時、緩衝區)、以及進階非阻塞多路復用架構指引。
master = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
if (!$this->master) {
die("socket_create failed: " . socket_strerror(socket_last_error()) . "\n");
}
// 2. 設定選項:地址重用 (避免 TIME_WAIT 阻礙重啟)
socket_set_option($this->master, SOL_SOCKET, SO_REUSEADDR, 1);
// 3. 綁定地址
if (!socket_bind($this->master, $this->host, $this->port)) {
die("socket_bind failed: " . socket_strerror(socket_last_error($this->master)) . "\n");
}
// 4. 監聽 (backlog=5)
if (!socket_listen($this->master, 5)) {
die("socket_listen failed: " . socket_strerror(socket_last_error($this->master)) . "\n");
}
$this->clients[] = $this->master;
echo "Server started on {$this->host}:{$this->port}\n";
// 5. 主事件循環
while (true) {
$read = $this->clients; // 複製陣列供 select 修改
// 6. 多路複用等待可讀 (阻塞直到有活動)
$num = socket_select($read, $write = null, $except = null, null);
if ($num === false) {
echo "socket_select failed: " . socket_strerror(socket_last_error()) . "\n";
continue;
}
// 7. 處理每個可讀 Socket
foreach ($read as $socket) {
if ($socket === $this->master) {
// 新連線
$client = socket_accept($this->master);
if ($client) {
$this->clients[] = $client;
$ip = $port = null;
socket_getpeername($client, $ip, $port);
echo "New client: {$ip}:{$port} (Total: " . count($this->clients) . ")\n";
// 歡迎訊息
socket_write($client, "Welcome! Type 'quit' to exit.\n");
}
} else {
// 現有客戶端資料
$data = @socket_read($socket, 8192, PHP_NORMAL_READ);
if ($data === false || $data === '') {
// 斷線
$index = array_search($socket, $this->clients);
if ($index !== false) unset($this->clients[$index]);
socket_close($socket);
echo "Client disconnected. (Total: " . count($this->clients) . ")\n";
} else {
// 回顯處理
$data = trim($data);
echo "Received: {$data}\n";
if (strtolower($data) === 'quit') {
socket_write($socket, "Bye!\n");
socket_close($socket);
$index = array_search($socket, $this->clients);
if ($index !== false) unset($this->clients[$index]);
} else {
socket_write($socket, "Echo: {$data}\n");
}
}
}
}
}
}
}
$server = new SocketServer();
$server->start();
";
$input = trim(fgets(STDIN));
if ($input === '') continue;
socket_write($socket, $input . "\n");
if (strtolower($input) === 'quit') break;
$response = socket_read($socket, 8192, PHP_NORMAL_READ);
echo $response;
}
socket_close($socket);
echo "Disconnected\n";
| 函數 | 用途 | 關鍵參數 |
|---|---|---|
| socket_create | 建立 Socket | AF_INET (IPv4), SOCK_STREAM (TCP), SOL_TCP |
| socket_bind | 綁定 IP:Port | |
| socket_listen | 進入監聽狀態 | backlog (等待隊列長度) |
| socket_accept | 接受新連線 (阻塞) | 返回新 Socket 資源 |
| socket_read | 讀取資料 | length, type (PHP_NORMAL_READ 讀到 \n/\r) |
| socket_write | 發送資料 | 返回寫入位元組數 |
| socket_close | 關閉 Socket | |
| socket_select | 多路複用 (核心) | read/write/except 陣列引用, timeout (秒) |
| socket_set_option | 設定選項 | SO_REUSEADDR, SO_RCVBUF, SO_SNDBUF |
| socket_getpeername | 獲取對端 IP:Port | |
| socket_set_nonblock | 設定非阻塞模式 | 配合 select 使用 |
max_execution_time 限制,長連線會超時php server.php、nohup php server.php &、systemd 服務$read = $this->clients 複製,否則原陣列被修改導致邏輯錯誤socket_read 可能讀到半包/多包\n)、固定長度PHP_NORMAL_READ:讀到 \n 或 \r 停止 (適合文本協議)PHP_BINARY_READ:讀指定長度 (適合二進位協議)select 受限於 FD_SETSIZE (通常 1024)stream_select + 非阻塞 + 多進程 Worker (參考 Workerman、Swoole)// 概念架構,生產建議直接使用 Workerman/Swoole
// Master 進程:監聽端口、accept 連線、分發給 Worker
// Worker 進程:處理具體業務邏輯、維持長連線
// 1. Master 建立監聽 Socket,設定非阻塞
$master = stream_socket_server("tcp://0.0.0.0:9000", $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN);
stream_set_blocking($master, 0);
// 2. Fork 多個 Worker 進程
$workers = [];
for ($i = 0; $i < 4; $i++) {
$pid = pcntl_fork();
if ($pid === 0) {
// Worker 邏輯
workerLoop($master);
exit;
}
$workers[] = $pid;
}
// 3. Master 只負責 accept 並傳遞 Socket (需 Unix Domain Socket 傳遞 fd)
// 簡化版:Worker 共享監聽 Socket (SO_REUSEPORT, Linux 3.9+)
// 實際生產請使用成熟框架:Workerman、Swoole、RoadRunner
| 需求 | 推薦方案 |
|---|---|
| 簡單 TCP 服務、低併發 | 原生 Socket + CLI + systemd 守護 |
| WebSocket 即時推送 | Workerman (純 PHP)、Swoole (C擴展、極高效能) |
| 高併發長連線 (萬級+) | Swoole / OpenSwoole / RoadRunner |
| 微服務 RPC | gRPC (PHP 擴展)、Thrift、JSON-RPC over TCP |
| 異步任務佇列 | Redis/RabbitMQ + Worker 消費 |
php server.phpphp client.phptelnet 127.0.0.1 9000 或 nc 127.0.0.1 9000掌握 Socket 基礎 API 與阻塞/非阻塞模型,是進入高效能網絡程式設計、即時通訊、微服務通訊的必經之路。