Почему TCusomWinSocket.ReceiveBuf никогда не возвращает 0? - PullRequest
5 голосов
/ 05 января 2012

Когда дело доходит до сокетов, TClientSocket и TServerSockets - мои любимые из-за их простого использования.

Моя задача очень проста. Мне нужно отправить файл (RAW) через эти 2 компонента, поэтому у меня есть 2 подпрограммы, подобные приведенным ниже:

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); //This loop repeats endlesly
      MSCli.Write(buf[0], cnt)
    until cnt = 0;
    finally
    MSCli.SaveToFile('somefile.dmp');
    MSCli.Free;
    end;
end;

И, конечно, отправитель:

  //...some code
    MSSErv.LoadFromFile('some file');
    MSServ.Position := 0;
    Socket.SendStream(MSServ);
  end;

Цикл в читателе повторяется бесконечно, и я не знаю почему. Что может быть источником проблемы?

1 Ответ

5 голосов
/ 06 января 2012

SendStream() не очень хороший выбор для использования - НИКОГДА. Он предназначен для отправки всего TStream, а затем освободить его, когда закончите. Однако, если сокет установлен в неблокирующий режим и сокет блокируется во время отправки, SendStream() немедленно завершается и НЕ освобождает TStream. Вам нужно снова позвонить SendStream(), чтобы продолжить отправку TStream с того места, где остановился SendStream(). Но есть и другие условия, которые могут заставить SendStream() выйти и освободить TStream, и вы не знаете, когда он освободил или не освободил TStream, поэтому попытка вызвать * 1012 становится очень опасной. * снова с тем же TStream объектом. Гораздо более безопасный подход - избегать SendStream() любой ценой и вместо этого вызывать SendBuf() в своем собственном цикле.

С учетом сказанного, SendStream() не сообщает получателю, сколько байтов будет отправлено, поэтому получатель не знает, когда прекратить чтение (если вы не закроете соединение после отправки TStream). Лучше выбрать отправку нужного количества байтов до отправки данных TStream. Таким образом, получатель может сначала прочитать количество байтов, а затем прекратить чтение, когда будет получено указанное количество байтов. Например:

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;

function ReadInt64FromSocket(Socket: TCustomWinSocket): Int64;
begin
  ReadRawFromSocket(Socket, @Result, SizeOf(Int64));
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; 

procedure SendRawToSocket(Socket: TCustomWinSocket; Buffer: Pointer; BufSize: Integer);
var 
  buf: PByte; 
  cnt: Integer; 
begin 
  buf := PByte(Buffer); 
  while BufSize > 0 do
  begin 
    cnt := Socket.SendBuf(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 SendInt64ToSocket(Socket: TCustomWinSocket; Value: Int64);
begin
  SendRawToSocket(Socket, @Value, SizeOf(Int64));
end;

procedure SendMemStreamToSocket(Socket: TCustomWinSocket: Stream: TMemoryStream);
begin
  SendInt64FromSocket(Socket, Stream.Size);
  SendRawToSocket(Socket, Stream.Memory, Stream.Size);
end;

begin
  ...
  MSSErv.LoadFromFile('some file'); 
  MSServ.Position := 0; 
  SendMemStreamToSocket(Socket, MSServ);
  ...
end;
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...