How to handle received data in the TCPClient ? (Delphi - Indy)

后端 未结 5 602
梦如初夏
梦如初夏 2021-02-01 22:59

When i send a message from TCPClient to a TCPServer it will be handled using OnExecute event in the server . Now i want to handle the rece

相关标签:
5条回答
  • 2021-02-01 23:31

    If you need the Indy client to handle incoming "messages" (definition of "message" depends on the protocol used), I recommend to take a look at the implementation of TIdTelnet in the protocols\IdTelnet unit.

    This component uses a receiving thread, based on a TIdThread, which asynchronously receives messages from the Telnet server, and passes them to a message handler routine. If you have a similar protocol, this could be a good starting point.

    Update: to be more specific, the procedure TIdTelnetReadThread.Run; in IdTelnet.pas is where the asynchronous client 'magic' happens, as you can see it uses Synchronize to run the data processing in the main thread - but of course your app could also do the data handling in the receiving thread, or pass it to a worker thread to keep the main thread untouched. The procedure does not use a loop, because looping / pausing / restarting is implemented in IdThread.

    0 讨论(0)
  • 2021-02-01 23:35

    Here is my code to Read / Write with Delphi 7. Using the Tcp Event Read.

    unit Unit1;
    
    interface
    
    uses
      Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
      Dialogs, StdCtrls, ScktComp;
    
    type
      TForm1 = class(TForm)
        ClientSocket1: TClientSocket;
        Button1: TButton;
        ListBox1: TListBox;
        Edit1: TEdit;
        Edit2: TEdit;
        procedure Button1Click(Sender: TObject);
        procedure ClientSocket1Read(Sender: TObject; Socket: TCustomWinSocket);
        procedure ClientSocket1Error(Sender: TObject; Socket: TCustomWinSocket;
          ErrorEvent: TErrorEvent; var ErrorCode: Integer);
      private
        { Private declarations }
      public
        { Public declarations }
      end;
    
    var
      Form1: TForm1;
    
    implementation
    
    {$R *.dfm}
    
    procedure TForm1.Button1Click(Sender: TObject);
    var
     UsePort: Integer;
     UseHost: String;
    
    begin
    UseHost := Edit1.Text;
    UsePort := STRTOINT(Edit2.Text);
    ClientSocket1.Port :=  UsePort;
    ClientSocket1.Host :=  UseHost;
    ClientSocket1.Active :=  true;
    end;
    
    procedure TForm1.ClientSocket1Read(Sender: TObject;
      Socket: TCustomWinSocket);
    begin
    ListBox1.Items.Add(ClientSocket1.Socket.ReceiveText);
    
    end;
    
    procedure TForm1.ClientSocket1Error(Sender: TObject;
      Socket: TCustomWinSocket; ErrorEvent: TErrorEvent;
      var ErrorCode: Integer);
    begin
      ErrorCode:=0;
      ClientSocket1.Active := False;
    end;
    
    procedure TForm1.BitBtn1Click(Sender: TObject);
    begin
      ClientSocket1.Socket.SendText(Edit1.Text);
    end;
    
    end.
    
    0 讨论(0)
  • 2021-02-01 23:38

    TCP doesn't operate with messages. That is stream-based interface. Consequently don't expect that you will get a "message" on the receiver. Instead you read incoming data stream from the socket and parse it according to your high-level protocol.

    0 讨论(0)
  • 2021-02-01 23:48

    As others said in response to your question, TCP is not a message oriented protocol, but a stream one. I'll show you how to write and read to a very simple echo server (this is a slightly modified version of a server I did this week to answer other question):

    The server OnExecute method looks like this:

    procedure TForm2.IdTCPServer1Execute(AContext: TIdContext);
    var
      aByte: Byte;
    begin
      AContext.Connection.IOHandler.Writeln('Write anything, but A to exit');
      repeat
        aByte := AContext.Connection.IOHandler.ReadByte;
        AContext.Connection.IOHandler.Write(aByte);
      until aByte = 65;
      AContext.Connection.IOHandler.Writeln('Good Bye');
      AContext.Connection.Disconnect;
    end;
    

    This server starts with a welcome message, then just reads the connection byte per byte. The server replies the same byte, until the received byte is 65 (the disconnect command) 65 = 0x41 or $41. The server then end with a good bye message.

    You can do this in a client:

    procedure TForm3.Button1Click(Sender: TObject);
    var
      AByte: Byte;
    begin
      IdTCPClient1.Connect;
      Memo1.Lines.Add(IdTCPClient1.IOHandler.ReadLn);  //we know there must be a welcome message!
      Memo1.Lines.Add('');// a new line to write in!
      AByte := 0;
      while (IdTCPClient1.Connected) and (AByte <> 65) do
      begin
        AByte := NextByte;
        IdTCPClient1.IOHandler.Write(AByte);
        AByte := IdTCPClient1.IOHandler.ReadByte;
        Memo1.Lines[Memo1.Lines.Count - 1] :=  Memo1.Lines[Memo1.Lines.Count - 1] + Chr(AByte);
      end;
      Memo1.Lines.Add(IdTCPClient1.IOHandler.ReadLn);  //we know there must be a goodbye message!
      IdTCPClient1.Disconnect;
    end;
    

    The next byte procedure can be anything you want to provide a byte. For example, to get input from the user, you can turn the KeyPreview of your form to true and write a OnKeyPress event handler and the NextByte function like this:

    procedure TForm3.FormKeyPress(Sender: TObject; var Key: Char);
    begin
      FCharBuffer := FCharBuffer + Key;
    end;
    
    function TForm3.NextByte: Byte;
    begin
      Application.ProcessMessages;
      while FCharBuffer = '' do  //if there is no input pending, just waint until the user adds input
      begin
        Sleep(10);
        //this will allow the user to write the next char and the application to notice that
        Application.ProcessMessages;
      end;  
      Result := Byte(AnsiString(FCharBuffer[1])[1]);  //just a byte, no UnicodeChars support
      Delete(FCharBuffer, 1, 1);
    end;
    

    Anything the user writes in the form will be sent to the server and then read from there and added to memo1. If the input focus is already in Memo1 you'll see each character twice, one from the keyboard and the other form the server.

    So, in order to write a simple client that gets info from a server, you have to know what to expect from the server. Is it a string? multiple strings? Integer? array? a binary file? encoded file? Is there a mark for the end of the connection? This things are usually defined at the protocol or by you, if you're creating a custom server/client pair.

    To write a generic TCP without prior known of what to get from the server is possible, but complex due to the fact that there's no generic message abstraction at this level in the protocol.

    Don't get confused by the fact there's transport messages, but a single server response can be split into several transport messages, and then re-assembled client side, your application don't control this. From an application point of view, the socket is a flow (stream) of incoming bytes. The way you interpret this as a message, a command or any kind of response from the server is up to you. The same is applicable server side... for example the onExecute event is a white sheet where you don't have a message abstraction too.

    Maybe you're mixing the messages abstraction with the command abstraction... on a command based protocol the client sends strings containing commands and the server replies with strings containing responses (then probably more data). Take a look at the TIdCmdTCPServer/Client components.

    EDIT

    In comments OP states s/he wants to make this work on a thread, I'm not sure about what's the problem s/he is having with this, but I'm adding a thread example. The server is the same as shown before, just the client part for this simple server:

    First, the thread class I'm using:

    type
      TCommThread = class(TThread)
      private
        FText: string;
      protected
        procedure Execute; override;
        //this will hold the result of the communication
        property Text: string read FText;
      end;
    
    procedure TCommThread.Execute;
    const
      //this is the message to be sent. I removed the A because the server will close 
      //the connection on the first A sent.  I'm adding a final A to close the channel.
      Str: AnsiString = 'HELLO, THIS IS _ THRE_DED CLIENT!A';
    var
      AByte: Byte;
      I: Integer;
      Client: TIdTCPClient;
      Txt: TStringList;
    begin
      try
        Client := TIdTCPClient.Create(nil);
        try
          Client.Host := 'localhost';
          Client.Port := 1025;
          Client.Connect;
          Txt := TStringList.Create;
          try
            Txt.Add(Client.IOHandler.ReadLn);  //we know there must be a welcome message!
            Txt.Add('');// a new line to write in!
            AByte := 0;
            I := 0;
            while (Client.Connected) and (AByte <> 65) do
            begin
              Inc(I);
              AByte := Ord(Str[I]);
              Client.IOHandler.Write(AByte);
              AByte := Client.IOHandler.ReadByte;
              Txt[Txt.Count - 1] :=  Txt[Txt.Count - 1] + Chr(AByte);
            end;
            Txt.Add(Client.IOHandler.ReadLn);  //we know there must be a goodbye message!
            FText := Txt.Text;
          finally
            Txt.Free;
          end;
          Client.Disconnect;
        finally
          Client.Free;
        end;
      except
        on E:Exception do
          FText := 'Error! ' + E.ClassName + '||' + E.Message;
      end;
    end;
    

    Then, I'm adding this two methods to the form:

    //this will collect the result of the thread execution on the Memo1 component.
    procedure TForm3.AThreadTerminate(Sender: TObject);
    begin
      Memo1.Lines.Text := (Sender as TCommThread).Text;
    end;
    
    //this will spawn a new thread on a Create and forget basis. 
    //The OnTerminate event will fire the result collect.
    procedure TForm3.Button2Click(Sender: TObject);
    var
      AThread: TCommThread;
    begin
      AThread := TCommThread.Create(True);
      AThread.FreeOnTerminate := True;
      AThread.OnTerminate := AThreadTerminate;
      AThread.Start;
    end;
    
    0 讨论(0)
  • 2021-02-01 23:52

    Add a TTimer. Set its Interval to 1. Write in OnTimer Event:

    procedure TForm1.Timer1Timer(Sender: TObject);
    var
    s: string;
    begin
    if not IdTCPClient1.Connected then Exit;
    if IdTCPClient1.IOHandler.InputBufferIsEmpty then Exit;
    s := IdTCPClient1.IOHandler.InputBufferAsString;
    Memo1.Lines.Add('Received: ' + s);
    end;
    

    Don't set Timer.Interval something else 1. Because, the received data deletes after some milliseconds.

    0 讨论(0)
提交回复
热议问题