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

PHP fsockopen 被禁用解決方案:pfsockopen、stream_socket_client、自定義 Socket 實現

     編輯:PHP 綜合知識

為什麼主機商禁用 fsockopen?

安全考量:防止 SSRF(服務端請求偽造)、惡意掃描內網、發送垃圾郵件、DDoS 反射攻擊。常見禁用方式:

; php.ini
disable_functions = fsockopen, pfsockopen, stream_socket_client, exec, shell_exec, ...
; 或
allow_url_fopen = Off

方案一:pfsockopen(持久連接版)

若僅禁用 fsockopen,pfsockopen 參數完全兼容:

<?php
// 直接替換函數名
$fp = pfsockopen($host, 80, $errno, $errstr, 30);
// 後續 fread/fwrite/fclose 操作完全一致
?>

注意:持久連接會複用進程級 Socket,高併發下需注意連接洩漏。

方案二:stream_socket_client(現代標準 API)

PHP 5.0+ 內建,參數形式不同:

<?php
// fsockopen($host, $port, $errno, $errstr, $timeout)
// stream_socket_client($remote_socket, $errno, $errstr, $timeout, $flags, $context)

$remote = "tcp://{$host}:80"; // 協議前綴 + host:port
$fp = stream_socket_client($remote, $errno, $errstr, 30, STREAM_CLIENT_CONNECT);

// 支持 SSL/TLS
$fp = stream_socket_client("ssl://{$host}:443", $errno, $errstr, 30);

// 支持上下文選項(超時、證書驗證等)
$context = stream_context_create([
    'ssl' => ['verify_peer' => false, 'verify_peer_name' => false],
    'socket' => ['bindto' => '192.168.1.100:0'] // 指定源 IP
]);
$fp = stream_socket_client($remote, $errno, $errstr, 30, STREAM_CLIENT_CONNECT, $context);
?>

方案三:PHP 版本 < 5.0 環境 - 純 Socket 擴展實現

極老環境(PHP 4)無 stream_socket_client,需使用 sockets 擴展:

<?php
function compat_fsockopen($host, $port, &$errno, &$errstr, $timeout = 30) {
    $ip = gethostbyname($host);
    $socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
    if ($socket === false) {
        $errno = socket_last_error();
        $errstr = socket_strerror($errno);
        return false;
    }
    
    // 非阻塞連接 + select 等待
    socket_set_nonblock($socket);
    $result = @socket_connect($socket, $ip, $port);
    
    if ($result === false) {
        $errno = socket_last_error($socket);
        if ($errno !== EINPROGRESS && $errno !== EWOULDBLOCK) {
            $errstr = socket_strerror($errno);
            socket_close($socket);
            return false;
        }
    }
    
    // 等待連接完成
    $write = [$socket];
    $null = null;
    if (socket_select($null, $write, $null, $timeout) > 0) {
        $errno = 0;
        return $socket; // 返回 Socket 資源
    }
    
    $errno = ETIMEDOUT;
    $errstr = 'Connection timed out';
    socket_close($socket);
    return false;
}

// 使用時需替換文件操作函數
$fp = compat_fsockopen($host, 80, $errno, $errstr, 30);
if ($fp) {
    socket_write($fp, $request);  // 替代 fwrite
    $response = socket_read($fp, 8192); // 替代 fread
    socket_close($fp);            // 替代 fclose
}
?>

方案四:cURL 替代方案(推薦用於 HTTP 請求)

若業務僅需 HTTP 通信,cURL 更健壯且通常未被禁用:

<?php
function curl_http($url, $data = '', $method = 'GET') {
    $ch = curl_init($url);
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT => 30,
        CURLOPT_CONNECTTIMEOUT => 10,
        CURLOPT_SSL_VERIFYPEER => false, // 僅測試環境
        CURLOPT_CUSTOMREQUEST => $method,
    ]);
    if ($method === 'POST') {
        curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
    }
    $response = curl_exec($ch);
    $errno = curl_errno($ch);
    $error = curl_error($ch);
    curl_close($ch);
    return $response ?: false;
}
?>

方案五:GuZZLE / HTTP Client 庫(現代項目首選)

<?php
// composer require guzzlehttp/guzzle
use GuzzleHttp\Client;
$client = new Client(['timeout' => 30]);
$response = $client->request('POST', $url, ['form_params' => $data]);
$body = $response->getBody()->getContents();
?>

遷移檢查清單

原代碼替換後注意事項
fsockopen(host, port, ...)stream_socket_client("tcp://host:port", ...)參數順序變化,增加協議前綴
fwrite($fp, $data)fwrite($fp, $data) / socket_write($fp, $data)Socket 資源需用 socket_* 系列
fread($fp, $len)fread($fp, $len) / socket_read($fp, $len)同上
fclose($fp)fclose($fp) / socket_close($fp)同上
feof($fp)feof($fp) / socket_last_error($fp) !== 0Socket 無 EOF 概念,需檢查錯誤碼

總結建議

  1. 現代項目:直接使用 Guzzle/HTTP Client,屏蔽底層差異
  2. 存量項目快速修復:批量替換 fsockopen → stream_socket_client,補全協議前綴
  3. 極老環境:封裝 compat_fsockopen() 兼容層
  4. 安全合規:評估業務是否真需要出站 Socket,能用 HTTP API 盡量不用 Socket

遇到 disable_functions 限制,優先考慮業務架構調整(如引入消息隊列、服務網格),而非單純繞過限制。

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