错误信息
Received an unexpected EOF or 0 bytes from the transport stream.
也可能输出为
- System.IO.IOException: Received an unexpected EOF or 0 bytes from the transport stream.
HttpClient 会把它包装在外层异常“The SSL connection could not be established, see inner exception.”中。在 .NET Framework 应用的日志中,同样的情况通常显示为“Authentication failed because the remote party has closed the transport stream.”。
.NET SslStream / HttpClient — IOException: the peer closed the connection during the TLS handshake
含义
在 SslStream 进行 TLS 握手的过程中,远端(或中间的某个设备)关闭了 TCP 连接,且没有发送 TLS 告警。除了流已关闭之外,.NET 没有其他原因可以报告。通常是服务器静默拒绝了握手,或者代理、防火墙中断了握手。
原因
服务器关闭它不接受的握手
常见没有共同的协议版本或加密算法、SNI 名称缺失或未知,或者服务器要求了客户端未提供的东西;而服务器直接关闭连接,不发送告警。
代理或防火墙中断了连接
常见要求使用代理的网络、过滤 TLS 的防火墙以及 VPN 客户端会静默地终止被拦截的连接。
只提供旧版 TLS 的 .NET Framework 应用
偶见面向 .NET Framework 4.5 或更早版本、且未采用操作系统默认 TLS 设置的应用,可能只提供 TLS 1.0,而现代服务器会直接断开这类连接。
修复方法
- 1.
在同一台机器上测试握手
如果 openssl s_client 也被断开,问题出在网络或服务器;如果它成功了,就对比它与 .NET 客户端使用的协议版本和 SNI。
bash # Does a plain TLS handshake from the same host work? openssl s_client -connect api.example.com:443 -servername api.example.com -brief </dev/null openssl s_client -connect api.example.com:443 -servername api.example.com -tls1_2 -brief </dev/null - 2.
使用要求的代理
当网络不允许直接连接时,在处理程序上配置代理(在 Linux 上也可以通过 HTTPS_PROXY 配置)。
csharp // Use the corporate proxy explicitly (HttpClient also honours HTTPS_PROXY / system settings) var handler = new SocketsHttpHandler { Proxy = new WebProxy("http://proxy.example.net:3128"), UseProxy = true }; using var client = new HttpClient(handler); - 3.
.NET Framework:启用 TLS 1.2
将目标框架改为 4.7 或更高版本,让操作系统选择协议;在做到这一点之前,先把 TLS 1.2 添加到 ServicePointManager.SecurityProtocol 中。
csharp // .NET Framework 4.x apps compiled for old targets may still offer only TLS 1.0: // target 4.7+ (OS defaults) or, as a stop-gap, enable TLS 1.2 explicitly System.Net.ServicePointManager.SecurityProtocol |= System.Net.SecurityProtocolType.Tls12;