Чтение онлайн

ЖАНРЫ

О чём не пишут в книгах по Delphi

Григорьев Антон Борисович

Шрифт:

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
.

Сразу отметим, что, хотя спецификация допускает частичное копирование функцией

send
данных в буфер сокета, на практике такое поведение наблюдать пока не удалось: все эксперименты показали, что функция
send
всегда либо копирует данные целиком, расширяя при необходимости буфер, либо
дает ошибку
WSAEWOULDBLOCK
. Далее этот вопрос будет обсуждаться подробнее. Тем не менее при написании программ следует учитывать возможность частичного копирования, т.к. оно может появиться в тех условиях или в тех реализациях библиотеки сокетов, которые в наших экспериментах не были проверены.

2.1.16. Сервер на неблокирующих сокетах

В этом разделе мы создадим сервер, основанный на неблокирующих сокетах. Это будет наш первый сервер, не использующий функцию

ReadFromSocket
(см. листинг 2.13). Этот сервер (пример
NonBlockingServer
на компакт-диске) состоит из одной нити, которая никогда не будет блокироваться сокетными операциями, т.к. все сокеты используют неблокирующий режим. На форме находится таймер, по сигналам которого сервер выполняет попытки чтения данных с сокетов всех подключившихся клиентов. Если данных нет, функция recv немедленно завершается с ошибкой
WSAEWOULDBLOCK
, и сервер переходит к попытке чтения из следующего сокета.

Запуск сервера (листинг 2.30) мало чем отличается от запуска многонитевого сервера (см. листинг 2.19). Практически вся разница заключается в том, что вместо запуска "слушающей" нити сокет переводится в неблокирующий режим и включается таймер.

Листинг 2.30. Инициализация сервера на неблокирующих сокетах

// Реакция на кнопку "Запустить" - запуск сервера

procedure TServerForm.BtnStartServerClick(Sender: TObject);

var

 // Адрес, к которому привязывается слушающий сокет

 ServerAddr: TSockAddr;

 NonBlockingArg: u_long;

begin

 // Формируем адрес для привязки.

 FillChar(ServerAddr.sin_zero, SizeOf(ServerAddr.sin_zero), 0);

 ServerAddr.sin_family := AF_INET;

 ServerAddr.sin_addr.S_addr := INADDR_ANY;

 try

ServerAddr.sin_port := htons(StrToInt(EditPortNumber.Text));

if ServerAddr.sin_port = 0 then

begin

MessageDlg('Номер порта должен находиться в диапазоне 1-65535',

mtError, [mbOK], 0);

Exit;

end;

// Создание сокета

FServerSocket := socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

if FServerSocket = INVALID_SOCKET then

begin

MessageDlg('Ошибка при создании сокета: '#13#10 + GetErrorString,

mtError, [mbOK], 0);

Exit;

end;

// Привязка сокета к адресу

if bind(FServerSocket, ServerAddr, SizeOf(ServerAddr)) = SOCKET_ERROR then

begin

MessageDlg('Ошибка при привязке сокета к адреcу: '#13#10 +

GetErrorString, mtError, [mbOK], 0);

closesocket(FServerSocket);

Exit;

end;

Поделиться с друзьями: