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

PHP Socket 模擬 POST 請求完整實作:fsockopen、HTTP 協議組裝、檔案上傳、錯誤處理與 cURL 對比

     編輯:PHP 綜合知識

PHP fsockopen() 底層建立 TCP 連線,手動組裝 HTTP 請求報文,可實作 POST 表單提交、JSON API 呼叫、multipart 檔案上傳。本文提供完整封裝函式、檔案上傳範例、錯誤處理、超時控制、與 cURL 對比優缺點,助你理解 HTTP 協議本質。

核心函式:fsockopen 參數詳解

$fp = fsockopen(
    string $hostname,    // 目標主機 (域名或 IP)
    int $port = -1,      // 連接埠 (HTTP=80, HTTPS=443)
    int &$errno = null,  // 錯誤碼引用
    string &$errstr = null, // 錯誤訊息引用
    float $timeout = ini_get('default_socket_timeout') // 連線超時秒數
): resource|false;
  • 回傳資源控制代碼 (resource),後續用 fwrite()/fread()/fclose() 操作
  • HTTPS 需用 ssl://hostname 或 tls://hostname 作為 $hostname,port 443

封裝函式:socket_post() 通用 POST

<?php
/**
 * Socket 模擬 POST 請求
 * @param string $url 完整 URL (含 scheme://host/path?query)
 * @param array $data POST 欄位陣列 ['key'=>'val']
 * @param string $referer Referer 標頭
 * @return array ['status'=>'ok'|'error', 'header'=>'', 'content'=>'', 'error'=>'']
 */
function socket_post(string $url, array $data, string $referer = ''): array {
    if (!function_exists('fsockopen')) return ['status'=>'error', 'error'=>'fsockopen 不存在'];
    
    $parsed = parse_url($url);
    if (($parsed['scheme'] ?? '') !== 'http') return ['status'=>'error', 'error'=>'僅支援 HTTP'];
    
    $host = $parsed['host'];
    $path = $parsed['path'] ?? '/';
    if ($parsed['query'] ?? '') $path .= '?' . $parsed['query'];
    $port = $parsed['port'] ?? 80;
    
    $postData = http_build_query($data); // 自動 urlencode
    $contentLength = strlen($postData);
    
    // 組裝 HTTP 請求報文 (注意 \r\n 換行)
    $request = "POST $path HTTP/1.1\r\n";
    $request .= "Host: $host\r\n";
    $request .= "User-Agent: PHP-Socket/1.0\r\n";
    $request .= "Accept: */*\r\n";
    $request .= "Referer: $referer\r\n";
    $request .= "Content-Type: application/x-www-form-urlencoded\r\n";
    $request .= "Content-Length: $contentLength\r\n";
    $request .= "Connection: Close\r\n\r\n";
    $request .= $postData;
    
    $fp = @fsockopen($host, $port, $errno, $errstr, 10); // 10秒連線超時
    if (!$fp) return ['status'=>'error', 'error'=>"$errstr ($errno)"];
    
    // 設定讀寫超時 (需 PHP 4.3+)
    stream_set_timeout($fp, 10);
    
    fwrite($fp, $request);
    
    $response = '';
    while (!feof($fp)) {
        $response .= fread($fp, 4096);
    }
    fclose($fp);
    
    // 簡易分離 Header 與 Body
    list($header, $body) = explode("\r\n\r\n", $response, 2) + ['', ''];
    
    return [
        'status' => 'ok',
        'header' => $header,
        'content' => $body,
    ];
}

// 使用範例
$result = socket_post('https://httpbin.org/post', ['name'=>'test', 'value'=>123]);
echo $result['content'];
?>

檔案上傳:multipart/form-data 組裝

<?php
function socket_upload(string $url, string $filePath, string $fieldName = 'file', array $extraFields = []): array {
    $parsed = parse_url($url);
    $host = $parsed['host'];
    $path = $parsed['path'] ?? '/';
    $port = $parsed['port'] ?? 80;
    
    $boundary = '----PHP_SOCKET_' . md5(microtime());
    $body = '';
    
    // 額外欄位
    foreach ($extraFields as $key => $val) {
        $body .= "--$boundary\r\n";
        $body .= "Content-Disposition: form-data; name=\"$key\"\r\n\r\n";
        $body .= "$val\r\n";
    }
    
    // 檔案欄位
    $fileName = basename($filePath);
    $mimeType = mime_content_type($filePath) ?: 'application/octet-stream';
    $fileContent = file_get_contents($filePath);
    
    $body .= "--$boundary\r\n";
    $body .= "Content-Disposition: form-data; name=\"$fieldName\"; filename=\"$fileName\"\r\n";
    $body .= "Content-Type: $mimeType\r\n\r\n";
    $body .= $fileContent . "\r\n";
    $body .= "--$boundary--\r\n";
    
    $request = "POST $path HTTP/1.1\r\n";
    $request .= "Host: $host\r\n";
    $request .= "Content-Type: multipart/form-data; boundary=$boundary\r\n";
    $request .= "Content-Length: " . strlen($body) . "\r\n";
    $request .= "Connection: Close\r\n\r\n";
    $request .= $body;
    
    $fp = @fsockopen($host, $port, $errno, $errstr, 30);
    if (!$fp) return ['status'=>'error', 'error'=>"$errstr ($errno)"];
    stream_set_timeout($fp, 30);
    fwrite($fp, $request);
    $response = '';
    while (!feof($fp)) $response .= fread($fp, 8192);
    fclose($fp);
    list($header, $content) = explode("\r\n\r\n", $response, 2) + ['', ''];
    return compact('status', 'header', 'content') + ['status'=>'ok'];
}

// 使用
$result = socket_upload('https://example.com/upload', '/path/to/image.jpg', 'avatar', ['user_id'=>123]);

關鍵細節與避坑

問題原因解決
大檔案記憶體爆炸file_get_contents 一次讀入分塊讀取寫入 Socket、或用 cURL CURLOPT_INFILE
HTTPS 連線失敗未用 ssl://host、或憑證驗證失敗fsockopen('ssl://host', 443)、或 stream_context_set_option($fp, 'ssl', 'verify_peer', false)
Chunked Transfer Encoding 回應無法解析簡易 explode 不支援分塊編碼需實作 Chunked 解碼、或改用 cURL/HTTP Client 庫
重導向 (301/302) 不自動追蹤Socket 為原始 TCP,無自動導向解析 Location 標頭手動重新請求
Keep-Alive 連線複用需手動解析 Content-Length、保持連線簡單場景用 Connection: Close,複雜場景改用 cURL

Socket vs cURL 對比

維度fsockopen (Socket)cURL
HTTP 協議支援手動組裝/解析完整內建 (重導向、Chunked、gzip、Cookies、認證、HTTP/2、TLS 1.3)
效能較低 (PHP 層處理)極高 (C 庫、非阻塞、多工 curl_multi)
靈活度完全控制每個位元組豐富選項覆蓋 99% 需求
除錯難度高 (需自行印報文)低 (CURLINFO_HEADER_OUT, verbose)
依賴核心擴展 (標準)需安裝 php-curl (幾乎標配)
適用場景教學理解 HTTP、極簡環境、特殊協議變體、無 cURL 環境所有生產環境 HTTP 客戶端需求

總結建議

  1. 生產環境請直接使用 cURL (或 Guzzle/HttpClient),除非極度受限環境。
  2. 學習 HTTP 協議、教學示範、嵌入式裝置無 cURL 時,Socket 手動組裝有教學價值。
  3. 若必須用 Socket,務必處理:超時 (stream_set_timeout)、錯誤碼、HTTPS、大檔案分塊、重導向、Chunked 回應、Keep-Alive。
  4. 封裝通用函式/類別,統一錯誤處理、日誌、重試邏輯。

相關專題:《PHP 網絡編程技巧總結》、《PHP Socket 用法總結》。

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