TCP套接字自动适配SSL异常:无SSL主机连接被中止
问题分析与解决方案
问题原因
当连接的主机不支持SSL时,调用stream_socket_enable_crypto尝试启动SSL握手,服务器无法识别SSL握手请求,会直接关闭连接或返回无效响应。此时函数会返回0并触发警告,后续再使用这个已处于异常状态的连接进行读写操作,就会出现连接被中止的错误(errno=10053)。
解决方案
- 抑制预期警告:在调用
stream_socket_enable_crypto时临时屏蔽警告,避免不必要的输出干扰。 - 正确处理返回值:当函数返回
false或0时,直接关闭当前无效连接,若需继续使用非SSL服务,重新建立不带SSL上下文的TCP连接。 - 分离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
相关产品推荐
相关产品推荐

