Как обрабатывать полученные данные в TCPClient? (Дельфы - Инди) - PullRequest
14 голосов
/ 03 апреля 2011

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

Ответы [ 5 ]

21 голосов
/ 04 апреля 2011

Как говорили другие в ответ на ваш вопрос, 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.

EDIT

В комментариях ОП заявляет, что он / она хочет сделать эту работу в потоке, я не уверен в том, что проблема, с которой он / она сталкивается с этим, но я добавляю пример потока. Сервер такой же, как показано выше, только клиентская часть для этого простого сервера:

Во-первых, класс потока, который я использую:

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;
4 голосов
/ 03 апреля 2011

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

3 голосов
/ 01 апреля 2012

Вот мой код для чтения / записи с Delphi 7. Использование события Tcp 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.
1 голос
/ 03 апреля 2011

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

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

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

0 голосов
/ 30 июня 2017

Добавить 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.Потому что полученные данные удаляются через несколько миллисекунд.

...