5

のカスタム タイムアウトを設定する必要がありTTcpClientます。デフォルトのタイムアウト時間は約 20 ~ 25 秒だと思いますが、500 ミリ秒に変更する必要があります。それは可能ですか?

procedure TForm1.Button1Click(Sender: TObject);
   begin
     TcpClient2.RemoteHost := '192.168.1.1';
     TcpClient2.RemotePort := '23';
     TcpClient2.Connect;

     tcpclient2.Receiveln();
     tcpclient2.Sendln('admin');
     tcpclient2.Receiveln;
   end;

オプションを試してみnon-blockingましたが、ボタンをクリックするとソフトウェアがエラーを返し、それを 4 ~ 5 回やり直す必要があります。何か助けはありますか?

ありがとう :)

4

1 に答える 1

8

Winsock には接続タイムアウトはありませんが、これは克服できます。

いくつかのオプションがあります:

  1. スレッドなし:

    • ノンブロッキング モードの使用: を呼び出してから、 Winsock select関数 ( TTcpClientによって継承されたTBaseSocket SelectConnectメソッドにカプセル化されている)を使用して待機します。

    • ブロッキング モードの使用: 一時的に非ブロッキング モードに変更し、前の場合と同様に処理を進めます。

  2. スレッドの場合: Remy Lebeau のWinsock API で接続タイムアウトを制御する方法に対する回答を参照してください。.

  3. インディを使用。

ブロッキング vs ノンブロッキング

ブロッキング モードまたは非ブロッキング モードの使用は、コードの多くに影響を与える非常に重要な設計上の決定であり、後で簡単に変更することはできません。

たとえば、ノンブロッキング モードでは、受信関数 (as Receiveln) は、十分な入力が利用可能になるまで待機せず、空の文字列を返す可能性があります。これが必要な場合は利点になる可能性がありますがTcpClient.WaitForData、受信関数を呼び出す前に使用して待機するなど、いくつかの戦略を実装する必要があります (この例では、そのままでは機能しReceiveln-Sendln-Receivelnません)。

単純なタスクの場合は、ブロッキング モードの方が扱いやすいです。

ノンブロッキングモード

次の関数は、接続が成功するか、タイムアウトが経過するまで待機します。

function WaitUntilConnected(TcpClient: TTcpClient; Timeout: Integer): Boolean;
var
  writeReady, exceptFlag: Boolean;
begin
  // Select waits until connected or timeout
  TcpClient.Select(nil, @writeReady, @exceptFlag, Timeout);
  Result := writeReady and not exceptFlag;
end;

使い方:

// TcpClient.BlockMode must be bmNonBlocking

TcpClient.Connect; // will return immediately
if WaitUntilConnected(TcpClient, 500) then begin // wait up to 500ms
  ... your code here ...
end;

また、TTcpClient のノンブロッキング モード設計には、次の欠点/欠陥があることに注意してください。

  • (10035)に設定するとOnError、いくつかの関数が呼び出されます。SocketErrorWSAEWOULDBLOCK
  • Connectedプロパティfalseは で割り当てられるためになりますConnect

ブロックモード

接続タイムアウトは、ソケットが作成された後で を呼び出す前に非ブロッキング モードに変更し、呼び出しConnect後にブロッキング モードに戻すことで実現できます。

TTcpClientを変更すると接続とソケットが閉じられるため、これはもう少し複雑です。また、接続とはBlockMode別にソケットを直接作成する方法もありません。

これを解決するには、ソケットの作成後、接続前にフックする必要があります。DoCreateHandleこれは、保護されたメソッドまたはOnCreateHandleイベントのいずれかを使用して実行できます。

最善の方法は TTcpClient からクラスを派生DoCreateHandleさせて を使用することですが、何らかの理由で派生クラスなしで TTcpClient を直接使用する必要がある場合は、 を使用してコードを簡単に書き直すことができますOnCreateHandle

type
  TExtendedTcpClient = class(TTcpClient)
  private
    FIsConnected: boolean;
    FNonBlockingModeRequested, FNonBlockingModeSuccess: boolean;
  protected
    procedure Open; override;
    procedure Close; override;
    procedure DoCreateHandle; override;
    function SetBlockModeWithoutClosing(Block: Boolean): Boolean;
    function WaitUntilConnected(Timeout: Integer): Boolean;
  public
    function ConnectWithTimeout(Timeout: Integer): Boolean;
    property IsConnected: boolean read FIsConnected;
  end;

procedure TExtendedTcpClient.Open;
begin
  try
    inherited;
  finally
    FNonBlockingModeRequested := false;
  end;
end;

procedure TExtendedTcpClient.DoCreateHandle;
begin
  inherited;
  // DoCreateHandle is called after WinSock.socket and before WinSock.connect
  if FNonBlockingModeRequested then
    FNonBlockingModeSuccess := SetBlockModeWithoutClosing(false);
end;

procedure TExtendedTcpClient.Close;
begin
  FIsConnected := false;
  inherited;
end;

function TExtendedTcpClient.SetBlockModeWithoutClosing(Block: Boolean): Boolean;
var
  nonBlock: Integer;
begin
  // TTcpClient.SetBlockMode closes the connection and the socket
  nonBlock := Ord(not Block);
  Result := ErrorCheck(ioctlsocket(Handle, FIONBIO, nonBlock)) <> SOCKET_ERROR;
end;

function TExtendedTcpClient.WaitUntilConnected(Timeout: Integer): Boolean;
var
  writeReady, exceptFlag: Boolean;
begin
  // Select waits until connected or timeout
  Select(nil, @writeReady, @exceptFlag, Timeout);
  Result := writeReady and not exceptFlag;
end;

function TExtendedTcpClient.ConnectWithTimeout(Timeout: Integer): Boolean;
begin
  if Connected or FIsConnected then
    Result := true
  else begin
    if BlockMode = bmNonBlocking then begin
      if Connect then // will return immediately, tipically with false
        Result := true
      else
        Result := WaitUntilConnected(Timeout);
    end
    else begin // blocking mode
      // switch to non-blocking before trying to do the real connection
      FNonBlockingModeRequested := true;
      FNonBlockingModeSuccess := false;
      try
        if Connect then // will return immediately, tipically with false
          Result := true
        else begin
          if not FNonBlockingModeSuccess then
            Result := false
          else
            Result := WaitUntilConnected(Timeout);
        end;
      finally
        if FNonBlockingModeSuccess then begin
          // revert back to blocking
          if not SetBlockModeWithoutClosing(true) then begin
            // undesirable state => abort connection
            Close;
            Result := false;
          end;
        end;
      end;
    end;
  end;
  FIsConnected := Result;
end;

使い方:

TcpClient := TExtendedTcpClient.Create(nil);
try
  TcpClient.BlockMode := bmBlocking; // can also be bmNonBlocking

  TcpClient.RemoteHost := 'www.google.com';
  TcpClient.RemotePort := '80';

  if TcpClient.ConnectWithTimeout(500) then begin // wait up to 500ms
    ... your code here ...
  end;
finally
  TcpClient.Free;
end;

前に述べたように、Connectedはノンブロッキング ソケットではうまく機能しないため、これIsConnectedを克服するために新しいプロパティを追加しました ( で接続する場合にのみ機能しますConnectWithTimeout)。

どちらもConnectWithTimeoutIsConnectedブロッキング ソケットとノンブロッキング ソケットの両方で機能します。

于 2013-08-28T20:07:23.070 に答える