PHP fsockopen() 底層建立 TCP 連線,手動組裝 HTTP 請求報文,可實作 POST 表單提交、JSON API 呼叫、multipart 檔案上傳。本文提供完整封裝函式、檔案上傳範例、錯誤處理、超時控制、與 cURL 對比優缺點,助你理解 HTTP 協議本質。
$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;
fwrite()/fread()/fclose() 操作ssl://hostname 或 tls://hostname 作為 $hostname,port 443<?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'];
?>
<?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 |
| 維度 | 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 客戶端需求 |
相關專題:《PHP 網絡編程技巧總結》、《PHP Socket 用法總結》。