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.
Листинг 2.5. Объявление констант SD_XXX для Delphi 5 и более ранних версий
const
SD_RECEIVE = 0;
SD_SEND = 1;
SD_BOTH = 2;
Для освобождения ресурсов, связанных с сокетом, служит функция
closesocket
, которая освобождает память, выделенную для буферов, и порт. Ее единственный параметр задает сокет, который требуется закрыть, а возвращаемое значение — ноль или
SOCKET_ERROR
. После вызова этой функции соответствующий дескриптор сокета перестает иметь смысл, и использовать его больше нельзя.
По умолчанию функция
closesocket
немедленно возвращает управление вызвавшей ее программе, а процесс закрытия сокета начинает выполняться в фоновом режиме. Под закрытием подразумевается не только освобождение ресурсов, но и отправка данных, которые остались в выходном буфере сокета. Вопрос о том, как изменить поведение функции
closesocket
, будет обсуждаться в разд. 2.1.17. Если сокет закрывается одной нитью в тот момент, когда другая нить пытается выполнить какую-либо операцию с этим сокетом, то эта операция завершается с ошибкой.
Функция
shutdown
нужна в первую очередь для того, чтобы заранее сообщить партнеру по связи о намерении завершить связь, причем это имеет смысл только для протоколов, поддерживающих соединение. В случае UDP функцию shutdown вызывать практически бессмысленно, можно сразу вызывать
closesocket
. При использовании TCP удаленная сторона получает сигнал о выключении партнера, но стандартная библиотека сокетов не позволяет программе обнаружить его получение (такие функции есть в сокетах Windows, о чем мы будем говорить далее). Но этот сигнал может быть важен для внутрисистемных функций, реализующих сокеты. Windows-версия библиотеки сокетов относится к отсутствию данного сигнала достаточно либерально, поэтому вызов shutdown в том случае, когда и клиент, и сервер работают под управлением Windows, не обязателен. Но реализации TCP в других системах не всегда столь же снисходительно относятся к подобной небрежности. Результатом может стать долгое (до двух часов) "подвешенное" состояние сокета в той системе, когда с ним и работать уже нельзя, и информации об ошибке программа не получает. Поэтому в случае TCP лучше не пренебрегать вызовом
shutdown
, чтобы сокет на другой стороне не имел проблем.
MSDN рекомендует следующий порядок закрытия TCP-сокета. Во-первых, сервер не должен закрывать свой сокет по собственной инициативе, он может это делать только после того, как был закрыт связанный с ним клиентский сокет. Клиент начинает закрытие сокета с вызова
shutdown
с параметром
SD_SEND
. Сервер после этого сначала получает все данные, которые оставались в буфере сокета клиента, а затем получает от клиента сигнал о завершении передачи. Тем не менее сокет клиента продолжает работать на прием, поэтому сервер при необходимости может на этом этапе послать клиенту какие-либо данные, если это необходимо. Затем сервер
вызывает
shutdown
с параметром
SD_SEND
, и сразу после этого —
closesocket
. Клиент продолжает читать данные из входящего буфера сокета до тех пор, пока не будет получен сигнал о завершении передачи сервером. После этого клиент также вызывает
closesocket
. Такая последовательность гарантирует, что данные не будут потеряны, но, как мы уже обсуждали ранее, она не может быть реализована в рамках стандартных сокетов из-за невозможности получить сигнал о завершении передачи, посланный удаленной стороной. Поэтому на практике следует реализовывать упрощенный способ завершения связи: клиент вызывает
shutdown
с параметром
SD_SEND
или
SD_BOTH
и сразу после этого —
closesocket
. Сервер при попытке выполнить операцию с сокетом получает ошибку, после которой также вызывает
closesocket
. Вызов
shutdown
на стороне сервера при этом не нужен, т.к. в этот момент соединение уже потеряно, и высылать данные из буфера вместе с сигналом завершения уже некуда.
2.1.9. Передача данных при использовании UDP
Мы наконец-то добрались до изучения того, ради чего сокеты и создавались: как передавать и получать с их помощью данные. По традиции начнем рассмотрение с более простого протокола UDP. Функции, которые рассматриваются в этом разделе, могут работать и с другими протоколами, и от этого их поведение может меняться. Мы здесь описываем только их поведение при использовании UDP.
Для передачи данных удалённому сокету предусмотрена функция
sendto
, описанная следующим образом:
function sendto(s: TSocket; var Buf; len, flags: Integer; var addrto: TSockAddr; tolen: Integer): Integer;
Первый параметр данной функции задаёт сокет, который служит для передачи данных. Здесь нужно указать значение, полученное ранее от функции
socket
. Параметр
Buf
задаёт буфер, в котором хранятся данные для отправки, а параметр
len
— размер этих данных в байтах. Параметр
flags
позволяет указать некоторые дополнительные опции, которых мы здесь касаться не будем, т.к. в большинстве случаев они не нужны. Пока следует запомнить, что параметр
flags
в функции
sendto
, а также в других функциях, где он встречается, должен быть равен нулю. Параметр
addrto
задает адрес (состоящий из IP-адреса и порта) удаленного сокета, который должен получить эти данные. Значение параметра
addrto
должно формироваться по тем же правилам, что значение аналогичного параметра функции bind, за исключением того, что IP-адрес и порт должны быть заданы явно (т.е. не допускаются значения
INADDR_ANY
и нулевой номера порта). Параметр
tolen
задает длину буфера, отведенного для адреса, и должен быть равен
SizeOf(TSockAddr)
. Один вызов функции
sendto
приводит к отправке одной дейтаграммы. Данные, переданные в
sendto
, никогда не разбиваются на несколько дейтаграмм, и данные, переданные последовательными вызовами
sendto
, никогда не объединяются в одну дейтаграмму.
Функцию
sendto
можно использовать с сокетами, не привязанными к адресу. В этом случае внутри библиотеки сокетов будет неявно вызвана функция
bind
для привязки сокета к адресу
INADDR_ANY
и нулевому порту (т.е. адрес и порт будут выбраны системой).
Если выходной буфер сокета имеет ненулевой размер,
sendto
помещает данные в этот буфер и сразу возвращает управление программе, а собственно отправка данных осуществляется библиотекой сокетов в фоновом режиме. Поэтому успешное завершение
sendto
гарантирует только то, что данные скопированы в буфер и что на момент их копирования не обнаружено никаких проблем, которые делали бы невозможной их отправку. Но такие проблемы могут возникнуть позже, поэтому даже в случае успешного завершения
sendto
отправитель не получает гарантии, что данные посланы. Если в выходном буфере сокета не хватает места для новой порции данных,
sendto
не возвращает управление программе (т.е. блокирует ее) до тех пор, пока в буфере за счет фоновой отправки не появится достаточно места или не будет обнаружена ошибка.