问题 尽管在连接上保持活动和活动,.NET WebSockets仍强制关闭


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

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

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

经过大量的挖掘,我们已经找到了原因,并找到了一个相当沉重的解决方法。尽管有很多谷歌和Stack Overflow搜索,我们只看到了其他几个人发布关于这个问题的例子,没有人回答,所以我发布这个以拯救他人的痛苦,并希望有人能够建议一个更好的解决方法。

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

问题是,就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之后,Load测试的所有HTTP连接都没有空闲超时。这感觉不太舒服,但实际上Web服务器应该关闭空闲连接。

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

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


10244
2017-11-09 08:51


起源

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


答案:


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

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

这是来自的违规代码 .Net参考资料来源

//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


9
2018-06-14 19:49



在客户端的最新.NET 4.7.1中仍然存在此行为,并且IIS Express 10.0.14358正在发送 Content-Length: 0 在它的 101 响应。 - Anton Tykhyy