Solved

INDY 10  , DELPHI 2010  and TCP server / client

Posted on 2010-08-25
12
11,735 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
  • 7
  • 4
12 Comments
 
LVL 3

Expert Comment

by:NevTon
Comment Utility
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
Comment Utility
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
Comment Utility
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
 
LVL 3

Expert Comment

by:NevTon
Comment Utility
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
Comment Utility
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
Comment Utility
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
Better Security Awareness With Threat Intelligence

See how one of the leading financial services organizations uses Recorded Future as part of a holistic threat intelligence program to promote security awareness and proactively and efficiently identify threats.

 
LVL 3

Expert Comment

by:NevTon
Comment Utility
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
Comment Utility
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
Comment Utility
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
Comment Utility
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
Comment Utility
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
Comment Utility
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

Do You Know the 4 Main Threat Actor Types?

Do you know the main threat actor types? Most attackers fall into one of four categories, each with their own favored tactics, techniques, and procedures.

Join & Write a Comment

Suggested Solutions

Title # Comments Views Activity
Secure Boot on W8 & 8.1 8 44
Master Detail with TADODataset 4 94
Strange behavior when a form is closed 6 42
select query - oracle 16 81
Introduction The parallel port is a very commonly known port, it was widely used to connect a printer to the PC, if you look at the back of your computer, for those who don't have newer computers, there will be a port with 25 pins and a small print…
Have you ever had your Delphi form/application just hanging while waiting for data to load? This is the article to read if you want to learn some things about adding threads for data loading in the background. First, I'll setup a general applica…
Excel styles will make formatting consistent and let you apply and change formatting faster. In this tutorial, you'll learn how to use Excel's built-in styles, how to modify styles, and how to create your own. You'll also learn how to use your custo…
This video explains how to create simple products associated to Magento configurable product and offers fast way of their generation with Store Manager for Magento tool.

763 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

Need Help in Real-Time?

Connect with top rated Experts

7 Experts available now in Live!

Get 1:1 Help Now