【问题标题】:.NET WebSockets forcibly closed despite keep-alive and activity on the connection.NET WebSockets 强制关闭,尽管连接上保持活动和活动
【发布时间】:2017-03-23 00:35:05
【问题描述】:

我们使用 System.Net.WebSockets 编写了一个简单的 WebSocket 客户端。 ClientWebSocket 上的 KeepAliveInterval 设置为 30 秒。

连接成功打开,流量按预期双向流动,或者如果连接空闲,客户端每 30 秒向服务器发送一次 Pong 请求(在 Wireshark 中可见)。

但是在 100 秒后,由于 TCP 套接字在客户端关闭(在 Wireshark 中我们看到客户端发送 FIN),连接突然终止。服务器在关闭套接字之前响应 1001 Going Away。

经过大量挖掘,我们找到了原因并找到了一个相当严厉的解决方法。尽管进行了很多 Google 和 Stack Overflow 搜索,但我们只看到了一些其他人发布有关该问题的示例,但没有人给出答案,所以我发布此内容是为了减轻其他人的痛苦,并希望有人能够提出更好的解决方法。

100 秒超时的来源是 WebSocket 使用了 System.Net.ServicePoint,它有一个 MaxIdleTime 属性来允许关闭空闲的套接字。如果 Uri 存在现有的 ServicePoint,则在打开 WebSocket 时,它将使用该 Uri,无论 MaxIdleTime 属性在创建时设置为什么。否则,将创建一个新的 ServicePoint 实例,并根据 System.Net.ServicePointManager MaxServicePointIdleTime 属性的当前值(默认为 100,000 毫秒)设置 MaxIdleTime。

问题在于,就 ServicePoint 空闲计时器而言,WebSocket 流量和 WebSocket 保持活动(Ping/Pong)似乎都没有注册为流量。因此,在打开 WebSocket 100 秒后,它就会被拆除,尽管有流量或保持活动。

我们的预感是,这可能是因为 WebSocket 以 HTTP 请求的形式开始生命,然后升级为 websocket。空闲计时器似乎只是在寻找 HTTP 流量。如果这确实是正在发生的事情,那似乎是 System.Net.WebSockets 实现中的一个主要错误。

我们使用的解决方法是将 ServicePoint 上的 MaxIdleTime 设置为 int.MaxValue。这允许 WebSocket 无限期地保持打开状态。但缺点是该值适用于该 ServicePoint 的任何其他连接。在我们的上下文(这是使用 Visual Studio Web 和负载测试的负载测试)中,我们为同一个 ServicePoint 打开了其他 (HTTP) 连接,事实上,在我们打开 WebSocket 时已经有一个活动的 ServicePoint 实例。这意味着在我们更新 MaxIdleTime 之后,所有用于负载测试的 HTTP 连接都不会出现空闲超时。这感觉不太舒服,尽管实际上 Web 服务器应该关闭空闲连接。

我们还简要探讨了是否可以创建一个仅为我们的 WebSocket 连接保留的新 ServicePoint 实例,但看不到这样做的干净方法。

另一个更难追踪的小转折是,虽然 System.Net.ServicePointManager MaxServicePointIdleTime 属性默认为 100 秒,但 Visual Studio 会覆盖此值并将其设置为 120 秒 - 这使得搜索变得更加困难.

【问题讨论】:

  • 巧合的是,我前几天偶然发现了这种行为。好像是个bug。考虑将其报告给 CLR 团队。应该可以通过设置一些带有反射的内部字段来保护 websocket 在 ServicePoint 超时时不被关闭,但是我对这两种解决方案都不完全满意。
  • 谢谢!我花了两天时间试图弄清楚为什么我的内部 ClientWebsocket 在大约 100 秒后神秘地一直断开连接,直到我偶然发现了这篇文章。似乎已经解决了我的问题。
  • System.Net.ServicePointManager.MaxServicePointIdleTime = int.MaxValue;

标签: c# .net websocket


【解决方案1】:

这周我遇到了这个问题。您的解决方法让我指出了正确的方向,但我相信我已经缩小了根本原因。

如果“Content-Length: 0”标头包含在来自 WebSocket 服务器的“101 Switching Protocols”响应中,WebSocketClient 会感到困惑,并安排在 100 秒内清理连接。

这是来自.Net Reference Source 的违规代码:

//if the returned contentlength is zero, preemptively invoke calldone on the stream.
//this will wake up any pending reads.
if (m_ContentLength == 0 && m_ConnectStream is ConnectStream) {
    ((ConnectStream)m_ConnectStream).CallDone();
}

根据 RFC 7230 第 3.3.2 节,1xx(信息)消息中禁止使用 Content-Length,但我发现它错误地包含在某些服务器实现中。

有关其他详细信息,包括用于诊断 ServicePoint 问题的一些示例代码,请参阅此线程:https://github.com/ably/ably-dotnet/issues/107

【讨论】:

  • 这种行为仍然存在于客户端最新的 .NET 4.7.1 中,并且 IIS Express 10.0.14358 在其101 响应中发送Content-Length: 0
【解决方案2】:

我将套接字的 KeepAliveInterval 设置为 0,如下所示:

theSocket.Options.KeepAliveInterval = TimeSpan.Zero;

这消除了 websocket 在达到超时时关闭的问题。但话又说回来,它也可能完全关闭 ping 消息的发送。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-07-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-11-12
    • 1970-01-01
    • 1970-01-01
    • 2017-10-29
    相关资源
    最近更新 更多