如何处理TCPClient中收到的数据?(德尔福 - 印地)

Ker*_*mia 14 delphi indy

当我将消息发送TCPClientTCPServer它会使用来处理OnExecute在服务器事件.现在我想在客户端处理收到的消息,但TCPClient没有任何事件.所以我必须创建一个线程来手动处理它们.我该怎么做 ?

jac*_*ate 21

正如其他人在回答您的问题时所说,TCP不是面向消息的协议,而是流式协议.我将向您展示如何编写和读取一个非常简单的echo服务器(这是我本周做的一个稍微修改过的服务器版本来回答其他问题):

服务器OnExecute方法如下所示:

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;
Run Code Online (Sandbox Code Playgroud)

此服务器以欢迎消息开头,然后只读取每个字节的连接字节.服务器回复相同的字节,直到接收的​​字节为65(断开命令)65 = 0x41或$ 41.然后服务器以良好的再见消息结束.

您可以在客户端执行此操作:

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;
Run Code Online (Sandbox Code Playgroud)

下一个字节过程可以是您想要提供字节的任何内容.例如,要从用户获取输入,您可以将表单的KeyPreview转为true并编写OnKeyPress事件处理程序和NextByte函数,如下所示:

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;
Run Code Online (Sandbox Code Playgroud)

用户在表单中写入的任何内容都将被发送到服务器,然后从那里读取并添加到memo1.如果输入焦点已经在Memo1中,您将看到每个字符两次,一个来自键盘,另一个来自服务器.

因此,为了编写一个从服务器获取信息的简单客户端,您必须知道服务器会发生什么.它是一个字符串?多个字符串?整数?阵列?一个二进制文件?编码文件?连接结束时是否有标记?如果您正在创建自定义服务器/客户端对,通常会在协议中或由您定义.

在没有事先知道从服务器获得什么的情况下编写通用TCP是可能的,但由于协议中此级别没有通用的消息抽象,因此很复杂.

不要对传输消息这一事实感到困惑,但是单个服务器响应可以拆分成几个传输消息,然后重新组装客户端,您的应用程序不会控制它.从应用程序的角度来看,套接字是传入字节的流(流).您将此解释为来自服务器的消息,命令或任何类型的响应的方式取决于您.同样适用于服务器端...例如onExecute事件是一张白板,你也没有消息抽象.

也许你将消息抽象与命令抽象混合......在基于命令的协议上,客户端发送包含命令的字符串,服务器回复包含响应的字符串(然后可能包含更多数据).查看TIdCmdTCPServer/Client组件.

编辑

在评论OP中他/她想要在一个线程上做这个工作,我不确定他/她有什么问题,但我正在添加一个线程示例.服务器与前面显示的相同,只是这个简单服务器的客户端部分:

首先,我正在使用的线程类:

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;
Run Code Online (Sandbox Code Playgroud)

然后,我将这两种方法添加到表单中:

//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;
Run Code Online (Sandbox Code Playgroud)