Эффективное программирование TCP-IP

       

Вызов shutdown


Как приложение закрывает свой конец соединения? Оно не может просто завершить сеанс или закрыть сокет, поскольку у партнера могут быть еще данные. " API сокетов есть интерфейс shutdown. Он используется так же, как и вызов close, но при этом передается дополнительный параметр, означающий, какую сторону соединения надо закрыть.

#include <sys/socket.h> /* UNIX. */

#include <winsock2.h> /* Windows. */

int shutdown( int  s, int  how ); /* UNIX. */

int shutdown( SOCKET s, int how ); /* Windows. */

Возвращаемое значение: 0- нормально, -1 (UNIX) или SOCKET_ERROR (Windows) - ошибка.

К сожалению, между реализациями shutdown в UNIX и Windows есть разли­чия в семантике и API. Традиционно в качестве значений параметра how вызова shutdown использовались числа. И в стандарте POSIX, и в спецификации Winsock им присвоены символические имена, только разные. В табл. 3.1 приведены значе­ния, символические константы для них и семантика параметра how.

Различия в символических именах можно легко компенсировать, определив в заголовочном файле одни константы через другие или используя числовые зна­чения. А вот семантические отличия гораздо серьезнее. Посмотрим, для чего пред­назначено каждое значение.

Таблица 3.1. Значения параметра how для вызова shutdown

Числовое

Значение how

Действие



POSIX

WINSOCK

0

SHUT_RD

SD_RECEIVE

Закрывается принимающая сторона соединения

1

SHUT_WR

SD_SEND

Закрывается передающая сторона соединения

2

SHUT_RDWR

SD_BOTH

Закрываются обе стороны

how = 0. Закрывается принимающая сторона соединения. В обеих реализациях в сокете делается пометка, что он больше не может принимать данные и должен вернуть EOF, если приложением делаются попытки еще что-то читать. Но отношение к данным, уже находившимся в очереди при­ложения в момент выполнения shutdown, а также к приему новых дан­ных от хоста на другом конце различное. В UNIX все ранее принятые, но еще не прочитанные данные уничтожаются, так что приложение их уже не получит. Если поступают новые данные, то TCP их подтверж­дает и тут же отбрасывает, поскольку приложение не хочет принимать новые данные. Наоборот, в соответствии с Winsock соединение вообще разрывается, если в очереди есть еще данные или поступают новые Поэтому некоторые авторы (например, [Quinn and Shute 1996]) считают, что под Windows использование конструкции


shutdown (s, 0) ;

небезопасно.

how = 1. Закрывается отправляющая сторона соединения. В сокете делается пометка, что данные посылаться больше не будут; все последующие пытки выполнить для него операцию записи заканчиваются ошибкой. После того как вся информация из буфера отправлена, TCP посылает сегмент FIN, сообщая партнеру, что данных больше не будет. Это называется полузакрытием (half close). Такое использование вызова shutdown наиболее типично, и его семантика в обеих реализациях одинакова.

how = 2. Закрываются обе стороны соединения. Эффект такой же, как при выполнении вызова shutdown дважды, один раз с how = 0, а другой - с how = 1. Хотя, на первый взгляд, обращение

shutdown (s, 2);

эквивалентно вызову close или closesocket, в действительности это не так. Обычно нет причин для вызова shutdown с параметром how = 2, но в работе [Quinn and Shute 1996] сообщается, что в некоторых реализациях Winsock вызов closesocket работает неправильно, если предварительно не было обращения к shutdown с how = 2. В соответствии с Winsock вы­зов shutdown с how= 2 создает ту же проблему, что и вызов с how = 0, - может быть разорвано соединение.

Между закрытием сокета и вызовом shutdown есть существенные различия. Во-первых, shutdown не закрывает сокет по-настоящему, даже если он вызван с параметром 2. Иными словами, ни сокет, ни ассоциированные с ним ресурсы (за исключением буфера приема, если how= 0 или 2) не освобождаются. Кроме того, воздействие shutdown распространяется на все процессы, в которых этот сокет от­крыт. Так, например, вызов shutdown с параметром how = 1 делает невозможной запись в этот сокет для всех его владельцев. При вызове же с lose или closesocket все остальные процессы могут продолжать пользоваться сокетом.

Последний факт во многих случаях можно обратить на пользу. Вызывая shutdown c how = 1, будьте уверены, что партнер получит EOF, даже если этот сокет открыт и другими процессами. При вызове close или closesocket это не гарантируется, поскольку TCP не пошлет FIN, пока счетчик ссылок на сокет не станет равным нулю. А это произойдет только тогда, когда все процессы закроют этот сокет.

Наконец, стоит упомянуть, что, хотя в этом разделе говорится о TCP, вызов shutdown применим и к UDP. Поскольку нет соединения, которое можно закрыть, польза обращения к shutdown с how = 1 или 2, остается под вопросом, но задавать параметр how - 0 можно для предотвращения приема датаграмм из конкретного UDP-порта.


Содержание раздела