Передача бинарных данных с использованием ICS в Delphi: решение для TCP/IP сервера и клиентов
В этой статье мы рассмотрим, как реализовать надежную передачу бинарных данных между сервером и несколькими клиентами с использованием библиотеки ICS (Internet Component Suite) в Delphi, с акцентом на работу с типом TBytes и кастомными разделителями сообщений.
Постановка задачи
Как видно из обсуждения на форуме, пользователь AJ_Oldendorf столкнулся с необходимостью:
Создать TCP/IP сервер и клиентские приложения (до 15 клиентов)
Передавать данные в виде TBytes (от 20 до 60000 байт)
Использовать фиксированный 8-байтовый маркер конца сообщения (EndOfLine)
Обеспечить высокую скорость передачи (80-90 сообщений/сек от сервера, 20 сообщений/сек от клиентов)
Обрабатывать данные в фоновом потоке, не блокируя GUI
Решение с использованием ICS
Выбор компонентов
Как правильно отметил Angus Robertson, для этой задачи подходят несколько компонентов из состава ICS:
TIcsIpStrmLog - удобен для простой передачи данных, но не поддерживает длинные разделители
TSslWSocketServer/TTcpSrvClient - более низкоуровневые компоненты, позволяющие реализовать кастомную логику
Мы сосредоточимся на втором варианте, так как он предоставляет больше гибкости.
Реализация сервера
type
TTcpSrvClient = class(TWSocketClient)
private
FBuffer: TBytes;
FEOLMarker: TBytes;
public
constructor Create(AOwner: TComponent); override;
procedure ProcessData(const Data: TBytes);
end;
constructor TTcpSrvClient.Create(AOwner: TComponent);
begin
inherited;
// Инициализируем 8-байтовый маркер конца сообщения
SetLength(FEOLMarker, 8);
FEOLMarker[0] := 65; // 'A'
FEOLMarker[1] := 65; // 'A'
FEOLMarker[2] := 66; // 'B'
FEOLMarker[3] := 66; // 'B'
FEOLMarker[4] := 67; // 'C'
FEOLMarker[5] := 67; // 'C'
FEOLMarker[6] := 68; // 'D'
FEOLMarker[7] := 68; // 'D'
SetLength(FBuffer, 0);
end;
procedure TTcpSrvClient.ProcessData(const Data: TBytes);
begin
// Здесь обрабатываем полное сообщение
// Можно передать в основной поток через Synchronize или Queue
end;
procedure TForm1.ClientDataAvailable(Sender: TObject; ErrCode: Word);
var
Client: TTcpSrvClient;
NewData, TempBuffer: TBytes;
I, J, EOLPos: Integer;
Found: Boolean;
begin
Client := Sender as TTcpSrvClient;
// Получаем новые данные
NewData := Client.ReceiveBytes;
// Добавляем к буферу
TempBuffer := Client.FBuffer;
SetLength(TempBuffer, Length(TempBuffer) + Length(NewData));
Move(NewData[0], TempBuffer[Length(TempBuffer) - Length(NewData)], Length(NewData));
// Ищем маркер конца сообщения
EOLPos := -1;
for I := 0 to Length(TempBuffer) - Length(Client.FEOLMarker) do
begin
Found := True;
for J := 0 to Length(Client.FEOLMarker) - 1 do
begin
if TempBuffer[I + J] <> Client.FEOLMarker[J] then
begin
Found := False;
Break;
end;
end;
if Found then
begin
EOLPos := I;
Break;
end;
end;
if EOLPos >= 0 then
begin
// Извлекаем полное сообщение
SetLength(NewData, EOLPos);
Move(TempBuffer[0], NewData[0], EOLPos);
// Обрабатываем сообщение
TThread.Queue(nil,
procedure
begin
Client.ProcessData(NewData);
end);
// Сохраняем оставшиеся данные
SetLength(Client.FBuffer, Length(TempBuffer) - EOLPos - Length(Client.FEOLMarker));
if Length(Client.FBuffer) > 0 then
Move(TempBuffer[EOLPos + Length(Client.FEOLMarker)], Client.FBuffer[0], Length(Client.FBuffer));
end
else
begin
// Сохраняем данные в буфер для следующего пакета
Client.FBuffer := TempBuffer;
end;
end;
Настройка сервера
procedure TForm1.StartButtonClick(Sender: TObject);
begin
SslWSocketServer1.Proto := 'tcp';
SslWSocketServer1.Addr := '0.0.0.0';
SslWSocketServer1.Port := '8080';
SslWSocketServer1.SslEnable := False;
SslWSocketServer1.ClientClass := TTcpSrvClient;
SslWSocketServer1.OnClientConnect := ClientConnect;
SslWSocketServer1.Listen;
end;
procedure TForm1.ClientConnect(Sender: TObject; Client: TWSocketClient; Error: Word);
begin
with Client as TTcpSrvClient do
begin
OnDataAvailable := ClientDataAvailable;
OnBgException := ClientBgException;
end;
end;
Реализация клиента
Клиентская часть реализуется аналогично, с использованием TWSocket:
procedure TClientForm.SendData(const Data: TBytes);
var
FullMessage: TBytes;
begin
// Добавляем маркер конца сообщения
SetLength(FullMessage, Length(Data) + Length(FEOLMarker));
Move(Data[0], FullMessage[0], Length(Data));
Move(FEOLMarker[0], FullMessage[Length(Data)], Length(FEOLMarker));
WSocket.Send(FullMessage, Length(FullMessage));
end;
procedure TClientForm.WSocketDataAvailable(Sender: TObject; ErrCode: Word);
begin
// Аналогичная серверу обработка входящих данных
end;
Альтернативное решение
Если реализация с кастомным поиском маркера кажется слишком сложной, можно рассмотреть альтернативный подход:
Передача длины сообщения - в начале каждого сообщения отправлять 4 байта с длиной данных
Использование TIcsBufferedStream - для упрощения работы с буферизацией
Пример альтернативной реализации:
procedure TForm1.AlternativeClientDataAvailable(Sender: TObject; ErrCode: Word);
var
Client: TTcpSrvClient;
NewData: TBytes;
DataSize: Integer;
begin
Client := Sender as TTcpSrvClient;
// Читаем доступные данные
NewData := Client.ReceiveBytes;
// Добавляем в буфер
Client.FBuffer := Client.FBuffer + NewData;
// Пока в буфере достаточно данных для чтения размера
while Length(Client.FBuffer) >= 4 do
begin
// Читаем размер данных
Move(Client.FBuffer[0], DataSize, 4);
// Проверяем, есть ли полное сообщение
if Length(Client.FBuffer) >= 4 + DataSize then
begin
// Извлекаем данные
SetLength(NewData, DataSize);
Move(Client.FBuffer[4], NewData[0], DataSize);
// Обрабатываем сообщение
Client.ProcessData(NewData);
// Удаляем обработанные данные из буфера
Delete(Client.FBuffer, 0, 4 + DataSize);
end
else
Break;
end;
end;
Рекомендации по производительности
Размер буфера - для высоких нагрузок увеличьте размер буфера (по умолчанию 8КБ): WSocket.BufSize := 65536; // 64KB
Многопоточность - обработку данных лучше выносить в отдельные потоки: TThread.CreateAnonymousThread( procedure begin ProcessDataIntensive(Data); end).Start;
Ограничение скорости - при необходимости можно ограничить скорость передачи: WSocket.SendFlags := WSocket.SendFlags + [wsSendThrottle];
WSocket.Throughput := 102400; // 100KB/s
Заключение
Библиотека ICS предоставляет гибкие инструменты для реализации TCP/IP серверов и клиентов в Delphi. Для работы с бинарными данными и кастомными разделителями сообщений лучше использовать низкоуровневые компоненты, такие как TWSocketServer и TWSocketClient, реализуя собственную логику буферизации и обработки данных.
Представленные решения позволяют:
- Надежно передавать бинарные данные любого размера
- Использовать кастомные маркеры конца сообщения
- Обрабатывать данные в фоновых потоках
- Масштабироваться на множество клиентских подключений
Для упрощения кода можно рассмотреть альтернативный подход с передачей длины сообщения, который может быть более эффективным для некоторых сценариев.
Реализация передачи бинарных данных между сервером и клиентами в Delphi с использованием библиотеки ICS, включая обработку кастомных маркеров и фоновую обработку данных.
Комментарии и вопросы
Получайте свежие новости и обновления по Object Pascal, Delphi и Lazarus прямо в свой смартфон. Подпишитесь на наш Telegram-канал delphi_kansoftware и будьте в курсе последних тенденций в разработке под Linux, Windows, Android и iOS
Материалы статей собраны из открытых источников, владелец сайта не претендует на авторство. Там где авторство установить не удалось, материал подаётся без имени автора. В случае если Вы считаете, что Ваши права нарушены, пожалуйста, свяжитесь с владельцем сайта.