Вызов 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-порта.