You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

TCP套接字自动适配SSL异常:无SSL主机连接被中止

问题分析与解决方案

问题原因

当连接的主机不支持SSL时,调用stream_socket_enable_crypto尝试启动SSL握手,服务器无法识别SSL握手请求,会直接关闭连接或返回无效响应。此时函数会返回0并触发警告,后续再使用这个已处于异常状态的连接进行读写操作,就会出现连接被中止的错误(errno=10053)。

解决方案

  1. 抑制预期警告:在调用stream_socket_enable_crypto时临时屏蔽警告,避免不必要的输出干扰。
  2. 正确处理返回值:当函数返回false或0时,直接关闭当前无效连接,若需继续使用非SSL服务,重新建立不带SSL上下文的TCP连接。
  3. 分离SSL探测与连接逻辑:确认SSL可用时保留加密连接,否则切换到普通TCP连接,确保后续操作的连接状态正常。

修改后的代码示例

$context = stream_context_create([
    'socket' => [
        'tcp_nodelay' => true,
    ],
    'ssl' => [
        'verify_peer' => false,
        'verify_peer_name' => false,
        'SNI_enabled' => false,
        'allow_self_signed' => true,
        'capture_peer_cert' => true,
        'capture_peer_cert_chain' => true
    ]
]);

$this->stream = @stream_socket_client('tcp://' . $this->ip . ':' . $this->port, $errno, $errstr, $this->timeout, STREAM_CLIENT_CONNECT, $context);
if ($this->stream === false) {
    throw new ConnectException($errstr, $errno);
}

if (!stream_set_blocking($this->stream, true)) {
    throw new ConnectException('Cannot set socket into blocking mode');
}

// 临时屏蔽警告,无SSL场景下的警告属于预期情况
$oldErrorLevel = error_reporting(E_ALL & ~E_WARNING);
$enableCrypto = stream_socket_enable_crypto($this->stream, true, STREAM_CRYPTO_METHOD_ANY_CLIENT);
error_reporting($oldErrorLevel);

if ($enableCrypto === true) {
    $params = stream_context_get_params($this->stream);

    if (isset($params['options']['ssl']['peer_certificate']) && is_resource($params['options']['ssl']['peer_certificate'])) {
        $cert = openssl_x509_parse($params['options']['ssl']['peer_certificate']);
        stream_context_set_params($this->stream, [
            'ssl' => [
                'verify_peer' => true,
                'verify_peer_name' => true,
                'SNI_enabled' => true,
                'peer_name' => $cert['subject']['CN'],
                'allow_self_signed' => count($params['options']['ssl']['peer_certificate_chain']) == 1 && $cert['subject'] == $cert['issuer']
            ]
        ]);
    }
} else {
    // 关闭因SSL握手失败导致的无效连接
    fclose($this->stream);
    // 重新建立普通TCP连接(若需继续使用非SSL服务)
    $plainContext = stream_context_create([
        'socket' => [
            'tcp_nodelay' => true,
        ]
    ]);
    $this->stream = @stream_socket_client('tcp://' . $this->ip . ':' . $this->port, $errno, $errstr, $this->timeout, STREAM_CLIENT_CONNECT, $plainContext);
    if ($this->stream === false) {
        throw new ConnectException($errstr, $errno);
    }
}

补充说明

  • 用error_reporting()临时调整错误级别比直接用@更可控,能精准屏蔽特定警告,不影响其他错误的输出。
  • 重新建立普通TCP连接是为了确保后续操作使用的是正常的非SSL连接,避免原连接因SSL握手失败而处于不可用状态。

内容的提问来源于stack exchange,提问作者oLDo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.23 12:24:21