どうも、〇NAKA(マルナカ)です。
これまでTCP/IPのソケット通信によるクライアント側の通信手順やC#でコードを記述する方法を記事にまとめました。(以下のリンクカードからアクセスすることができます)

実際にソケット通信を使う場合、クライアントはサーバーへリクエストを送信する為に頻繁に接続を要求することになります。サーバーが「接続待ち」の状態であれば通信を確立させることができます。
しかし、サーバーが何かしらのトラブルで「接続待ち」をしていなかった時はクライアントの接続は失敗してしまい、永遠に接続を待ち続けることになってしまいます。その為予め設定された一定時間のタイムアウト時間が経過しても応答がない場合には、処理を打ち切って通信を終了する処理を記述する必要があります。
この記事ではSoket通信の接続タイムアウトを任意の時間を設定して通信を終了させる方法について紹介しています。是非参考にしてみてください。
Soket通信のタイムアウト処理
ここではいくつかの設定方法について紹介します。
WaitHandle.WaitOne(int millisecoundsTimeout)を使用する
try
{
// IPアドレスとポート番号を取得
IPEndPoint endpoint = new IPEndPoint(IPAddress.Parse(ipaddress), port);
// TCP/IPのソケットを作成
using (Socket client = new Socket(IPAddress.Parse(ipaddress).AddressFamily, SocketType.Stream, ProtocolType.Tcp))
{
// エンドポイント(IPアドレスとポート)へ接続
client.BeginConnect(endpoint, new AsyncCallback(ConnectCallback), client);
if (!connectDone.WaitOne(1000))
{
//タイムアウトの例外
throw new SocketException(10060);
}
// 接続後の処理を記述
// ソケット接続終了
client.Shutdown(SocketShutdown.Both);
}
}
catch (Exception e)
{
Debug.WriteLine(ex.Message);
}
非同期のSoket通信で接続処理をするBeginConnectは、接続の有無に関わらず次の処理を実行します。なのでManualResetEventのシグナル状態を監視して、接続ができていない場合は、次の処理が実行されないように待機するようにしています。シグナル状態を監視するWaitOne()に数値を引数で渡すと、指定された数値でタイムアウトをします。
try
{
// IPアドレスとポート番号を取得
IPEndPoint endpoint = new IPEndPoint(IPAddress.Parse(ipaddress), port);
// TCP/IPのソケットを作成
using (Socket client = new Socket(IPAddress.Parse(ipaddress).AddressFamily, SocketType.Stream, ProtocolType.Tcp))
{
// エンドポイント(IPアドレスとポート)へ接続
var ar = client.BeginConnect(endpoint, new AsyncCallback(ConnectCallback), client);
var ret = ar.AsyncWaitHandle.WaitOne(1000, true);
if (!ret)
{
//タイムアウトの例外
throw new SocketException(10060);
}
// 接続後の処理を記述
// ソケット接続終了
client.Shutdown(SocketShutdown.Both);
}
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
}
これらの方法には1つ欠点があり、タイムアウトされるまでデッドロックが発生します。接続シグナルを受信するまでスレッドがブロッグされる為、待機している1秒間はUIが操作不可能になってしまいます。
try
{
// IPアドレスとポート番号を取得
IPEndPoint endpoint = new IPEndPoint(IPAddress.Parse(ipaddress), port);
// TCP/IPのソケットを作成
using (Socket client = new Socket(IPAddress.Parse(ipaddress).AddressFamily, SocketType.Stream, ProtocolType.Tcp))
{
// エンドポイント(IPアドレスとポート)へ接続
var ar = client.BeginConnect(endpoint, new AsyncCallback(ConnectCallback), client);
var ret = await Task.Run(() => ar.AsyncWaitHandle.WaitOne(1000, true));
if (!ret)
{
//タイムアウトの例外
throw new SocketException(10060);
}
// 接続後の処理を記述
// ソケット接続終了
client.Shutdown(SocketShutdown.Both);
}
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
}
まとめ
今回は接続タイムアウトの方法について複数のサンプルを記述しながら説明を行いました。ソケット通信はWaitOne()の引数に数値を渡すことで通信処理を強制的に終了させることができます。非同期で処理を実行する際はTask.Runを使用し、デッドロックが起きないようにすることも可能です。
接続以外にも送信・受信にもタイムアウト処理を記述して、ソケットに問題がある場合は応答待ちの時間がかかりすぎないようにしましょう。
以上、最後まで読んでいただきありがとうございました。
コメント