Закрыть против отключения сокета?
В C я понял, что если мы закроем сокет, это означает, что сокет будет уничтожен и может быть использован позже.
Как насчет выключения? В описании сказано, что он закрывает половину дуплексного соединения с этим сокетом. Но будет ли эта розетка разрушена как close
системный вызов?
8 ответов
Это объясняется в сетевом руководстве Биджа. shutdown
это гибкий способ блокировать общение в одном или обоих направлениях. Когда второй параметр SHUT_RDWR
, он будет блокировать как отправку и получение (как close
). Тем не мение, close
способ уничтожить сокет
С shutdown
, вы все равно сможете получать ожидающие данные, уже отправленные пэром (спасибо Джои Адамсу за то, что отметили это).
Ни один из существующих ответов не говорит людям, как 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 будет отправлен другой стороне, чтобы сообщить им, что у нас нет больше данных для отправки.
Есть некоторые ограничения с close()
этого можно избежать, если использовать shutdown()
вместо.
close()
завершит оба направления на TCP-соединении. Иногда вы хотите сообщить другой конечной точке, что вы закончили отправку данных, но все же хотите получать данные.
close()
уменьшает счетчик ссылок дескрипторов (поддерживается в элементе таблицы файлов и подсчитывает количество открытых в данный момент дескрипторов, которые ссылаются на файл / сокет) и не закрывает сокет / файл, если дескриптор не равен 0. Это означает, что если вы разветвляетесь, очистка происходит только после того, как количество ссылок падает до 0. С shutdown()
можно инициировать нормальную последовательность закрытия TCP, игнорируя счетчик ссылок.
Параметры следующие:
int shutdown(int s, int how); // s is socket descriptor
int how
может быть:
SHUT_RD
или же 0
Дальнейшие получения запрещены
SHUT_WR
или же 1
Дальнейшие посылки запрещены
SHUT_RDWR
или же 2
Дальнейшая отправка и получение запрещены
Это может зависеть от платформы, я почему-то сомневаюсь в этом, но в любом случае лучшее объяснение, которое я видел, здесь на этой странице msdn, где они объясняют о выключении, опциях задержки, закрытии сокета и общих последовательностях завершения соединения.
Таким образом, используйте shutdown, чтобы отправить последовательность завершения работы на уровне TCP, и используйте close, чтобы высвободить ресурсы, используемые структурами данных сокетов в вашем процессе. Если вы не выдавали явную последовательность выключения к тому времени, когда вы вызываете close, то она инициируется для вас.
Я также имел успех под Linux, используя shutdown()
из одной pthread, чтобы вызвать другую pthread, заблокированную connect()
прервать рано.
Под другими операционными системами (OSX по крайней мере) я нашел вызов close()
было достаточно, чтобы получить connect()
потерпеть поражение.
"shutdown() на самом деле не закрывает файловый дескриптор, он просто меняет его удобство использования. Чтобы освободить дескриптор сокета, вам нужно использовать close()". 1
близко
Когда вы закончили использовать сокет, вы можете просто закрыть его файловый дескриптор с помощью close; Если все еще есть данные, ожидающие передачи по соединению, обычно закрыть пытается завершить эту передачу. Вы можете управлять этим поведением, используя опцию сокета SO_LINGER, чтобы указать период ожидания; см. Параметры сокета.
Неисправность
Вы также можете отключить только прием или передачу по соединению, вызвав завершение работы.
Функция отключения отключает подключение розетки. Его аргумент how указывает, какое действие нужно выполнить: 0 Прекратить получение данных для этого сокета. Если поступят дополнительные данные, отклоните их. 1 Прекратите пытаться передавать данные из этого сокета. Откажитесь от любых данных, ожидающих отправки. Прекратить поиск подтверждения уже отправленных данных; не передавайте его повторно, если оно потеряно. 2 Остановите прием и передачу.
Возвращаемое значение равно 0 в случае успеха и -1 при ошибке.
В моем тесте.
close
отправит пакет fin и немедленно уничтожит fd, если сокет не используется совместно с другими процессами
shutdown
SHUT_RD, процесс все еще может получать данные из сокета, но recv
вернет 0, если буфер TCP пуст. После того, как узел отправит больше данных, recv
вернет данные снова.
shutdown
SHUT_WR отправит пакет fin, чтобы указать, что дальнейшие отправки запрещены. узел может получить данные, но он получит 0, если его буфер TCP пуст
shutdown
SHUT_RDWR (равнозначный использованию SHUT_RD и SHUT_WR) отправит первый пакет, если узел отправит больше данных.
Linux : shutdown() заставляет поток слушателя select() просыпаться и вызывать ошибку. неисправность(); Закрыть(); приведет к бесконечному ожиданию.
winsock: наоборот - shutdown() не действует, а close() успешно отлавливается.