Ни один из существующих ответов не рассказывает людям о том, как shutdown
и как close
работает на уровне протокола TCP, поэтому стоит добавить это.
Стандартное TCP-соединение завершается четырехсторонним завершением:
- Когда у участника больше нет данных для отправки, он отправляет пакет FIN другому
- Другая сторона возвращает ACK для FIN.
- Когда другая сторона также закончила передачу данных, она отправляет другой пакет FIN
- Первоначальный участник возвращает ACK и завершает перевод.
Тем не менее, существует еще один «эмерджентный» способ закрыть TCP-соединение:
- Участник отправляет пакет RST и прекращает соединение
- Другая сторона получает RST и затем также оставляет соединение
В моем тесте с Wireshark, с параметрами сокета по умолчанию, shutdown
отправляется пакет FIN на другой конец, но это все, что он делает. Пока другая сторона не отправит вам пакет FIN, вы все равно сможете получать данные. Как только это произойдет, вы Receive
получите результат 0 размера. Так что, если вы первый, кто отключил «send», вы должны закрыть сокет, как только закончите получать данные.
С другой стороны, если вы звоните, close
когда соединение все еще активно (другая сторона все еще активна, и у вас также могут быть неотправленные данные в системном буфере), пакет RST будет отправлен на другую сторону. Это хорошо для ошибок. Например, если вы считаете, что другая сторона предоставила неверные данные или отказалась предоставить данные (атака DOS?), Вы можете сразу же закрыть сокет.
Мое мнение о правилах будет:
- Рассмотреть
shutdown
раньше, close
когда это возможно
- Если вы завершили прием (получили данные размером 0) до того, как решили завершить работу, закройте соединение после завершения последней отправки (если есть).
- Если вы хотите нормально закрыть соединение, отключите соединение (с помощью SHUT_WR и, если вас не волнует получение данных после этой точки, а также с SHUT_RD), и подождите, пока вы получите данные размера 0, а затем закройте разъем.
- В любом случае, если произошла какая-либо другая ошибка (например, тайм-аут), просто закройте сокет.
Идеальные реализации для SHUT_RD и SHUT_WR
Следующие не были проверены, доверяйте на свой страх и риск. Тем не менее, я считаю, что это разумный и практичный способ ведения дел.
Если стек TCP получает завершение только с SHUT_RD, он должен пометить это соединение как ожидаемое больше данных. Любые ожидающие и последующие read
запросы (независимо от того, в каком потоке они находятся) будут возвращены с нулевым результатом. Тем не менее, соединение все еще активно и доступно - вы все еще можете получать данные OOB, например. Кроме того, ОС будет отбрасывать любые данные, которые она получает для этого подключения. Но это все, посылки не будут отправлены на другую сторону.
Если стек TCP получает завершение только с SHUT_WR, он должен пометить это соединение, так как больше данных не может быть отправлено. Все ожидающие запросы на запись будут завершены, но последующие запросы на запись не будут выполнены. Кроме того, пакет FIN будет отправлен другой стороне, чтобы сообщить им, что у нас нет больше данных для отправки.