2017-08-29 14 views
1

使用して、タイムアウトで一部のデータを受信:? はどのようにTIdTcpClientを使用して、以下の条件で100バイトの文字列を受け取るためにTIdTcpClient

  • 何も入ってきていない場合は

    、読むの呼び出しはブロックされなければならないとスレッドが永遠に
  • をお待ちしております
  • 100バイトを受信した場合、Read呼び出しはバイト文字列を返す必要があります
  • 0より大きいが100未満の値を受け取った場合、少なくとも何らかのタイムアウト(たとえば1秒)後にRead呼び出しが返されますDelphi IDEのデバッグモードでの例外処理がmになっていないため、タイムアウト例外を発生させることなく、妥当な時間便利です。

今の私の最適でないコードは次のとおりです。

unit Unit2; 

interface 

uses 
    System.Classes, IdTCPClient; 

type 
    TTcpReceiver = class(TThread) 
    private 
    _tcpc: TIdTCPClient; 
    _onReceive: TGetStrProc; 
    _buffer: AnsiString; 
    procedure _receiveLoop(); 
    procedure _postBuffer; 
    protected 
    procedure Execute(); override; 
    public 
    constructor Create(); reintroduce; 
    destructor Destroy(); override; 
    property OnReceive: TGetStrProc read _onReceive write _onReceive; 
    end; 

implementation 

uses 
    System.SysUtils, Vcl.Dialogs, IdGlobal, IdExceptionCore; 

constructor TTcpReceiver.Create(); 
begin 
    inherited Create(True); 
    _buffer := ''; 
    _tcpc := TIdTCPClient.Create(nil); 
    //_tcpc.Host := '192.168.52.175'; 
    _tcpc.Host := '127.0.0.1'; 
    _tcpc.Port := 1; 
    _tcpc.ReadTimeout := 1000; 
    _tcpc.Connect(); 
    Suspended := False; 
end; 

destructor TTcpReceiver.Destroy(); 
begin 
    _tcpc.Disconnect(); 
    FreeAndNil(_tcpc); 
    inherited; 
end; 

procedure TTcpReceiver.Execute; 
begin 
    _receiveLoop(); 
end; 

procedure TTcpReceiver._postBuffer(); 
var buf: string; 
begin 
    if _buffer = '' then Exit; 
    buf := _buffer; 
    _buffer := ''; 
    if Assigned(_onReceive) then begin 
    Synchronize(
     procedure() 
     begin 
     _onReceive(buf); 
     end 
    ); 
    end; 
end; 

procedure TTcpReceiver._receiveLoop(); 
var 
    c: AnsiChar; 
begin 
    while not Terminated do begin 
    try 
     c := AnsiChar(_tcpc.IOHandler.ReadByte()); 
     _buffer := _buffer + c; 
     if Length(_buffer) > 100 then 
     _postBuffer(); 
    except 
     //Here I have to ignore EIdReadTimeout in Delphi IDE everywhere, but I want just to ignore them here 
     on ex: EIdReadTimeout do _postBuffer(); 
    end; 
    end; 
end; 

end. 
+1

このハードワークを自分でやってみるのではなく、['IOHandler.ReadBytes()'](http://www.indyproject.org/docsite/html/[email protected]@[email protected]) ? –

+2

@Paul Indyがバッファリングを処理するのは、ほとんどの人が管理したいと思っているよりも優れていると私は確信しています。 50バイトだけが必要な場合は、なぜ100を求めるのですか?ある時点では、有用な意味を持つことができる最小のパケットサイズがあります。そのパケットを待ってください。それが処理されたらそれを処理し、それがなければ何もしません。あなたが失われたデータを気にしないならば、おそらくUDPはTCPより良いプロトコルです。 –

+0

@Paul盲目的に読んでから例外を処理しようとするよりも、読み込みを試みる前にバッファをチェックする方が良いでしょう( 'IOHandler.InputBufferIsEmpty then ...')。空のバッファから読み取ろうとしても意味がありません。文字列を読んでコマンドプロトコルを制御している場合は、分かりやすいコマンドターミネータを選び、 'ReadLn'を使うだけです。 –

答えて

5

TCPはストリーム指向ではなく、UDPのような指向のメッセージがあります。構造を持たない任意のバイトを読み込むのは悪い設計です。読み込みを途中で止め、読み込みを停止した後に読み込みたいバイトが到着すると、通信が簡単に破損します。バイトは読み込まれるまでソケットから削除されないので、次回の読み込みで期待されるより多くの/異なるバイトがある可能性があります。

100バイトが必要な場合は、100バイトを読み込んで完了してください。送信者が50バイトしか送信しない場合、50バイトが受信された後に読み込みを停止できるように、事前に通知する必要があります。送信者がそれをしていない場合、これは非常にうまく設計されていないプロトコルです。一般的に、タイムアウトを使用して送信終了を検出するのは悪い設計です。ネットワーク遅延は、誤った検出を容易に引き起こす可能性があります。

TCPメッセージは、受信者が1つのメッセージがどこで終了し、次のメッセージが始まるかを正確に把握できるように、適切に構成されている必要があります。 TCPでこれを行うには3つの方法があります。

  1. 固定長メッセージを使用します。受信側は、予想されるバイト数が到着するまで読み取りを続けることができます。

  2. メッセージ自体を送信する前にメッセージの長さを送信してください。レシーバは長さを最初に読み取ってから、指定されたバイト数が到着するまで読み取りを続けます。

  3. メッセージデータに表示されない一意の区切り文字でメッセージを終了します。レシーバは、その区切り文字が到着するまでバイトを読みとることができます。 TCPで何ができるかあなたがを求めている言われていること


、(しかしはTCPで行われていないはず!)。また、手動バッファをまったく使用せずに、Indyの内蔵バッファを代わりに使用することもできます。たとえば:サイドノートで

unit Unit2; 

interface 

uses 
    System.Classes, IdTCPClient; 

type 
    TTcpReceiver = class(TThread) 
    private 
    _tcpc: TIdTCPClient; 
    _onReceive: TGetStrProc; 
    procedure _receiveLoop; 
    procedure _postBuffer; 
    protected 
    procedure Execute; override; 
    public 
    constructor Create; reintroduce; 
    destructor Destroy; override; 
    property OnReceive: TGetStrProc read _onReceive write _onReceive; 
    end; 

implementation 

uses 
    System.SysUtils, Vcl.Dialogs, IdGlobal; 

constructor TTcpReceiver.Create; 
begin 
    inherited Create(False); 
    _tcpc := TIdTCPClient.Create(nil); 
    //_tcpc.Host := '192.168.52.175'; 
    _tcpc.Host := '127.0.0.1'; 
    _tcpc.Port := 1; 
end; 

destructor TTcpReceiver.Destroy; 
begin 
    _tcpc.Free; 
    inherited; 
end; 

procedure TTcpReceiver.Execute; 
begin 
    _tcpc.Connect; 
    try 
    _receiveLoop; 
    finally 
    _tcpc.Disconnect; 
    end; 
end; 

procedure TTcpReceiver._postBuffer; 
var 
    buf: string; 
begin 
    with _tcpc.IOHandler do 
    buf := ReadString(IndyMin(InputBuffer.Size, 100)); 
    { alternatively: 
    with _tcpc.IOHandler.InputBuffer do 
    buf := ExtractToString(IndyMin(Size, 100)); 
    } 
    if buf = '' then Exit; 
    if Assigned(_onReceive) then 
    begin 
    Synchronize(
     procedure 
     begin 
     if Assigned(_onReceive) then 
      _onReceive(buf); 
     end 
    ); 
    end; 
end; 

procedure TTcpReceiver._receiveLoop; 
var 
    LBytesRecvd: Boolean; 
begin 
    while not Terminated do 
    begin 
    while _tcpc.IOHandler.InputBufferIsEmpty do 
    begin 
     _tcpc.IOHandler.CheckForDataOnSource(IdTimeoutInfinite); 
     _tcpc.IOHandler.CheckForDisconnect; 
    end; 

    while _tcpc.IOHandler.InputBuffer.Size < 100 do 
    begin 
     // 1 sec is a very short timeout to use for TCP. 
     // Consider using a larger timeout... 
     LBytesRecvd := _tcpc.IOHandler.CheckForDataOnSource(1000); 
     _tcpc.IOHandler.CheckForDisconnect; 
     if not LBytesRecvd then Break; 
    end; 

    _postBuffer; 
    end; 
end; 

end. 

は、「デルファイIDEのデバッグモードで例外処理が便利なされていない」というあなたの文は単にばかげています。 IndyのIOHandlerには、例外動作を制御するためのプロパティとメソッドのパラメータがあります。また、デバッガが例外を処理する方法が気に入らなければ、例外を無視するように構成するだけです。特定の例外タイプを無視するようにデバッガを構成することも、ブレークポイントを使用して特定のコードブロックで例外処理をスキップするようにデバッガに指示することもできます。

+2

@Paul Indyは、デリミタベースのテキストプロトコルを処理するために設計されています。 IOHandlerには 'WaitFor()'と 'ReadLn()'メソッドと 'ReadLnTimedOut'プロパティがあります。開始文字を 'WaitFor'して返すものをすべて破棄し、次の文字を' ReadLn'してチェックサムをチェックし、それを繰り返します。 –

+1

@Paul:私はVisual Studioを使用しませんが、IDEに関係なく、デバッガは常に例外を取得してから、デバッグされたプロセスに渡します。これは、デバッガと対話するOSレイヤで処理されます。 [Visual Studioでデバッグ中の例外について理解する](https://blogs.msdn.microsoft.com/devops/2015/01/07/understanding-exceptions-while-debugging-with-visual-studio/)を参照してください。 VSとDelphiの両方は、「最初のチャンス」例外を壊さず、デバッグされたプロセスが正常に処理できるように設定できます。 –

+1

@Paul:WaitFor( '$')を呼び出してIOHandlerの 'InputBuffer.IndexOf()'メソッドを利用すると、82バイトの制限を処理するときにCRLFを探すのに役立ちます。しかし、あなたは非常に扱いにくく、ひどく設計されたTCPプロトコルを扱っています。このデータはどこから来ていますか? –

関連する問題