none
非同期ソケット通信での不具合 RRS feed

  • 質問

  • はじめまして。dev@777と申します。
    クライアント-サーバ型非同期ソケット通信で困った現象が発生したのですが、原因が全くわかりません。
    サンプルコードはクライアント側のコードで、以下のような現象に陥ります。

    ・クライアントは、通信スレッドとUI描画スレッドのマルチスレッド構成である。
    ・クライアント側のBeginReceiveでコールバックが全く帰ってこなくなる。
    ・サーバ側は、クライアントがいなくなったと判断し、コネクションを解放している。
    ・クライアント側のソケットオブジェクトのconnectedプロパティは"true"のままである。
    ・UIスレッドは正常に動作する。

    どなたか、このような現象に陥った場合の解決策をご教示いただけないでしょうか?
    宜しくお願い致します。

    【以下サンプルコード】
            /// <summary>
            /// データを受信します。
            /// </summary>
            /// <param name="_client">Berkeley ソケットクラスオブジェクト</param>
            private voidReceiveAllData(Socket _client) {
                try {
                    if (state_all_in == null) {
                        // ユーザ定義オブジェクトのインスタンシング
                        state_all_in = new StateObject();
                    }
                    // ユーザ定義オブジェクトにソケットオブジェクトを格納
                    state_all_in.workSocket = _client;
                    // バッファを確保する。
                    state_all_in.SetBufferSize(MAX_BUFFER_SIZE);

                    // シグナルオブジェクトのインスタンシング
                    if (receiveDone == null) receiveDone = new System.Threading.ManualResetEvent(false);
                    receiveDone.Reset();

                    //通信成功/失敗フラグを成功で初期化
                    this._SocketSuccessed = true;

                    // メインヘッダーデータを非同期に受信します。
                    // このメソッドで通信スレッドは停止するが、UIスレッドは正常動作している。
                    _client.BeginReceive(state_all_in.buffer, 0, state_all_in.BufferSize, 0, new AsyncCallback(ReceiveAll_Callback), state_all_in);

                    // 受信完了シグナルを受け取るまで待機します。
                    receiveDone.WaitOne();

               
                    return;
                } catch (Exception e) {
                    GTRClient.Utils.ErrorLog.APPwrite("[ReceiveAllData] Exception.\r\n" + e.Message + "\r\n" + e.StackTrace);
                } finally {
                    //本スコープ内の処理を実行しないと不要なメモリが残る
                    //(その不要なメモリにはGCが働かない模様)
                    if (state_all_in != null) {
                        state_all_in.SetBufferSize(0);
                        state_all_in.workSocket = null;
                    }
                }
            }

            /// <summary>
            /// 受信操作に関する情報を格納するユーザー定義のオブジェクト。
            /// このオブジェクトは、操作の完了時に EndReceive デリゲートに渡されます。
            /// </summary>
            class StateObject {
                // Berkeley ソケットクラスオブジェクト
                public Socket workSocket = null;
                // 受信するバッファサイズ
                public int BufferSize = 8192;
                // Receive buffer.
                public byte[] buffer = new byte[0];
                /// <summary>
                /// 受信したコマンドの列挙体
                /// </summary>
                public enmCmdKind_snd CmdSend;
                /// <summary>
                /// 一番最近に受信したコマンドの文字列
                /// </summary>
                public string CurrentCommand = "";
                /// <summary>
                /// 受信するバッファサイズを設定し、配列のサイズも初期化します。
                /// </summary>
                /// <param name="size"></param>
                public void SetBufferSize(int size) {
                    BufferSize = size;
                    buffer = new byte[size];
                }
            }

    2009年1月13日 5:42

すべての返信

  • dev@777です。
    もうしわけありません。情報不足でした。

    開発環境は以下のとおり。
    【OS】       クライアント:WindowsXP     サーバ:RedHatLinuxEnterpriseES
    【開発環境】 クライアント:C#2.0

    ・コールバックメソッドではdelegate経由でUIスレッドオブジェクトに処理を委譲している。
    (スレッド境界はこえていない。)

    ・その他例外は発生していない。

    2009年1月13日 5:52
  • こんにちは、プログラムのデザインは下記のとおりでしょうか?

     1.UIを持つプログラムで、通信部分(サンプルコード部分)はUIスレッドではなく他スレッドで動いている

     2.UIスレッド以外のスレッドで非同期ソケットを利用している

     

    上記のとおりだとしますと、デザインで意図が分からない箇所があります

     ・UIスレッド以外にスレッドを作成しているのに、なぜ非同期ソケットを利用しているのか

     ・たしかWinsockの非同期ソケットはメッセージループを利用したような気がしますので

      UIスレッドから利用する事を想定しているのではないでしょうか?

      (その為UIスレッド以外からだとそのスレッドのメッセージループをポンプする処理が無いのでは?)

     

    また、コーディング上で気になった点は、finally内でSocketをCloseしていないので

    クライアントのソケットがクローズされていないのではないでしょうか?

     

    疑問符だらけですみませんが、いかがでしょうか?

     

    2009年1月13日 15:45
  • ピッチ様
    dev@777です。
    お世話になります。

    > ・UIスレッド以外にスレッドを作成しているのに、なぜ非同期ソケットを利用しているのか

    1通信あたりのレスポンスが非同期ソケットのほうがはやいため使用しています。

    > ・たしかWinsockの非同期ソケットはメッセージループを利用したような気がしますので

    >  UIスレッドから利用する事を想定しているのではないでしょうか?

    >  (その為UIスレッド以外からだとそのスレッドのメッセージループをポンプする処理が無いのでは?)

    すみません。勉強不足でした。

    >また、コーディング上で気になった点は、finally内でSocketをCloseしていないので

    >クライアントのソケットがクローズされていないのではないでしょうか?

    本プログラムは監視系で、原則24時間データを取得し続けます(1通信:200msec)。
    なのでクライアントソケットのクローズは行わず、コールバックでEndReceiveメソッドを
    呼び出し、ソケットを使いまわしています。


    ちなみに、本現象が発生したのは稼働してから6か月後というタイミングでした。
    2009年1月13日 23:49
  • こんにちは!(^^)!ふ~です。

     

    >ちなみに、本現象が発生したのは稼働してから6か月後というタイミングでした。
    >本プログラムは監視系で、原則24時間データを取得し続けます(1通信:200msec)。
     
    皆さまが、何を考えれば良いかのを左右しまので、このような条件は重要と思います。
    私も、『プログラムを作成したが受信が出来ない』との意味に受け取っておりました。
     
    既に、ハードチェックは完了していると思われますが?(IFボード、ケーブルの不具合)
    更に、LinuxOS、(ドライバー類含む)WindowsXP(ドライバー類)は6ヶ月の間変更
    更新はなかったという条件でしょうか?
     
    今までは、問題無く連続運転を続けていたのでしょうか?
    2009年1月14日 1:53
  •   !(^^)!ふ~
    dev@777です。
    お世話になります。

    >皆さまが、何を考えれば良いかのを左右しまので、このような条件は重要と思います。
    >私も、『プログラムを作成したが受信が出来ない』との意味に受け取っておりました。
    申し訳ありません。情報提供不足でした。

    >既に、ハードチェックは完了していると思われますが?(IFボード、ケーブルの不具合)
    >更に、LinuxOS、(ドライバー類含む)WindowsXP(ドライバー類)は6ヶ月の間変更
    >更新はなかったという条件でしょうか?
    ハードは他メーカーによる製品検査で異常がなく、稼働中になんらかの
    更新がかかったことはありません。(完全なオフライン環境でのネットワーク構築な
    ため、自動アップデートもかかりません。)

    この条件で、半年は問題なく稼働していたのですが。
    2009年1月14日 2:33
  • こんにちは!(^^)!ふ~です。

     

    そうしますと、パソコンをリセットすると、問題無く稼働し、半年後に再びダウンする

    と言うことでしょうか?

     

    GTRClient.Utils.ErrorLog.APPwrite("[ReceiveAllData] Exception.\r\n" + e.Message + "\r\n" + e.StackTrace);

    ここで、ログを作成されていらっしゃるようです。どのような内容になっているのでしょうか?

    2009年1月14日 3:32
  • dev@777です。

    >そうしますと、パソコンをリセットすると、問題無く稼働し、半年後に再びダウンする

    >と言うことでしょうか?

    アプリケーションの再起動だけで、正常に動作するようになります。

    半年という期間は、”今回たまたま半年だった”だけみたいです。

    私のテスト環境では9日間ほったらかしにしておいたら再現できました。(今日たった今)

    その間は誰も操作することなく、です。

    2009年1月14日 3:38
  • dev@777です。


    >GTRClient.Utils.ErrorLog.APPwrite("[ReceiveAllData] Exception.\r\n" + e.Message + "\r\n" + e.StackTrace);

    >ここで、ログを作成されていらっしゃるようです。どのような内容になっているのでしょうか?

    本現象は例外は発生しません。

    BeginREceiveメソッドを呼び出したあと、ManualResetEventオブジェクトでコールバックメソッドが

    完了するまで待機しますが、WaitOne()メソッドまで到達せず、BeginREceiveメソッドで抜けてしまいます。

    通常は、BeginREceive() → ManualResetEvent.WaitOne() → ManualResetEvent.Set() → ・・・

    で正常にWaitOne()で待機しています。


    2009年1月14日 3:43
  • こんにちは!(^^)!ふ~です。

    >WaitOne()メソッドまで到達せず、BeginREceiveメソッドで抜けてしまいます。

    と言うことですので

    _client.BeginReceive(state_all_in.buffer, 0, state_all_in.BufferSize, 0, new AsyncCallback(ReceiveAll_Callback),

    この中の処理で、エラーが発生し、アプリ終了している分けですね。。。

     

    何か、ReceiveAll_Callback処理中に、エラー発生で抜ける所が無いでしょうか?

    2009年1月14日 4:30
  • !(^^)!ふ~様
    dev@777です。

    >_client.BeginReceive(state_all_in.buffer, 0, state_all_in.BufferSize, 0, new AsyncCallback(ReceiveAll_Callback),

    >この中の処理で、エラーが発生し、アプリ終了している分けですね。。。

     

    >何か、ReceiveAll_Callback処理中に、エラー発生で抜ける所が無いでしょうか?


    本現象はReceiveAll_Callbackにも処理が全く帰ってきません。

    BeginReceiveメソッドを実行して、そのまま通信スレッドそのものが終了してしまいます。


    ちなみに、コールバックの中では以下のような処理を行っています。

    受信したデータ(バイト配列)を解析し、データをメモリ上にストアしておく。
    前回受信したデータ値と今回受信したデータ値が異なれば、同期イベントを発生させUIスレッドに通知する。



    2009年1月14日 5:07
  • こんにちは!(^^)!ふ~です。

     

        そうなりますと、受信したデータを受信バッファーに格納する処理でしょうか?

        受信するごとに受信データーサイズが異なるようですと、ダウンした受信データサイズ

        は既知でしょうか?

     

        MAX_BUFFER_SIZEが固定サイズであれば、これを受信データーがオーバーランした場合

        の状態はどうなりますか?(であればサイズ変更のNewは不要)

     

       また、逆に、受信0バイトの場合はどうなるでしょうか?

     

        使用メモリに余裕が無い場合、受信データサイズが確保出来ないのかもしれません。

        また、仮想メモリなどのメインテナンスが必要である場合もございます。

     

      DateTime startTime = DateTime.Now;

     

        測定コードを入れる

     

      TimeSpan diff = DateTime.Now - startTime;

      Debug.WriteLine("処理時間=" + (diff.Ticks / 10000) + " ms");

      

      // 使用メモリを表示する

      Debug.WriteLine(System.GC.GetTotalMemory(false).ToString());

     

       受信データサイズの確認と合わせて、 このコードを加え連続運転します。 処理時間が増加する場合や

      メモリ使用量が増加して行く場合は要注意です。

    2009年1月14日 5:37
  • !(^^)!ふ~様
    dev@777です。

    > MAX_BUFFER_SIZEが固定サイズであれば、これを受信データーがオーバーランした場合

    > の状態はどうなりますか?(であればサイズ変更のNewは不要)


    BeginReceiveメソッドの第1,2,3引数
      第一引数 受信したデータのストレージ場所となる Byte 型の配列。
      第二引数 受信データを格納する、bufferパラメータ内の、インデックス番号が 0 から始まる位置。
      第三引数 受信するバイト数。
    で、ソースコード上、受信時にオーバーフローするケースは存在しません。
    本アプリケーションの仕様として、サーバはクライアントにデータを送信する
    場合、データヘッダとして固定サイズのデータを必ず送信します。

      /*C言語表記で10byte (データ末尾に\0含めて)*/
      char[5] //端末識別子
    int message_length //サーバがクライアントに送信するデータ長

    クライアントはコールバックメソッド内で、まずデータヘッダを解析しサーバから送信される
    データ長を取得して、指定サイズ分ポートからちまちまとデータを抜き出すようになっています。

    また、当然納品前に連続稼働試験はおこなっており、メモリ使用量、処理時間が
    単調増加することなく安定しています。
    2009年1月14日 6:13
  • こんにちは!(^^)!ふ~です。

     
    >データヘッダとして固定サイズのデータを必ず送信します。
    サーバー側で、伝文ヘッダーを作成する場合、実際のデータサイズより、大きなデータ長を作成
    して送ってくる場合も考えた方が良さそうです。
    その場合、どのような動作になるのかその逆の場合も検討するのも面白いと私は思います。
     
    通信が停止した場合の伝文を取り出し、ヘッダーとデータを解析して、正しい事を確認する作業が
    必要なようです。
     
    テスト用のサーバーで電文を自由に作成しループバックでテストすると弱点が見えると思います。
    以上です。
     

     

     

    2009年1月14日 7:01
  • !(^^)!ふ~様
    dev@777です。
    お世話になります。


    >サーバー側で、伝文ヘッダーを作成する場合、実際のデータサイズより、大きなデータ長を作成
    >して送ってくる場合も考えた方が良さそうです。
    >その場合、どのような動作になるのかその逆の場合も検討するのも面白いと私は思います。
    現状、ポートにたまっているデータ長が、サーバが送信したであろうサイズと異なる場合、
    クライアント側は電文エラーとみなし、ソケットの開放・再接続処理を行っています。
     
    >通信が停止した場合の伝文を取り出し、ヘッダーとデータを解析して、正しい事を確認する作業が
    >必要なようです。
    コールバックまで処理がかえってくれば解析可能ですが、
    本件はコールバックすらかえってきません。(当然ポートにデータは届いていません)

    2009年1月14日 7:55
  •  dev777 さんからの引用

    現状、ポートにたまっているデータ長が、サーバが送信したであろうサイズと異なる場合、
    クライアント側は電文エラーとみなし、ソケットの開放・再接続処理を行っています。
     
    取りあえず、何がおこっているのか原因を掴む必要がありますので、エラー処理を入れて見ては如何でしょうか?
     
    ◆ご参考ページ
     
        try
        {
            _client.BeginReceive(state_all_in.buffer, 0, state_all_in.BufferSize, 0, new AsyncCallback(ReceiveAll_Callback), state_all_in);

        }
        catch (SocketException e)
        {
            Console.WriteLine("{0} Error code: {1}.", e.Message, e.ErrorCode);
            return (e.ErrorCode);
        }
     
    ArgumentNullException
    ObjectDisposedException
    ArgumentOutOfRangeException
    このようなエラーも取得すると解析に役立つと思います。
     
    私としては、受信データよりも設定した受信サイズが大きく、受信待ち状態が続いているのではと思ってます。
     
     dev777 さんからの引用
     
    コールバックまで処理がかえってくれば解析可能ですが、
    本件はコールバックすらかえってきません。(当然ポートにデータは届いていません)
     

     

    回線アナライザーが見当たらないのであれば、サーバー側の送信プログラム伝文ヘッダーを変更するのも

    良いのではと思います。

     

     

    2009年1月14日 9:07