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
// Отправляемое сообщение состоит из длины строки и самой строки.
// Выделяем для буфера память, достаточную для хранения
// и того и другого.
SetLength(SendBuf, SizeOf(Integer) + Length(S));
// Копируем в буфер длину строки
PInteger(@SendBuf[0])^ := Length(S);
// А затем - саму строку
Move(S[1], SendBuf[SizeOf(Integer)], Length(S));
BytesSent := 0;
// повторяем попытку отправить до тех пор, пока все содержимое
// буфера не будет отправлено серверу.
while BytesSent < Length(SendBuf) do
begin
SendRes :=
send(FSocket, SendBuf[BytesSent], Length(SendBuf) - BytesSent, 0);
if SendRes > 0 then Inc(BytesSent, SendRes)
else if WSAGetLastError = WSAEWOULDBLOCK then Sleep(10)
else
begin
MessageDlg('Ошибка при отправке данных серверу'#13#10 +
GetErrorString, mtError, [mbOK], 0);
OnDisconnect;
Exit;
end;
end;
end;
end;
Имитация блокирующего режима осуществляется очень просто: если сообщение не удалось отправить сразу, после небольшой паузы производится попытка отправить то, что ещё не отправлено, и так до тех пор, пока не будет отправлено все или пока не возникнет ошибка. В программе SimpleClient мы отправляли длину строки и саму строку разными вызовами send
. Теперь, из-за того, что функция send
может отправить только часть переданных ей данных, это становится неудобным из-за громоздкости многочисленных проверок. Поэтому мы создаем один буфер, куда заносим и длину строки, и саму строку, и затем передаем его как единое целое.
Читать дальше
Конец ознакомительного отрывка
Купить книгу