Ранее мы столкнулись с функциями, которые могут надолго приостановить работу вызвавшей их нити, если действие не может быть выполнено немедленно. Это функции accept
, recv
, recvfrom
, send
, sendto
и connect
(в дальнейшем в этом разделе мы не будем упоминать функции recvfrom
и sendto
, потому что они в смысле блокирования эквивалентны функциям recv
и send
соответственно, и все, что будет здесь сказано о recv
и send
, применимо к recvfrom
и sendto
). Такое поведение не всегда удобно вызывающей программе, поэтому в библиотеке сокетов предусмотрен особый режим работы сокетов — неблокирующий. Этот режим может быть установлен или отменен дм каждого сокета индивидуально с помощью функции ioctlsocket
, имеющей следующий прототип:
function ioctlsocket(s: TSocket; cmd: DWORD; var arg: u_long): Integer;
Данная функция предназначена для выполнения нескольких логически мало связанных между собой действий. Возможно, у разработчиков первых версий библиотеки сокетов были причины экономить на количестве функций, потому что мы и дальше увидим, что иногда непохожие операции выполняются одной функцией. Но вернемся к ioctlsocket
. Ее параметр cmd
определяет действие, которое выполняет функция, а также смысл параметра arg
. Допустимы три значения параметра cmd
: SIOCATMARK
, FIONREAD
и FIONBIO
. При задании SIOCATMARK
параметр arg
рассматривается как выходной: в нем возвращается ноль, если во входном буфере сокета имеются высокоприоритетные данные, и ненулевое значение, если таких данных нет (как уже было оговорено, мы в этой книге не будем касаться передачи высокоприоритетных данных).
При cmd
, равном FIONREAD
, в параметре arg
возвращается размер данных, находящихся во входном буфере сокета, в байтах. При использовании TCP это число равно максимальному количеству информации, которое можно получить на данный момент за один вызов recv
. Для UDP это значение равно суммарному размеру всех находящихся в буфере дейтаграмм (напомним, что прочитать несколько дейтаграмм за один вызов recv
нельзя). Функция ioctlsocket
с параметром FIONREAD
может использоваться для проверки наличия данных с целью избежать вызова recv тогда, когда это может привести к блокированию, или для организации вызова recv в цикле до тех пор, пока из буфера не будет извлечена вся информация.
При задании аргумента FIONBIO
параметр arg
рассматривается как входной. Если его значение равно нулю, сокет будет переведен в блокирующий режим, если не равно нулю — в неблокирующий. Таким образом, чтобы перевести который сокет s
в неблокирующий режим, нужно выполнить следующие действия (листинг 2.28).
var
S: TSocket;
Arg: u_long;
begin
...
Arg := 1;
ioctlsocket(S, FIONBIO, Arg);
Пока программа использует только стандартные сокеты (а не сокеты Windows), сокет может быть переведен в неблокирующий или обратно в блокирующий режим в любой момент. Неблокирующим может быть сделан любой сокет (серверный или клиентский) независимо от протокола.
Функция ioctlsocket
возвращает нулевое значение в случае успеха и ненулевое — при ошибке. В примере, как всегда, проверка результата для краткости опущена.
Итак, по умолчанию сокет работает в блокирующем режиме. С особенностями работы функций accept
, connect
, recv
и send
в этом режиме мы уже познакомились. Теперь рассмотрим то, как они ведут себя в неблокирующем режиме. Для этого сначала вспомним, когда эти функции блокируют вызвавшую их нить.
□ accept
— блокирует нить, если на момент ее вызова очередь подключений пуста.
□ connect
— в случае TCP блокирует сокет практически всегда, потому что требуется время на установление связи с удаленным сокетом. Без блокирования вызов connect
выполняется только в том случае, если какая-либо ошибка не дает возможности приступить к операции установления связи. Также без блокирования функция connect выполняется при использовании UDP, потому что в данном случае она только устанавливает фильтр для адресов.
□ recv
— блокирует нить, если на момент вызова входной буфер сокета пуст.
□ send
— блокирует нить, если в выходном буфере сокета недостаточно места, чтобы скопировать туда переданную информацию.