【问题标题】:TCPClient : Custom timeout timeTCPClient : 自定义超时时间
【发布时间】:2014-04-27 20:51:25
【问题描述】:

我需要为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 次。有什么帮助吗?

谢谢:)

【问题讨论】:

  • TTcpClient 类不属于 Indy。
  • @TLama 是的,抱歉已编辑
  • 好吧,我会在OnCreateHandle 事件处理程序中设置连接超时套接字选项。嗯,如果有such timeout option...我已经说得够多了,希望知道WinSock的人能提供帮助。
  • 事实上,我们喜欢要求很多的用户 :-)

标签: delphi tcpclient


【解决方案1】:

Winsock 没有连接超时,但这可以克服。

您有多种选择:

  1. 没有线程:

    • 使用非阻塞模式:调用Connect,然后使用Winsock select函数等待(封装在TTcpClient继承的TBaseSocket Select方法中)。

    • 使用阻塞模式:暂时更改为非阻塞模式,并按照前一种情况继续。

  2. 使用线程:请参阅 Remy Lebeau 对How to control the connect timeout with the Winsock API? 的回答。

  3. 使用 Indy。

阻塞与非阻塞

使用阻塞或非阻塞模式是一个非常重要的设计决策,它会影响您的许多代码,并且之后您无法轻易更改。

例如,在非阻塞模式下,接收函数(如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 的非阻塞模式设计中的以下缺点/缺陷:

  • 多个函数将调用 OnError,并将 SocketError 设置为 WSAEWOULDBLOCK (10035)。
  • Connected 属性将是 false,因为在 Connect 中分配。

拦截模式

连接超时可以通过在socket创建后调用Connect之前切换到非阻塞模式,调用后恢复阻塞模式来实现。

这有点复杂,因为如果我们更改BlockModeTTcpClient 会关闭连接和套接字,而且也没有直接的方法可以单独创建套接字而不连接它。

为了解决这个问题,我们需要在套接字创建之后但在连接之前进行挂钩。这可以使用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 都适用于阻塞和非阻塞套接字。

【讨论】:

    猜你喜欢
    • 2012-12-01
    • 2013-06-11
    • 2015-03-25
    • 2015-10-04
    • 1970-01-01
    • 2020-09-15
    • 2012-12-04
    • 1970-01-01
    相关资源
    最近更新 更多