我们使用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属性,允许关闭空闲套接字。在打开WebSocket时,如果存在Uri的现有ServicePoint,则使用该服务点,并使用创建时设置的MaxIdleTime属性。如果没有,则将创建新的ServicePoint实例,并将MaxIdleTime设置为当前System.Net.ServicePointManager MaxServicePointIdleTime属性的值(默认为100,000毫秒)。
问题在于,WebSocket流量和WebSocket keep-alives(Ping / Pong)似乎都不会被ServicePoint空闲计时器视为流量。因此,在打开WebSocket后的100秒钟后,它就被关闭了,尽管存在流量或保持活动状态的消息。
我们的猜测是,这可能是因为WebSocket最初是作为HTTP请求启动的,然后升级为websocket。似乎空闲计时器仅在寻找HTTP流量。如果确实发生了这种情况,则System.Net.WebSockets实现中存在重大错误。
我们目前采用的解决办法是将ServicePoint的MaxIdleTime设置为int.MaxValue,这样WebSocket就可以无限期地保持开启状态。但缺点是该值适用于该ServicePoint上的任何其他连接。在我们的情境中(即使用Visual Studio Web和Load测试进行负载测试),我们还为同一个ServicePoint打开了其他(HTTP)连接,并且实际上在我们打开WebSocket之前已经有一个活动的ServicePoint实例。这意味着在更新MaxIdleTime后,所有负载测试的HTTP连接都将没有空闲超时时间。虽然实际上Web服务器应该关闭空闲连接,但这并不令人感到舒适。我们还简要探讨了是否可以创建一个仅用于WebSocket连接的新ServicePoint实例,但没有找到一种干净的方法。
还有一个小细节使得这更加难以跟踪,即尽管System.Net.ServicePointManager的MaxServicePointIdleTime属性默认为100秒,但Visual Studio会覆盖此值并将其设置为120秒,这使得搜索变得更加困难。