web-dev-qa-db-ja.com

受信したデータをTCPClientで処理する方法は? (デルファイ-インディ)

TCPClientからTCPServerにメッセージを送信すると、サーバーでOnExecuteイベントを使用して処理されます。クライアントで受信したメッセージを処理したいのですが、TCPClientにはこのイベントがありません。だから私はそれらを手動で処理するためのスレッドを作らなければなりません。どうすればできますか?

15
Kermia

他の人があなたの質問に応じて言ったように、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にある場合、各文字が2回表示されます。1つはキーボードから、もう1つはサーバーからです。

したがって、サーバーから情報を取得する単純なクライアントを作成するには、サーバーから何が期待できるかを知っている必要があります。ひもですか?複数の文字列?整数?アレイ?バイナリファイル?エンコードされたファイル?接続の終了のマークはありますか?これは通常、カスタムサーバー/クライアントのペアを作成している場合は、プロトコルまたはユーザーによって定義されます。

一般的な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;

次に、この2つのメソッドをフォームに追加します。

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

TCPはメッセージを処理しません。それはストリームベースのインターフェースです。したがって、受信者に「メッセージ」が表示されることを期待しないでください。代わりに、ソケットから着信データストリームを読み取り、高レベルのプロトコルに従って解析します。

以下は、Delphi 7で読み取り/書き込みを行うためのコードです。Tcpイベントの読み取りを使用します。

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.
3
Tprice88

Indyクライアントで着信「メッセージ」を処理する必要がある場合(「メッセージ」の定義は使用するプロトコルによって異なります)、protocols\IdTelnetユニットのTIdTelnetの実装を確認することをお勧めします。

このコンポーネントは、TelThreadサーバーから非同期にメッセージを受信し、メッセージハンドラルーチンに渡すTIdThreadに基づく受信スレッドを使用します。同様のプロトコルを使用している場合、これは良い出発点になる可能性があります。

更新:より具体的には、procedure TIdTelnetReadThread.Run; IdTelnet.pasは、非同期クライアントの「マジック」が発生する場所です。同期を使用してメインスレッドでデータ処理を実行していることがわかりますが、アプリは受信スレッドでデータ処理を行うこともできます。これをワーカースレッドに渡して、メインスレッドをそのまま維持します。ループ/一時停止/再起動はIdThreadに実装されているため、プロシージャはループを使用しません。

1
mjn

TTimerを追加します。 Interval1に設定します。 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を設定しないでください。なぜなら、受信したデータは数ミリ秒後に削除されます。

0
H. Farid