PHP ext-sockets 提供 BSD Socket 介面,支援 TCP/UDP、阻塞/非阻塞、多工。適用於:長連線服務 (WebSocket、MQTT、即時推播)、微服務 RPC、舊系統整合、高效能 CLI 工具。Web 短請求請用 HTTP/cURL/Guzzle。
<?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);
}
<?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";
<?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);
}
}
}
}
<?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");
}
}
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();
| 模型 | 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。stream_socket_enable_crypto 或 ReactPHP SecureServer。完整專案 (含單元測試、Docker Compose、Prometheus Metrics) 見 GitHub。