Question

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.

Was it helpful?

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.

OTHER TIPS

Don't use the TStrings overload as it seems to use line breaks as separator between strings which does not work if your strings contain line breaks themselves.

You can easily write your own wrapper method to send a list of strings over the wire (take that as pseudocode):

procedure WriteStrings(IOHandler : TIdIOHandler; Strings : TStrings);
var
  Str : String;
begin
IOHandler.WriteBufferOpen;
try
  IOHandler.Write(Strings.Count);
  for Str in Strings do
    IOHandler.Write(Str);  
finally
  IOHandler.WriteBufferClose;
end;
end;

procedure ReadStrings(IOHandler : TIdIOHandler; Strings : TStrings);
var
  Count, I : Integer;
begin
Count := IOHandler.ReadInteger;
for I := 1 to Count do
  Strings.Add(IOHandler.ReadString);
end;
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top