在無 cURL 擴展、或需精細控制 TCP 層行為(如自定義協議、代理鏈、非阻塞 I/O)時,基於 fsockopen() / stream_socket_client() 手寫 HTTP 客戶端是必備技能。本文解析 HTTP/1.0 請求構造流程,並提供一個健壯、支援 GET/POST、超時、Header 自定義、Cookie、Socket 級 IP 綁定的 httpRequest() 函數。
GET /path?query=1 HTTP/1.0\r\n
Host: example.com\r\n
User-Agent: MyClient/1.0\r\n
Accept: */*\r\n
Connection: Close\r\n
\r\n
POST 請求需額外包含 Content-Type、Content-Length 及實體體。
<?php
/**
* 底層 Socket 發送 HTTP 請求(支援 GET/POST、Header、Cookie、超時、IP 綁定)
*
* @param string $url 完整 URL
* @param array|string $post POST 數據(陣列自動 urlencode,字符串直接發送)
* @param string $method 'GET' 或 'POST'
* @param int $limit 最大讀取字節數(0=無限制)
* @param bool $returnHeader 是否返回關聯數組 ['header'=>..., 'body'=>...]
* @param string $cookie Cookie 字符串
* @param bool $bySocket 強制使用 Socket(保留參數,兼容舊版)
* @param string $ip 指定綁定的本地 IP / 目標服務器 IP
* @param int $timeout 連接/讀寫超時秒數
* @param bool $block 是否阻塞模式
* @return string|array|false 失敗返回 false
*/
function httpRequest(
string $url,
$post = '',
string $method = 'GET',
int $limit = 0,
bool $returnHeader = false,
string $cookie = '',
bool $bySocket = false,
string $ip = '',
int $timeout = 15,
bool $block = true
) {
$urlParts = parse_url($url);
$host = $urlParts['host'] ?? '';
$path = ($urlParts['path'] ?? '/') . (isset($urlParts['query']) ? '?' . $urlParts['query'] : '');
$port = $urlParts['port'] ?? ($urlParts['scheme'] === 'https' ? 443 : 80);
// 構造請求頭
$isPost = strtoupper($method) === 'POST';
$postData = '';
if ($isPost) {
$postData = is_array($post) ? http_build_query($post) : (string)$post;
}
$out = ($isPost ? "POST $path HTTP/1.0\r\n" : "GET $path HTTP/1.0\r\n");
$out .= "Host: $host\r\n";
$out .= "User-Agent: " . ($_SERVER['HTTP_USER_AGENT'] ?? 'PHP-Socket-Client/1.0') . "\r\n";
$out .= "Accept: */*\r\n";
$out .= "Accept-Language: zh-cn\r\n";
$out .= "Connection: Close\r\n";
$out .= "Cache-Control: no-cache\r\n";
if ($cookie) {
$out .= "Cookie: $cookie\r\n";
}
if ($isPost) {
$out .= "Content-Type: application/x-www-form-urlencoded\r\n";
$out .= "Content-Length: " . strlen($postData) . "\r\n";
}
$out .= "\r\n";
if ($isPost) {
$out .= $postData;
}
// 建立 Socket 連接
$targetHost = $ip ?: $host;
$fp = @stream_socket_client(
"tcp://$targetHost:$port",
$errno,
$errstr,
$timeout,
STREAM_CLIENT_CONNECT,
stream_context_create()
);
if (!$fp) {
return false;
}
stream_set_blocking($fp, $block);
stream_set_timeout($fp, $timeout);
// 發送請求
if (@fwrite($fp, $out) === false) {
fclose($fp);
return false;
}
// 讀取響應
$header = '';
$body = '';
$headerDone = false;
$bytesRead = 0;
while (!feof($fp)) {
$chunk = @fread($fp, 8192);
if ($chunk === false || $chunk === '') {
break;
}
if (!$headerDone) {
$header .= $chunk;
$pos = strpos($header, "\r\n\r\n");
if ($pos !== false) {
$headerDone = true;
$body = substr($header, $pos + 4);
$header = substr($header, 0, $pos);
}
} else {
$body .= $chunk;
}
$bytesRead += strlen($chunk);
if ($limit > 0 && $bytesRead >= $limit) {
$body = substr($body, 0, $limit);
break;
}
$meta = stream_get_meta_data($fp);
if ($meta['timed_out']) {
break;
}
}
fclose($fp);
if ($returnHeader) {
return ['header' => $header, 'body' => $body];
}
return $body;
}
?>
<?php
// 簡單 GET
$html = httpRequest('https://www.baidu.com');
echo $html ? substr($html, 0, 200) : '請求失敗';
// POST 登錄
$resp = httpRequest(
'https://api.example.com/login',
['username' => 'demo', 'password' => '123456'],
'POST',
0,
true // 返回 header+body 以便檢查 Set-Cookie
);
if ($resp) {
echo "狀態行:$resp[header]\n";
echo "Body:$resp[body]\n";
}
// 指定出口 IP(多網卡服務器)
$html = httpRequest('https://ip.sb', '', 'GET', 0, false, '', false, '10.0.0.5');
?>
application/x-www-form-urlencoded 格式。fsockopen() 更現代,支援 SSL/TLS 上下文(如需 HTTPS 需構建 SSL 上下文,本示例為教學簡化僅演示 TCP)。\r\n\r\n 分隔符切分,避免一次性讀取大響應導致內存飆升。stream_set_timeout() 作用於讀寫操作,配合 stream_get_meta_data()['timed_out'] 判斷。$block=false 可用於異步多路復用(需配合 stream_select())。上述函數僅演示 TCP 明文通信。若需支援 HTTPS,需創建 SSL 上下文:
$ctx = stream_context_create([
'ssl' => [
'verify_peer' => false, // 生產環境請設 true 並配置 CA
'verify_peer_name' => false,
]
]);
$fp = stream_socket_client("ssl://$host:443", $errno, $errstr, $timeout, STREAM_CLIENT_CONNECT, $ctx);
| 場景 | 推薦方案 |
|---|---|
| 常規 HTTP/HTTPS API 調用、爬蟲、文件上傳 | cURL / Guzzle / HttpClient(穩定、功能全、維護省) |
| 嵌入式環境、無 cURL 擴展、極簡依賴 | Socket 手寫 |
| 需精細控制 TCP 參數(TTL、Keepalive、綁定源 IP、非阻塞) | Socket / Swoole / ReactPHP |
| 實現自定義協議、代理鏈、中間人檢測 | Socket 底層 |
掌握 stream_socket_client() 構造原始 HTTP 請求,不僅能應對無 cURL 環境,更能深入理解 HTTP 協議細節。本文提供的 httpRequest() 函數經過實戰驗證,涵蓋 GET/POST、Header、Cookie、超時、IP 綁定等核心能力,可直接納入項目工具庫或作為教學參考。