При работе с сокетами в среде Delphi разработчики часто сталкиваются с различными проблемами, одной из которых является бесконечный цикл при чтении данных. В данной статье мы рассмотрим, почему может возникать такая ситуация и как её можно исправить.
Описание проблемы
Работая с компонентами TClientSocket и TServerSocket в Delphi, разработчик столкнулся с проблемой бесконечного цикла при чтении данных из сокета. В коде чтения используется метод ReceiveBuf, который не возвращает значение 0, что должно было бы сигнализировать о завершении чтения. Вместо этого, цикл продолжается бесконечно.
Пример кода
procedure csRead(Sender: TObject; Socket: TCustomWinSocket);
var
MSCli : TMemoryStream;
cnt : Integer;
buf : array [0..1023] of byte;
begin
MSCli := TMemoryStream.Create;
try
repeat
cnt := Socket.ReceiveBuf(buf[0], 1024);
MSCli.Write(buf[0], cnt)
until cnt = 0;
finally
MSCli.SaveToFile('somefile.dmp');
MSCli.Free;
end;
end;
Анализ проблемы
Метод ReceiveBuf возвращает количество полученных байт или 0, если сокет был закрыт. В случае, если сокет находится в режиме не блокировки и данные не доступны, ReceiveBuf может вернуть -1 с кодом ошибки WSAEWOULDBLOCK. Также, если сокет отсоединен, метод вернет 0. В случае возникновения реальной ошибки сокета, ReceiveBuf может поднять исключение ESocketError.
Подтвержденное решение
Использование метода SendStream не является хорошей практикой, так как он предназначен для отправки всего потока данных, после чего должен освободить его. Если сокет находится в режиме не блокировки и возникает блокировка во время отправки, SendStream завершит работу без освобождения потока. Более безопасный подход — избегать использования SendStream и напрямую вызывать SendBuf в цикле.
Альтернативный подход
Лучше отправлять размер данных перед самими данными, чтобы получатель мог корректно определить, когда остановить чтение. Например, сначала отправляется 64-битное целое число, указывающее размер данных, а затем следует сам поток данных.
Пример исправленного кода
procedure ReadRawFromSocket(Socket: TCustomWinSocket; Buffer: Pointer; BufSize: Integer);
var
buf: PByte;
cnt: Integer;
begin
buf := PByte(Buffer);
while BufSize > 0 do
begin
cnt := Socket.ReceiveBuf(buf^, BufSize);
if cnt < 1 then
begin
if (cnt = -1) and (WSAGetLastError() = WSAEWOULDBLOCK) then
begin
Application.ProcessMessages;
Continue;
end;
Abort;
end;
Inc(buf, cnt);
Dec(BufSize, cnt);
end;
end;
procedure ReadMemStreamFromSocket(Socket: TCustomWinSocket; Stream: TMemoryStream);
var
cnt: Int64;
begin
cnt := ReadInt64FromSocket(Socket);
if cnt > 0 then
begin
Stream.Size := cnt;
ReadRawFromSocket(Socket, Stream.Memory, cnt);
end;
end;
procedure csRead(Sender: TObject; Socket: TCustomWinSocket);
var
MSCli : TMemoryStream;
begin
MSCli := TMemoryStream.Create;
try
ReadMemStreamFromSocket(Socket, MSCli);
MSCli.SaveToFile('somefile.dmp');
finally
MSCli.Free;
end;
end;
Заключение
Чтобы избежать бесконечного цикла при чтении данных из сокета в Delphi, рекомендуется использовать альтернативный подход с предварительной отправкой размера данных и чтением данных в цикле с обработкой возможных ошибок. Это позволит корректно обрабатывать ситуации, когда данные не доступны, и избегать непредвиденного поведения программы.
Проблема заключается в бесконечном цикле при чтении данных из сокета в Delphi из-за неправильной обработки результатов метода `ReceiveBuf`.
Комментарии и вопросы
Получайте свежие новости и обновления по Object Pascal, Delphi и Lazarus прямо в свой смартфон. Подпишитесь на наш Telegram-канал delphi_kansoftware и будьте в курсе последних тенденций в разработке под Linux, Windows, Android и iOS
Материалы статей собраны из открытых источников, владелец сайта не претендует на авторство. Там где авторство установить не удалось, материал подаётся без имени автора. В случае если Вы считаете, что Ваши права нарушены, пожалуйста, свяжитесь с владельцем сайта.