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

ЖАНРЫ

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

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

Шрифт:

Как обычно, для каждого соединения создается экземпляр записи

TConnection
, которая на этот раз выглядит так, как показано в листинге 2.76.

Листинг 2.76. Тип
TConnection

// Информация о соединении с клиентом:

// ClientSocket - сокет, созданный для взаимодействия с клиентом

// ClientAddr - строковое представление адреса клиента

// MsgSite - длина строки, получаемая от клиента

// Msg - строка, получаемая от клиента или отправляемая ему

// Offset - количество
байтов, уже полученных от клиента

// или отправляемых ему на данном этапе

// BytesLeft - сколько байтов осталось получить от клиента

// или отправить ему на данном этапе

// Overlapped - структура для выполнения перекрытой операции

PConnection = ^TConnection;

TConnection = record

 ClientSocket: TSocket;

 ClientAddr: string;

 MsgSize: Integer;

 Msg: string;

 Offset: Integer;

 BytesLeft: Integer;

 Overlapped: TWSAOverlapped;

end;

Основное отличие этого варианта типа

TConnection
от того, что применялся ранее в примерах
NonBlockingServer
и
AsyncSelectServer
(см. разд. 2.1.16 и 2.2.6, а также листинг 2.31) — это отсутствие поля
Phase
, которое хранит этап взаимодействия с клиентом. Разумеется, в программе
OverlappedServer
взаимодействие с клиентом также разбивается на три этапа, но реализуется другой способ для того, чтобы различать этапы — для каждого этапа создается своя процедура завершения.

Примечание

Использование одной процедуры завершения для всех трех этапов и распознавание в ней этапов с помощью поля

Phase
в случае перекрытого ввода-вывода также возможно. Рекомендуем написать такой вариант сервера в качестве самостоятельного упражнения.

Поле

Overlapped
содержит структуру
TWSAOverlapped
, которой программа непосредственно не пользуется, она только передает указатель на эту структуру в функции
WSARecv
и
WSASend
. Напомним, что одновременно может выполняться несколько операций перекрытого ввода-вывода, но у каждой из этих операций должен быть свой экземпляр
TWSAOverlapped
. Гак как в нашем случае с одним клиентом в каждый момент времени может выполняться не более одной операции, мы создаем по одному экземпляру
TWSAOverlapped
на каждого клиента.

Функция для перекрытого подключения клиентов существует — это

AcceptEx
, с которой мы познакомимся в разд. 2.2.12. Но она неудобна при работе совместно с
WSARecv
и
WSASend
, особенно в таком строго типизированном языке, как Delphi. Поэтому подключение клиентов мы будем отслеживать с помощью уже опробованной технологии асинхронных сокетов на сообщениях. Код запуска сервера OverlappedServer выглядит идентично коду запуска AsyncSelectServer (см. листинг 2.30): точно так же создается сокет, ставится в режим прослушивания, а затем его событие
FD_ACCEPT
привязывается к сообщению
WM_ACCEPTMESSAGE

Сам обработчик

WM_ACCEPTMESSAGE
выглядит теперь следующим образом (листинг 2.77).

Листинг 2.77.
Обработчик сообщения WM_
ACCEPTMESSAGE

procedure TServerForm.WMAcceptMessage(var Msg: TWMSocketMessage);

var

 NewConnection: PConnection;

 // Сокет, который создается для вновь подключившегося клиента

 ClientSocket: TSocket;

 // Адрес подключившегося клиента

 ClientAddr: TSockAddr;

 // Длина адреса

 AddrLen: Integer;

 // Аргумент для перевода сокета в неблокирующий режим

 Arg: u_long;

 // Буфер для операции перекрытого чтения

 Buf: TWSABuf;

 NumBytes, Flags: DWORD;

begin

 // Страхуемся от "тупой" ошибки

 if Msg.Socket <> FServerSocket then

raise ESocketError.Create(

'Внутренняя ошибка сервера - неверный серверный сокет');

 // Обрабатываем ошибку на сокете, если она есть

 if Msg.SockError <> 0 then

 begin

MessageDlg('Ошибка при подключении клиента:'#13#10 +

GetErrorString(Msg.SockError) +

#13#10'Сервер будет ocтановлен', mtError, [mbOK], 0);

ClearConnections;

closesocket(FServerSocket);

OnStopServer;

Exit;

 end;

 // Страхуемся от ещё одной "тупой" ошибки

 if Msg.SockEvent <> FD_ACCEPT then

raise ESocketError.Create(

'Внутренняя ошибка сервера - неверное событие на сокете');

 AddrLen := SizeOf(TSockAddr);

 ClientSocket := accept(FServerSocket, @ClientAddr, @AddrLen);

 if ClientSocket = INVALID_SOCKET then

 begin

// Если произошедшая ошибка - WSAEWOULDBLOCK, это просто означает

// что на данный момент подключений нет, а вообще все а порядке,

// поэтому ошибку WSAEWOULDBLOCK мы просто игнорируем. Прочие же

// ошибки могут произойти только в случае серьезных проблем,

// которые требуют остановки сервера.

if WSAGetLastError <> WSAEWOULDBLOCK then

begin

MessageDlg('Ошибка при подключении клиента:'#13#10 +

GetErrorString + #13#10'Сервер будет остановлен',

mtError, [mbOK], 0);

ClearConnections;

closesocket(FServerSocket);

OnStopServer;

end;

 end

 else

 begin

// Новый сокет наследует свойства слушающего сокета.

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