我们使用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秒 - 这使得搜索更加困难。