Ранее мы уже говорили, что в системах с несколькими сетевыми картами привязка сокета к адресу в том случае, когда его выбор доверен системе, может осуществляться не во время выполнения функции bind
, а позже, когда системе станет понятно, зачем используется этот сокет. Например, когда TCP-клиент осуществляет подключение к серверу, система по адресу этого сервера определяет, через какую карту должен идти обмен, и выбирает соответствующий адрес. То же самое происходит с UDP-клиентом: когда он отправляет первую дейтаграмму, система по адресу получателя определяет, к какой карте следует привязать сокет. Поэтому клиент и в данном случае может оставить выбор адреса на усмотрение системы. С серверами все несколько сложнее. Система привязывает сокет UDP-сервера к адресу, он ожидает получения пакета. В этот момент система не имеет никакой информации о том, с какими узлами будет вестись обмен через данный сокет, и может выбрать не тот адрес, который нужен. Поэтому сокеты UDP-серверов, работающих в подобных системах, должны явно привязываться к требуемому адресу. Сокеты TCP-серверов, находящиеся в режиме ожидания и имеющие адрес INADDR_ANY
, допускают подключение к ним по любому сетевому интерфейсу, который имеется в системе. Сокет, который создается таким сервером при подключении клиента, будет автоматически привязан к IP-адресу того сетевого интерфейса, через который осуществляется взаимодействие с подключившимся клиентом. Таким образом, сокеты, созданные для взаимодействия с разными клиентами, могут оказаться привязанными к разным адресам.
После успешного завершения функций socket
и bind
сокет создан и готов к работе. Дальнейшие действия с ним зависят от того, какой протокол он реализует и для какой роли предназначен. Мы разберем эти операции в разделах, посвященных соответствующим протоколам. Там же мы увидим, что в некоторых случаях можно обойтись без вызова функции bind
, поскольку она будет неявно вызвана при вызове других функций библиотеки сокетов.
Когда сокет больше не нужен, следует освободить связанные с ним ресурсы. Это выполняется в два этапа: сначала сокет "выключается", а потом закрывается.
Для выключения сокета предусмотрена функция shutdown
, имеющая следующий прототип:
function shutdown(s: TSocket; how: Integer): Integer;
Параметр s
определяет сокет, который необходимо выключить, параметр how
может принимать значения SD_RECEIVE
, SD_SEND
или SD_BOTH
. Функция возвращает ноль при успешном выполнении и SOCKET_ERROR
— в случае ошибки. Вызов функции с параметром SD_RECEIVE
запрещает чтение данных из входного буфера сокета. Однако на у ровне протокола вызов этой функции игнорируется: дейтаграммы UDP и пакеты TCP, посланные данному сокету, продолжают помещаться в буфер, хотя программа уже не может их оттуда забрать.
При указании значения SD_SEND
функция запрещает отправку данных через сокет. В случае протокола TCP при этом удаленный сокет получает специальный сигнал, предусмотренный данным протоколом, уведомляющий о том, что больше данные посылаться не будут. Если на момент вызова shutdown
в буфере для исходящих остаются данные, сначала посылаются они. а потом только сигнал о завершении. Поскольку протокол UDP подобных сигналов не предусматривает, то в этом случае shutdown
просто запрещает библиотеке сокетов использовать указанный сокет для отправки данных.
Параметр SD_BOTH
позволяет одновременно запретить и прием, и передачу данных через сокет.
Модуль WinSock
до пятой версии Delphi включительно содержит ошибку: в нем не определены константы SD_XXX
. Чтобы использовать их в своей программе, нужно объявить их так, как показано в листинге 2.5.
const
SD_RECEIVE = 0;
SD_SEND = 1;
SD_BOTH = 2;
Для освобождения ресурсов, связанных с сокетом, служит функция closesocket
, которая освобождает память, выделенную для буферов, и порт. Ее единственный параметр задает сокет, который требуется закрыть, а возвращаемое значение — ноль или SOCKET_ERROR
. После вызова этой функции соответствующий дескриптор сокета перестает иметь смысл, и использовать его больше нельзя.