C#

【C#】クライアント側のSocket通信のタイムアウト設定について

どうも、〇NAKA(マルナカ)です。

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

【C#】ソケット通信(非同期)でクライアントの処理を記述する方法非同期でクライアント側のソケット通信を実装する方法について記載しています。本記事で紹介しているプログラム言語はC#です。ソケット通信について詳しく解説していきますので、参考にして下さい。...

実際にソケット通信を使う場合、クライアントはサーバーへリクエストを送信する為に頻繁に接続を要求することになります。サーバーが「接続待ち」の状態であれば通信を確立させることができます。

しかし、サーバーが何かしらのトラブルで「接続待ち」をしていなかった時はクライアントの接続は失敗してしまい、永遠に接続を待ち続けることになってしまいます。その為予め設定された一定時間のタイムアウト時間が経過しても応答がない場合には、処理を打ち切って通信を終了する処理を記述する必要があります。

この記事ではSoket通信の接続タイムアウトを任意の時間を設定して通信を終了させる方法について紹介しています。是非参考にしてみてください。

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を使用し、デッドロックが起きないようにすることも可能です。

接続以外にも送信・受信にもタイムアウト処理を記述して、ソケットに問題がある場合は応答待ちの時間がかかりすぎないようにしましょう。

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

プログラミングを学習したいなら…

プログラミングスキルを身に付けるなら、プログラミングを効率良く学べる
プログラミングスクール」がオススメです。

特にこんな方にオススメ!!
これからエンジニアを目指したい
プログラミングの専門性を高めたい
プログラミングを学んで副業をしたい
エンジニアに転職して年収をアップさせたい

プログラミングを触ったことがない未経験からでも、プログラミングスクールで学習すれば、エンジニアへ就職・転職することも可能です。

あなたの「行動力」と「やる気」で、あなたの人生を大きく変えるチャンスになることでしょう。

プログラミングスクールに興味がある方は是非チェックしてみてください。

> プログラミングを学ぶ <

COMMENT

メールアドレスが公開されることはありません。

CAPTCHA