Winsock には接続タイムアウトはありませんが、これは克服できます。
いくつかのオプションがあります:
スレッドなし:
スレッドの場合: Remy Lebeau のWinsock API で接続タイムアウトを制御する方法に対する回答を参照してください。.
インディを使用。
ブロッキング 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
、いくつかの関数が呼び出されます。SocketError
WSAEWOULDBLOCK
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
)。
どちらもConnectWithTimeout
、IsConnected
ブロッキング ソケットとノンブロッキング ソケットの両方で機能します。