, будет считаться готовым, если он соединен, а в его выходном буфере есть место. Сокет, входящий в множество
SetE
, будет считаться готовым, если попытка соединения не удалась. До тех пор, пока попытка соединения не завершилась (успехом или неудачей), ни одно из этих условий готовности не будет выполнено. Таким образом, в данном случае
select
завершит работу только после того, как будет выполнена попытка соединения, и о результатах этой попытки можно будет судить по тому, в какое из множеств входит сокет.
Из приведенного примера не видно, какие преимущества дает неблокирующий сокет по сравнению с блокирующим. Казалось бы, проще вызвать connect в блокирующем режиме, дождаться результата и лишь потом переводить сокет в неблокирующий режим. Во многих случаях это действительно может оказаться удобнее. Преимущества соединения в неблокирующем режиме связаны с тем, что между вызовами
connect
и
select
программа может выполнить какую-либо полезную работу, а в случае блокирующего сокета программа будет вынуждена сначала дождаться завершения работы функции connect и лишь потом сделать что-то еще.
Функция
send
для неблокирующего сокета также имеет некоторые специфические черты поведения. Они проявляются, когда свободное место в выходном буфере есть, но его недостаточно для хранения данных, которые программа пытается отправить с помощью этой функции. В этом случае функция
send
, согласно документации, может скопировать в выходной буфер такой объем данных, для которого хватает места. При этом она вернет значение, равное этому объему (оно будет меньше, чем значение параметра
len
, заданного программой). Оставшиеся данные программа должна отправить позже, вызвав еще раз функцию
send
. Такое поведение функции send возможно только при использовании TCP. В случае UDP дейтаграмма никогда не разделяется на части, и если в выходном буфере не хватает места для всей дейтаграммы, то функция
send
возвращает ошибку, a
WSAGetLastError
—
WSAEWOULDBLOCK
.
Сразу отметим, что, хотя спецификация допускает частичное копирование функцией
send
данных в буфер сокета, на практике такое поведение наблюдать пока не удалось: все эксперименты показали, что функция
send
всегда либо копирует данные целиком, расширяя при необходимости буфер, либо
дает ошибку
WSAEWOULDBLOCK
. Далее этот вопрос будет обсуждаться подробнее. Тем не менее при написании программ следует учитывать возможность частичного копирования, т.к. оно может появиться в тех условиях или в тех реализациях библиотеки сокетов, которые в наших экспериментах не были проверены.
2.1.16. Сервер на неблокирующих сокетах
В этом разделе мы создадим сервер, основанный на неблокирующих сокетах. Это будет наш первый сервер, не использующий функцию
ReadFromSocket
(см. листинг 2.13). Этот сервер (пример
NonBlockingServer
на компакт-диске) состоит из одной нити, которая никогда не будет блокироваться сокетными операциями, т.к. все сокеты используют неблокирующий режим. На форме находится таймер, по сигналам которого сервер выполняет попытки чтения данных с сокетов всех подключившихся клиентов. Если данных нет, функция recv немедленно завершается с ошибкой
WSAEWOULDBLOCK
, и сервер переходит к попытке чтения из следующего сокета.
Запуск сервера (листинг 2.30) мало чем отличается от запуска многонитевого сервера (см. листинг 2.19). Практически вся разница заключается в том, что вместо запуска "слушающей" нити сокет переводится в неблокирующий режим и включается таймер.
Листинг 2.30. Инициализация сервера на неблокирующих сокетах