リモートコンピューターのリスナーにデータを送信するために使用するTcpClientがあります。リモートコンピューターの電源がオンまたはオフになる場合があります。このため、TcpClientは頻繁に接続できません。 TcpClientが1秒後にタイムアウトするようにしたいので、リモートコンピューターに接続できないときはそれほど時間がかかりません。現在、私はこのコードをTcpClientに使用しています。
try
{
TcpClient client = new TcpClient("remotehost", this.Port);
client.SendTimeout = 1000;
Byte[] data = System.Text.Encoding.Unicode.GetBytes(this.Message);
NetworkStream stream = client.GetStream();
stream.Write(data, 0, data.Length);
data = new Byte[512];
Int32 bytes = stream.Read(data, 0, data.Length);
this.Response = System.Text.Encoding.Unicode.GetString(data, 0, bytes);
stream.Close();
client.Close();
FireSentEvent(); //Notifies of success
}
catch (Exception ex)
{
FireFailedEvent(ex); //Notifies of failure
}
これは、タスクを処理するのに十分に機能します。可能な場合は送信し、リモートコンピューターに接続できない場合は例外をキャッチします。ただし、接続できない場合、例外をスローするのに10〜15秒かかります。約1秒でタイムアウトする必要がありますか?タイムアウト時間を変更するにはどうすればよいですか?
同期的に接続しようとする代わりに、BeginConnect
のasync TcpClient
メソッドを使用する必要があります。これはコンストラクターが行うことです。このようなもの:
var client = new TcpClient();
var result = client.BeginConnect("remotehost", this.Port, null, null);
var success = result.AsyncWaitHandle.WaitOne(TimeSpan.FromSeconds(1));
if (!success)
{
throw new Exception("Failed to connect.");
}
// we have connected
client.EndConnect(result);
.NET 4.5以降、TcpClientにはクールな ConnectAsync メソッドがあり、次のように使用できるため、非常に簡単になりました。
var client = new TcpClient();
if (!client.ConnectAsync("remotehost", remotePort).Wait(1000))
{
// connection failure
}
https://stackoverflow.com/a/25684549/3975786 を使用する別の代替手段:
var timeOut = TimeSpan.FromSeconds(5);
var cancellationCompletionSource = new TaskCompletionSource<bool>();
try
{
using (var cts = new CancellationTokenSource(timeOut))
{
using (var client = new TcpClient())
{
var task = client.ConnectAsync(hostUri, portNumber);
using (cts.Token.Register(() => cancellationCompletionSource.TrySetResult(true)))
{
if (task != await Task.WhenAny(task, cancellationCompletionSource.Task))
{
throw new OperationCanceledException(cts.Token);
}
}
...
}
}
}
catch(OperationCanceledException)
{
...
}
注意すべきことの1つは、タイムアウトが期限切れになる前にBeginConnect呼び出しが失敗する可能性があることです。これは、ローカル接続を試行している場合に発生する可能性があります。これがJonのコードの修正バージョンです...
var client = new TcpClient();
var result = client.BeginConnect("remotehost", Port, null, null);
result.AsyncWaitHandle.WaitOne(TimeSpan.FromSeconds(1));
if (!client.Connected)
{
throw new Exception("Failed to connect.");
}
// we have connected
client.EndConnect(result);
上記の回答では、タイムアウトになった接続を適切に処理する方法は扱っていません。 TcpClient.EndConnectを呼び出し、成功したがタイムアウト後に接続を閉じ、TcpClientを破棄します。
それはやり過ぎかもしれませんが、これは私のために動作します。
private class State
{
public TcpClient Client { get; set; }
public bool Success { get; set; }
}
public TcpClient Connect(string hostName, int port, int timeout)
{
var client = new TcpClient();
//when the connection completes before the timeout it will cause a race
//we want EndConnect to always treat the connection as successful if it wins
var state = new State { Client = client, Success = true };
IAsyncResult ar = client.BeginConnect(hostName, port, EndConnect, state);
state.Success = ar.AsyncWaitHandle.WaitOne(timeout, false);
if (!state.Success || !client.Connected)
throw new Exception("Failed to connect.");
return client;
}
void EndConnect(IAsyncResult ar)
{
var state = (State)ar.AsyncState;
TcpClient client = state.Client;
try
{
client.EndConnect(ar);
}
catch { }
if (client.Connected && state.Success)
return;
client.Close();
}
NetworkStreamでReadTimeoutまたはWriteTimeoutプロパティを設定して、同期読み取り/書き込みを行います。 OPのコードの更新:
try
{
TcpClient client = new TcpClient("remotehost", this.Port);
Byte[] data = System.Text.Encoding.Unicode.GetBytes(this.Message);
NetworkStream stream = client.GetStream();
stream.WriteTimeout = 1000; // <------- 1 second timeout
stream.ReadTimeout = 1000; // <------- 1 second timeout
stream.Write(data, 0, data.Length);
data = new Byte[512];
Int32 bytes = stream.Read(data, 0, data.Length);
this.Response = System.Text.Encoding.Unicode.GetString(data, 0, bytes);
stream.Close();
client.Close();
FireSentEvent(); //Notifies of success
}
catch (Exception ex)
{
// Throws IOException on stream read/write timeout
FireFailedEvent(ex); //Notifies of failure
}
mcandal ソリューションに基づいたコードの改善を次に示します。 client.ConnectAsync
タスクから生成されたすべての例外のキャッチ例外を追加しました(例:サーバーに到達できない場合のSocketException)
var timeOut = TimeSpan.FromSeconds(5);
var cancellationCompletionSource = new TaskCompletionSource<bool>();
try
{
using (var cts = new CancellationTokenSource(timeOut))
{
using (var client = new TcpClient())
{
var task = client.ConnectAsync(hostUri, portNumber);
using (cts.Token.Register(() => cancellationCompletionSource.TrySetResult(true)))
{
if (task != await Task.WhenAny(task, cancellationCompletionSource.Task))
{
throw new OperationCanceledException(cts.Token);
}
// throw exception inside 'task' (if any)
if (task.Exception?.InnerException != null)
{
throw task.Exception.InnerException;
}
}
...
}
}
}
catch (OperationCanceledException operationCanceledEx)
{
// connection timeout
...
}
catch (SocketException socketEx)
{
...
}
catch (Exception ex)
{
...
}
Async&awaitを使用し、ブロックせずにタイムアウトを使用したい場合、mcandalが提供する回答からの代替のより簡単なアプローチは、バックグラウンドスレッドで接続を実行し、結果を待つことです。例えば:
Task<bool> t = Task.Run(() => client.ConnectAsync(ipAddr, port).Wait(1000));
await t;
if (!t.Result)
{
Console.WriteLine("Connect timed out");
return; // Set/return an error code or throw here.
}
// Successful Connection - if we get to here.
詳細とその他の例については、 Task.Wait MSDNの記事 を参照してください。
Simon Mourierが述べたように、ConnectAsync
TcpClientのメソッドをTask
とともに使用して、できるだけ早く操作を停止してください。
例えば:
// ...
client = new TcpClient(); // Initialization of TcpClient
CancellationToken ct = new CancellationToken(); // Required for "*.Task()" method
if (client.ConnectAsync(this.ip, this.port).Wait(1000, ct)) // Connect with timeout as 1 second
{
// ... transfer
if (client != null) {
client.Close(); // Close connection and dipose TcpClient object
Console.WriteLine("Success");
ct.ThrowIfCancellationRequested(); // Stop asynchronous operation after successull connection(...and transfer(in needed))
}
}
else
{
Console.WriteLine("Connetion timed out");
}
// ...