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

PHP Socket 程式設計完整實戰:TCP 伺服器/客戶端、非阻塞 I/O、多進程與 ReactPHP 非同步框架

     編輯:PHP 相關知識

前言:Socket 是網路通訊的基石

PHP ext-sockets 提供 BSD Socket 介面,支援 TCP/UDP、阻塞/非阻塞、多工。適用於:長連線服務 (WebSocket、MQTT、即時推播)、微服務 RPC、舊系統整合、高效能 CLI 工具。Web 短請求請用 HTTP/cURL/Guzzle。

基礎阻塞式 TCP 伺服器 (單進程、循序處理)

<?php
// server.php
$host = '127.0.0.1';
$port = 9501;

$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_set_option($socket, SOL_SOCKET, SO_REUSEADDR, 1);
socket_bind($socket, $host, $port);
socket_listen($socket, 128); // backlog

echo "Server listening on $host:$port\n";

while (true) {
    $client = socket_accept($socket); // 阻塞等待連線
    if ($client === false) continue;

    $peer = socket_getpeername($client, $addr, $port);
    echo "Client connected: $addr:$port\n";

    // 讀取資料 (阻塞直到 \n 或 EOF)
    $data = socket_read($client, 8192, PHP_NORMAL_READ);
    if ($data === false) {
        socket_close($client);
        continue;
    }
    $request = trim($data);
    echo "Received: $request\n";

    // 簡單回應
    $response = "HTTP/1.1 200 OK\r\nContent-Length: 13\r\n\r\nHello, World!";
    socket_write($client, $response, strlen($response));

    socket_close($client);
}

基礎阻塞式 TCP 客戶端

<?php
// client.php
$host = '127.0.0.1';
$port = 9501;

$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
$connected = socket_connect($socket, $host, $port);
if (!$connected) {
    die("Connect failed: " . socket_strerror(socket_last_error()));
}

$request = "GET / HTTP/1.1\r\nHost: $host\r\nConnection: close\r\n\r\n";
socket_write($socket, $request, strlen($request));

$response = '';
while ($buf = socket_read($socket, 8192, PHP_NORMAL_READ)) {
    $response .= $buf;
}
socket_close($socket);

echo "Response:\n$response\n";

進階:非阻塞 I/O + select() 多工 (單進程並發)

<?php
// async_server.php
$master = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_set_option($master, SOL_SOCKET, SO_REUSEADDR, 1);
socket_bind($master, '127.0.0.1', 9502);
socket_listen($master, 512);
socket_set_nonblock($master); // 關鍵:非阻塞

$clients = [$master => $master]; // fd => resource
$buffers = [];

echo "Async server started\n";

while (true) {
    $read = $clients;
    $write = $except = null;

    // select() 監控可讀/可寫/例外 fd
    $num = @socket_select($read, $write, $except, 1); // 1 秒 timeout
    if ($num === false) continue;

    foreach ($read as $fd => $sock) {
        if ($sock === $master) {
            // 新連線
            $client = socket_accept($master);
            if ($client !== false) {
                socket_set_nonblock($client);
                $clients[(int)$client] = $client;
                $buffers[(int)$client] = '';
                echo "New client: " . (int)$client . "\n";
            }
        } else {
            // 資料讀取
            $data = @socket_read($sock, 8192, PHP_BINARY_READ);
            if ($data === false || $data === '') {
                // 斷線
                $fdKey = (int)$sock;
                socket_close($sock);
                unset($clients[$fdKey], $buffers[$fdKey]);
                echo "Client $fdKey disconnected\n";
                continue;
            }
            $buffers[(int)$sock] .= $data;

            // 簡單協定:以 \n 結束
            if (str_ends_with($buffers[(int)$sock], "\n")) {
                $request = trim($buffers[(int)$sock]);
                $buffers[(int)$sock] = '';
                $response = "Echo: $request\n";
                @socket_write($sock, $response);
            }
        }
    }
}

生產級架構:多進程 Worker (Pre-fork 模型)

<?php
// prefork_server.php
$workerCount = 4;
$master = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_set_option($master, SOL_SOCKET, SO_REUSEADDR, 1);
socket_bind($master, '127.0.0.1', 9503);
socket_listen($master, 1024);

$pids = [];
for ($i = 0; $i < $workerCount; $i++) {
    $pid = pcntl_fork();
    if ($pid === -1) die("Fork failed");
    if ($pid === 0) {
        // Child: 共享 master socket,各自 accept
        runWorker($master);
        exit(0);
    }
    $pids[] = $pid;
}

// Parent: 監控子進程
pcntl_signal(SIGCHLD, function($signo) use (&$pids, $master, $workerCount) {
    while (($pid = pcntl_waitpid(-1, $status, WNOHANG)) > 0) {
        echo "Worker $pid exited, restarting...\n";
        $newPid = pcntl_fork();
        if ($newPid === 0) { runWorker($master); exit(0); }
        $pids[] = $newPid;
    }
});

while (true) { sleep(1); } // Keep parent alive

function runWorker($master) {
    while (true) {
        $client = socket_accept($master); // Kernel 負載均衡
        if ($client === false) continue;
        handleRequest($client);
        socket_close($client);
    }
}

function handleRequest($client) {
    $data = socket_read($client, 8192, PHP_NORMAL_READ);
    if ($data) {
        socket_write($client, "OK: " . trim($data) . "\n");
    }
}

現代非同步框架:ReactPHP (事件迴圈、Promise、串流)

composer require react/socket react/stream

use React\EventLoop\Factory;
use React\Socket\Server;

$loop = Factory::create();
$server = new Server('127.0.0.1:9504', $loop);

$server->on('connection', function (React\Socket\ConnectionInterface $conn) {
    $conn->write("Welcome!\n");
    $conn->on('data', function ($data) use ($conn) {
        $conn->write("You said: $data");
    });
    $conn->on('close', function () { echo "Client disconnected\n"; });
});

echo "ReactPHP server running on 127.0.0.1:9504\n";
$loop->run();

效能對比 (10,000 並發連線、每連線 1 次請求/回應)

模型QPS記憶體CPU複雜度
阻塞單進程~500低低簡單
非阻塞 select~8,000低中中
Pre-fork (4 workers)~12,000中高中高
ReactPHP (EventLoop)~15,000低中高
Swoole / OpenSwoole~50,000+中高高 (擴充套件)

常見坑點

  • 阻塞模式下 socket_read 參數:PHP_NORMAL_READ 讀到 \n 或 \r\n;PHP_BINARY_READ 讀指定長度或 EOF。
  • 非阻塞 socket_read 回傳 '' 非 false:表示無資料可讀 (EAGAIN),非斷線。斷線回傳 false 或 '' 視平台。
  • 大量連線 EMFILE (Too many open files):ulimit -n 65535、sysctl net.core.somaxconn=65535。
  • TCP 粘包/拆包:應用層必須自訂協定 (長度前綴、分隔符、固定長度)。
  • SSL/TLS:使用 stream_socket_enable_crypto 或 ReactPHP SecureServer。

完整專案 (含單元測試、Docker Compose、Prometheus Metrics) 見 GitHub。

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