この知恵ノートを「知恵コレクション」に追加しました。
追加した知恵ノートはMy知恵袋の「知恵コレクション」ページで確認できます。
「知恵コレクション」に登録済みです。
再登録しました。
追加に失敗しました。
ノートに戻り、もう一度やり直してください。
すでに1,000件のノートが登録されています。
新しく追加したい場合は、My知恵袋の「知恵コレクション」ページで登録されているノートを削除してください。
追加できませんでした。
ノートは削除されました。
知恵コレに追加する:0人
.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 バイト受信できなかったからと言ってクライアントに受信データのサイズを送り、クライアントから再送信してもらう必要はないのである。
アドバイス(このノートのライターへのメッセージ)を送る
このノートはどうでしたか? いいと思ったことや、こうしたらもっとよくなるといったメッセージを送りましょう! ノートの内容やライターについて質問がある場合は、Q&Aから質問してみましょう
アドバイスを送るには、
Yahoo! JAPAN IDでのログインおよび
Yahoo!知恵袋の利用登録が必要です。
感想アドバイス履歴
-
現在アドバイスはありません
このノートに関するQ&A
このノートに関するQ&Aは、まだありません。
あなたにおすすめの知恵ノート
あなたにおすすめのQ&A
- このC#をCに直していただけませんか? using System; using System.Collections.Generic; using Sys...
- すいませんおしえてください。C#でTCPについてなんですが、クライアント側から送信されたデータをサ...
- 【急!!!VB.NET】System.Collections.ArrayListで一度追加した要素の値を変更したい。 環境:WinXP vb...
- VBでTCPを使ってデータ送信を作成しています。けれど、なかなかうまく行きません。今、開発している...
- 下のサイトのソフトは本当に無料ですか?またダウンロードした後、解凍ソフトなどが必要ですか? あ...