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

后端 未结 5 605
梦如初夏
梦如初夏 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: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;
    

提交回复
热议问题