Сервер TCP Socket Периодически создает CLOSE_WAITs С течением времени, пока не выйдет из строя
-
22-08-2019 - |
Вопрос
Надеюсь, кто-нибудь сможет нам помочь, поскольку мы продвигаемся настолько далеко, насколько это возможно в расследовании!
У нас есть простой асинхронный сервер сокетов, написанный на C #, который принимает подключения от ASP.NET веб-приложения, отправляет сообщение, выполняет некоторую обработку (обычно в базе данных, но также и в других системах), а затем отправляет ответ обратно клиенту.Клиент отвечает за закрытие соединения.
У нас возникали проблемы, из-за которых, если система находится под большой нагрузкой в течение длительного периода времени (обычно дней), сокеты CLOSE_WAIT накапливаются на сервере (netstat -a) до такой степени, что процесс не будет принимать никаких дальнейших подключений.В этот момент мы должны прервать процесс, и он снова запустится.
Мы попытались запустить несколько нагрузочных тестов нашего приложения ASP.NET чтобы попытаться воспроизвести проблему (поскольку вывести какую-то проблему из кода было невозможно).Мы думаем, что нам это удалось, и в итоге мы получили WireShark трассировка пакетов о проблеме, проявляющейся как исключение SocketException в журналах сервера сокетов:
System.Net.Sockets.Исключение SocketException:Существующее соединение было принудительно закрыто удаленным хостом в System.Net.Sockets.Сокет.BeginSend(буфер Байт[], смещение Int32, размер Int32, SocketFlags socketFlags, обратный вызов AsyncCallback, состояние объекта)
Я попытался воспроизвести проблему из трассировки пакетов как однопоточный процесс, напрямую взаимодействующий с сервером сокетов (используя тот же код, что и приложение ASP.NET), но не смог.
Есть ли у кого-нибудь какие-либо предложения о том, что еще можно попробовать, проверить или очевидные вещи, которые мы, возможно, делаем неправильно?
Решение
Посмотрите на диаграмму
http://en.wikipedia.org/wiki/File:Tcp_state_diagram_fixed.svg
Ваш клиент закрыл соединение, вызвав close() , который отправил FIN на серверный сокет, который подтвердил FIN и состояние которого теперь изменилось на CLOSE_WAIT , и остается таким, если сервер не выполнит вызов close() для этого сокета.
Ваша серверная программа должна определить, прервал ли клиент соединение, а затем немедленно закрыть () его, чтобы освободить порт.Каким образом?Обратитесь к read().При чтении конца файла (что означает получение FIN) возвращается ноль.
Другие советы
Если ваш сервер накапливает CLOSE_WAIT
сокеты, то он не закрывает свой сокет после завершения подключения.Если вы взглянете на диаграмму состояний в комментарии к сообщению Криса, вы увидите, что CLOSE_WAIT
переходы к LAST_ACK
как только розетка будет закрыта и FIN
был отправлен.
Вы говорите, что сложно определить, где это сделать, из-за асинхронного характера?Это не должно быть проблемой, вы должны закрыть сокет, если обратный вызов из вашего recv возвращает 0 байт (при условии, что вам больше нечего делать, как только ваш клиент закроет свою сторону соединения).Если вам действительно нужно беспокоиться о продолжении отправки, то выполните завершение работы (recv) здесь и отметьте, что ваш клиент закрыт, как только вы закончите отправку, выполните завершение работы (send) и закрытие.
ВОЗМОЖНО, вы выдаете новое чтение при обратном вызове из read, которое возвращает 0, указывающее, что клиент закрыт, и это может вызывать у вас проблемы?
Клиент отвечает за закрытие соединения.
И клиент, и сервер должны закрыть и выключить сокет.Либо клиент не завершает закрытие (маловероятно - поскольку у него был бы запущен финализатор), либо сервер не закрывает сокет (вероятно).
using (Socket s = new Socket(/* */)) {
/* Do stuff */
s.Shutdown(SocketShutdown.Both);
s.Close();
}
Вы не должны перекладывать ответственность за закрытие TCP-сокетов только на клиента.Что произойдет, если клиентский процесс / компьютер выйдет из строя?
В идеале у вас должен быть установлен тайм-аут, чтобы, если по истечении определенного промежутка времени на подключенный сокет не поступает трафик, сервер закрывал его.
Независимо от того, что произойдет, когда клиент завершит все операции с сокетом, и ему больше не нужно выполнять никаких операций чтения с сокетом, клиент должен выполнить команду закрытия.
Эта выдача команды закрытия просто сообщает слушателю (серверу), что соединение необходимо завершить.
Проще говоря, когда сервер снова выдает команду чтения (listener.read() или listener.beginread(...) в асинхронном режиме), чтение вернет прочитанное значение в 0 байт, это само по себе указывает на то, что сокет должен быть закрыт слушателем, поскольку любые другие операции с сокетом были прекращены клиентом.
CLOSE_WAIT предназначены для зависания на некоторое время после закрытия сокета, чтобы предотвратить повторное использование того же номера сокета и получение пакетов от старого соединения.Это доставит вам неприятности только в том случае, если вы действительно быстро открываете и закрываете огромное количество сокетов.
РЕДАКТИРОВАТЬ - Это должно быть TIME_WAIT, а не CLOSE_WAIT выше.