【发布时间】: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;