none
NetworkStream でデータを最後まで Read する方法について RRS feed

  • 質問

  • あるアプリケーションサーバから TcpClient を使いデータを受信するために、以下のようなコードを使っていました。

    stream = this.TcpClient.GetStream();
    using
     (System.IO.MemoryStream ms = new System.IO.MemoryStream())
    {
        byte[] resBytes = new byte[256];
        int resSize;
        do
        {
            resSize = stream.Read(resBytes, 0, resBytes.Length);
            if (resSize == 0)
                return null;

            ms.Write(resBytes, 0, resSize);
        } while (stream.DataAvailable);
        resMsg = System.Text.Encoding.ASCII.GetString(ms.ToArray());
        ms.Close();
    }

    しかし、このループ処理の終了条件の NetworkStream.DataAvailable はデータの受信が完了しなくても true になることがわかりました。
    実際に、受信データの途中でー上記ループ処理を抜けてしまうことがあります。

    本来であれば、サーバ側から最初にレングスを取得し、そのレングスに達するまでデータを取得すべきかもしれませんが、サーバ側を修正することも出来ず、どのように終了判断をすべきか悩んでおります。

    何か基本的な理解が足りないのかもしれませんが、何かございましたらご指摘いただければ幸いです。

    2011年7月4日 3:04

回答

  • 外池と申します。

    一般的に、NetworkStreamから単純にReadするだけでは、データの受信完了を判定することはできないです。何らかの形で、サーバー側とクライアント側で、「データは全体でこういう形をしているものだ」という了解が必要です。いわゆる「プロトコル」です。

    どんなデータをやり取りしているんでしょうか? データの長さの情報がなくても、データの末尾を判定するようなヒントは無いのでしょうか? 既によく知られているプロトコルをサーバ側が使っているなら、それを示せば回答を得やすいと思いますよ?


    (ホームページを再開しました)
    2011年7月4日 6:23
  • サーバからの切断を以て受信終了とするパターン(HTTP 1.0 などがそうですね)なら、Read の返値が 0 か否かで判断できます。

    そうでなければ外池さんのおっしゃるとおりですね。

    2011年7月4日 6:40

すべての返信

  • 外池と申します。

    一般的に、NetworkStreamから単純にReadするだけでは、データの受信完了を判定することはできないです。何らかの形で、サーバー側とクライアント側で、「データは全体でこういう形をしているものだ」という了解が必要です。いわゆる「プロトコル」です。

    どんなデータをやり取りしているんでしょうか? データの長さの情報がなくても、データの末尾を判定するようなヒントは無いのでしょうか? 既によく知られているプロトコルをサーバ側が使っているなら、それを示せば回答を得やすいと思いますよ?


    (ホームページを再開しました)
    2011年7月4日 6:23
  • サーバからの切断を以て受信終了とするパターン(HTTP 1.0 などがそうですね)なら、Read の返値が 0 か否かで判断できます。

    そうでなければ外池さんのおっしゃるとおりですね。

    2011年7月4日 6:40
  • いろいろとご返信ありがとうございます。

    あまりメジャーではないのですが、オープンソースのPBX の Asterisk というサーバです。TCP/IP のあるポートで接続することで各種ステータスが取得できます。
    セッションを維持するので、情報取得終了 = 切断とはなりません。

    残念ながら、送信するデータのレングスは送信されないようですので、やはり受信するデータのパターンを洗い出して、終了を判断するしかなさそうです。

    データ送信が正常終了したという、メッセージは受信できるのですが、場合によってはさらにその後にもデータが付与されていることもあり処理はしにくそうですが、頑張ってみます。

    2011年7月4日 8:01
  • 外池です。解決マークが付されていますが・・・、老婆心ながら念のため。

    「PBX」と「Astersik」でググってみて、Wikipediaの記事をざっと読みました。「利用可能なプロトコル」が列挙されていますから、プロトコルはハッキリしているわけですよね? であれば、プロトコルの仕様を丹念に調査することが最優先だと思います。演繹的にやるべきです。

    「データのパターンを洗い出す」というのは、何か不具合があった場合の調査としては良いですが、プログラムを作り始めるときの動作仕様の設計の基にすることは良くないと思います。帰納的にやるべきではありません。


    (ホームページを再開しました)
    2011年7月4日 8:47
  • そもそも、DataAvailable プロパティの意味を取り違えられているかと思います。このプロパティは、データがネットワークを流れて手元に到着しているかどうかを判断するプロパティであって、データの終端を判断する材料になるようなプロパティではありません。

    ※ SIP なら、CRLFCRLF の4バイトが発生するまで受信して、Content-Length に指定された分だけ追加で読み取ればよいはずです。

    ※ HTTP 準拠なので、WebClient で接続しても受信できちゃったりするんじゃないですかねー

    2011年7月4日 9:25
  • 後半には同意しますが、
    そもそも、DataAvailable プロパティの意味を取り違えられているかと思います。このプロパティは、データがネットワークを流れて手元に到着しているかどうかを判断するプロパティであって、データの終端を判断する材料になるようなプロパティではありません。

    取り違えてはいないと思います。

    Read()メソッドが0byteを返したときがend of streamを表しますので、逆に言うとコネクションが切れていない限り1byte以上返す必要があります。そのため、データが受信されていない場合、nonblockであってもblockされます。blockされないようにDataAvailableプロパティで事前に1byte以上受信できているかを確認するわけです。

    ところがこの質問になっているようにDataAvailableプロパティは実は信用できません。詳しくは覚えていませんが、データがあってもfalseを返したり、データがなくてもtrueを返したり、したと思います。

    # 試してないので間違っていたら指摘してください。

    2011年7月4日 13:36
  • 外池です。

    私が経験した限りでは、DataAvailableが変な結果を返したことは無いですが・・・。いずれにせよ、Read()メソッドでブロックさせたまま待つようなプログラムで不都合を感じたことが無いので、DataAvailableを積極的に使ったことがありません。

    ところで、るいるい1さんが最初にお示しになったコードを拝見した限り、やはり、DataAvailableの使い方としては誤解されているように、私は思います。DataAvailableがFalseになったらループを抜けて、受信が一区切りしたと見なして、関数から出るプログラムになっていますから・・・。

    DataAvailableがFalseであっても、受信が一区切りしたのか、受信の途中で回線が滞ってデータが届かず待つべき局面なのか、区別はつかないです。この区別をつけるには、プロトコルの知識をもって、受信できているデータから判断するしかありません。

    Read()メソッドがゼロを返した場合は、受信が一区切りしたことは確かですが、正常に受信が終わったのか、サーバー側のバグで中途半端な受信で終わったのか、区別はつかないです。この区別をつけるには、プロトコルの知識をもって、受信できているデータから判断しなければなりません。

    TCPなので、回線(接続)に不具合が出れば、コネクションが切れて、例外が出る(タイムアウトも含む)・・・、ハズですから、回線(接続)の障害の検知は比較的簡単でしょう。

    もし、サーバー側のプロトコルの実装が正確であることを信じて、かつ、一方的に受信して、一区切りつくごとにコネクションが正常に切れるタイプのプロトコルであれば、Read()がゼロを返すまで受信を続けることが一番簡単でしょうか。

    コネクションを生かしたまま、クライアントとサーバーがデータをやりとりするタイプのプロトコルなら、いずれにせよ、プロトコルの詳細がわかってなければどうにもなりません。


    (ホームページを再開しました)
    2011年7月5日 0:50
  • すみません、K. Takaokaさんや外池さんのおっしゃる通り、よく見たらループ構造が変でした。

    ループの終了条件が、サーバーがコネクションを切らずにデータが途切れた時、なんですね。

    using( var reader = new StreamReader( stream, Encoding.ASCII ) )
      return reader.ReadToEnd();

    相当のコードだと誤解していました。

    2011年7月5日 12:54