Если условия, при которых эти функции выполняются без блокирования, выполнены, то их поведение в блокирующем и неблокирующем режимах идентично. Если же выполнение операции без блокирования невозможно, функции возвращают результат, указывающий на ошибку . Чтобы понять, произошла ли ошибка из-за необходимости блокирования или из-за чего-либо еще. программа должна вызвать функцию WSAGetLastError
. Если она вернет WSAEWOULDBLOCK
, значит, никакой ошибки не было, но выполнение операции без блокирования невозможно. Закрывать сокет и создавать новый после WSAEWOULDBLOCK
, разумеется, не нужно, т.к. ошибки не было, и связь (в случае TCP) осталась неразорванной.
Следует отметить, что при нулевом выходном буфере сокета (т.е. когда функция send
передаст данные напрямую в сеть) и большом объеме информации функция send
может выполняться достаточно долго, т.к. эти данные отправляются по частям, и на каждую часть в рамках протокола TCP получаются подтверждения. Но эта задержка не считается блокированием, и в данном случае send
будет одинаково вести себя с блокирующими и неблокирующими сокетами, т.е. вернет управление программе лишь после того, как все данные окажутся в сети.
Для функций accept
, recv
и send
WSAEWOULDBLOCK
означает, что операцию следует повторить через некоторое время, и, может быть, в следующий раз она не потребует блокирования и будет выполнена. Функция connect
в этом случае начинает фоновую работу по установлению соединения. О завершении этой работы можно судить по готовности сокета, которая проверяется с помощью функции select
. Листинг 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
завершит работу только после того, как будет выполнена попытка соединения, и о результатах этой попытки можно будет судить по тому, в какое из множеств входит сокет.
Из приведенного примера не видно, какие преимущества дает неблокирующий сокет по сравнению с блокирующим. Казалось бы, проще вызвать connect в блокирующем режиме, дождаться результата и лишь потом переводить сокет в неблокирующий режим. Во многих случаях это действительно может оказаться удобнее. Преимущества соединения в неблокирующем режиме связаны с тем, что между вызовами connect
и select
программа может выполнить какую-либо полезную работу, а в случае блокирующего сокета программа будет вынуждена сначала дождаться завершения работы функции connect и лишь потом сделать что-то еще.
Функция send
для неблокирующего сокета также имеет некоторые специфические черты поведения. Они проявляются, когда свободное место в выходном буфере есть, но его недостаточно для хранения данных, которые программа пытается отправить с помощью этой функции. В этом случае функция send
, согласно документации, может скопировать в выходной буфер такой объем данных, для которого хватает места. При этом она вернет значение, равное этому объему (оно будет меньше, чем значение параметра len
, заданного программой). Оставшиеся данные программа должна отправить позже, вызвав еще раз функцию send
. Такое поведение функции send возможно только при использовании TCP. В случае UDP дейтаграмма никогда не разделяется на части, и если в выходном буфере не хватает места для всей дейтаграммы, то функция send
возвращает ошибку, a WSAGetLastError
— WSAEWOULDBLOCK
.