Как запретить библиотекам .NET отправлять RST-пакет при закрытии

Я делаю некоторые тесты, пытаясь изолировать некоторые странные поведения в библиотеках (.NET). Когда я использую Winsock API через C++ и просто вызываю closesocket(), я вижу, что сторона Windows отправляет пакет FIN/ACK, а удаленная сторона отправляет обратно пакет ACK. Это то, что я бы назвал грациозным закрытием. Тем не менее, при программировании на С# я не вижу того, что я бы назвал грациозным закрытием.

В С# я открываю свой сокет и затем, закрывая его, я вижу, что окна отправляют пакет FIN только при первом вызове Socket.Shutdown(). Однако независимо от того, когда я вызываю Socket.Close() в С#, отправляется пакет RST, и соединение Socket.Close(). Это сбивает меня с толку, потому что из того, что я прочитал в режиме онлайн, процесс закрытия TCP должен быть FIN/ACK → ACK (на самом деле с обеих сторон, но сейчас меня интересует только "моя" сторона); т.е. в миксе не должно быть пакета RST. Из того, что я прочитал, по-видимому, пакет RST отправляется только тогда, когда получатель не уверен в состоянии соединения и хочет выйти.

Почему этот пакет RST отправляется при плановом завершении работы в .NET, а не при запланированном завершении работы из Winsock API? Есть ли способ предотвратить передачу пакета RST во время постепенного завершения работы из .NET?

В случае, если это важно, в обоих путях кода я читаю все доступные данные в сокете перед вызовом соответствующего метода close().

Ответ 1

Читая .NET документ Socket.Close, вот что я нашел:

Для протоколов, ориентированных на соединение, рекомендуется вызывать Shutdown перед вызовом метода Close. Это гарантирует, что все данные отправляются и принимаются на подключенном сокете до его закрытия. Если вам нужно вызвать Close без предварительного вызова Shutdown, вы можете убедиться, что данные, поставленные в очередь для исходящей передачи, будут отправлены, установив для параметра DontLinger Socket значение false и указав ненулевой интервал времени ожидания. Затем Close будет блокироваться до тех пор, пока эти данные не будут отправлены или пока не истечет указанное время ожидания. Если для DontLinger задано значение false и указан нулевой интервал времени ожидания, Close освобождает соединение и автоматически отбрасывает исходящие данные в очереди.

что на самом деле имеет смысл. Если в буфере все еще есть данные (операционная система), и вы разрываете соединение или уничтожаете процесс, соединение будет сброшено (более подробная информация в спецификации TCP).

Относительно того, когда отправляется RST, ознакомьтесь с TCP Guide.

Я предполагаю, что все еще есть некоторые неустановленные или в транзитных данных, или в исходящих/входящих буферах, которые запускают RST.

Ответ 2

Следующий код может предотвратить пакет RST. Это происходит потому, что подождите 60 секунд после отправки пакета FIN.

socket.Close(60);

Описание метода закрытия здесь.

Ответ 3

Совсем недавно ударил аналогичную проблему, когда приложение говорило с POP3 на Exchange Server. Когда программа .NET закончила работу, она закрылась без посторонней помощи, и Exchange обработал сеанс POP3 как "прерванный" и отбросил его назад.

Причиной этого я считаю, что TCP RST является "односторонним" сообщением - вам не нужно ждать ответа. Так что то, что отправляет .NET(или, возможно, любая программа после ее завершения). Одно из возможных решений - немного подождать после закрытия сокета и перед выходом из программы:

Thread.Sleep(5000);

Это работало на проблему POP3, о которой я упоминал ранее; время ожидания заставило соединение отключиться изящно, а Exchange больше не прерывал сеанс.