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

PHP 使用 Socket 模擬 HTTP 請求獲取頁面內容

     編輯:PHP 相關知識

PHP 使用 Socket 模擬 HTTP 請求獲取頁面內容

不依賴 cURL、Guzzle 等外部庫,直接用 fsockopen 建立 TCP 連線,手工組裝 HTTP 請求標頭,即可實現頁面抓取、API 呼叫、壓力測試等功能。本文提供完整可用的封裝範例。

核心實作:GET/POST 通用函式

function httpRequestBySocket(string $url, array $options = []): string {
    $defaults = [
        'method'  => 'GET',
        'data'    => [],
        'headers' => [],
        'timeout' => 10,
        'port'    => 80,
    ];
    $opts = array_merge($defaults, $options);

    $parsed = parse_url($url);
    $host   = $parsed['host'];
    $path   = $parsed['path'] ?? '/';
    if (!empty($parsed['query'])) $path .= '?' . $parsed['query'];
    $port   = $parsed['port'] ?? $opts['port'];
    $scheme = $parsed['scheme'] ?? 'http';

    // 建立連線
    $fp = @fsockopen($host, $port, $errno, $errstr, $opts['timeout']);
    if (!$fp) {
        throw new RuntimeException("Socket 連線失敗 [$errno]: $errstr");
    }
    stream_set_timeout($fp, $opts['timeout']);

    // 組裝請求
    $request  = "{$opts['method']} $path HTTP/1.1\r\n";
    $request .= "Host: $host\r\n";
    $request .= "Connection: Close\r\n";
    $request .= "User-Agent: PHP-Socket/1.0\r\n";
    $request .= "Accept: */*\r\n";

    // 自訂標頭
    foreach ($opts['headers'] as $k => $v) {
        $request .= "$k: $v\r\n";
    }

    // POST 資料
    if ($opts['method'] === 'POST' && !empty($opts['data'])) {
        $body = is_array($opts['data']) ? http_build_query($opts['data']) : $opts['data'];
        $request .= "Content-Type: application/x-www-form-urlencoded\r\n";
        $request .= "Content-Length: " . strlen($body) . "\r\n";
        $request .= "\r\n$body";
    } else {
        $request .= "\r\n";
    }

    // 發送
    fwrite($fp, $request);

    // 讀取回應(分離標頭與內容)
    $response = '';
    $headerDone = false;
    while (!feof($fp)) {
        $line = fgets($fp, 8192);
        if (!$headerDone) {
            if ($line === "\r\n" || $line === "\n") {
                $headerDone = true;
            }
        } else {
            $response .= $line;
        }
    }
    fclose($fp);
    return $response;
}

使用範例

GET 請求

$html = httpRequestBySocket('https://httpbin.org/get?foo=bar', [
    'timeout' => 15,
    'headers' => ['Accept-Language' => 'zh-TW']
]);

POST 請求(含 Cookie)

$result = httpRequestBySocket('https://httpbin.org/post', [
    'method'  => 'POST',
    'data'    => ['username' => 'test', 'password' => '123456'],
    'headers' => [
        'Cookie' => 'session_id=abc123; theme=dark',
        'X-Custom-Header' => 'MyValue'
    ],
    'timeout' => 20
]);

HTTPS 支援

將 fsockopen 改為 stream_socket_client 並加入 SSL 上下文:

$context = stream_context_create([
    'ssl' => [
        'verify_peer'      => false, // 生產環境請設 true 並配置 CA
        'verify_peer_name' => false,
    ]
]);
$fp = stream_socket_client("ssl://$host:$port", $errno, $errstr, $opts['timeout'], STREAM_CLIENT_CONNECT, $context);

進階:處理 Chunked Transfer Encoding

若回應含 Transfer-Encoding: chunked,需手動解碼:

function decodeChunked(string $data): string {
    $output = '';
    $pos = 0;
    while ($pos < strlen($data)) {
        $lenLineEnd = strpos($data, "\r\n", $pos);
        if ($lenLineEnd === false) break;
        $length = hexdec(trim(substr($data, $pos, $lenLineEnd - $pos)));
        if ($length === 0) break;
        $chunkStart = $lenLineEnd + 2;
        $output .= substr($data, $chunkStart, $length);
        $pos = $chunkStart + $length + 2; // 跳過 \r\n
    }
    return $output;
}

效能與適用場景對比

方案優點缺點適用場景
fsockopen零依賴、完全控制、教學價值高手動處理 HTTPS/Chunked/Keep-Alive/Redirect、易出錯嵌入式環境、特殊協定、學習 HTTP 協定
cURL功能完整、效能佳、支援所有 HTTP 特性需安裝擴充生產環境首選
Guzzle物件導向、中介軟體、非同步、測試友善Composer 依賴、記憶體較大現代 PHP 專案、微服務呼叫

避坑指南

  • 請求標頭結尾必須雙 \r\n\r\n
  • Host 標頭在 HTTP/1.1 必填
  • 大量請求時啟用 Connection: Keep-Alive 並複用連線
  • 設定 stream_set_timeout 避免卡死
  • 生產環境 務必驗證 SSL 憑證

掌握 Socket 底層運作,能在無法安裝擴充的受限環境中完成 HTTP 整合,也是理解高層庫原理的最佳途徑。

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