Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why doesn't TCusomWinSocket.ReceiveBuf ever return 0?

Tags:

sockets

delphi

When it comes to sockets, TClientSocket and TServerSockets are my favourite because of their simple usage.

My task is very simple. I need to send a file (RAW) through these 2 components, so I have 2 routines like the ones below:

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;

And of course the sender :

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

The loop in the reader is repeating endelessly and I don't know why. What could be the source of the problem?

like image 536
opc0de Avatar asked Jan 05 '12 19:01

opc0de


1 Answers


SendStream() is not a particularly good choice to use - EVER. It is intended to send the entire TStream and then free it when finished. However, if the socket is set to non-blocking mode and the socket blocks during sending, SendStream() exits immediately and DOES NOT free the TStream. You have to call SendStream() again to continue sending the TStream from where SendStream() left off. But there are other conditions that can cause SendStream() to exit AND free the TStream, and you don't really know when it did or did not free the TStream, so it becomes very dangerous to try to call SendStream() again with the same TStream object. A much safer approach is to avoid SendStream() at all costs and call SendBuf() directly in your own loop instead.

With that said, SendStream() does not inform the receiver how many bytes will be sent, so the receiver does not know when to stop reading (unless you close the connection after sending the TStream). A better choice is to send the intended byte count before then sending the TStream data. That way, the receiver can read the byte count first, then stop reading when the specified number of bytes have been received. For example:

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;
like image 179
Remy Lebeau Avatar answered Oct 11 '22 03:10

Remy Lebeau