Socket 程式設計是網路通訊基礎。本文提供兩個完整可執行範例:TCP Echo Server(反轉字串回傳)與 Client,涵蓋建立、綁定、監聽、接受、讀寫、關閉全流程,並說明常見問題與改進方向。
<?php
// server.php - 在終端機執行:php server.php
declare(ticks=1); // 讓訊號處理生效
$host = '0.0.0.0';
$port = 9501;
$maxClients = 10;
// 1. 建立 Socket
$server = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
if (!$server) die("socket_create 失敗: " . socket_strerror(socket_last_error()) . "\n");
// 2. 設定選項:地址重用(避免重啟時 Address already in use)
socket_set_option($server, SOL_SOCKET, SO_REUSEADDR, 1);
// 3. 綁定地址
if (!socket_bind($server, $host, $port)) {
die("socket_bind 失敗: " . socket_strerror(socket_last_error($server)) . "\n");
}
// 4. 開始監聽
if (!socket_listen($server, $maxClients)) {
die("socket_listen 失敗: " . socket_strerror(socket_last_error($server)) . "\n");
}
echo "Server started on $host:$port, waiting for connections...\n";
// 訊號處理:優雅關閉
$shutdown = false;
pcntl_signal(SIGTERM, function() use (&$shutdown) { $shutdown = true; });
pcntl_signal(SIGINT, function() use (&$shutdown) { $shutdown = true; });
$clients = [$server];
while (!$shutdown) {
$read = $clients;
$write = $except = null;
// 多路複用:等待任一 Socket 可讀
$num = @socket_select($read, $write, $except, 1); // 1秒 timeout
if ($num === false) {
echo "socket_select 錯誤: " . socket_strerror(socket_last_error()) . "\n";
continue;
}
if ($num === 0) continue; // timeout,檢查 shutdown flag
foreach ($read as $socket) {
if ($socket === $server) {
// 新連線
$client = @socket_accept($server);
if ($client) {
$clients[] = $client;
$ip = socket_getpeername($client, $addr, $port);
echo "[+] Client connected: $addr:$port\n";
}
} else {
// 資料到達
$data = @socket_read($socket, 4096, PHP_NORMAL_READ);
if ($data === false || $data === "") {
// 斷線
$idx = array_search($socket, $clients);
if ($idx !== false) unset($clients[$idx]);
$addr = socket_getpeername($socket, $a, $p) ? "$a:$p" : 'unknown';
echo "[-] Client disconnected: $addr\n";
socket_close($socket);
} else {
// 處理邏輯:反轉字串並加上換行
$response = strrev(trim($data)) . "\n";
socket_write($socket, $response, strlen($response));
echo "[↔] Echo to client: " . trim($response) . "\n";
}
}
}
}
// 清理
foreach ($clients as $sock) socket_close($sock);
echo "Server shutdown.\n";
?>
<?php
// client.php - 在另一個終端機執行:php client.php
$host = '127.0.0.1';
$port = 9501;
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
if (!$socket) die("socket_create 失敗\n");
echo "Connecting to $host:$port...\n";
if (!socket_connect($socket, $host, $port)) {
die("socket_connect 失敗: " . socket_strerror(socket_last_error($socket)) . "\n");
}
echo "Connected!\n";
// 互動模式:從 stdin 讀取輸入發送
while (true) {
echo "> ";
$input = trim(fgets(STDIN));
if ($input === 'quit' || $input === 'exit') break;
if ($input === '') continue;
socket_write($socket, $input . "\n", strlen($input) + 1);
$response = socket_read($socket, 4096, PHP_NORMAL_READ);
if ($response === false || $response === "") {
echo "Server disconnected.\n";
break;
}
echo "Server: " . trim($response) . "\n";
}
socket_close($socket);
echo "Bye!\n";
?>
php server.phpphp client.phpCtrl+C 測試優雅關閉。// 簡易長度前綴協議:4 bytes 長度 + JSON 資料
function sendFrame($socket, $data) {
$json = json_encode($data);
$len = strlen($json);
socket_write($socket, pack('N', $len) . $json, $len + 4);
}
function recvFrame($socket) {
$header = socket_read($socket, 4, PHP_BINARY_READ);
if (strlen($header) < 4) return null;
$len = unpack('N', $header)[1];
$body = '';
while (strlen($body) < $len) {
$chunk = socket_read($socket, $len - strlen($body), PHP_BINARY_READ);
if ($chunk === false || $chunk === '') return null;
$body .= $chunk;
}
return json_decode($body, true);
}
將 socket_set_nonblock() 搭配 stream_select() 或 libevent 實現單執行緒高併發。
| 錯誤 | 原因 | 解決 |
|---|---|---|
| Address already in use | Port 佔用或 TIME_WAIT | SO_REUSEADDR、等待 60 秒、換 Port |
| Connection refused | Server 未啟動/IP Port 錯誤/防火牆 | 確認 server 監聽中、telnet 測試 |
| Resource temporarily unavailable | 非阻塞模式下無資料 | 正常現象,配合 select 處理 |
| Broken pipe | 對端已關閉仍嘗試寫入 | 檢查回傳值、捕捉 SIGPIPE |
pcntl_signal 不支援,去除訊號處理區塊,用 register_shutdown_function 清理。socket_select 最大監聽 Socket 數受限(預設 512),大量連線需遷移 Linux。php.exe CLI 執行,不要用瀏覽器存取(會超時)。本文提供可直接執行的 TCP Server/Client 範例,展示 PHP Socket 核心流程:create → bind → listen → accept → read/write → close。理解此基礎後,實務開發強烈建議使用 Swoole/Workerman 等框架,專注業務邏輯而非底層網路細節。