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

PHP Socket 客戶端與服務端完整實作:從阻塞 IO 到非阻塞多進程架構

     編輯:PHP 綜合知識

PHP Socket 程式設計是理解網絡通訊本質、構建高效能長連線服務 (WebSocket、即時推送、遊戲伺服器) 的基石。本文提供標準阻塞式 Server/Client 完整代碼、關鍵 API 解析、常見坑點 (CLI 模式、超時、緩衝區)、以及進階非阻塞多路復用架構指引。

一、阻塞式 Socket 服務端 (單進程循環)

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();

二、阻塞式 Socket 客戶端

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

三、關鍵 API 快速查閱

函數用途關鍵參數
socket_create建立 SocketAF_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 使用

四、⚠️ 關鍵坑點與解決

  1. 必須在 CLI 模式運行
    • Web (CGI/FPM) 模式有 max_execution_time 限制,長連線會超時
    • 命令列:php server.php、nohup php server.php &、systemd 服務
  2. socket_select 修改傳入陣列
    • 傳入前必須 $read = $this->clients 複製,否則原陣列被修改導致邏輯錯誤
  3. 緩衝區與訊息邊界
    • TCP 是串流無邊界,socket_read 可能讀到半包/多包
    • 應用層需定義協議:長度前綴、分隔符 (\n)、固定長度
  4. PHP_NORMAL_READ vs PHP_BINARY_READ
    • PHP_NORMAL_READ:讀到 \n 或 \r 停止 (適合文本協議)
    • PHP_BINARY_READ:讀指定長度 (適合二進位協議)
  5. 大量連線效能瓶頸
    • 單進程 select 受限於 FD_SETSIZE (通常 1024)
    • 進階:stream_select + 非阻塞 + 多進程 Worker (參考 Workerman、Swoole)

五、進階架構:非阻塞多進程 (Workerman 模式)

// 概念架構,生產建議直接使用 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
微服務 RPCgRPC (PHP 擴展)、Thrift、JSON-RPC over TCP
異步任務佇列Redis/RabbitMQ + Worker 消費

七、測試驗證

  1. 終端 1:php server.php
  2. 終端 2:php client.php
  3. 終端 3:telnet 127.0.0.1 9000 或 nc 127.0.0.1 9000
  4. 多開幾個 Client 驗證併發

掌握 Socket 基礎 API 與阻塞/非阻塞模型,是進入高效能網絡程式設計、即時通訊、微服務通訊的必經之路。

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