2017-05-26 22 views
3

私はDelphiを初めて使用しています。ネットワーク操作をいくつか試みようとしています。この場合は、イベントが発生するたびに文字列を送信する通知サーバーに接続しようとします。TIdTCPClientを使用した非同期読み取り

最初のアプローチは次のとおりです。 私はTIdTCPClientを独自のスレッドで実行し、ReadTimeoutを設定して、常にブロックされるとは限りません。こうすることで、スレッドのTerminatedステータスを確認できます。

ConnectionToServer.ReadTimeout := MyTimeOut; 
while(Continue) do 
begin 
    // 
    try 
     Command := ConnectionToServer.ReadLn(); 
    except 
    on E: EIdReadTimeout do 
     begin 
       //AnotarMensaje(odDepurar, 'Timeout ' + E.Message); 
     end; 
    on E: EIdConnClosedGracefully do 
     begin 
       AnotarMensaje(odDepurar, 'Conexión cerrada ' + E.Message); 
       Continue := false; 
     end; 
    on E: Exception do 
     begin 
       AnotarMensaje(odDepurar, 'Error en lectura ' + E.Message); 
       Continue := false; 
     end; 
    end; 
    // treat the command 
    ExecuteRemoteCommand(Command);  
    if(self.Terminated) then 
    begin 
     Continue := false; 
    end; 
end; // while continue 

ReadLnコードを読む私は、バッファサイズを常にチェックするrepeat untilループでアクティブな待機を行っていることがわかりました。

TIdTCPServerがOnExecuteなどのメソッドで動作する方法でこれを非同期的に行う方法はありますか?または、少なくとも、アクティブな待ちを避けるための方法です。

答えて

3

Indyは、クライアント側とサーバー側の両方のブロッキングソケットを使用します。それについては何も非同期です。 TIdTCPServerの場合、クライアントで実行しようとしているのと同じように、別のワーカースレッドで各クライアントソケットを実行します。 TIdTCPClient はマルチスレッドではないため、独自のスレッドを実行する必要があります。

:あなたはインディ10にアップグレードする場合は、サーバーから受信したパケットのためのTIdCommandHandler.OnCommandイベントをトリガーする、あなたのための独自のスレッドを実行し、マルチスレッドでTIdCmdTCPClientクライアントを持っています。

ReadLn()InputBufferに指定されたATerminatorが見つかるまで、またはタイムアウトが発生するまでループを実行します。 ATerminatorが見つかるまで、ReadLn()はソケットからさらにInputBufferにデータを読み込み、再度スキャンします。バッファサイズのチェックは、すでにスキャンしたデータを再スキャンしないことを確認することです。

ブロッキングReadLn()コール(またはブロッキングソケットコール)を「起床」する唯一の方法は、別のスレッドからソケットを閉じることです。それ以外の場合は、通常はタイムアウトになるまで待たなければなりません。

また、ReadLn()は、タイムアウト時にEIdReadTimeout例外を発生させません。それは例えば、TrueにReadLnTimedoutプロパティを設定し、空の文字列を返します。

ConnectionToServer.ReadTimeout := MyTimeOut; 

while not Terminated do 
begin 
    try 
    Command := ConnectionToServer.ReadLn; 
    except 
    on E: Exception do 
    begin 
     if E is EIdConnClosedGracefully then 
     AnotarMensaje(odDepurar, 'Conexión cerrada') 
     else 
     AnotarMensaje(odDepurar, 'Error en lectura: ' + E.Message); 
     Exit; 
    end; 
    end; 

    if ConnectionToServer.ReadLnTimedout then begin 
    //AnotarMensaje(odDepurar, 'Timeout'); 
    Continue; 
    end; 

    // treat the command 
    ExecuteRemoteCommand(Command);  
end; 

あなたはこのモデルが気に入らない場合、あなたはインディを使用する必要はありません。より効率的で反応の良いモデルは、代わりにWinSockを直接使用することです。WSARecv()とオーバーラップしたI/Oを使用して、CreateEvent()またはTEventを介して待機可能なイベントを作成して、スレッド終了を通知してから、スレッドは何もしないときにスリープ中にソケットと終了の両方を同時に待つことができます例えば:あなたは、Delphi XE2以降を使用している場合は

hSocket = socket(...); 
connect(hSocket, ...); 
hTermEvent := CreateEvent(nil, True, False, nil); 

... 

var 
    buffer: array[0..1023] of AnsiChar; 
    wb: WSABUF; 
    nRecv, nFlags: DWORD; 
    ov: WSAOVERLAPPED; 
    h: array[0..1] of THandle; 
    Command: string; 
    Data, Chunk: AnsiString; 
    I, J: Integer; 
begin 
    ZeroMemory(@ov, sizeof(ov)); 
    ov.hEvent := CreateEvent(nil, True, False, nil); 
    try 
    h[0] := ov.hEvent; 
    h[1] := hTermEvent; 

    try 
     while not Terminated do 
     begin 
     wb.len := sizeof(buffer); 
     wb.buf := buffer; 

     nFlags := 0; 

     if WSARecv(hSocket, @wb, 1, @nRecv, @nFlags, @ov, nil) = SOCKET_ERROR then 
     begin 
      if WSAGetLastError() <> WSA_IO_PENDING then 
      RaiseLastOSError; 
     end; 

     case WaitForMultipleObjects(2, PWOHandleArray(@h), False, INFINITE) of 
      WAIT_OBJECT_0: begin 
      if not WSAGetOverlappedResult(hSocket, @ov, @nRecv, True, @nFlags) then 
       RaiseLastOSError; 

      if nRecv = 0 then 
      begin 
       AnotarMensaje(odDepurar, 'Conexión cerrada'); 
       Exit; 
      end; 

      I := Length(Data); 
      SetLength(Data, I + nRecv); 
      Move(buffer, Data[I], nRecv); 

      I := Pos(Data, #10); 
      while I <> 0 do 
      begin 
       J := I; 
       if (J > 1) and (Data[J-1] = #13) then 
       Dec(J); 

       Command := Copy(Data, 1, J-1); 
       Delete(Data, 1, I); 

       ExecuteRemoteCommand(Command); 
      end; 
      end; 

      WAIT_OBJECT_0+1: begin 
      Exit; 
      end; 

      WAIT_FAILED: begin 
      RaiseLastOSError; 
      end; 
     end; 
     end; 
    except 
     on E: Exception do 
     begin 
     AnotarMensaje(odDepurar, 'Error en lectura ' + E.Message); 
     end; 
    end; 
    finally 
    CloseHandle(ov.hEvent); 
    end; 
end; 

TThreadはあなたがTThread.Terminate()が呼び出されたときにhTermEventを知らせるためにオーバーライドできる仮想TerminatedSet()方法があります。そうでない場合は、Terminate()を呼び出した後にSetEvent()に電話してください。

+0

ブレークダウンありがとう。私はReadLnの性質をブロックすることを心配していません、私はバッファサイズをチェックするビジーな待ちループが心配です。 私はインディとデルファイ7(レガシーコード)の古いバージョンを使用していますので、変更の余地はありません。 –

+1

@HéctorC。読み込みタイムアウトを完全に取り除き、読み込むデータがないときに 'ReadLn'がスレッドをブロックし、スレッドを終了する準備ができたらクライアントを切断することを検討してください。 –

3

これは別のスレッドで行うことができます。

TIdTCPServerは、バックグラウンドでスレッドを使用して、複数のクライアントのリッスンと通信をサポートします。

TIdTCPClientは1つのサーバーに接続するため、この機能は組み込まれていないと思いますが、自分で別のスレッドでTIdTCPClientを作成して使用することができます。私は同じ方法でそれを解決するだろう。

タイムアウトを非常に小さくすると問題はありません。データが欠落しないように、ソケットはまだその期間に開いています。 10msのような小さな値にタイムアウトを設定することができます。そうすれば、あなたのスレッドは長引くことはありませんが、タイムアウトはreadlnを抜けて再入力するための重大なオーバーヘッドを引き起こさないように十分に長くなります。

+0

これは私がやっていることです。しかし、ReadLn操作から定期的に終了してTerminatedスレッドプロパティをチェックする必要があります。 また、このスレッドを他の場所で終了すると、スレッドから終了するためにReadLnタイムアウトまで待たなければなりません。 –

+1

はい。タイムアウトを非常に小さくすると問題になりますか?その期間にソケットが開いているので、10msのような小さな値にタイムアウトを設定することができます。そうすれば、あなたのスレッドは長引くことはありませんが、タイムアウトはreadlnを抜けて再入力するための重大なオーバーヘッドを引き起こさないように十分に長くなります。 – GolezTrol

+1

Btw、私は好奇心を持って、グーグルグーグル。私は同様の質問[Indy 10 IdTCPClient別のスレッドを使用してデータを読む](https://stackoverflow.com/questions/554142/indy-10-idtcpclient-reading-data-using-a-separate-thread)と[Indy TIdTCPClientデータの読解](https://stackoverflow.com/questions/17372366/delphi-indy-tidtcpclient-reading-data)を参照してください。どちらも、このスレッドを実装する方法のより精巧な例を示しています。このイベントをメインスレッド。 – GolezTrol

関連する問題