закрыть против отключения сокета?

214

В C я понял, что если мы закроем сокет, это означает, что сокет будет уничтожен и может быть использован позже.

Как насчет выключения? В описании сказано, что он закрывает половину дуплексного соединения с этим сокетом. Но будет ли этот сокет уничтожен как closeсистемный вызов?

tshepang
источник
Это не может быть использовано позже. Закрыто. Законченный. Готово.
маркиз Лорн

Ответы:

191

Это объясняется в сетевом руководстве Биджа. shutdownэто гибкий способ блокировать общение в одном или обоих направлениях. Когда второй параметр имеет значение SHUT_RDWR, он будет блокировать отправку и получение (как close). Тем не менее, closeэто способ действительно уничтожить сокет.

С помощью shutdownвы все равно сможете получать ожидающие данные, уже отправленные пэром (спасибо Джои Адамсу за то, что отметили это).

Мэтью Флэшен
источник
23
Имейте в виду, что даже если вы закроете () сокет TCP, он не обязательно будет повторно использован в любом случае, поскольку он будет находиться в состоянии TIME_WAIT, в то время как ОС гарантирует, что нет ожидающих пакетов, которые могут быть запутаны как новая информация, если вы должны были немедленно использовать этот сокет для чего-то другого.
Алесплин
91
Большой разницей между выключением и закрытием сокета является поведение, когда сокет используется другими процессами. Функция shutdown () влияет на все копии сокета, а close () влияет только на дескриптор файла в одном процессе.
Zan Lynx
5
Одна из причин, по которой вы можете использовать shutdownоба направления, но это не так close, если вы сделали FILEссылку на сокет, используя fdopen. Если вы closeиспользуете сокет, недавно открытому файлу может быть назначен тот же самый fd, и последующее использование FILEбудет считывать / записывать неправильное место, что может быть очень плохо. Если вы просто shutdown, последующее использование FILEпросто выдаст ошибки, пока не fcloseбудет вызвано.
R .. GitHub ОСТАНОВИТЬ ЛЬДА
25
Комментарий о TIME_WAIT неверен. Это относится к портам, а не к сокетам. Вы не можете повторно использовать сокеты.
маркиз Лорн
48
-1. Как в этом посте, так и в ссылке отсутствует важная концептуальная причина, по которой нужно использовать shutdown: сигнализировать EOF одноранговому узлу и при этом иметь возможность получать ожидающие данные, которые отправил одноранговый узел.
Джои Адамс
144

Ни один из существующих ответов не рассказывает людям о том, как shutdownи как closeработает на уровне протокола TCP, поэтому стоит добавить это.

Стандартное TCP-соединение завершается четырехсторонним завершением:

  1. Когда у участника больше нет данных для отправки, он отправляет пакет FIN другому
  2. Другая сторона возвращает ACK для FIN.
  3. Когда другая сторона также закончила передачу данных, она отправляет другой пакет FIN
  4. Первоначальный участник возвращает ACK и завершает перевод.

Тем не менее, существует еще один «эмерджентный» способ закрыть TCP-соединение:

  1. Участник отправляет пакет RST и прекращает соединение
  2. Другая сторона получает RST и затем также оставляет соединение

В моем тесте с Wireshark, с параметрами сокета по умолчанию, shutdownотправляется пакет FIN на другой конец, но это все, что он делает. Пока другая сторона не отправит вам пакет FIN, вы все равно сможете получать данные. Как только это произойдет, вы Receiveполучите результат 0 размера. Так что, если вы первый, кто отключил «send», вы должны закрыть сокет, как только закончите получать данные.

С другой стороны, если вы звоните, closeкогда соединение все еще активно (другая сторона все еще активна, и у вас также могут быть неотправленные данные в системном буфере), пакет RST будет отправлен на другую сторону. Это хорошо для ошибок. Например, если вы считаете, что другая сторона предоставила неверные данные или отказалась предоставить данные (атака DOS?), Вы можете сразу же закрыть сокет.

Мое мнение о правилах будет:

  1. Рассмотреть shutdownраньше, closeкогда это возможно
  2. Если вы завершили прием (получили данные размером 0) до того, как решили завершить работу, закройте соединение после завершения последней отправки (если есть).
  3. Если вы хотите нормально закрыть соединение, отключите соединение (с помощью SHUT_WR и, если вас не волнует получение данных после этой точки, а также с SHUT_RD), и подождите, пока вы получите данные размера 0, а затем закройте разъем.
  4. В любом случае, если произошла какая-либо другая ошибка (например, тайм-аут), просто закройте сокет.

Идеальные реализации для SHUT_RD и SHUT_WR

Следующие не были проверены, доверяйте на свой страх и риск. Тем не менее, я считаю, что это разумный и практичный способ ведения дел.

Если стек TCP получает завершение только с SHUT_RD, он должен пометить это соединение как ожидаемое больше данных. Любые ожидающие и последующие readзапросы (независимо от того, в каком потоке они находятся) будут возвращены с нулевым результатом. Тем не менее, соединение все еще активно и доступно - вы все еще можете получать данные OOB, например. Кроме того, ОС будет отбрасывать любые данные, которые она получает для этого подключения. Но это все, посылки не будут отправлены на другую сторону.

Если стек TCP получает завершение только с SHUT_WR, он должен пометить это соединение, так как больше данных не может быть отправлено. Все ожидающие запросы на запись будут завершены, но последующие запросы на запись не будут выполнены. Кроме того, пакет FIN будет отправлен другой стороне, чтобы сообщить им, что у нас нет больше данных для отправки.

Земля Двигатель
источник
2
«если вы звоните близко, пока соединение еще живо», тавтологично и не вызывает отправку RST. (1) не обязательно. В (4) тайм-ауты не обязательно являются фатальными для соединения и не означают, что вы можете закрыть его.
Маркиз Лорн
4
@ EJP Нет, это не тавтологическое. Вы можете shutdown()установить соединение, и тогда оно больше не будет живым. У вас все еще есть дескриптор файла. Вы все еще можете recv()из приемного буфера. И вам все равно нужно позвонить, close()чтобы избавиться от дескриптора файла.
Павел Шимерда
Это лучший ответ относительно формата проводов. Мне было бы интересно узнать больше о завершении работы с SHUT_RD. Там нет сигнализации TCP, чтобы не ожидать больше данных, верно? Разве не существует только FIN для сигнализации, чтобы не отправлять больше данных?
Павел Шимерда
3
@ PavelŠimerda Да TCP не сигнализирует о том, что не ожидает больше данных. Это следует учитывать в протоколах высокого уровня. На мой взгляд, это вообще не обязательно. Вы можете закрыть свою дверь, но вы не можете помешать людям ставить подарки перед дверью. Это их решение, а не ваше.
Земля Двигатель
1
@EJP У тебя есть реальные аргументы? Иначе мне это не интересно.
Павел Шимерда
35

Есть некоторые ограничения 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 далее отправлять и получать запрещено

Милан
источник
8
Эти две функции предназначены для совершенно разных целей. Тот факт, что последнее закрытие сокета инициирует последовательность выключения, если он еще не был инициирован, не меняет того факта, что закрытие предназначено для очистки структур данных сокета, а завершение - для инициирования последовательности выключения уровня tcp.
Лен Холгейт
25
Вы не можете использовать вместо этого выключение. Вы можете использовать это также. но вы должны закрыть розетку некоторое время.
маркиз Лорн
15

Это может зависеть от платформы, я почему-то в этом сомневаюсь, но в любом случае лучшее объяснение, которое я видел, здесь, на этой странице msdn, где они объясняют о выключении, опциях задержки, закрытии сокета и общих последовательностях завершения соединения.

Таким образом, используйте shutdown, чтобы отправить последовательность выключений на уровне TCP, и используйте close, чтобы высвободить ресурсы, используемые структурами данных сокетов в вашем процессе. Если вы не выдавали явную последовательность выключения к тому времени, когда вы вызываете close, то она инициируется для вас.

Лен Холгейт
источник
9

Я также добился успеха при использовании linux shutdown()из одной pthread, чтобы заставить другой pthread, заблокированный в данный момент, connect()прервать работу раньше.

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

Тоби
источник
7

«shutdown () на самом деле не закрывает файловый дескриптор - он просто меняет его удобство использования. Чтобы освободить дескриптор сокета, вам нужно использовать close ()». 1


источник
3

близко

Когда вы закончили использовать сокет, вы можете просто закрыть его файловый дескриптор с помощью close; Если все еще есть данные, ожидающие передачи по соединению, обычно закрыть пытается завершить эту передачу. Вы можете контролировать это поведение, используя опцию сокета SO_LINGER, чтобы указать период ожидания; см. Параметры сокета.

Неисправность

Вы также можете отключить только прием или передачу по соединению, вызвав завершение работы.

Функция отключения отключает подключение розетки. Его аргумент how указывает, какое действие нужно выполнить: 0 Прекратить получение данных для этого сокета. Если поступят дополнительные данные, отклоните их. 1 Прекратите пытаться передавать данные из этого сокета. Откажитесь от любых данных, ожидающих отправки. Прекратите искать подтверждение данных, уже отправленных; не передавайте его повторно, если оно потеряно. 2 Остановите прием и передачу.

Возвращаемое значение равно 0 в случае успеха и -1 при ошибке.

Неха Агравал
источник
2

в моем тесте.

close отправит пакет fin и немедленно уничтожит fd, если сокет не используется совместно с другими процессами

shutdown SHUT_RD , процесс может все еще получать данные из сокета, но recvвернет 0, если буфер TCP пуст. После того, как узел отправит больше данных, recvвернет данные снова.

shutdown SHUT_WR отправит пакет fin, чтобы указать, что дальнейшие отправки запрещены. узел может получить данные, но он получит 0, если его буфер TCP пуст

shutdown SHUT_RDWR (равнозначно использованию SHUT_RD и SHUT_WR ) отправит первый пакет, если узел отправит больше данных.

simpx
источник
Я просто попробовал и close()отправил RST на Linux вместо FIN.
Павел Шимерда
Также вы действительно хотели заявить, что после закрытия читающей стороны программа получит больше данных?
Павел Шимерда
@ PavelŠimerda Я думаю, это может зависеть от состояния TCP, чтобы отправить RST или FIN? Я не уверен.
Симпс
1. «После того, как узел отправит больше данных, recv()вернет данные снова», неверно. 2. Поведение, если одноранговый узел отправляет больше данных после, SHUT_RDзависит от платформы.
маркиз Лорн
@EJP Я попробовал себя, извините, я забыл, на какой платформе я делаю этот тест, Centos или Mac. и вот что я получил
симплекс