Solved

INDY 10  , DELPHI 2010  and TCP server / client

Posted on 2010-08-25
12
12,335 Views
Last Modified: 2012-06-21
for the demo program at  https://sourceforge.net/projects/indy10clieservr/   already strings exchange and records exchange is working fine,
now the is a extension of the demo using a bit more complex communication

idea:

 client app has select box,
 itemindex = 0   :   exchange strings between Client and Server
                    1   :   exchange a record betwenn C & S
                    2   :  exchange a file / stream between   C &  S

some one can see the bug in my implementation ?  only interessted in a solution of this demo code using Indy 10

we may put a reference to the EE forum in the source code if you like

//  read a single integer from the client and then
//  excute one of the 3 communications flows
//  ExchangeStrings  &  ExchangeRecords  already seem to be functional
//  in previous code versions !!!
procedure TServerMainForm.IdTCPServerExecute(AContext: TIdContext);
var    CommBlock, NewCommBlock   :    TINDYCMD;
       buf                       :    TIdBytes;
       line                      :    String;
       i                         :    integer;

begin

   memo1.Lines.Add('server execute start' );


   with  AContext.Connection  do
         begin
             //    read data from client  ...

            IOHandler.Readln(line);
         end;
   try
    if length(line) > 0  then
        begin
         memo1.Lines.Add('cmd select input Line' +  line );
         i:= strToInt(Line);
        end
        else
         i:=-1;
         memo1.Lines.Add('cmd select input Line is size 0 '  );
   except

  end;


   case i of
    0:  begin

          TCPServerExecuteExchangeStrings(AContext);
        end;

    1 : begin

         TCPServerExecuteExchangeRecords(AContext);

        end;
    2:  begin

          TCPServerExecuteREADSTREAM(AContext);
        end;
    else
       //
    end;

   LEDShape.brush.Color := clgreen;



   memo1.Lines.Add('server execute done' );

end;




///  select a command from a CMD List box, today only trial code
///  first  client  send a Integer value to the server
///  then starting record or stream or strings exchange process
///  it is just a demo !!!!
procedure TIndyClientMainForm.ClientExecuteButtonClick(Sender: TObject);
var    CommBlock, NewCommBlock   :  TINDYCMD;
       line   : String;
begin



        Memo1.Lines.Add ('client execute : ');



        MyIdTCPClient.IOHandler.writeln(IntToStr(CMDComboBox.ItemIndex));   { Send the text to the server }



        case CMDComboBox.ItemIndex  of

        0:   //   send strings to the server
        begin

         ClientSendString (sender) ;

        end;

        1:   //
        begin
         CommBlock.CMD_CLASS := StrToInt(CMD_CLASS_Edit.Text);   //  user must take care only integer val as input
         CommBlock.CMD_VALUE := CMD_VALUE_Edit.Text;             //  here a string
         CommBlock.CMD_TIMESTAMP := now;

         ClientTRecordExchange( CommBlock, NewCommBlock ) ;
        end;
        2:
        begin
         ClientSendStream(FilenameEdit.Text);
        end;
        else
         Memo1.Lines.Add (' please select a valid command ');
        end;







end;

Open in new window

0
Comment
Question by:BdLm
[X]
Welcome to Experts Exchange

Add your voice to the tech community where 5M+ people just like you are talking about what matters.

  • Help others & share knowledge
  • Earn cash & points
  • Learn & ask questions
  • 7
  • 4
12 Comments
 
LVL 3

Expert Comment

by:NevTon
ID: 33525309
Hi,
Try to load datas to TMemoryStream and than read them from TStringStream (binary stream), for example see code snipet.
You can made couple of functions to simplify the code.

var
  ms: TMemoryStream;
  ss: TStringStream;

...

ms:=TMemoryStream.Create;
try
// load the data to memory
//  ms.LoadFromStream(other_strem);
//  ms.LoadFromFile(file_name);
//  ms.Write(buffer, SizeOf(buffer)); // can convert any record to binary string
  ms.Position:=0;
  ss:=TStringStream.Create('');
  try
    ms.SaveToStream(ss);
    ss.Position:=0;
    MyIdTCPClient.IOHandler.writeln(IntToStr(CMDComboBox.ItemIndex)+ss.ReadString(ss.Size));
  finally
    ss.Free;
  end;
finally
  ms.Free;
end;

Open in new window

0
 
LVL 3

Expert Comment

by:NevTon
ID: 33527706
Hi,
I made some tests on my solution and understand that this not work as I expected, so I've made some upgrades, and now have something like this (code snipet).
This solution is tested and working.

unit MemoryStringStream;

interface

uses Classes;

type
  TMemoryStringStream = class(TMemoryStream)
  public
    function FromString(AString: String): Integer;
    function ToString: String;
  end;

implementation

uses SysUtils;

{ TMemoryStringStream }

function TMemoryStringStream.FromString(AString: String): Integer;
var
  i: Integer;
  s2: String[2];
  b: Byte;
begin
  AString:=Trim(AString);
  Result:=Length(AString) mod 2;
  if Result > 0 then // x mod y = x — (x div y) * y.
    raise Exception.Create('Input string size must be divideable by 2!');
  Result:=Length(AString) div 2;
  Self.Clear;
  for i:=1 to Result do begin
    s2:=AString[i * 2 - 1] + AString[i * 2 - 0];
    b:=StrToInt('$' + s2);
    Self.Write(b, SizeOf(Byte));
  end;
end;

function TMemoryStringStream.ToString: String;
var
  i: Integer;
  b: Byte;
begin
  Result:='';
  for i:=0 to (Self.Size - 1) do begin
    b:=Byte(Pointer(Longint(Self.Memory) + i)^);
    Result:=Result + IntToHex(b, 2);
  end;
end;

end.

Open in new window

0
 
LVL 3

Expert Comment

by:NevTon
ID: 33527715
Test project.
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TForm1 = class(TForm)
    Memo1: TMemo;
    Button1: TButton;
    Button2: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
  public
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

uses MemoryStringStream;

type
  TTestRecord = packed record
    FType: Integer;
    FData: ShortString;
  end;

procedure TForm1.Button1Click(Sender: TObject); // record to string
var
  ms: TMemoryStringStream;
  tr: TTestRecord;
begin
  FillChar(tr, SizeOf(tr), 0);
  tr.FType:=128;
  tr.FData:='This Is A Test';
  ms:=TMemoryStringStream.Create;
  try
    // load the data to memory
//    ms.LoadFromStream(other_strem);
//    ms.LoadFromFile(file_name);
    ms.Write(tr, SizeOf(tr)); // can convert any record to binary string
    ms.Position:=0;
//    ShowMessage(IntToStr(ms.Size));
//      MyIdTCPClient.IOHandler.writeln(IntToStr(CMDComboBox.ItemIndex)+ms.ToString);
    Memo1.Lines.Add(ms.ToString);
  finally
    ms.Free;
  end;
end;

procedure TForm1.Button2Click(Sender: TObject); // string to record
var
  ms: TMemoryStringStream;
  tr: TTestRecord;
begin
  ms:=TMemoryStringStream.Create;
  try
    // load the data to memory
//    ms.LoadFromStream(other_strem);
//    ms.LoadFromFile(file_name);
//    ms.Write(tr, SizeOf(tr)); // can convert any record to binary string
    ms.FromString(Memo1.Text);
    ms.Position:=0;
    ShowMessage(IntToStr(ms.Size));
    FillChar(tr, SizeOf(tr), 0);
    ms.Read(tr, SizeOf(tr));
    Memo1.Lines.Add('TTestRecord{FType = '+IntToStr(tr.FType)+'; FData = "'+tr.FData+'"}');
  finally
    ms.Free;
  end;
end;

end.

Open in new window

0
Industry Leaders: We Want Your Opinion!

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

 
LVL 3

Expert Comment

by:NevTon
ID: 33655509
Hi,

Maybe you should  try another solution than selecting from a list of what type of  transmission you operate.
The professional solutions apply a little  more complex methods, I am thinking of setting out the applicable record  data type and some other information, instead of selecting manual, build  automation, so that the programs themselves know how to react.
In this way, you  take off the responsibility for the accuracy of information input.
A simple  example:

// record helpers declarations
type
  TDataTypes = (dtString, dtRecord, dtStream);
  TDataTypeHeader = packed record
    DataType: TDataTypes;
    DataLength: Integer;
//    DataChecksum: LongInt;
  end;
  TStreamHeader = packed record
    FileName: ShortString;
    FilePath: ShortString;
    FileSize: Integer;
//    FileAttr: Byte;
  end;

// class declarations
  private
    // client side
    procedure SendString(AString: String);
    procedure SendRecord(const ARecord);
    procedure SendStream(AFile: String);

    // server side     
    function  ReadString(IOHandler: TIdIOHandler): String;
    procedure ReadRecord(IOHandler: TIdIOHandler; ASize: Integer; var ARecord);
    function  ReadStream(IOHandler: TIdIOHandler; ASize: Integer; var AStreamHeader: TStreamHeader): TMemoryStream;


//implementation

//////////////////////////////////////////////////////////////////////////////////////////////////////
// CLIENT SIDE
//////////////////////////////////////////////////////////////////////////////////////////////////////

procedure TForm1.SendString(AString: String);
var
  DataTypeHeader: TDataTypeHeader;
  buf: TIdBytes;
begin
// to send string first send DataTypeHeader
  DataTypeHeader.DataType:=dtString;
  DataTypeHeader.DataLength:=Length(AString);
  buf:=RawToBytes(DataTypeHeader, SizeOf(TDataTypeHeader));
  IdTCPClient.IOHandler.Write(buf);
// and now send the string
  IdTCPClient.IOHandler.writeln(AString);
end;

procedure TForm1.SendRecord(const ARecord);
var
  DataTypeHeader: TDataTypeHeader;
  buf: TIdBytes;
begin
// to send record first send DataTypeHeader
  DataTypeHeader.DataType:=dtRecord;
  DataTypeHeader.DataLength:=SizeOf(ARecord);
  buf:=RawToBytes(DataTypeHeader, SizeOf(TDataTypeHeader));
  IdTCPClient.IOHandler.Write(buf);
// and now send the record
  buf:=RawToBytes(ARecord, SizeOf(ARecord));
  IdTCPClient.IOHandler.Write(buf);
end;

procedure TForm1.SendStream(AFile: String);
var
  DataTypeHeader: TDataTypeHeader;
  FS: TFileStream;
  StreamHeader: TStreamHeader;
  buf: TIdBytes;
begin
  IdTCPClient.IOHandler.LargeStream := True;
  FS:=TFileStream.Create(AFile, fmOpenRead + fmShareDenyNone);
  try
// to send stream first fill StreamHeader
    StreamHeader.FileName:=ExtractFileName(AFile);
    StreamHeader.FilePath:=ExtractFilePath(AFile);
    StreamHeader.FileSize:=FS.Size;
//    StreamHeader.FileAttr:=
// second send DataTypeHeader
    DataTypeHeader.DataType:=dtStream;
    DataTypeHeader.DataLength:=SizeOf(TStreamHeader);
    buf:=RawToBytes(DataTypeHeader, SizeOf(TDataTypeHeader));
    IdTCPClient.IOHandler.Write(buf);
// third send StreamHeader
    buf:=RawToBytes(StreamHeader, SizeOf(TStreamHeader));
    IdTCPClient.IOHandler.Write(buf);
// now send the stream
    IdTCPClient.IOHandler.Write(FS);
  finally
    FreeAndNil(FS);
  end;
end;

//////////////////////////////////////////////////////////////////////////////////////////////////////
// SERVER SIDE
//////////////////////////////////////////////////////////////////////////////////////////////////////

procedure TForm1.IdTCPServerExecute(AContext: TIdContext);
var
  buf: TIdBytes;
  DataTypeHeader: TDataTypeHeader;
  line: String;
  somerecord: array of Byte;
  MemStream: TMemoryStream;
  StreamHeader: TStreamHeader;
begin
  while AContext.Connection.Connected do begin
    AContext.Connection.IOHandler.ReadBytes(buf, SizeOf(TDataTypeHeader));
    BytesToRaw(buf, DataTypeHeader, SizeOf(TDataTypeHeader));
    case DataTypeHeader.DataType of
      dtString: begin
        line:=ReadString(AContext.Connection.IOHandler);
        if Length(line) < DataTypeHeader.DataLength then
          Exception.Create('Reading string error!');
        // do something with line variable here
      end;
      dtRecord: begin
        ReadRecord(AContext.Connection.IOHandler, DataTypeHeader.DataLength, somerecord);
        // do something with somerecord variable here
      end;
      dtStream: begin
        MemStream:=ReadStream(AContext.Connection.IOHandler, DataTypeHeader.DataLength, StreamHeader);
        // do something with StreamHeader variable here, example
        ShowMessage('Filename: '+StreamHeader.FileName+#13#10+
                    'Filepath: '+StreamHeader.FilePath+#13#10+
                    'Filesize: '+IntToStr(StreamHeader.FileSize));
        //
        // do something with memory stream here
        MemStream.Free;
      end;
    end;
  end;
end;

function TForm1.ReadString(IOHandler: TIdIOHandler): String;
begin
  Result:=IOHandler.Readln;
end;

procedure TForm1.ReadRecord(IOHandler: TIdIOHandler; ASize: Integer; var ARecord);
var
  buf: TIdBytes;
begin
  IOHandler.ReadBytes(buf, ASize);
  BytesToRaw(buf, ARecord, ASize);
end;

function TForm1.ReadStream(IOHandler: TIdIOHandler; ASize: Integer; var AStreamHeader: TStreamHeader): TMemoryStream;
begin
  FillChar(AStreamHeader, SizeOf(TStreamHeader), 0);
  ReadRecord(IOHandler, ASize, AStreamHeader);
  Result:=TMemoryStream.Create;
  IOHandler.ReadStream(Result, AStreamHeader.FileSize);
end;

Open in new window

0
 
LVL 8

Author Comment

by:BdLm
ID: 33660886
thanks for your inputs,  would like to use that code inputs to publish a new sample at source forge , as there is still now working complete sample for tcp ip  server client communication
 
0
 
LVL 3

Accepted Solution

by:
NevTon earned 500 total points
ID: 33661054
You're welcome.
I myself worked on a project of my instant messenger, a lot of  time was spent to construct its own protocol for exchanging information.
The best solution is to read and understand on  what basis does the TCP / IP work.
Next you put your  own records onto the records of the information transport protocol.
Simple to make and very elegant.
0
 
LVL 3

Expert Comment

by:NevTon
ID: 33661104
The bug in Your implementation of stream sending was easy.
You can't send strings (file name and file size) without informing the other side of what type of data and the length of it to read from the whole stream.
I hope that You understand me.

AStream := TFileStream.Create(filename, fmOpenRead + fmShareDenyNone);
try
  // the reader do not know how long is this file name, eg.: "abcde" or "abc"
  MyIdTCPClient.IOHandler.WriteLN(ExtractFilename(filename));     // send file name
  // the reader do not know how long is this stream size as sting, eg.: "12345" or "123"
  MyIdTCPClient.IOHandler.WriteLN(IntToStr(AStream.Size));        //send file size
  MyIdTCPClient.IOHandler.Write(AStream);                         //  send

Open in new window

0
 
LVL 8

Author Comment

by:BdLm
ID: 33663248
BTW:   the source forge project is downloaded ~ 30 times a day ....  shows that many people are searching for indy 10 samples ....  the working ones !!!!

0
 
LVL 3

Expert Comment

by:NevTon
ID: 33665857
I think so.
You must remember that the Indy (8,9,10) demos are for the beginners.
And even more I say, whole idea of Indy protocols is based on blocking sockets, so for the application based on events, this type of communication is very hard to use.
Why ? Because, when You send something You must wait for replay, and then the application is freazed for some time.
There is a solution for this, use additional threads to convince the application that we use non-blocking sockets.
This solution is easy to explain:
1. create thread
2. redirect any reading from TCP/IP protocol to this thread
3. use IOHandler.ReadXXXX inside thread execute method
4. and when we have something readed from our connection, fire the event or procedure to handle the data
5. or if there was any exception, handle it

I am thinking on about to publish my source codes to the world, but first I must make my own web page to handle this idea.
So, if You have any questions about my solutions, please ask me, I will answer as simply as I can.
0
 
LVL 5

Expert Comment

by:briangochnauer
ID: 33770512
Indy 9 to 10 doesn't translate well;
When I converted a major app it was quite a bit of work.
Replacing IdTCPServer with IdCMDTCPServer made life somewhat easier;
For your app; I would have used IdCMDTCPServer and created a command
GetString
GetRecord
GetFile
Then implementation would be a snap!
 

Clients send "GetString" command when and get data back like;

case CMDComboBox.ItemIndex  of

        0: begin
                 idTCPClient1.IOHandler.Writeln('GetString');
                 s := idTCPClient1.IOHandler.ReadLn;
            end; 

        1: begin
                 idTCPClient1.IOHandler.Writeln('GetRecord');
                 idTCPClient1.IOHandler.ReadStream(AStream,-1,true);            
              end; 

        2: begin
                 idTCPClient1.IOHandler.Writeln('GetFile');
                 idTCPClient1.IOHandler.ReadStream(AStream,-1,true);  
            end;  
end.


//I use this to have the server send me a jpg of the users' screen;
// from a IdCMDTCPServer
procedure TForm1.IdCmdScreenShotCommand(ASender: TIdCommand);
var fn:string;
begin
  try
    ASender.PerformReply := false;
    GetScreenShot(System32Dir+'\logfiles','scr.jpg',true);
    fn := System32Dir+'\logfiles\scr.jpg';
    if FileExists(fn) then
    begin
      try
        ASender.Context.Connection.IOHandler.WriteFile(fn);
      finally
        ASender.Context.Connection.Disconnect;
      end;
    end
     else   ASender.Context.Connection.IOHandler.WriteLn('failed no file');
  except
    On E:Exception do
    begin
      LogMessageSt('Exception Screen shot: '+e.message,1);
    end;
  end;
end;

Open in new window

0
 
LVL 8

Author Comment

by:BdLm
ID: 33881983
pls. do not close this question - still need a time to work with the solutions given here -- many other trouble came up with higher priority ....
0
 
LVL 8

Author Comment

by:BdLm
ID: 37851239
meanwhile the source forge code samples improved a lot,
still the complex demo 3 has a small bug  ...  in the server , the client is clean
0

Featured Post

Free Tool: Port Scanner

Check which ports are open to the outside world. Helps make sure that your firewall rules are working as intended.

One of a set of tools we are providing to everyone as a way of saying thank you for being a part of the community.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Introduction I have seen many questions in this Delphi topic area where queries in threads are needed or suggested. I know bumped into a similar need. This article will address some of the concepts when dealing with a multithreaded delphi database…
In my programming career I have only very rarely run into situations where operator overloading would be of any use in my work.  Normally those situations involved math with either overly large numbers (hundreds of thousands of digits or accuracy re…
Michael from AdRem Software outlines event notifications and Automatic Corrective Actions in network monitoring. Automatic Corrective Actions are scripts, which can automatically run upon discovery of a certain undesirable condition in your network.…
In this brief tutorial Pawel from AdRem Software explains how you can quickly find out which services are running on your network, or what are the IP addresses of servers responsible for each service. Software used is freeware NetCrunch Tools (https…
Suggested Courses
Course of the Month5 days, 9 hours left to enroll

626 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question