このブログでは、TCP/IP のソケット通信によるクライアント側の通信手順や C# でコードを記述する方法を記事にまとめました。(以下のリンクカードからアクセスすることができます)
実際にソケット通信を使う場合、クライアントはサーバーへリクエストを送信する為に頻繁に接続を要求することになります。サーバーが「接続待ち」の状態であれば通信を確立させることができます。
しかし、サーバーが何かしらのトラブルで「接続待ち」をしていなかった時はクライアントの接続は失敗してしまい、永遠に接続を待ち続けることになってしまいます。その為予め設定された一定時間のタイムアウト時間が経過しても応答がない場合には、処理を打ち切って通信を終了する処理を記述する必要があります。
この記事では Soket 通信の接続タイムアウトを任意の時間を設定して通信を終了させる方法について紹介しています。是非参考にしてみてください。
オススメの参考書
C#の使い方が丁寧に解説しており、「基礎からしっかりと学びたい」という初心者の方にオススメの一冊です。サンプルコードも記載してあり、各章の最後に復習問題があるので理解度を確認しながら読み進めることができます。新しい C# のバージョンにも対応している書籍です。
Soket通信のタイムアウト処理
ここではいくつかの設定方法について紹介します。
WaitHandle.WaitOne(int millisecoundsTimeout)を使用する
クライアントのSoket通信の記事で紹介しているサンプルにタイムアウト処理を追加した例が以下になります。
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()
に数値を引数で渡すと、指定された数値でタイムアウトをします。
ManualResetEvent
を使いたくない場合は、BeginConnect
の返り値であるIAsyncResult
からWaitHandle
を取得し、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が操作不可能になってしまいます。
ブロッグされる同期処理をTask.Run()
を使って別スレッドで実行することで、接続待機中のUI画面のフリーズを改善することができます。Task.Run()
は簡単に非同期で処理をすることが実現できるのでとても便利です。
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
を使用し、デッドロックが起きないようにすることも可能です。
接続以外にも送信・受信にもタイムアウト処理を記述して、ソケットに問題がある場合は応答待ちの時間がかかりすぎないようにしましょう。
以上、最後まで読んでいただきありがとうございました。