Как обрабатывать полученные данные в TCPClient? (Delphi - Indy)

Когда я отправляю сообщение от TCPClient до TCPServer, он будет обрабатываться с помощью события OnExecute на сервере. Теперь я хочу обрабатывать полученные сообщения в клиенте, но TCPClient для этого не имеет никакого события. Поэтому я должен сделать поток, чтобы обрабатывать их вручную. как я могу это сделать?

Ответы

Ответ 1

Как говорили другие в ответ на ваш вопрос, TCP не является ориентированным на сообщения протоколом, а потоковым. Я покажу вам, как писать и читать очень простой сервер эха (это немного измененная версия сервера, который я сделал на этой неделе, чтобы ответить на другой вопрос):

Метод 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;

Этот сервер начинается с приветственного сообщения, а затем просто считывает байты подключения на каждый байт. Сервер отвечает на тот же байт, пока принятый байт не станет 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;

Следующая процедура байта может быть любым, что вы хотите предоставить байт. Например, чтобы получить вход от пользователя, вы можете превратить 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;

Все, что пользователь записывает в форме, будет отправлено на сервер, а затем прочитано оттуда и добавлено в memo1. Если фокус ввода уже находится в Memo1, вы увидите каждый символ дважды, один с клавиатуры, а другой - на сервере.

Итак, чтобы написать простой клиент, который получает информацию с сервера, вы должны знать, чего ожидать от сервера. Это строка? несколько строк? Integer? массив? двоичный файл? закодированный файл? Есть ли отметка для конца соединения? Обычно это определяется протоколом или вами, если вы создаете пользовательскую пару "клиент/сервер".

Чтобы написать общий 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;

Затем я добавляю эти два метода к форме:

//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;

Ответ 2

TCP не работает с сообщениями. Это потоковый интерфейс. Следовательно, не ожидайте, что вы получите "сообщение" на получателе. Вместо этого вы считываете входящий поток данных из сокета и анализируете его в соответствии с протоколом высокого уровня.

Ответ 3

Вот мой код для чтения/записи с Delphi 7. Использование 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.

Ответ 4

Если вам нужен клиент Indy для обработки входящих "сообщений" (определение "сообщение" зависит от используемого протокола), я рекомендую взглянуть на реализацию TIdTelnet в модуле протоколов \IdTelnet.

Этот компонент использует принимающий поток на основе TIdThread, который асинхронно принимает сообщения с сервера Telnet и передает их в процедуру обработки сообщений. Если у вас есть аналогичный протокол, это может быть хорошей отправной точкой.

Обновление: более конкретно, procedure TIdTelnetReadThread.Run; в IdTelnet.pas - это то, где происходит "магия" асинхронного клиента, поскольку вы можете видеть, что он использует Synchronize для запуска обработки данных в основном потоке, но, конечно, ваше приложение также может выполнять обработку данных в принимающем потоке или передавать его в рабочий поток, чтобы сохранить основной поток нетронутым. Процедура не использует цикл, поскольку в IdThread реализован цикл/приостановка/перезапуск.

Ответ 5

Добавьте TTimer. Установите Interval на 1. Запись в OnTimer Событие:

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;

Не устанавливайте Timer.Interval что-то еще 1. Потому что полученные данные удаляются через несколько миллисекунд.