我们使用System.Net.WebSockets编写了一个简单的WebSocket客户端。ClientWebSocket的KeepAliveInterval设置为30秒。
连接打开成功,双向流量如预期,或者如果连接空闲,客户端每30秒向服务器发送Pong请求(在Wireshark中可见)。
但是在100秒后,由于TCP套接字在客户端关闭,连接突然终止(在Wireshark中我们看到客户端发送FIN)。在关闭套接字之前,服务器响应1001 Going Away。
经过大量的挖掘,我们已经找到了原因,并找到了一个相当严厉的解决办法。尽管谷歌和Stack Overflow搜索了很多,但我们只看到了几个关于这个问题的人发布的其他例子,没有人给出答案,所以我发布这篇文章是为了避免其他人的痛苦,并希望有人能够提出更好的解决方案。
100秒超时的来源是WebSocket使用了System.Net。它有一个MaxIdleTime属性,允许关闭空闲套接字。在打开WebSocket时,如果Uri有一个现有的ServicePoint,它将使用它,无论MaxIdleTime属性在创建时被设置为什么。如果没有,将创建一个新的ServicePoint实例,MaxIdleTime设置为System.Net.ServicePointManager MaxServicePointIdleTime属性的当前值(默认为100,000毫秒)。
问题是WebSocket流量和WebSocket keepalive (Ping/Pong)似乎都没有注册为ServicePoint空闲计时器所关注的流量。所以正好在打开WebSocket 100秒后,它就会被拆除,尽管有流量或keep-alive。
我们的直觉是,这可能是因为WebSocket从HTTP请求开始,然后升级为WebSocket。空闲计时器似乎只查找HTTP流量。如果这确实是正在发生的事情,这似乎是System.Net.WebSockets实现中的一个主要错误。
我们正在使用的解决方案是将ServicePoint上的MaxIdleTime设置为int.MaxValue。这允许WebSocket无限期地保持打开状态。但缺点是该值适用于该ServicePoint的任何其他连接。在我们的上下文中(这是一个使用Visual Studio Web和Load测试的负载测试),我们为同一个ServicePoint打开了其他(HTTP)连接,事实上,在我们打开WebSocket的时候,已经有一个活动的ServicePoint实例。这意味着在我们更新MaxIdleTime之后,所有用于Load测试的HTTP连接都没有空闲超时。这让人感觉不太舒服,尽管实际上web服务器应该关闭空闲连接。
我们还简要地探讨了是否可以为我们的WebSocket连接创建一个新的ServicePoint实例,但没有找到一个干净的方法。
另一个让这个更难追踪的小转折是,尽管System.Net.ServicePointManager MaxServicePointIdleTime属性默认为100秒,Visual Studio重写了这个值并将其设置为120秒——这使得它更难搜索。
我本周遇到了这个问题。你的变通办法给我指出了正确的方向,但我相信我已经缩小了根本原因。
如果WebSocket服务器的"101交换协议"响应中包含"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 Section 3.3.2, Content-Length在1xx (Informational)消息中是被禁止的,但是我发现在一些服务器实现中错误地包含了它。
要了解更多细节,包括诊断ServicePoint问题的一些示例代码,请参阅此线程:https://github.com/ably/ably-dotnet/issues/107
我将套接字的KeepAliveInterval设置为0,如下所示:
theSocket.Options.KeepAliveInterval = TimeSpan.Zero;
这消除了websocket在超时时关闭的问题。但话又说回来,它也可能完全关闭ping消息的发送。
我最近研究了这个问题,比较了Wireshark(python的webclient-client和。net的WebSocketClient)中的捕获包,发现了发生的情况。在WebSocketClient中,"options . keepaliveinterval";如果在这段时间内没有收到服务器的消息,则只向服务器发送一个报文。但是有些服务器只判断是否有来自客户端的活动消息。因此,即使服务器端连续发送数据包,我们也必须定期手动发送任意数据包(不一定是ping数据包,WebSocketMessageType没有ping类型)到服务器。这就是解决办法。