为什么 dotnet keepalive Http 连接在 "A connection that was expected to be kept alive was closed by the server." 的第二个请求上失败?

Why do dotnet keepalive Http connections fail on the second request with "A connection that was expected to be kept alive was closed by the server."?

我有一个 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 连接完成对服务器的第二次请求时,因此在负载较重的生产系统中更频繁地发生,而在开发环境中则更少或根本不发生。

我们已经尝试过:

禁用 HTTP keep-alive 似乎可以解决此问题。 (HttpWebRequest.KeepAlive = false)

有没有办法在不禁用 http keep-alive 的情况下解决这个问题?

Apache 设置 KeepAliveTimeout 默认为 5 秒不活动后空闲保持活动连接将被关闭。 (https://httpd.apache.org/docs/2.4/mod/core.html#keepalivetimeout)

这会导致以下情况:

  1. dotnet 打开到 apache 的连接并发出 POST
  2. apache returns 200 OK。
  3. 连接处于“空闲”状态,正在等待另一个请求。
  4. 2 秒后,dotnet 打开一个新的 HttpWebRequest 并对其调用 GetRequestStream() 以准备写入请求。由于池中有空闲连接,因此使用该连接。
  5. 5秒后(KeepAliveTimeout),apache发送FIN包关闭底层连接
  6. 在(比如)30 秒后 dotnet 尝试 写入 到流,它尝试使用现已失效的套接字并立即失败 The underlying connection was closed: A connection that was expected to be kept alive was closed by the server.

这在大型 POST 调用(例如,调用 SOAP API)中尤其成问题,在这种情况下形成有效负载可能需要大量时间。

可能的解决方案是:

  1. 在开始发送数据之前不要调用 HttpWebRequest.GetRequestStream()
  2. 禁用保持活动状态(HttpWebRequest.KeepAlive = false)。但是请注意,如果您的应用程序中的任何其他线程正在使用 keep-alive,则会出现问题(上面的两个请求可以在完全不同的线程中)
  3. 最可靠的解决方案似乎是实施应用程序级重试。

请注意,这种行为(“将流锁定到套接字”)似乎只发生在 dotnet 框架中,而不是在 dotnet 5/core 中。