问题描述
我有一个 dotnet 框架应用程序,它向运行 Apache 的远程服务器执行 POST api 请求。它间歇性地失败并显示错误:
The underlying connection was closed: A connection that was expected to be kept alive was closed by the server.
这发生在通过 keepalive TLS 连接向服务器发出的第二个请求时,因此在负载较重的生产系统中更频繁地发生,而在开发环境中则不那么频繁或根本不发生。
我们已经尝试过:
- 禁用
Expect: 100-Continue
标头行为 (ServicePoint.Expect100Continue = false
) - 启用 TCP 保持活动状态 (
ServicePoint.SetTcpKeepAlive()
)
禁用 HTTP keep-alive 似乎可以解决此问题。 (HttpWebRequest.KeepAlive = false
)
有没有办法在不禁用 http keep-alive 的情况下解决这个问题?
解决方法
Apache 设置 KeepAliveTimeout
默认为 5 秒不活动,然后空闲的保持活动连接将关闭。 (https://httpd.apache.org/docs/2.4/mod/core.html#keepalivetimeout)
这会导致以下情况:
- dotnet 打开与 apache 的连接并发出 POST
- apache 返回 200 OK。
- 连接处于“空闲”状态,正在等待另一个请求。
- 在 2 秒后 dotnet 打开一个新的 HttpWebRequest 并在其上调用 GetRequestStream() 准备写入请求。由于池中有空闲连接,因此使用该连接。
- 5 秒后 (
KeepAliveTimeout
),apache 发送一个 FIN 数据包以关闭底层连接。 - (比如说)30 秒后,dotnet 尝试写入到流中,该流尝试使用现已失效的套接字并立即失败并显示
The underlying connection was closed: A connection that was expected to be kept alive was closed by the server.
这在大型 POST 调用(例如调用 SOAP API)中尤其成问题,其中形成有效负载可能需要大量时间。
可能的解决方案是:
- 在开始发送数据之前不要调用
HttpWebRequest.GetRequestStream()
。 - 禁用保持活动状态 (
HttpWebRequest.KeepAlive = false
)。但是请注意,如果您的应用程序中的任何其他线程正在使用 keep-alive,则会出现问题(上述两个请求可以在完全不同的线程中) - 最可靠的解决方案似乎是实施应用级重试。
请注意,这种行为(“将流锁定到套接字”)似乎只发生在 dotnet 框架中,而不是 dotnet 5/core 中。