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

ЖАНРЫ

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

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

Шрифт:

2.2.6. Пример сервера, основанного на сообщениях

В этом разделе мы напишем сервер, использующий асинхронные сокеты и их сообщения (пример AsyncSelectServer на компакт-диске). Этот сервер будет во многом похож на сервер на основе неблокирующих сокетов (см. разд. 2.1.16), только он не станет проверять по таймеру наличие данных в буфере и возможность отправки данных, а будет выполнять это тогда, когда поступят соответствующие сообщения.

Такая схема работы требует более осторожного подхода. По сигналу от таймера мы сами проверяем, на каком этапе в данный момент находится обмен данными с клиентом. Если, например, идет этап отправки данных, то проверять входной

буфер сокета не нужно, можно оставить это до тех пор, пока не наступит этап чтения данных. При использовании сообщений приходится учитывать, что сообщение о поступлении данных в буфер сокета может прийти в любой момент, в том числе и тогда, когда обмен с клиентом находится на этапе отправки строки. По протоколу сервер не должен читать сообщение в этот момент, необходимо сначала закончить отправку, поэтому приходится данное уведомление игнорировать. Но второго уведомления система не пришлет, соответственно, после окончания отправки данных сервер должен сам вспомнить, что было уведомление, и перейти к операции чтения.

Примечание

Вообще говоря, ситуация, когда сервер не отправит данные за один раз, и их отправка растянется на несколько итераций петли сообщений, настолько редка, что при разработке сервера, к которому не предъявляются повышенные требования по надежности, ее можно было бы вообще не учитывать. Соответственно, возможность получения сервером нового уведомления о поступлении данных до того, как на старое сообщение будет дан ответ, возможна только тогда, когда клиент не соблюдает принятый протокол и посылает несколько сообщений подряд, не дожидаясь ответа. Наш пример призван продемонстрировать наиболее надежный к подобным действиям клиента сервер, поэтому мы его напишем "по всем правилам".

Как обычно, работа сервера начинается с инициализации слушающего сокета, выполняющейся при нажатии кнопки Запустить (листинг 2.50).

Листинг 2.50. Инициализация сервера, основанного на сообщениях

procedure TServerForm.BtnStartServerClick(Sender: TObject);

var

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

 ServerAddr: TSockAddr;

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('Ошибка
при привязке сокета к адресу:'#13#10 +

GetErrorString, mtError, [mbOK], 0);

closesocket(FServerSocket);

Exit;

end;

// Перевод сокета в режим прослушивания

if listen(FServerSocket, SOMAXCONN) = SOCKET_ERROR then

begin

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

GetErrorString, mtError, [mbOK], 0);

closesocket(FServerSocket);

Exit;

end;

// Связь слушающего сокета с событием FD_ACCEPT

if WSAAsyncSelect(FServerSocket, Handle,

WM_ACCEPTMESSAGE, FD_ACCEPT) = SOCKET_ERROR then

begin

MessageDlg('Ошибка при установке асинхронного режима ' +

'cлушающего сокета:'#13#10 + GetErrorString, mtError, [mbOK], 0);

closesocket(FServerSocket);

Exit;

end;

// Перевод элементов управления в состояние "Сервер работает"

LabelPortNumber.Enabled := False;

EditPortNumber.Enabled := False;

BtnStartServer.Enabled := False;

LabelServerState.Caption := 'Сервер работает';

 except

on EConvertError do

// Это исключение может возникнуть только в одном месте -

// при вызове StrToInt(EditPortNumber.Text)

MessageDlg('"' + EditPortNumber.Text +

'" не является целый числом', mtError, [mbOK], 0);

on ERangeError do

// Это исключение может возникнуть только в одном месте -

// при присваивании значения номеру порта

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

mtError, [mbOK], 0);

 end;

end;

Этот код мало чем отличается от того, что мы уже видели (сравните, например, с листингами 2.19 и 2.30). Единственное существенное отличие здесь — вызов функции

WSAAsyncSelect
после перевода сокета в режим прослушивания. Этот вызов связывает событие
FD_ACCEPT
с сообщением
WM_ACCEPTMESSAGE
.

Сообщение

WM_ACCEPTMESSAGE
нестандартное, мы должны сами определить его. Использовать это сообщение сервер будет только для определения момента подключения нового клиента, определять момент прихода данных мы будем с помощью другого сообщения —
WM_SOCKETMESSAGE
, которое тоже нужно определить. И, чтобы легче было писать обработчики для этих сообщений, объявим тип
TWMSocketMessage
, "совместимый" с типом
TMessage
(листинг 2.51).

Листинг 2.51. Сообщения, связанные с сокетами, и тип
TWMSocketMessage
Поделиться с друзьями: