このブログでは、TCP 通信のクライアント側の通信手順や C# でコードを記述する方法をまとめました。(以下のリンクカードから確認出来ます。)

実際に TCP 通信を使うことになった際、クライアントはサーバーへリクエストを送信する為に頻繁に接続を要求することになります。サーバーが接続待ちの状態であれば通信を確立させることができますが、サーバーが接続待ち状態ではない場合だったらクライアントの接続は失敗してしまいます。
TcpClient の Connect(非同期なら ConnectAsync)は約21秒経過してからタイムアウトします。下図のようにタイムアウトされるまでクライアントは長時間待たなければなりません。

TCP のタイムアウトは、サーバーから応答が返ってくるまで一定時間待ちます。1回目は3秒間待ちます。3秒間待って応答がなかったら、次は6秒間待ちます。6秒待って応答がなかったら、12秒間待ちます。2回リトライして接続できなかったらタイムアウトで例外を吐くという仕組みになっています。この待ち時間の合計が21秒(3秒+6秒+12秒)になるということです。(参考)
この記事では TcpClient の接続タイムアウトの時間を任意の値に設定する方法について紹介しています。是非参考にしてみてください。

オススメの参考書
C#の使い方が丁寧に解説しており、「基礎からしっかりと学びたい」という初心者の方にオススメの一冊です。サンプルコードも記載してあり、各章の最後に復習問題があるので理解度を確認しながら読み進めることができます。新しい C# のバージョンにも対応している書籍です。
TcpClientのタイムアウト処理

ここではいくつかの設定方法について紹介します。
①Task.Wait()を使用する
ConnectAsync
のタスクの実行が完了するまで指定されたミリ秒以内の間だけ待機する方法です。
try
{
using (var tcpclient = new TcpClient())
{
//タイムアウトの時間を設定
var timeout = 1000;
var task = tcpclient.ConnectAsync(ipaddress, port);
if (!task.Wait(timeout)) //ここで画面がフリーズしてしまう。
{
//タイムアウトの例外
throw new SocketException(10060);
}
//接続後の処理を記述
}
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
}
上記の例だと接続処理の実行を待機し、1秒間経過しても接続されなかったらタイムアウトの例外を吐きます。
しかし、Wait
を使ってしまうとデッドロックが発生するという欠点があります。1秒間待機中している間はUIが操作不可能になってしまいます。これだとあまりよくありませんよね。
②Task.WhenAny()を使用する
指定されたタスクのいずれかが完了してから次の処理に進むWhenAny
を使う方法です。
try
{
using (var tcpclient = new TcpClient())
{
//タイムアウトの時間を設定
var timeout = 1000;
var task = tcpclient.ConnectAsync(ipaddress, port);
if (await Task.WhenAny(task, Task.Delay(timeout)) != task)
{
//タイムアウトの例外
throw new SocketException(10060);
}
//接続後の処理を記述
}
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
}
ConnectAsync
のタスクもしくは1秒間のタスクを同時に実行して、1秒間のタスクが先に完了したらタイムアウトの例外を吐きます。
この方法ならデッドロックは発生せずに、非同期で実行することが可能です。

まとめ

デフォルトのタイムアウトでは長時間の待機を強いられるので、タスクを待つ関数を上手いこと使ってタイムアウトの時間を短縮できるようにしましょう。
Task.WhenAny()
を使うことでデッドロックが発生せず、待機することができますので是非活用してみてください。



以上、最後まで読んでいただきありがとうございました。