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

ЖАНРЫ

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

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

Шрифт:

const

 // Размер буфера для получения данных

 RecvBufSize = 4096;

var

 // Буфер для получения данных

 RecvBuf: array[0..RecvBufSize - 1] of Byte;

 RecvRes: Integer;

 P: Integer;

begin

 // Защита от "тупой" ошибки

 if Msg.Socket <> FSocket then

 begin

MessageDlg('Внутренняя ошибка программы — неверный сокет',

mtError, [mbOK], 0);

Exit;

 end;

 if Msg.SockError <> 0 then

 begin

MessageDlg('Ошибка
при взаимодействии с сервером'#13#10 +

GetErrorString(Msg.SockError), mtError, [mbOK], 0);

OnDisconnect;

Exit;

 end;

 case Msg.SockEvent of

 FD_READ:

 // Получено сообщение от сервера

 begin

// Читаем столько, сколько можем

RecvRes := recv(FSocket, RecvBuf, RecvBufSize, 0);

if RecvRes > 0 then

begin

// Увеличиваем строку на размер прочитанных данных

P := Length(FRecvStr);

SetLength(FRecvStr, P + RecvRes);

// Копируем в строку полученные данные

Move(RecvBuf, FRecvStr[Р + 1], RecvRes);

// В строке может оказаться несколько строк от сервера,

// причем последняя может прийти не целиком.

// Ищем в строке символы #0, которые, согласно протоколу,

// являются разделителями строк.

P := Pos(#0, FRecvStr));

while P > 0 do

begin

AddMessageToRecvMemo('Сообщение от сервера: ' +

Copy(FRecvStr, 1, P - 1));

// Удаляем из строкового буфера выведенную строку

Delete(FRecvStr, 1, P);

P := Pos(#0, FRecvStr);

end;

end

else if RecvRes = 0 then

begin

MessageDlg('Сервер закрыл соединение'#13#10 +

GetErrorString, mtError, [mbOK], 0);

OnDisconnect;

end

else

begin

if WSAGetLastError <> WSAEWOULDBLOCK then

begin

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

GetErrorString, mtError, [mbOK], 0);

OnDisconnect;

end;

end;

 end;

 FD_CLOSE: begin

MessageDlg('Сервер закрыл соединение', mtError, [mbOK], 0);

shutdown(FSocket, SD_BOTH);

OnDisconnect;

 end;

 else begin

MessageDlg('Внутренняя ошибка программы — неизвестное событие ' +

IntToStr(Msg.SockEvent), mtError, [mbOK], 0);

OnDisconnect;

 end;

 end;

end;

Здесь

мы используем новый способ чтения данных. Он во многом похож на тот, который применен в сервере. Функция
recv
вызывается один раз за один вызов обработчика значений и передаст данные в буфер фиксированного размера
RecvBuf
. Затем в буфере ищутся границы отдельных строк (символы
#0
), строки, полученные целиком, выводятся. Если строка получена частично (а такое может случиться не только из-за того, что она передана по частям, но и из-за того, что в буфере просто не хватило место для приема ее целиком), её начало следует сохранить в отдельном буфере, чтобы добавить к тому, что будет прочитано при следующем событии
FD_READ
. Этот буфер реализуется полем
FRecvStr
типа
string
. После чтения к содержимому этой строки добавляется содержимое буфера
RecvBuf
, а затем из строки выделяются все подстроки, заканчивающиеся на
#0
. То, что остается в строке
FRecvStr
после этого, — это начало строки, прочитанной частично. Оно будет учтено при обработке следующего события
FD_READ
.

Примечание

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

RecvBuf
оказывается сразу несколько строк. Это связано с тем, что при добавлении содержимого
RecvBuf
к
FRecvStr
и последующем поочередном удалении строк из
FRecvStr
происходит многократное перераспределение памяти, выделенной для строки. Алгоритм можно оптимизировать: все строки, которые поместились в
RecvBuf
целиком, выделять непосредственно из этого буфера, не помещая в
FRecvStr
, а помещать туда только то, что действительно нужно сохранить между обработкой разных событий
FD_READ
. Реализацию такого алгоритма рекомендуем выполнить в качестве самостоятельного упражнения.

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

SendString
(листинг 2.67).

Листинг 2.67. Метод
SendString
, имитирующий блокирующим режим отправки

// Отправка строки серверу. Функция имитирует блокирующий

// режим работы сокета: если за один раз не удается отправить

// данные, попытка отправить их продолжается до тех пор,.

// пока все данные не будут отправлены или пока не возникнет ошибка.

procedure TESClientForm.SendString(const S: string);

var

 SendRes: Integer;

 // Буфер, куда помещается отправляемое сообщение

 SendBuf: array of Byte;

 // Сколько байтов уже отправлено

 BytesSent: Integer;

begin

 if Length(S) > 0 then

 begin

// Отправляемое сообщение состоит из длины строки и самой строки.

// Выделяем для буфера память, достаточную для хранения

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