方案一:cURL Multi 並發 HTTP 請求
核心代碼
function streamSocketConcurrent(array $targets, int $timeout = 10) {
$sockets = [];
$results = [];
foreach ($targets as $key => $target) {
$host = $target['host'];
$port = $target['port'] ?? 80;
$path = $target['path'] ?? '/';
$method = $target['method'] ?? 'GET';
$body = $target['body'] ?? '';
$headers = $target['headers'] ?? [];
$socket = stream_socket_client(
"$host:$port", $errno, $errstr, $timeout,
STREAM_CLIENT_ASYNC_CONNECT | STREAM_CLIENT_CONNECT
);
if (!$socket) {
$results[$key] = ['error' => "Connect failed: $errstr ($errno)"];
continue;
}
stream_set_blocking($socket, 0); // 非阻塞
stream_set_timeout($socket, $timeout);
// 構建 HTTP 請求
$req = "$method $path HTTP/1.1\r\nHost: $host\r\n";
foreach ($headers as $h) $req .= "$h\r\n";
if ($body) {
$req .= "Content-Length: " . strlen($body) . "\r\n";
}
$req .= "Connection: close\r\n\r\n$body";
fwrite($socket, $req);
$sockets[$key] = $socket;
}
// 事件循環
$read = $sockets;
$write = $except = null;
$start = microtime(true);
while (count($read) > 0 && (microtime(true) - $start) < $timeout) {
$rv = stream_select($read, $write, $except, 0, 200000); // 200ms
if ($rv === false) break;
if ($rv === 0) continue; // 超時繼續
foreach ($read as $socket) {
$key = array_search($socket, $sockets);
$data = fread($socket, 8192);
if (strlen($data) === 0) {
// 連接關閉
fclose($socket);
unset($sockets[$key], $read[array_search($socket, $read)]);
} else {
$results[$key] = ($results[$key] ?? '') . $data;
}
}
$read = array_values($sockets); // 重置索引
}
// 清理剩餘
foreach ($sockets as $socket) fclose($socket);
return $results;
}
優缺點
| 優點 | 缺點 |
| 底層 Socket 控制、無 cURL 開銷 | 需手寫 HTTP 協議解析(Chunked、Gzip、重定向) |
| 可用於任意 TCP 協議 | 單進程、無法利用多核 CPU |
| 內存佔用極低 | 錯誤處理複雜、調試困難 |
方案三:PCNTL 多進程(真正並行)
核心代碼:Worker Pool 模式
$i, 'data' => $i * 10]; }, range(1, 20));
$results = pcntlWorkerPool(function($task) {
sleep(rand(1, 3)); // 模擬耗時任務
return ['task_id' => $task['id'], 'result' => $task['data'] * 2];
}, $tasks, 4);
進階:共享內存 / 消息隊列替代文件