Newsgroups : Borland : borland.public.delphi.internet.winsock : 2008 Feb : Re: Indy 9 TidTCPClient.WriteStream problem

www.cryer.info
Managed Newsgroup Archive

Re: Indy 9 TidTCPClient.WriteStream problem

Subject:Re: Indy 9 TidTCPClient.WriteStream problem
Posted by:"Tomislav Stamac" (tstam..@vip.hr)
Date:Thu, 28 Feb 2008 09:17:21

Thanks for reply Remy.


>
> The only way that can happen is if the TStream is empty to begin with.
> The OnWorkBegin event should always be getting triggered regardless,
> specifying a value in its AWorkCountMax parameter, even if it is zero.
>

1. Stream is not Empty.
2. OnWorkBegin does get triggered, OnWorkEnd also.
    The problem is that OnWork does not get triggered.

3. Debugging Indy source:

procedure TIdTCPConnection.WriteStream(AStream: TStream; const AAll: boolean
= true;
  const AWriteByteCount: Boolean = False; const ASize: Integer = 0);
var
  LBuffer: TMemoryStream;
  LSize: Integer;
  LStreamEnd: Integer;
  LBufferingStarted: Boolean;
begin
  if AAll then begin
    AStream.Position := 0;
  end;
  // This is copied to a local var because accessing .Size is very
inefficient
  if ASize = 0 then begin
    LStreamEnd := AStream.Size;
  end else begin
    LStreamEnd := ASize + AStream.Position;
  end;
  LSize := LStreamEnd - AStream.Position;
  LBufferingStarted := FWriteBuffer = nil;
  if LBufferingStarted then
  begin
    OpenWriteBuffer;
  end;
  try
    if AWriteByteCount then begin
      WriteInteger(LSize);
    end;
    BeginWork(wmWrite, LSize); try
      LBuffer := TMemoryStream.Create; try
        LBuffer.SetSize(FSendBufferSize);
        while True do begin
          LSize := Min(LStreamEnd - AStream.Position, FSendBufferSize);
          if LSize = 0 then begin
            Break;
          end;
          // Do not use ReadBuffer. Some source streams are real time and
will not
          // return as much data as we request. Kind of like recv()
          // NOTE: We use .Size - size must be supported even if real time
          LSize := AStream.Read(LBuffer.Memory^, LSize);
<---- INDY DOES READ CHUNKS OF STREAM IN INTERNAL
          if LSize = 0 then begin
BUFFER
            raise EIdNoDataToRead.Create(RSIdNoDataToRead);
          end;
          WriteBuffer(LBuffer.Memory^, LSize);
<---- THEN IT CALLS WRITEBUFFER WITH WRITENOW=FALSE
        end;
      finally FreeAndNil(LBuffer); end;
    finally EndWork(wmWrite); end;
    if LBufferingStarted then
    begin
      CloseWriteBuffer;
    end;
.......


WRITEBUFFER IS CALLED WITH WRITENOW=FALSE

procedure TIdTCPConnection.WriteBuffer(const ABuffer; AByteCount: Integer;
  const AWriteNow: boolean = false);
var
  LBuffer: TIdSimpleBuffer;
  nPos, nByteCount: Integer;
begin
  if (AByteCount > 0) and (@ABuffer <> nil) then begin
    // Check if disconnected
    CheckForDisconnect(True, True);
    if connected then begin
      if (FWriteBuffer = nil) or AWriteNow then begin           <---------
THIS IS FALSE SO WE SKEEP TO ELSE
        LBuffer := TIdSimpleBuffer.Create; try
          LBuffer.WriteBuffer(ABuffer, AByteCount);
          if Assigned(Intercept) then begin
            LBuffer.Position := 0;
            Intercept.Send(LBuffer);
            AByteCount := LBuffer.Size;
          end;
          nPos := 1;
          repeat
            nByteCount := IOHandler.Send(PChar(LBuffer.Memory)[nPos - 1],
LBuffer.Size - nPos + 1);
            FClosedGracefully := nByteCount = 0;
            // Check if other side disconnected
            CheckForDisconnect;
            // Check to see if the error signifies disconnection
            if GStack.CheckForSocketError(nByteCount, [ID_WSAESHUTDOWN,
Id_WSAECONNABORTED, Id_WSAECONNRESET]) then begin
              DisconnectSocket;
              GStack.RaiseSocketError(GStack.LastError);
            end;
            // TODO - Have a AntiFreeze param which allows the send to be
split up so that process
            // can be called more. Maybe a prop of the connection,
MaxSendSize?
            TIdAntiFreezeBase.DoProcess(False);
            DoWork(wmWrite, nByteCount);                       <---- DO WORK
WOULD BE CALLED IF WE PASSED THROUGH THIS BLOCK
            nPos := nPos + nByteCount;
          until nPos > AByteCount;
        finally FreeAndNil(LBuffer); end;
      // Write Buffering is enabled
      end else begin
<--- SINCE CONDITION WAS FALSE WE ARE GOING THROUGH THIS BLOCK
        FWriteBuffer.WriteBuffer(ABuffer, AByteCount);         AND THERE IS
NO DOWORK HERE
        if (FWriteBuffer.Size >= FWriteBufferThreshhold) and
(FWriteBufferThreshhold > 0) then begin
          // TODO: Maybe? instead of flushing - Write until buffer is
smaller than Threshold.
          // That is do at least one physical send.
          FlushWriteBuffer(FWriteBufferThreshhold);
        end;
      end;
    end
    else
    begin
      raise EIdNotConnected.Create(RSNotConnected);
    end;
  end;
end;


>> In Indy 8 I used tprogresstream for showing progressbar
>> where tprogressstream had OnRead(Bytesread:cardinal);
>> method. In Indy 9 I cant use it
>
> Yes, you can.
>
>> because Indy 9 seems to read all stream in the internal buffer
>> or something and after that it sends stream,
>
> No, that is not what Indy 9 does.
>
>
> Gambit
>

Oh but it does, :-) as You can see in WriteStream the buffer is callled
LBuffer and itself it is a TMemoryStream.
The problem is that it copies chunks of original stream in that buffer and
sends them with writebuffer(..,..,false)... ofcourse that lasts 10ms, and my
progressbar is at 100% at that point, but sending over socket is still going
on and it lasts around 45s over GPRS or about 5s over HSDPA.

Am I missing something here?

I suppose that I can copy internal Indy behaviour and send stream like this:

Size:=MyProgressStream.Size;
MyProgressStream.OnRead:=DoProgress;
Client.WriteInteger(Size);
Client.WriteBuffer(MyProgressStream.Memory^,size,true);

Bit It's ugly :), there is Client.WriteStream to do that job.

Replies:

In response to:

www.cryer.info
Managed Newsgroup Archive