Сокет.Close на самом деле не закрывает tcp-сокет? (c #)

#c# #windows #sockets #tcp

#c# #Windows #сокеты #tcp

Вопрос:

Кажется, что использование socket.Close() для tcp-сокета не полностью закрывает сокет. В следующем примере я пытаюсь подключиться к example.com на порту 9999, который не открывается, и после короткого тайм-аута я пытаюсь закрыть сокет.

   for (int i = 0; i < 200; i  )
  {
    Socket sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
    sock.LingerState = new LingerOption(false, 0);
    sock.BeginConnect("www.example.com", 9999, OnSocketConnected, sock);
    System.Threading.Thread.Sleep(50);
    sock.Close();
  }
  

Но когда я смотрю на netstat после завершения цикла, я обнаруживаю, что там много полуоткрытых сокетов:

   TCP    israel-xp:6506         www.example.com:9999   SYN_SENT
  TCP    israel-xp:6507         www.example.com:9999   SYN_SENT
  TCP    israel-xp:6508         www.example.com:9999   SYN_SENT
  TCP    israel-xp:6509         www.example.com:9999   SYN_SENT
  

Редактировать
.
Хорошо, некоторый контекст отсутствовал. Я использую beginconnect, потому что я ожидаю, что соединение с сокетом завершится ошибкой (9999 не открывается), и в моем реальном коде я вызываю сокет.Close() после установки таймера.
В OnSocketConnected я вызываю EndConnect, который выдает исключение (пытаясь вызвать метод удаленного объекта).
Моя цель — установить короткий тайм-аут для этапа подключения к сокету.

Есть какие-нибудь подсказки, что я делаю не так? Спасибо!

Ответ №1:

По замыслу вы всегда должны вызывать Shutdown перед закрытием сокета.

 mySocket.Shutdown(SocketShutdown.Both);
mySocket.Close();
  

Это эффективно отключает отправку и получение в сокете, поэтому он не будет принимать входящие данные после того, как вы его закроете, даже если ОС все еще контролирует их.

Джон Скит также считает, что, поскольку вы открываете соединение асинхронно, оно может фактически подключаться, пока вы пытаетесь его закрыть. Однако, если вы вызовете Shutdown его, это не позволит получать информацию в том виде, в каком вы ее используете.

Редактировать: Вы можете использовать только Shutdown уже подключенный сокет, поэтому имейте это в виду при написании своего кода.

Комментарии:

1. Завершение работы вызывает исключение, если сокет еще не подключен — что в точности соответствует моему сценарию.

2. Затем вы должны использовать OnSocketConnected, чтобы вызвать ShutDown для него и закрыть его, или что-то в этом роде.

3. @Dean, это второй вопрос за сегодняшний день, ответы на который мы с Джоном опубликовали одновременно. У него и так слишком хорошая репутация, может быть, он сможет мне ее дать. 🙂

4. Но при вызове ShutDown из OnSocketConnected упускается смысл прерывания процесса подключения. Ожидание вызова OnSocketConnected может занять очень много времени.

5. Но это нормально, потому что сокет вас больше не волнует. Невозможно полностью отключить сокет, если вы попытаетесь подключить его таким образом, как сейчас. Вам нужно либо установить флаг, а затем отключить / закрыть его, либо не использовать подобные асинхронные соединения. В качестве альтернативы вы можете просто закрыть его и помнить, что некоторые сообщения могут быть отправлены до того, как ОС очистит сокет.

Ответ №2:

Это закроет .NET-часть сокета. Однако в соответствии со спецификацией TCP ОС должна поддерживать лакомые кусочки сокета нижнего уровня открытыми в течение определенного периода времени, чтобы обнаружить повторную передачу и тому подобное. В данном конкретном случае, скорее всего, сокет некоторое время остается включенным, чтобы обнаружить ответ на отправленный SYN-пакет, чтобы он мог отвечать более разумно и не путать ответ с другими отправленными пакетами.

Комментарии:

1. это имеет смысл, но разве установка значения linger в false не должна решить проблему?

2. о, linger этого не делает. Итак, на самом деле не существует способа заставить Windows завершить работу сокета.

3. по умолчанию он будет находиться в состоянии FIN_WAIT в течение 4 минут, прежде чем Windows действительно закроет его. Еще интереснее то, что если вы повторно откроете другое соединение с тем же адресатом / портом, Windows повторно использует соответствующие соединения в FIN_WAIT.

4. @Jay: Последний момент может вызвать некоторое значительное раздражение, если иметь дело с оборудованием TCP, которое имеет тенденцию выбирать одни и те же номера портов назначения каждый раз, когда оно включается и запускает новый сокет. Если соединение находится в режиме реального времени, когда это произойдет, устройство решит, что номер порта не подходит, и попробует новый номер порта. Однако, если соединение находится в режиме FIN_WAIT, устройству требуется много времени, чтобы понять, что что-то не так.

Ответ №3:

Вы вызываете *Begin*Connect — значит, делаете это асинхронно. Вполне возможно, что вы пытаетесь закрыть сокет еще до его подключения, поэтому, когда он подключается, он остается открытым.

Попробуйте подключиться синхронно — или закрыть его OnSocketConnected , чтобы вы могли увидеть эффект закрытия действительно подключенного сокета.

Комментарии:

1. Смотрите мою правку. Я специально использую BeginConnect, и мой фактический код ожидает тайм-аута перед вызовом Close. Простой вызов EndConnect для такого сокета (где порт dest не открыт) просто займет много времени для завершения. Или, другими словами, я хочу установить тайм-аут для этапа подключения сокета через X миллисекунд (а не абсурдно большое количество миллисекунд, которое Windows имеет по умолчанию).

2. Я думаю, что вызов Socket.Dispose() — это единственный способ вроде как отменить асинхронное соединение с текущим API. Похоже, что это было сделано специально.

Ответ №4:

Вы инициализируете новый сокет в каждом цикле … с помощью *.close() вы закрываете старый и в начале создаете новый с теми же параметрами, что и предыдущий сокет.