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

ЖАНРЫ

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

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

Шрифт:

// В частности, он работает в асинхронном режиме,

// и его событие FD_ACCEPT связано с сообщением WM_ACCEPTMESSAGE.

// Так как нам это совершенно не нужно, отменяем асинхронный

// режим и делаем сокет блокирующим.

if WSAAsyncSelect(ClientSocket, Handle, 0, 0) = SOCKET_ERROR then

begin

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

'подключившегося сокета:'#13#10 + GetErrorString,

mtError, [mbOK], 0);

closesocket(ClientSocket);

Exit;

end;

Arg := 0;

if ioctlsocket(ClientSocket, FIONBIO, Arg) = SOCKET_ERROR then

begin

MessageDlg('Ошибка
при переводе подключившегося сокета ' +

'в блокирующий режим:'#13#10 + GetErrorString,

mtError, [mbOK], 0);

closesocket(ClientSocket);

Exit;

end;

// Создаем запись для нового подключения и заполняем ее

New(NewConnection);

NewConnection.ClientSocket := ClientSocket;

NewConnection.ClientAddr :=

Format('%u.%u.%u.%u:%u, [

Ord(ClientAddr.sin_addr.S_un_b.s_b1),

Ord(ClientAddr.sin_addr.S_un_b.s_b2),

Ord(ClientAddr.sin_addr.S_un_b.s_b3),

Ord(ClientAddr.sin_addr.S_un_b.s_b4),

ntohs(ClientAddr.sin_port)]);

NewConnection.Offset := 0;

NewConnection.BytesLeft := SizeOf(Integer);

NewConnection.Overlapped.hEvent := 0;

// Добавляем запись нового соединения в список

FConnections.Add(NewConnection);

AddMessageToLog('Зафиксировано подключение с адреса ' +

NewConnection.ClientAddr);

// Начинаем перекрытый обмен с сокетом.

// Начинаем, естественно, с чтения длины строки,

// в качестве принимающего буфера используем NewConnection.MsgSize

Buf.Len := NewConnection.BytesLeft;

Buf.Buf := @NewConnection.MsgSize;

Flags := 0;

if WSARecv(NewConnection.ClientSocket, @Buf, 1, NumBytes, Flags,

@NewConnection.Overlapped, ReadLenCompleted) = SOCKET_ERROR then

begin

if WSAGetLastError <> WSA_IO_PENDING then

begin

AddMessageToLog('Клиент ' + NewConnection.ClientAddr +

' - ошибка при чтении длины строки: ' + GetErrorString);

RemoveConnection(NewConnection);

end;

end;

 end;

end;

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

TConnection
и добавления его в список, запускается первая операция перекрытого чтения с помощью функции
WSARecv
. Об окончании этой операции будет сигнализировать вызов функции
ReadLenCompleted
, которая передана в
WSARecv
в качестве параметра.

Как мы уже говорили ранее, в программе

OverlappedServer
есть три разных функции завершения:
ReadLenCompleted
,
ReadMsgCompleted
и
SendMsgCompleted
. Последовательность работы с ними такая: сначала для чтения длины строки вызывается
WSARecv
, в качестве буфера передастся
Connection.MsgSize
, в качестве функции завершения —
ReadLenCompleted
(это мы уже видели в листинге 2.77). Когда вызывается
ReadLenCompleted
, это значит, что операция чтения уже завершена и прочитанная длина находится в
Connection.MsgSize
. Поэтому в функции
ReadLenCompleted
выделяем нужный размер для строки
Connection.Msg
и запускаем следующую операцию перекрытого чтения — с буфером
Connection.Msg
и функцией завершения
ReadMsgCompleted
. В этой функции полученная строка показывается пользователю, формируется ответ, и запускается следующая операция перекрытого ввода-вывода — отправка строки клиенту. В качестве буфера в функцию
WSASend
передаётся
Connection.Msg
, а в качестве функции завершения —
SendMsgCompleted
. В функции
SendMsgCompleted
вновь вызывается
WSARecv
с буфером
Connection.MsgSize
и функцией завершения
ReadLenCompleted
, и таким образом сервер возвращается к первому этапу взаимодействия с клиентом.

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

Листинг 2.78. Функции завершения

// Функция ReadLenCompleted используется в качестве функции завершения

// для перекрытого чтения длины строки

procedure ReadLenCompleted(dwError: DWORD; cdTransferred: DWORD; lpOverlapped: PWSAOverlapped; dwFlags: DWORD); stdcall;

var

 // Указатель на соединение

 Connection: PConnection;

 // Указатель на буфер

 Buf: TWSABuf;

 // Параметры для WSARecv

 NumBytes, Flags: DWORD;

begin

 // Для идентификации операции в функцию передается указатель

 // на запись TWSAOverlapped. Ищем по этому указателю

 // подходящее соединение в списке FConnections.

 Connection := ServerForm.GetConnectionByOverlapped(lpOverlapped);

 if Connection = nil then

 begin

ServerForm.AddMessageToLog(

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

Exit;

 end;

 // Проверяем, что не было ошибки

 if dwError <> 0 then

 begin

ServerForm.AddMessageToLog('Клиент ' + Connection.ClientAddr +

' - ошибка при чтении длины строки: ' + GetErrorString(dwError));

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