KnigaRead.com/
KnigaRead.com » Компьютеры и Интернет » Программирование » А. Григорьев - О чём не пишут в книгах по Delphi

А. Григорьев - О чём не пишут в книгах по Delphi

На нашем сайте KnigaRead.com Вы можете абсолютно бесплатно читать книгу онлайн А. Григорьев, "О чём не пишут в книгах по Delphi" бесплатно, без регистрации.
Перейти на страницу:

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

  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));

  ServerForm.RemoveConnection(Connection);

  Exit;

 end;

 // Уменьшаем число оставшихся к чтению байтов

 // на размер полученных данных

 Dec(Connection.BytesLeft, cdTransferred);

 if Connection.BytesLeft < 0 then


  // Страховка от "тупой" ошибки

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

   ' - внутренняя ошибка программы: получено больше байтов, ' +

   'чем ожидалось');

  ServerForm.RemoveConnection(Connection);

 end

 else if Connection.BytesLeft = 0 then

 begin

  // Длина строки прочитана целиком

  if Connection.MsgSize <= 0 then

  begin

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

    ' — получена неверная длина строки ' +

    IntToStr(Conneсtion.MsgSizе));

   ServerForm.RemoveConnection(Connection);

   Exit;

  end;

  // Делаем строку нужной длины

  SetLength(Connection.Msg, Connection.MsgSize);

  // Данные пока не прочитаны, поэтому смещение - ноль,

  // осталось прочитать полную длину.

  Connection.Offset := 0;

  Connection.BytesLeft := Connection.MsgSize;

  // Заносим размер буфера и указатель на него в Buf.

  // Данные будут складываться в строку,

  // на которую ссылается Connection.Msg.

  Buf.Len := Connection.MsgSize;

  Buf.Buf := Pointer(Connection.Msg);

  // Вызываем WSARecv для чтения самой строки

  Flags := 0;

  if WSARecv(Connect ion.ClientSocket, @Buf, 1, NumBytes, Flags,

   @Connection.Overlapped, ReadMsgCompleted) = SOCKET_ERROR then

  begin

   if WSAGetLastError <> WSA_IO_PENDING then

   begin

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

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

    ServerForm.RemoveConnection(Connection);

   end;

  end;

 end

 else

 begin

  // Connection.BytesLeft < 0 - длина строки

  // прочитана не до конца.

  // Увеличиваем смещение на число прочитанных байтов

  Inc(Connection.Offset, cdTransferred);

  // Формируем буфер для чтения оставшейся части длины

  Buf.Len := Connection.BytesLeft;

  Buf.Buf := PChar(@Connection.MsgSize) + Connection.Offset;

  // вызываем WSARecv для чтения оставшейся части длины строки

  Flags := 0;

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

   @Connection.Overlapped, ReadMsgCompleted) = SOCKET_ERROR then

  begin

   if WSAGetLastError <> WSA_IO_PENDING then

   begin

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

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

     GetErrorString(dwError));

    ServerForm.RemoveConnection(Connection);

   end;

  end;

 end;

end;


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

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

// Она во многом аналогична функции ReadLenCompleted

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

var

 Connection: PConnection;

 Buf: TWSABuf;

 NumBytes, Flags: DWORD;

begin

 Connection := ServerForm.GetConnectionByOverlapped(lpOverlapped);

 if Connection = nil then

Перейти на страницу:
Прокомментировать
Подтвердите что вы не робот:*