Search code examples
delphiunicodedelphi-xe3indy10tstringlist

How to pass multilined TStrings data from a TIdTCPServer to TIdTCPClient


I tried to pass a database record from my server-side application to my client-side application. On the client-side I need to store my data into a TStrings collection.

When I pass a multiline field, I receive two separate data items at the client-side, instead of one multiline data item! I've also tried to do that with Unicode UTF8 based commands, but unfortunately the result is same.

Server-side code:

procedure TForm1.IdCmdTCPServer1CommandHandlers0Command(ASender: TIdCommand);
var
  myData: TStrings;
begin
  myData := TStringList.Create;

  myData.Add('12'); // ID
  myData.Add('This is a multi line' + #13#10 + 'description.'); // Descriptions
  myData.Add('Thom Smith'); // Name

  try
    ASender.Context.Connection.Socket.Write(myData, True{, TIdTextEncoding.UTF8});
  finally
    myData.Free;
  end;
end;

myData debug-time values on server-side are:

myData[0] = '12'
myData[1] = 'This is a multi line'#$D#$A'description.'
myData[2] = 'Thom Smith'

Client-side code:

procedure TForm1.Button1Click(Sender: TObject);
var
  myData: TStrings;
begin
  with TIdTCPClient.Create(nil) do
  begin
    Port := 1717;
    Host := 'localhost';

    try
      Connect;
      //IOHandler.DefStringEncoding := TIdTextEncoding.UTF8;

      myData := TStringList.Create;
      try
        SendCmd('greating');
        Socket.ReadStrings(myData, -1{, TIdTextEncoding.UTF8});

        eID.Text    := myData[0];  // ID TEdit
        mDes.Text   := myData[1];  // Descriptions TMemo
        eTelNo.Text := myData[2];  // Name TEdit
      finally
        myData.Free;
      end;
    finally
      Disconnect;
      Free;
    end;
  end;
end;

myData debug-time valuese on client-side:

myData[0] = '12' myData1 = 'This is a multi line' myData[2] = 'description.'

Telnet result:

enter image description here

Actually, myData[2] that should keep 'Thom Smith' was replaced with the second line of the Description field! and there are no items after myData[2]. myData[3] is not accessible any more.

I think this issue is related to Indy's Write or ReadStrings procedures, because it sends ItemCount as 3, but it sends two items (one correct, and next beaked to two items!).

How can I pass a Carriage Return character to the other side without having the Write procedure break myData[1] into two separate lines?

Thanks a lot.


Solution

  • If you want TStrings.Text be oblivious to special characters - you should escape them before sending by net, and un-escape after that. There are a lot of ways of escaping, so choose one that suits you.

    function EscapeString:(String): String --- your choice
    function DeEscapeString:(String): String --- your choice
    
    procedure SendEscapedStrings(const socket: TIdSocket; const data: TStrings);
    var s: string; temp: TStringList;
    begin
      temp := TStringList.Create;
      try
        temp.Capacity := data.Count;
        for s in data do
            temp.Add( EscapeString( s ) );
        socket.Write(temp);
      finally
        temp.Destroy;
      end;
    end;
    
    procedure ReadDeescapedStrings(const socket: TIdSocket; const data: TStrings);
    var s: string; temp: TStringList;
    begin
      temp := TStringList.Create;
      try
        Socket.ReadStrings(temp, -1);
        data.Clear;
        data.Capacity := temp.Count;
        for s in temp do
            temp.Add( DeEscapeString( s ) );
      finally
        temp.Destroy;
      end;
    end;
    

    Now the question is what would you choose for DeEscapeString and EscapeString ? The options are many.

    • You can choose convert string to base64 before sending and from base64 after reading
    • You can choose UUEEncode for escapgin and UUEDecode for de-escaping
    • You can choose yEnc: http://en.wikipedia.org/wiki/YEnc
    • Or you can choose very simplistic functions StrStringToEscaped and StrEscapedToString from JclString unit of from Jedi CodeLib ( http://jcl.sf.net ):

    what kind of escaping

    If you ask for suggestion i would suggest not using raw TCP Server. There is well-known and standard HTTP protocol, there are many libraries for Delphi implementing both HTTP server and HTTP client. And in the protocol (and libraries) there are already decided things like ciphering, compressing, languages support, etc. And if somethign goes wrong - you can take any HTTP sniffer and see who is in the wrong- clent or server - with your own eyes. Debugging is much simpler.

    If you are just starting, i suggest you looking into HTTP+JSON Synopse mORMot library, maybe it would cover your needs. You can take sample server code from http://robertocschneiders.wordpress.com/2012/11/22/datasnap-analysis-based-on-speed-stability-tests/ for example, or from demos in the lib.

    Then, if to arrange around raw TCP server, i'd send compressed data, so it would work better (networks are slower than CPU usually). See http://docwiki.embarcadero.com/CodeExamples/XE5/en/ZLibCompressDecompress_(Delphi).

    Sending:

    • 1: Send into network (int32) - TStringList.Count
    • 2: for every string doing
    • 2.1 creating TStringStream from the string[i]
    • 2.2 passing it via TZCompressionStream
    • 2.3 sending (int32) size of compressed data
    • 2.4 sending the data itself
    • 2.5 freeing the temporary streams

    Receiving

    • 1: Receive from net (int32) - count of packets
    • 1.1 ResultStringList.Clear; ResultStringList.Capacity := read_count.
    • 2: for every string doing
    • 2.1 creating TBytesStream
    • 2.2 read from net (int32) size of compressed data
    • 2.3 read N bytes from the network into BytesStream
    • 2.4 unpack it via TZDecompressionStream into TStringStream
    • 2.5 ResultStringList.Add( StringStream -> string );
    • 2.6 freeing the temporary streams

    Now, if you really don't want ot change almost anything, then JCL escaping would hopefully be enough for you. At least it worked for me, but my task was very different and was not about networks at all. But you can just test them all and see how it works for you.