О чём не пишут в книгах по 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
// Отправляемое сообщение состоит из длины строки и самой строки.
// Выделяем для буфера память, достаточную для хранения
Поделиться с друзьями: