MSDN утверждает, что Socket.Shutdown может выдать SocketException
. Это случилось со мной на производстве недавно после того, как я представил балансировщик нагрузки между моими клиентами и моим сервером. Но я не могу воспроизвести это в тестировании без балансировщика нагрузки. Вы можете?
Немного предыстории - у меня есть серверное приложение, написанное на C #, которое использует TCP
сокеты для связи с клиентами. Протокол приложения очень прост для сервера: принять соединение, запрос на чтение, отправить ответ, дождаться завершения работы клиента (чтение с ожиданием 0 байт), завершение работы.
Этот код находится в производстве без проблем в течение многих лет. Однако после введения балансировщика нагрузки перед несколькими серверными машинами один из процессов сервера завершился сбоем из-за необработанного SocketException
, который был вызван, когда сервер вызвал Socket.Shutdown
. У определенного клиента истекло время ожидания ответа сервера, и он попытался закрыть соединение пораньше. Сообщение об исключении на сервере было «Существующее соединение было принудительно закрыто удаленным хостом». Это не является необычным для клиента, но, очевидно, до балансировки нагрузки сервер поднимал эту ошибку в другой точке кода. Тем не менее, это явно ошибка сервера, а исправление очевидно - обработайте исключение.
Однако, используя тестовое клиентское приложение (также написанное на C #), я не могу найти последовательность операций, которая приведет к тому, что сервер вызовет исключение во время Socket.Shutdown
. Похоже, что балансировщик нагрузки сделал что-то необычное для пакетов TCP
, но я все же не хотел использовать это в качестве оправдания за невозможность воспроизвести проблему.
Я могу запустить как серверный, так и клиентский код в режиме отладки, и у меня есть WireShark, наблюдающий за пакетами.
На стороне клиента после установления соединения выполняются следующие операции:
Socket.Send() // single call
Socket.Receive() // this one times out in our scenario
Socket.XXX() // various choices as described below
На стороне сервера после установления соединения выполняются следующие операции:
1) Socket.Receive() //multiple calls until complete message is received
2) // Processing...
3) Socket.Write() //single call
4) Socket.Receive() // single call expecting 0 bytes
5) Socket.Shutdown()
Предположим, что каждый вызов заключен в try..catch(SocketException)
A) Если я приостановлю работу сервера во время шага 2, подождите, пока клиент истечет время ожидания, и инициируйте завершение работы клиента, используя Socket.Shutdown(SocketShutDown.Send)
, на сервер будет отправлен пакет FIN. Когда сервер возобновит обработку, все вызовы будут успешными (от 3 до 5), потому что это совершенно приемлемый поток TCP.
B) Если я приостановлю работу сервера во время шага 2, подождите, пока клиент истечет время ожидания, и инициируйте завершение работы клиента, используя Socket.Shutdown(SocketShutDown.Both)
или Socket.Close()
, снова пакет FIN отправляется на сервер. Когда сервер возобновляет обработку, этап 3 завершается успешно, но он заставляет клиента отправлять RST-пакет в ответ, поскольку он не принимает больше данных. Если этот RST прибывает до шага 4, то Socket.Receive
бросает и шаг 5 завершается успешно. Если он прибывает после шага 4, то Socket.Receive
завершается успешно (возвращает 0 байтов), и все же шаг 5 завершается успешно.
C) Если на клиенте установлен параметр «Dont Linger» (Linger включен с 0 тайм-аутом), и я приостанавливаю сервер во время обработки, дождитесь истечения времени ожидания клиента и инициируйте завершение работы клиента, используя Socket.Shutdown(SocketShutDown.Both)
или Socket.Close()
пакет "RST" немедленно отправляется на сервер. Когда сервер возобновляет обработку, шаги 3 и 4 завершатся неудачно, но все равно шаг 5 будет успешным.
Я думаю, что меня больше всего озадачивает то, что Socket.Shutdown
, кажется, игнорирует RST-пакеты моего тестового клиента, и, тем не менее, очевидно, что мой балансировщик нагрузки смог отправить RST-пакет, который не был проигнорирован. Что мне не хватает? Что еще можно попробовать?