HTTP/1.1 預設支援 Connection: Keep-Alive,允許單一 TCP 連線發送多個請求,減少三次握手開銷。PHP 透過 fsockopen() 可手動控制 Socket 連線。
<?php
/**
* 發送多次 HTTP 請求於單一 Socket 連線
*/
function sendMultipleRequests(string $host, int $port, int $count = 5): void
{
// 建立連線
$conn = @fsockopen($host, $port, $errno, $errstr, 5);
if (!$conn) {
throw new RuntimeException("連線失敗 [$errno]: $errstr");
}
// 設定逾時
stream_set_timeout($conn, 5);
for ($i = 0; $i < $count; $i++) {
$postData = "user_name=admin{$i}";
$response = sendHttpRequest($conn, $host, $postData);
echo "請求 #{$i}: " . strlen($response) . " bytes 回應\n";
// 避免發送過快
usleep(100000); // 100ms
}
fclose($conn);
}
/**
* 發送單一 HTTP/1.1 請求並讀取完整回應
*/
function sendHttpRequest($conn, string $host, string $body): string
{
$headers = [
"POST /test.php HTTP/1.1",
"Host: {$host}",
"Content-Type: application/x-www-form-urlencoded",
"Content-Length: " . strlen($body),
"Connection: Keep-Alive",
"", // 空行分隔 header 與 body
$body,
""
];
$request = implode("\r\n", $headers);
fwrite($conn, $request);
// 讀取回應
$response = '';
while (!feof($conn)) {
$chunk = fread($conn, 8192);
if ($chunk === '') break;
$response .= $chunk;
// 簡易判斷回應結束(實際應解析 Content-Length 或 Transfer-Encoding)
if (strpos($response, "\r\n\r\n") !== false &&
preg_match('/\r\n\r\n(.*)$/s', $response, $m)) {
// 這裡簡化處理,實際需完整解析 HTTP 回應
}
}
return $response;
}
// 執行
sendMultipleRequests('127.0.0.1', 80, 5);
<?php
// 記錄接收到的資料
$logFile = __DIR__ . '/received.log';
$data = $_POST['user_name'] ?? 'no-data';
$timestamp = date('Y-m-d H:i:s');
file_put_contents($logFile, "{$timestamp} - {$data}\n", FILE_APPEND);
// 簡單回應
echo "OK: {$data}";
| 關鍵點 | 說明 |
|---|---|
| Connection: Keep-Alive | 告知伺服器保持連線,不關閉 TCP |
| Content-Length | 必須精確,伺服器才知道何時結束讀取 body |
| \r\n\r\n 分隔 | Header 與 Body 必須以雙 CRLF 分隔 |
| 回應讀取 | 需解析 Content-Length 或 Chunked 編碼判斷結束 |
| 錯誤處理 | 網路中斷、逾時、伺服器主動關閉需妥善處理 |