Здесь мы используем новый способ чтения данных. Он во многом похож на тот, который применен в сервере. Функция recv
вызывается один раз за один вызов обработчика значений и передаст данные в буфер фиксированного размера RecvBuf
. Затем в буфере ищутся границы отдельных строк (символы #0
), строки, полученные целиком, выводятся. Если строка получена частично (а такое может случиться не только из-за того, что она передана по частям, но и из-за того, что в буфере просто не хватило место для приема ее целиком), её начало следует сохранить в отдельном буфере, чтобы добавить к тому, что будет прочитано при следующем событии FD_READ
. Этот буфер реализуется полем FRecvStr
типа string
. После чтения к содержимому этой строки добавляется содержимое буфера RecvBuf
, а затем из строки выделяются все подстроки, заканчивающиеся на #0
. То, что остается в строке FRecvStr
после этого, — это начало строки, прочитанной частично. Оно будет учтено при обработке следующего события FD_READ
.
Описанный алгоритм разбора буфера прост, но неэффективен с точки зрения нагрузки на процессор и использования динамической памяти, особенно в тех случаях, когда в буфере RecvBuf
оказывается сразу несколько строк. Это связано с тем, что при добавлении содержимого RecvBuf
к FRecvStr
и последующем поочередном удалении строк из FRecvStr
происходит многократное перераспределение памяти, выделенной для строки. Алгоритм можно оптимизировать: все строки, которые поместились в RecvBuf
целиком, выделять непосредственно из этого буфера, не помещая в FRecvStr
, а помещать туда только то, что действительно нужно сохранить между обработкой разных событий FD_READ
. Реализацию такого алгоритма рекомендуем выполнить в качестве самостоятельного упражнения.
При отправке данных вероятность того, что функция send не сможет быть выполнена сразу, достаточно мала. Кроме того, как мы уже говорили, блокировка клиента при отправке данных часто бывает вполне приемлема из-за редкости и непродолжительности. Таким образом, блокирующий режим из-за своей простоты наиболее удобен при отправке данных серверу клиентом. Но мы не можем перевести сокет, работающий в асинхронном режиме, в блокирующий режим на время отправки, зато можем этот режим имитировать. Занимается этим метод SendString
(листинг 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;
// повторяем попытку отправить до тех пор, пока все содержимое
// буфера не будет отправлено серверу.