ここから本文です

この知恵ノートを「知恵コレクション」に追加しました。

追加した知恵ノートはMy知恵袋の「知恵コレクション」ページで確認できます。

知恵コレクション」に登録済みです。

再登録しました。

追加に失敗しました。

ノートに戻り、もう一度やり直してください。

すでに1,000件のノートが登録されています。

新しく追加したい場合は、My知恵袋の「知恵コレクション」ページで登録されているノートを削除してください。

追加できませんでした。

ノートは削除されました。

.NET System.Net.Sockets.NetworkStream.Read について

ライターlivingintheblueshadowsさん(最終更新日時:2012/1/9)投稿日:2012/1/9 アドバイス受付中!

  • ナイス!:

    13

  • 閲覧数:2647

印刷用のページを表示する

.NET で System.Net.Sockets.TcpClient を使用して TCP の送受信を行う時、
GetStream メソッドを使用して System.Net.Sockets.NetworkStream を取得し、
取得した System.Net.Sockets.NetworkStream の Read、Write を使用する。


MSDN・TcpClient クラス
http://msdn.microsoft.com/ja-jp/library/system.net.sockets.tcpclient(v=vs.80).aspx

にはサンプルも載っている。このサンプルでは受信は


C#
Int32 bytes = stream.Read(data, 0, data.Length);


VB
Dim bytes As Int32 = stream.Read(data, 0, data.Length)


のようになっている。


ここで、MSDN の NetworkStream.Read メソッドのリファレンスを見てみよう。


.NET Framework 2.0
http://msdn.microsoft.com/ja-jp/library/system.net.sockets.networkstream.read(v=vs.80).aspx

.NET Framework 3.0
http://msdn.microsoft.com/ja-jp/library/system.net.sockets.networkstream.read(v=VS.85).aspx

.NET Framework 3.5
http://msdn.microsoft.com/ja-jp/library/system.net.sockets.networkstream.read(v=VS.90).aspx


を見ると、「解説」のところに


「Read 操作は、size パラメータで指定されたバイト数に達するまで、使用できるデータをすべて読み取ります。」


と書かれている。これはどういうことなのだろうか?
例えば、通信相手の PC から


C#
String s = new String('A',100);
Byte[] data = System.Text.Encoding.ASCII.GetBytes(s);
stream.Write(data, 0, data.Length);


VB
Dim s As String = New String("A"c, 100)
Dim data As Byte() = System.Text.Encoding.ASCII.GetBytes(s)
stream.Write(data, 0, data.Length)


のようにして送信された100バイトのデータを、


C#
Byte[] data = new Byte[256];
Int32 bytes = stream.Read(data, 0, data.Length);


VB
Dim data As Byte() = New Byte(255) {}
Dim bytes As Int32 = stream.Read(data, 0, data.Length)


のようにして受信しようとしたとき、あと 156 バイトが送られてくるまでブロックされてしまう ( Read 呼び出し側に処理が戻ってこない状態になる ) のだろうか?
実際に実行して確かめてみるとわかるが、このような場合、100バイトだけ受信した時点で Read は完了し、bytes には 100 がセットされる。

実は、


MSDN・NetworkStream.Read メソッド
.NET Framework 4.0
http://msdn.microsoft.com/ja-jp/library/system.net.sockets.networkstream.read(v=VS.100).aspx


では「解説」の内容が修正されている。要は、Read の第三引数はバッファの最大長を指定しているに過ぎないのだ。

注意

  • このように、MSDN の日本語版は紛らわしい表現があったり、抜けている記述があったりすることがあるので、注意が必要だ。
    「おや?」と思ったら英語版 ( United States - English ) を見た方がよい。

 
さて、Read メソッドであるが、受信データが 256 バイト以下とわかっているならば、


C#
Byte[] data = new Byte[256];
Int32 bytes = stream.Read(data, 0, data.Length);


VB
Dim data As Byte() = New Byte(255) {}
Dim bytes As Int32 = stream.Read(data, 0, data.Length)


とすれば問題なさそうである。

もっと大きなデータ、例えば 2000 バイトのデータを受信しなければならない場合は、単にバッファのサイズを大きくして、


C#
Byte[] data = new Byte[2000];
Int32 bytes = stream.Read(data, 0, data.Length);


VB
Dim data As Byte() = New Byte(1999) {}
Dim bytes As Int32 = stream.Read(data, 0, data.Length)


とすれば問題ないだろうか?結論を先に言うと、問題になる場合がある。


MSDN・TcpListener クラス
http://msdn.microsoft.com/ja-jp/library/zsyxy9k2(v=VS.80).aspx


MSDN・TcpListener.BeginAcceptTcpClient メソッド
http://msdn.microsoft.com/ja-jp/library/system.net.sockets.tcplistener.beginaccepttcpclient(v=VS.80).aspx


のサンプルを参考にして C# でプログラムを作成して確認してみた。
(Windows フォームアプリケーションではなく、コンソールアプリケーションとして作成した。)


// サーバー側(データ受信機能のみ)
using System;
using System.Net;
using System.Net.Sockets;
using System.Threading;

namespace TcpServerTest
{
    class MyTcpListener
    {
        private static ManualResetEvent tcpClientConnected = new ManualResetEvent(false);

        public static void Main()
        {
            TcpListener server = null;

            try
            {
                Int32 port = 13000;    // サーバーのポート
                IPAddress localAddr = IPAddress.Parse("192.168.0.3");    // サーバーの IP アドレス

                server = new TcpListener(localAddr, port);
                server.Start();

                while(true)
                {
                    DoBeginAcceptTcpClient(server);
                }
            }
            catch(SocketException e)
            {
                Console.WriteLine("SocketException: {0}", e);
            }
            finally
            {
                server.Stop();
            }
        }

       
        public static void DoBeginAcceptTcpClient(TcpListener listener)
        {
            tcpClientConnected.Reset();

            listener.BeginAcceptTcpClient( new AsyncCallback(DoAcceptTcpClientCallback), listener);

            tcpClientConnected.WaitOne();
        }


        public static void DoAcceptTcpClientCallback(IAsyncResult ar)
        {
            Byte[] data = new Byte[2000];

            TcpListener listener = (TcpListener)ar.AsyncState;

            using ( TcpClient client = listener.EndAcceptTcpClient(ar) )
            {
                using ( NetworkStream stream = client.GetStream() )
                {
                    int bytes = stream.Read(data,0,data.Length);
                    if( bytes < data.Length ) {
                        // 受信データのバイト数が 2000 バイト未満の場合は、
                        // クライアントの IP アドレス、ポート、受信データのバイト数を出力する
                        Console.WriteLine("{0} : {1} bytes recieved", client.Client.RemoteEndPoint.ToString(), bytes.ToString() );
                    }
                }
            }
            tcpClientConnected.Set();
        }
    }
}


// クライアント側(データ送信機能のみ)
using System;

namespace TcpClientTest
{
    class Program
    {
        static void Main(string[] args)
        {
            // 2000 バイトのデータを 1000 回連続で送信してみる。

            string s = new string('A', 2000);
            byte[] data = System.Text.Encoding.ASCII.GetBytes(s);

            for (int i = 0; i < 1000; i++)
            {
                SendData(data);
            }
            Console.WriteLine("送信完了");
            Console.Read();

        }

       
        static void SendData(byte[] data)
        {
            using (System.Net.Sockets.TcpClient client = new System.Net.Sockets.TcpClient())
            {
                try
                {
                    client.Connect("192.168.0.3", 13000);

                    using (System.Net.Sockets.NetworkStream stream = client.GetStream())
                    {
                        // サーバーへ送信
                        stream.Write(data, 0, data.Length);
                        stream.Flush(); // フラッシュ(強制書き出し)
                    }
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.Message);
                }
            }
        }
    }
}


サーバー側の出力結果は次のようになった。


192.168.0.5:1705 : 1460 bytes recieved
192.168.0.5:1707 : 1460 bytes recieved
192.168.0.5:1907 : 1460 bytes recieved
192.168.0.5:1983 : 1460 bytes recieved
192.168.0.5:2101 : 1460 bytes recieved
192.168.0.5:2170 : 1460 bytes recieved
 

注意

  • 一台の PC でサーバーの IP アドレスをループバックアドレス ( 127.0.0.1 ) にして実行すると、ここで期待する結果が得られない。テストを行うなら複数の PC を使用しなければならない。( Virtual PC 等の仮想 PC も使用可能 )

  
2000 バイト送信したにもかかわらず 1460 バイトしか受信できなかった場合が何度か存在する。このような場合に対処するためには、
 
・サーバーは、受信したバイト数をクライアントに送信する。
・クライアントは、サーバーから送られてきたバイト数と送信したバイト数を比較し、
 サーバーから送られてきたバイト数 < 送信したバイト数
  であれば再送信する.。
 
のようにしなければならないのだろうか?
確かにそうするのも一つの方法ではある。
しかし、上述のテストでは実は 1460 バイトしか送られてこなかったのではなく、1460 バイトと 540 バイトに分割されている場合がほとんどだ。
なぜ、このように分割される場合があるのかということについては、TCP/IP の解説書等を参照されたい。


MSDN の NetworkStream.Read メソッドのリファレンス

.NET Framework 2.0 〜 .NET Framework 3.5

に、「指定したサイズに達するまで」とあったのは、このように分割されて受信しても NetWorkStream.Read が 2000 バイトに結合してくれるのかと思ったが、そうではないようだ。


では、分割されている場合に対応するにはどうすればよいのかというと、


NetworkStream.Read メソッド
http://msdn.microsoft.com/ja-jp/library/system.net.sockets.networkstream.read(v=vs.80).aspx


のサンプルにあるように


do {
    ...
} while( stream.DataAvailable );


のようにすればよい。
と思っていたが、これには落とし穴があった。stream.Read の後に少し重い処理が入る場合は良いが、stream.Read の後すぐに stream.DataAvailable で判定しようとすると、2000 バイト未満しか Read できていない場合でも stream.DataAvailable が false になる場合があるのだ。

do {
    ...
} while( stream.DataAvailable );

は使用せず、
    
while ((bytes = stream.Read(data, 0, data.Length)) != 0)
{
    ...
}

のようにした方が無難である。

先のサーバー側のプログラムの DoAcceptTcpClientCallback を


        public static void DoAcceptTcpClientCallback(IAsyncResult ar)
        {
            Byte[] data = new Byte[2000];

            TcpListener listener = (TcpListener)ar.AsyncState;

            using ( TcpClient client = listener.EndAcceptTcpClient(ar) )
            {
                using ( NetworkStream stream = client.GetStream() )
                {
                    System.Text.StringBuilder sb = new System.Text.StringBuilder();
                    bool splited = false;
                    int bytes;
                    while ((bytes = stream.Read(data, 0, data.Length)) != 0)
                    {
                        sb.Append(System.Text.Encoding.ASCII.GetString( data, 0, bytes));
                        if( bytes < data.Length ) {
                            splited = true;
                            Console.WriteLine("{0} : {1} bytes recieved", client.Client.RemoteEndPoint.ToString(), bytes.ToString() );
                        }
                    }
                    if( splited ) {
                        Console.WriteLine("{0} : Total {1} bytes recieved", client.Client.RemoteEndPoint.ToString(), sb.Length.ToString());
                    }
                }
            }
            tcpClientConnected.Set();
        }
    }


にして再度テストしてみるとサーバー側の出力結果は次のようになった。


192.168.0.5:1516 : 1460 bytes recieved
192.168.0.5:1516 : 540 bytes recieved
192.168.0.5:1516 : Total 2000 bytes recieved
192.168.0.5:1530 : 1460 bytes recieved
192.168.0.5:1530 : 540 bytes recieved
192.168.0.5:1530 : Total 2000 bytes recieved
192.168.0.5:1803 : 1460 bytes recieved
192.168.0.5:1803 : 540 bytes recieved
192.168.0.5:1803 : Total 2000 bytes recieved
192.168.0.5:2234 : 1460 bytes recieved
192.168.0.5:2234 : 540 bytes recieved
192.168.0.5:2234 : Total 2000 bytes recieved
192.168.0.5:2436 : 1460 bytes recieved
192.168.0.5:2436 : 540 bytes recieved
192.168.0.5:2436 : Total 2000 bytes recieved
 
全て合計は 2000 バイトになっていることがわかる。この場合、一回の Read では 2000 バイト受信できなかったからと言ってクライアントに受信データのサイズを送り、クライアントから再送信してもらう必要はないのである。 

Yahoo!ブックマークに登録

アドバイス(このノートのライターへのメッセージ)を送る

このノートはどうでしたか?  いいと思ったことや、こうしたらもっとよくなるといったメッセージを送りましょう! ノートの内容やライターについて質問がある場合は、Q&Aから質問してみましょう

アドバイスを送るには、
Yahoo! JAPAN IDでのログインおよび
Yahoo!知恵袋の利用登録が必要です。

利用登録ナビへ

感想アドバイス履歴

  • 現在アドバイスはありません

このノートに関するQ&A

このノートに関するQ&Aは、まだありません。

このノートについて質問する

このノートについてライターの方に質問できます。

※ライターの方から必ず回答をいただけるとは限りません

※別ウィンドウで開きます

PR

PR

ピックアップ

アクション映画の銃撃戦はウソ...
アクション映画と言えば派手な銃撃戦が売りの一つですよね。...
◆TDR◆ 持ち物チェックリスト...
東京ディズニーリゾートに行く時って、準備している時からワ...
緑黄色野菜は色の濃い野菜の事...
緑黄色野菜と言って思いつくことは?私は色が濃い野菜だと思...

こんなノートがほしい! リクエストする

最大文字数100文字入力できます

リクエストが送信されました。

いただいたリクエストは、Yahoo!知恵袋スタッフが確認のうえ、ライターの方向けに書いていただきたいテーマとして掲出いたします。

続けてリクエストする

リクエストされている知恵ノートを書く

いま求められているのは、こんな「知恵」。お題をクリックして投稿してみましょう!

知恵ノートを書いてみませんか?知恵ノートの書き方はこちら

知恵ノートとは?

役立つ知恵情報は、Yahoo!知恵袋公式Twitter@yahoochiebukuroをフォロー
本文はここまでです このページの先頭へ

お得情報

[10万名に当たる!]
サントリー製品が当たるアンケート
(20歳以上限定)

その他のキャンペーン