PHP 本身無執行緒,但可透過非阻塞 I/O 多路複用實現「類多執行緒」的併發效果。本文以檢查多台主機 HTTP 服務為例,對比阻塞/非阻塞寫法,詳細講解 stream_socket_client + stream_select 實作。
$hosts = ['host1.example.com', 'host2.example.com', 'host3.example.com'];
$timeout = 15;
$status = [];
foreach ($hosts as $host) {
$errno = $errstr = '';
$fp = fsockopen($host, 80, $errno, $errstr, $timeout);
if (!$fp) { $status[$host] = "Failed: $errstr"; continue; }
fwrite($fp, "HEAD / HTTP/1.0\r\nHost: $host\r\n\r\n");
$data = '';
while (!feof($fp)) { $data .= fread($fp, 8192); }
fclose($fp);
$status[$host] = $data ?: 'Connected (no response)';
}
// 總耗時 ≈ N * timeout
<?php
$hosts = ['host1.example.com', 'host2.example.com', 'host3.example.com'];
$timeout = 15;
$status = [];
$sockets = [];
// 1. 發起所有非阻塞連線
foreach ($hosts as $id => $host) {
$fp = stream_socket_client(
"tcp://{$host}:80",
$errno, $errstr, $timeout,
STREAM_CLIENT_CONNECT | STREAM_CLIENT_ASYNC_CONNECT
);
if ($fp) {
$sockets[$id] = $fp;
$status[$id] = 'connecting';
} else {
$status[$id] = "failed: $errstr";
}
}
// 2. 事件迴圈
while ($sockets) {
$read = $write = $sockets;
$n = stream_select($read, $write, $except = null, $timeout);
if ($n === false) { break; } // error
if ($n === 0) { // timeout
foreach ($sockets as $id => $fp) {
$status[$id] = 'timeout';
fclose($fp);
}
break;
}
// 可寫:連線成功,發送請求
foreach ($write as $fp) {
$id = array_search($fp, $sockets, true);
fwrite($fp, "HEAD / HTTP/1.0\r\nHost: {$hosts[$id]}\r\n\r\n");
$status[$id] = 'waiting_response';
}
// 可讀:收到回應或連線失敗
foreach ($read as $fp) {
$id = array_search($fp, $sockets, true);
$chunk = fread($fp, 8192);
if ($chunk === '' || $chunk === false) {
// EOF 或錯誤
if ($status[$id] === 'connecting') $status[$id] = 'connect_failed';
fclose($fp);
unset($sockets[$id]);
} else {
$status[$id] .= $chunk;
}
}
}
// 3. 輸出結果
foreach ($hosts as $id => $host) {
echo "Host: $host\nStatus: " . ($status[$id] ?? 'unknown') . "\n\n";
}
?>
STREAM_CLIENT_ASYNC_CONNECT:stream_socket_client 立即回傳資源,不等待 TCP 三向交握完成。stream_select:封裝系統 select(2)/poll(2)/epoll(7),單執行緒監聽多個 fd 的讀/寫/例外事件。connecting → waiting_response → reading → done,根據事件推進。$client->sendAsync() 返回 Promise,支援並發、重試、中介軟體。Co::sleep、CoHttp\Client 寫法同步、效能極高。掌握 stream_select 核心模型有助於理解所有非阻塞框架的底層原理。實務上優先選擇成熟庫(Guzzle、Swoole),僅在極簡環境或教學場景手寫事件迴圈。