RHenningsgard
RHenningsgard

Reputation: 41

Why does TTcpClient drop data on SendStream()?

When I call TTcpClient.SendStream(MyStream), it advances MyStream.Position to 8704, and I have to call SendStream() repeatedly to get it to completely send my stream. However, the data received is missing chunks of 512 bytes about every 8K.

Note: This question is rhetorical, because I suffered through trying and failing to find a solution on the web. I found the bug in Delphi 7 Sockets.pas, and want to publish the solution for the good of the community.

Upvotes: 1

Views: 126

Answers (1)

RHenningsgard
RHenningsgard

Reputation: 41

The problem is a coding bug in Delphi 7 Sockets.pas. The bug causes any stream larger than about 8K (exact size is OS-dependent) to lose 512-byte chunks of data. The SendStream implementation uses a repeat..until loop to pull 512-byte buffers from the caller's stream for sending with SendBuf(), and it continues as long as the stream has data and SendBuf() does not return equal to SOCKET_ERROR. The loss occurs when the Windows socket buffer fills, causing SendBuf() to return equal to SOCKET_ERROR, but at that point up to 512 bytes have already been read from the caller's stream and the stream Position has been advanced - but that Position is not restored on exit. Original Sockets.pas code:

    function TBaseSocket.SendStream(AStream: TStream): Integer;
    var
      BufLen : Integer;
      Buffer: array[0..511] of Byte;
    begin
      Result := 0;
      if Assigned(AStream) then begin
        repeat
          BufLen := AStream.Read(Buffer, SizeOf(Buffer));
        until (BufLen = 0) or (SendBuf(Buffer, BufLen) = SOCKET_ERROR);
      end;
    end;

And here's a fix:

    function TBaseSocket.SendStream(AStream: TStream): Integer;
    var
      Quit : boolean;
      BufLen,OldPosition : Integer;
      Buffer: array[0..511] of Byte;
    begin
      Result := 0;
      if Assigned(AStream) then begin
        repeat
          OldPosition := AStream.Position;
          BufLen := AStream.Read(Buffer, SizeOf(Buffer));
          if (BufLen > 0) then begin
            Quit := (SendBuf(Buffer, BufLen) = SOCKET_ERROR);
            if Quit then AStream.Position := OldPosition; //restore!
          end else begin //BufLen = 0
            Quit := true;
          end;
        until Quit;
      end;
    end;

Upvotes: 2

Related Questions