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

PHP Socket 伺服器與客戶端完整實作範例(TCP 通訊)

     編輯:PHP 綜合知識

前言

Socket 程式設計是網路通訊基礎。本文提供兩個完整可執行範例:TCP Echo Server(反轉字串回傳)與 Client,涵蓋建立、綁定、監聽、接受、讀寫、關閉全流程,並說明常見問題與改進方向。

一、伺服器端 server.php

<?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";
?>

二、客戶端 client.php

<?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";
?>

三、執行測試步驟

  1. 開啟終端機 1:php server.php
  2. 開啟終端機 2:php client.php
  3. 在 client 輸入文字,按 Enter,觀察 server 回傳反轉字串。
  4. 多開幾個 client 終端機,驗證多連線支援。
  5. 在 server 終端機按 Ctrl+C 測試優雅關閉。

四、進階改進方向

1. 協議幀處理(解決 TCP 黏包/拆包)

// 簡易長度前綴協議: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);
}

2. 非阻塞 + 事件迴圈(參考 ReactPHP / Amp)

將 socket_set_nonblock() 搭配 stream_select() 或 libevent 實現單執行緒高併發。

3. 使用成熟框架

  • Swoole:C 擴展、多進程、協程、極高效能。
  • Workerman:純 PHP、多進程、易部署、社群活躍。
  • ReactPHP:事件驅動、非阻塞、元件化。

五、常見錯誤排查

錯誤原因解決
Address already in usePort 佔用或 TIME_WAITSO_REUSEADDR、等待 60 秒、換 Port
Connection refusedServer 未啟動/IP Port 錯誤/防火牆確認 server 監聽中、telnet 測試
Resource temporarily unavailable非阻塞模式下無資料正常現象,配合 select 處理
Broken pipe對端已關閉仍嘗試寫入檢查回傳值、捕捉 SIGPIPE

六、Windows 環境注意事項

  • 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 等框架,專注業務邏輯而非底層網路細節。

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