При задании аргумента FIONBIO
параметр arg
рассматривается как входной. Если его значение равно нулю, сокет будет переведен в блокирующий режим, если не равно нулю — в неблокирующий. Таким образом, чтобы перевести который сокет s
в неблокирующий режим, нужно выполнить следующие действия (листинг 2.28).
Листинг 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
— блокирует нить, если в выходном буфере сокета недостаточно места, чтобы скопировать туда переданную информацию.
Если условия, при которых эти функции выполняются без блокирования, выполнены, то их поведение в блокирующем и неблокирующем режимах идентично. Если же выполнение операции без блокирования невозможно, функции возвращают результат, указывающий на ошибку . Чтобы понять, произошла ли ошибка из-за необходимости блокирования или из-за чего-либо еще. программа должна вызвать функцию WSAGetLastError
. Если она вернет WSAEWOULDBLOCK
, значит, никакой ошибки не было, но выполнение операции без блокирования невозможно. Закрывать сокет и создавать новый после WSAEWOULDBLOCK
, разумеется, не нужно, т.к. ошибки не было, и связь (в случае TCP) осталась неразорванной.
Следует отметить, что при нулевом выходном буфере сокета (т.е. когда функция send
передаст данные напрямую в сеть) и большом объеме информации функция send
может выполняться достаточно долго, т.к. эти данные отправляются по частям, и на каждую часть в рамках протокола TCP получаются подтверждения. Но эта задержка не считается блокированием, и в данном случае send
будет одинаково вести себя с блокирующими и неблокирующими сокетами, т.е. вернет управление программе лишь после того, как все данные окажутся в сети.
Для функций accept
, recv
и send
WSAEWOULDBLOCK
означает, что операцию следует повторить через некоторое время, и, может быть, в следующий раз она не потребует блокирования и будет выполнена. Функция connect
в этом случае начинает фоновую работу по установлению соединения. О завершении этой работы можно судить по готовности сокета, которая проверяется с помощью функции select
. Листинг 2.29 иллюстрирует это.
Листинг 2.29. Установление связи при использовании неблокирующего сокета
var
S: TSocket;
Block: u_long;
SetW, SetE: TFDSet;
begin
S :=socket(AF_INET, SOCK_STREAM, 0);
...
Block := 1;
ioctlsocket(S, FIONBIO, Block);
connect(S, ...);
if WSAGetLastError <> WSAEWOULDBLOCK then
begin
// Произошла ошибка
raise ...
end;
FD_ZERO(SetW);
FD_SET(S, SetW);
FD_ZERO(SetE);
FD_SET(S, SetE);
select(0, nil, @SetW, @SetE, nil);
if FD_ISSET(S, SetW) then
// Connect выполнен успешно
else if FD_ISSET(S, SetE) then
// Соединиться не удалось
else
// Произошла еще какая-то ошибка
Напомним, что сокет, входящий в множество SetW
, будет считаться готовым, если он соединен, а в его выходном буфере есть место. Сокет, входящий в множество SetE
, будет считаться готовым, если попытка соединения не удалась. До тех пор, пока попытка соединения не завершилась (успехом или неудачей), ни одно из этих условий готовности не будет выполнено. Таким образом, в данном случае select
завершит работу только после того, как будет выполнена попытка соединения, и о результатах этой попытки можно будет судить по тому, в какое из множеств входит сокет.
Читать дальше
Конец ознакомительного отрывка
Купить книгу