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;
我尝试了非阻塞选项,但是在我点击按钮后软件返回错误而我必须再次执行4-5次.有帮助吗?
谢谢 :)
解决方法
你有几个选择:
>没有线程:
>使用非阻塞模式:调用Connect,然后使用Winsock select函数等待(封装在TTcpClient继承的TBaseSocket Select方法中).
>使用阻止模式:暂时更改为非阻塞模式,并按上一种情况继续进行.
>有线索:请参阅Remy Lebeau对How to control the connect timeout with the Winsock API?的回答.
>使用Indy.
阻止与非阻塞
使用阻塞或非阻塞模式是一个非常重要的设计决策,它将影响您的许多代码,之后您无法轻易更改.
例如,在非阻塞模式下,接收函数(如Receiveln)不会等到有足够的输入可用并且可以返回空字符串.如果这是您需要的,这可能是一个优势,但您需要实现一些策略,例如在调用receive函数之前等待使用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中已分配.
阻止模式
连接超时可以通过在创建套接字之后但在调用Connect之前更改为非阻塞模式,并在调用它之后恢复为阻塞模式来实现.
这有点复杂,因为如果我们更改BlockMode,TTcpClient会关闭连接和套接字,并且也没有直接创建套接字的方法来连接它.
要解决这个问题,我们需要在创建套接字之后但在连接之前挂钩.这可以使用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,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连接时才有效).
ConnectWithTimeout和IsConnected都可以使用阻塞和非阻塞套接字.