Подтвердить что ты не робот

.NET WebSockets принудительно закрыт, несмотря на сохранение активности и активности в соединении

Мы создали простой клиент WebSocket, используя System.Net.WebSockets. KeepAliveInterval на ClientWebSocket установлен на 30 секунд.

Соединение успешно завершено, и трафик протекает как ожидалось в обоих направлениях, или если соединение простаивает, клиент отправляет запросы Pong каждые 30 секунд на сервер (видимый в Wireshark).

Но через 100 секунд соединение резко прекращается из-за закрытия TCP-сокета на стороне клиента (просмотр в Wireshark мы видим, что клиент отправляет FIN). Сервер закрывает сокет 1001 Going Away.

После большого рытья мы выследили причину и обнаружили довольно тяжелое обходное решение. Несмотря на множество поисков Google и Stack Overflow, мы видели только пару других примеров публикации людей о проблеме, и никто с ответом, поэтому я публикую это, чтобы сохранить другие боль и надеяться, что кто-то сможет чтобы предложить лучшее обходное решение.

Источником 100-секундного таймаута является то, что WebSocket использует System.Net.ServicePoint, у которого есть свойство MaxIdleTime, позволяющее закрывать незанятые сокеты. При открытии WebSocket, если существует существующий ServicePoint для Uri, он будет использовать это, независимо от свойства MaxIdleTime, которое было установлено при создании. Если нет, будет создан новый экземпляр ServicePoint с MaxIdleTime, установленным из текущего значения свойства System.Net.ServicePointManager MaxServicePointIdleTime (которое по умолчанию составляет 100 000 миллисекунд).

Проблема заключается в том, что ни трафик WebSocket, ни хранители WebSocket (Ping/Pong) не регистрируются как трафик до таймера простоя ServicePoint. Таким образом, ровно через 100 секунд после открытия WebSocket он просто срывается, несмотря на трафик или keep-alives.

Наша догадка заключается в том, что это может быть связано с тем, что WebSocket начинает свою жизнь как HTTP-запрос, который затем обновляется до websocket. Похоже, что таймер простоя ищет HTTP-трафик. Если это действительно то, что происходит, это похоже на серьезную ошибку в реализации System.Net.WebSockets.

Обходной путь, который мы используем, - установить MaxIdleTime в ServicePoint на int.MaxValue. Это позволяет WebSocket оставаться открытым неограниченное время. Но недостатком является то, что это значение применяется к любым другим соединениям для этого ServicePoint. В нашем контексте (который представляет собой тест нагрузки с использованием тестирования Visual Studio Web и Load) у нас есть другие (HTTP) соединения, открытые для одного и того же ServicePoint, и на самом деле уже есть активный экземпляр ServicePoint к моменту открытия нашего WebSocket. Это означает, что после обновления MaxIdleTime все HTTP-соединения для теста нагрузки не будут иметь тайм-аут простоя. Это не очень удобно, хотя на практике веб-сервер должен в любом случае закрывать простоя.

Мы также кратко рассмотрим, можно ли создать новый экземпляр ServicePoint, зарезервированный только для нашего соединения с WebSocket, но не смог увидеть чистый способ сделать это.

Еще одна небольшая завивка, которая усложнила отслеживание, заключается в том, что хотя свойство System.Net.ServicePointManager MaxServicePointIdleTime по умолчанию составляет 100 секунд, Visual Studio переопределяет это значение и устанавливает его на 120 секунд, что затрудняет поиск.

4b9b3361

Ответ 1

Я столкнулся с этой проблемой на этой неделе. Ваше обходное решение заставило меня указать в правильном направлении, но я считаю, что я сузил основную причину.

Если заголовок "Content-Length: 0" включен в ответ "101 Switching Protocols" с сервера WebSocket, WebSocketClient запутывается и планирует подключение для очистки за 100 секунд.

Здесь оскорбительный код из .NET Reference Source:

//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, Content-Length запрещен в сообщениях 1xx (информационный), но я обнаружил, что он ошибочно включен в некоторые реализации сервера.

Дополнительные сведения, включая пример кода для диагностики проблем ServicePoint, см. в этом разделе: https://github.com/ably/ably-dotnet/issues/107

Ответ 2

Я установил KeepAliveInterval для сокета в 0 следующим образом:

theSocket.Options.KeepAliveInterval = TimeSpan.Zero;

Это устранило проблему с отключением веб-сокета по истечении времени ожидания. Но опять же, это также, вероятно, полностью отключает отправку сообщений ping.