Newsgroups : Borland : borland.public.delphi.internet.winsock : 2007 Nov : Re: tidTCPClient/Server - problem transfering data.

www.cryer.info
Managed Newsgroup Archive

Re: tidTCPClient/Server - problem transfering data.

Subject:Re: tidTCPClient/Server - problem transfering data.
Posted by:"Remy Lebeau (TeamB)" (no.spam@no.spam.com)
Date:Tue, 20 Nov 2007 10:52:33

"Arvid Haugen" <Arvid@elis.no> wrote in message
news:47431673@newsgroups.borland.com...

> We can not rewrite this to TidCmdTCPServer and
> TidCmdTCPClient!!!

You should not be trying to use TIdCmdTCPServer and TIdCmdTCPClient together
anyway, as they are not designed to work together.  Use TIdTCPClient with
TIdCmdTCPServer, and TIdCmdTCPClient with TIdTCPServer.

> On the client side we the code looks like this:

That code is wrong to begin with.  Your looping is not accurate and can
potentially miss data.  You are misusing the IOHandler's InputBuffer, which
you really shouldn't be relying on directly to begin with.  It is meant for
Indy's internal use, and its semantics do not follow the logic you are
trying to use with it.

> On the server side we have a code like this:

That code is not thread-safe.  You must use the TIdSync class, or other
manual synchronization method, to safely access the TMemo.  For example:

    type
        TMemoSync = class(TIdSync)
        protected
            fMemo: TMemo;
            fLines: TStrings;
            procedure DoSynchronize; override;
        public
            constructor Create(AMemo: TMemo; ALines: TStrings);
            class procedure GetLines(AMemo: TMemo; ALines: TStrings);
        end;

    constructor TMemoSync.Create(AMemo: TMemo; ALines: TStrings);
    begin
        inherited Create;
        fMemo := AMemo;
        fLines := ALines;
    end;

    procedure TMemoSync.DoSynchronize;
    begin
        fLines.Assign(fMemo1.Lines);
    end;

    class procedure TMemoSync.GetLines(AMemo: TMemo; ALines: TStrings);
    begin
        with Create(AMemo, ALines) do
        try
            Synchronize;
        finally
            Free;
        end;
    end;

    procedure TForm5.IdTCPServer1Execute(AContext: TIdContext);
    var
        LLines: TStrings;
    begin
        //...
        LLines := TStringList.Create;
        try
            TMemoSync.GetLines(Memo1, LLines);
            // send LLines as needed...
        finally
            LLines.Free;
        end;
        //...
    end;

>  if trim(lline) = '' then
>    AContext.Connection.IOHandler.WriteLn('')
>  else
>  begin
>    AContext.Connection.IOHandler.Write(Memo1.lines);
>  end;

You are not sending any reply code back, but your client code is expecting
one.  You need to do so, ie:

    procedure TForm5.IdTCPServer1Execute(AContext: TIdContext);
    var
        LLine: String;
        LLines: TStrings;
    begin
        LLine := Trim(AContext.Connection.IOHandler.ReadLn);
        if Fetch(LLine) = '100' then
        begin
            LLines := TStringList.Create;
            try
                TMemoSync.GetLines(Memo1, LLines);
                // send LLines as needed...
            finally
                LLines.Free;
            end;
        end
        else
            AContext.Connection.IOHandler.WriteLn('500 Unknown command');
        //...
    end;

As for the lines themselves, I strongly suggest that you either:

1) set the AWriteLinesCount parameter of Write(TStrings) to True, and then
use ReadStrings() on the client:

    --- server ---

    procedure TForm5.IdTCPServer1Execute(AContext: TIdContext);
        //...
    begin
        // ...
        AContext.Connection.IOHandler.Write('201 Data follows');
        AContext.Connection.IOHandler.Write(LLines, True);
        // ...
    end;

    --- client ---

    var
        LLines: TStrings;
    begin
        Result := IdTCPClient1.SendCmd('100 Test', [201, 202]);
        if Result = 201 then
        begin
            LLines := TStringList.Create;
            try
                IdTCPClient1.IOHandler.ReadStrings(LLines);
                // loop through LLines as needed...
            finally
                LLines.Free;
            end;
        end;
        else
            //...
    end;

2) write an explicit "end of data" line after sending the rest of your
lines, and then your client can look for that.  Stop looking at the
InputBuffer at all, just keep calling ReadLn() unconditionally until that
ending line is received:

    --- server ---

    procedure TForm5.IdTCPServer1Execute(AContext: TIdContext);
        //...
    begin
        // ...
        AContext.Connection.IOHandler.Write('201 Data follows');
        AContext.Connection.IOHandler.Write(LLines);
        AContext.Connection.IOHandler.Write('<endofdata>');
        // ...
    end;

    --- client ---

    var
        LLine: String;
    begin
        Result := IdTCPClient1.SendCmd('100 Test', [201, 202]);
        if Result = 201 then
        begin
            LLine := IdTCPClient1.IOHandler.ReadLn;
            while LLine <> '<endofdata>' do
            begin
                // process LLine as needed...
            end;
        end;
        else
            //...
    end;

You can take that a step further by using standard RFC formatting, which
uses '.' as the ending line, and escapes any leading '.' on the lines.  This
way, you can use WriteRFCStrings() and Capture() instead:

    --- server ---

    procedure TForm5.IdTCPServer1Execute(AContext: TIdContext);
        //...
    begin
        // ...
        AContext.Connection.IOHandler.Write('201 Data follows');
        AContext.Connection.IOHandler.WriteRFCStrings(LLines);
        // ...
    end;

    --- client ---

    var
        LLines: TStrings;
    begin
        Result := IdTCPClient1.SendCmd('100 Test', [201, 202]);
        if Result = 201 then
        begin
            LLines := TStringList.Create;
            try
                IdTCPClient1.IOHandler.Capture(LLines);
                // loop through LLines as needed...
            finally
                LLines.Free;
            end;
        end;
        else
            //...
    end;

3) since your client is using SendCmd() anyway, write the lines in a format
that SendCmd() can handle for you directly.  The client's LastCmdResult will
then contain the lines after SendCmd() exits:

    --- server ---

    procedure TForm5.IdTCPServer1Execute(AContext: TIdContext);
    var
        LLine: String;
        LLines: TStrings;
        I: Integer;
    begin
        LLine := Trim(AContext.Connection.IOHandler.ReadLn);
        if Fetch(LLine) = '100' then
        begin
            LLines := TStringList.Create;
            try
                TMemoSync.GetLines(Memo1, LLines);
                if LLines.Count > 0 then
                begin
                    AContext.Connection.IOHandler.WriteLn('201-Data
follows');
                    if LLines.Count > 1 then
                    begin
                        for I := 0 to LLines.Count-2 do
                            AContext.Connection.IOHandler.WriteLn('201-' +
LLines[I]);
                    end;
                    AContext.Connection.IOHandler.WriteLn('201 ' +
LLines[LLines.Count-1]);
                end else
                    AContext.Connection.IOHandler.WriteLn('202 No data');
            finally
                LLines.Free;
            end;
        end
        else
            AContext.Connection.IOHandler.WriteLn('500 Unknown command');
        //...
    end;

    --- client ---

    begin
        Result := IdTCPClient1.SendCmd('100 Test', [201, 202]);
        if Result = 201 then
        begin
            // loop through IdTCPClient1.LastCmdResult.Text needed...
        end;
        else
            //...
    end;


#3 is how TIdCmdTCPServer works internally.  SendCmd() is designed to be
used with the types of protocols that TIdCmdTCPServer is designed to handle
automatically for you.  I know you said you don't want to use
TIdCmdTCPServer, but you really should reconsider that.  #3 above can be
translated to the following simplified OnCommand event handler:

    procedure TForm5.100Command(ASender: TIdCommand);
    begin
        TMemoSync.GetLines(Memo1, ASender.Response);
        if ASender.Response.Count > 0 then
            ASender.Reply.SetReply(201, 'Data follows')
        else
            ASender.Reply.SetReply(202, 'No data');
    end;

> For both cases the data is there next time I try to write something
> from the client.

That is because your client is not reading everything the first time around.
It is exiting its loop prematurely, leaving the rest of the data unread.

> Should I use some kind of flush command to ensure that everything is
> written???

No.


Gambit

Replies:

In response to:

www.cryer.info
Managed Newsgroup Archive