プロが教えるわが家の防犯対策術!

VB2005 の初心者です。

System.Net.Sockets を利用して、Windows端末のAシステムとLinux端末のBシステムの通信部分のサービスを開発しています。

その際、NetworkStream で受信したデータのバイト数を取得したいのですが、やり方が分かりません。

データを取得している部分のソースは下記です。
10000バイトずつ取得していますが、
最後、10000バイトに満たない場合、取得したデータのバイト数が知りたいです。

Dim LNetworkStream As NetworkStream
Dim LbEndFlg  As Boolean
Dim LucData(10000) As Byte

Do While LbEndFlg
LNetworkStream = objClient.GetStream()

Redim LucData(10000)
LsLen = LNetworkStream.Read(LucData, 0, LucData.Length)

 LbEndFlg = FindEndOfClaim(LucData)
LstrText = System.Text.Encoding.UTF8.GetString(LucData, 0, LsLen)

MstrXML = MstrXML & LstrText

Loop


NetworkStream には、SetLength というメソッドがありますが、
これは必ず例外を発生させてしまうので、
例外を発生させずにバイト数を取得したいです。

何かヒントになることでも良いので、
皆様の知恵をお借りできれば幸いです。
よろしくお願い致します。

A 回答 (4件)

追記。



>以下のように「自分で実装する必要」があります。

もちろん、自分で実装などせずに「ReadTimeoutプロパティに適切な値をセットし、DataAvailableプロパティがTrueだろうがFalseだろうが構わずにReadメソッドを呼び出し、Readメソッドが0以外を返した場合は処理を継続し、Readメソッドが0を返した場合はセッションが切れたかタイムアウトした時だからそこで強制終了する」と言う実装をした方が、遥かに簡単です。
    • good
    • 0
この回答へのお礼

お礼が遅くなり、申し訳ございません。

自分で実装せずに、Readメソッドが0以外を返した場合は処理を継続し、Readメソッドが0を返した場合はセッションが切れたかタイムアウトした時だからそこで強制終了する、という処理にしました。
途中で途切れたデータを送信する送信側のテストプログラムを作成し、動作確認もしました。

説明いただいた内容がとても分かり易く、よく理解できました。
本当にありがとうございました。

お礼日時:2008/03/28 10:05

>LNetworkStream.ReadTimeout にタイムアウトミリ秒数を設定できますが、


>タイムアウトの秒数に達したことはどのように判定すれば良いのでしょうか。

ReadTimeoutは「DataAvailableプロパティがFalseの状態でReadメソッドを呼び出した際に、Readメソッドの中でデータが来るまで何ミリ秒待つか?」を指示するプロパティです。0を指定した場合は「データが来るまで永久に待つ」になっていたと思います。

DataAvailableプロパティがTrueであれば、Readメソッドは「今読めた分だけ読んで、読んだバイト数」を返します。

DataAvailableプロパティがFalseであれば、Readメソッドは「ReadTimeoutの時間だけデータが来るのを待ち、来た場合は来た分だけ読んで読んだバイト数を、待っても来なかった場合は0」を返します。

多分、質問者さんは「DataAvailableプロパティがFalseであれば、Readメソッドを呼ばない」と言う実装をしている筈ですから、タイムアウト処理は、以下のように「自分で実装する必要」があります。

1.タイマーによるイベントが起きていないかどうかを示すBoolean型グローバル変数をFalseにセットする
2.指定時間後にイベントを起こすタイマーをEnableにする
3.DataAvailableプロパティがTrueになっていてReadメソッドを呼んだ場合は、指定時間後にイベントを起こすタイマーをリセットし、再度、指定時間後にイベントを起こすようにする
4.DataAvailableプロパティがFalseになっていてReadが呼べずに待っている間は、タイマーイベント発生を示すBoolean型グローバル変数がTrueになってないかチェックする。Trueであれば「タイムアウト」として、処理を中断する
5.リード処理のループを抜けたらタイマーをDisableにする(正常終了時も、中断終了時も、どちらの場合でも)

指定時間後にイベントを起こすタイマーからコールバックするイベント関数では、タイマーイベント発生を示すBoolean型グローバル変数をTrueにセットし、タイマーをDisableにする。
    • good
    • 0

>サービスだとNetworkStream の DataAvailable では、


>終了を正しく判定できないのでしょうか。

DataAvailableプロパティは終了を判定するプロパティではありません。

DataAvailableプロパティは「今の瞬間、Readメソッドで読み取り可能なデータがあるか?」を示すのみです。

>LNetworkStream.DataAvailable が、FormだとTrueの所が、
>サービスだとFalseと判定して、データの途中で読み込みを終了してしまいます。

データの送受信は非同期で行われる為、送信よりも受信の方が早く処理されると「受信が送信に追い付いてしまい、途中で受信すべきデータが尽き、続きが送られて来るのを待たなければならない」と言う状況になります。

「途中で受信すべきデータを待つ」とは「途中でReadメソッドで読み取り可能なデータが尽き、DataAvailableプロパティがFalseになるので、再びTrueになるのを待つ」と言う意味です。

つまり「DataAvailableプロパティがFalseになる状況」とは、
・すべてのデータが届いて、それ以上送信するデータが無い時
・受信が送信に追い付いてしまい、途中で受信すべきデータが尽きた時
・送信側がハングアップした時
・送信側が故意に途中で通信を切断した時
・通信の途中で不意に回線が切れた時
などです。

質問者さんは「DataAvailableプロパティがFalseになる状況」を
・すべてのデータが届いて、それ以上送信するデータが無い時
しか想定していないので、想定外の状況が起きると
>データの途中で読み込みを終了してしまいます。
と言う事になります。

「データの終了」は「来なかったら終り」ではなく「データの中には存在しない、特殊な終了マークが来たら終わり」とか「送り側は、データ長情報など、最初に固定長の制御情報を送り、それに続いて可変長のデータを送り、受け側は、最初に受け取った制御情報の中のデータ長情報を使って、その長さまで読んだら終わり」とかって処理で判定します。

むろん、こういう処理を行えば、送信側がハングアップした時などに「データが途中までしか来てないが、いっこうに続きが来る気配がない」なんて事も起きます。つまり、強制切断時の対処やタイムアウトの処理も必要になるって事です。
    • good
    • 0
この回答へのお礼

再度、詳細な説明をいただき、本当にありがとうございます。

通信では、XMLデータを受信しているのですが、
3種類の文字コードで送信される可能性があるため、
受信データの末尾がEOT、かつ、
受信データをMSXML2.DOMDocumentでLoadできれば、
終了と判定してループから抜けるようにしました。

とりあえず、正常処理は正しく終了するようになりましたが、
送信側がハングアップした時などのために、タイムアウトの処理を追加したいのですが、
やり方が分かりません。

LNetworkStream.ReadTimeout にタイムアウトミリ秒数を設定できますが、
タイムアウトの秒数に達したことはどのように判定すれば良いのでしょうか。

それらしきメソッドはないのでしょうか。

何度も初歩的な質問で大変申し訳ありませんが、
何かご存知でしたらご教授いただければ幸いです。

よろしくお願い致します。

お礼日時:2008/03/18 18:43

>LsLen = LNetworkStream.Read(LucData, 0, LucData.Length)



>LstrText = System.Text.Encoding.UTF8.GetString(LucData, 0, LsLen)

この2行の意味、理解してますか?

1行目:LucDataの位置に、LucDataのサイズ(10000バイト)分、読み込んで、結果として「読み込んだバイト数」をLsLenに格納する。
2行目:LucDataの位置の「さっき読んだバイト数(LsLen)」の分のデータを、UTF8の文字列と思ってエンコードして、結果文字列をLstrTextに格納する。

>最後、10000バイトに満たない場合、取得したデータのバイト数が知りたいです。

「取得したデータのバイト数」とは何か?
「LsLen」には何が格納されているか?
この2点を「よ~く考えて」みましょう。

それと「10000バイトづつ読んで、最後だけ10000に満たない」と思っているようですが、そんな事はありません。仕様書のどこにも必ずそうなるとは書かれていません。

もしかしたら「1回目は9997バイト、2回目は9996バイト、3回目は3521バイトで終了」って可能性もあります。

あと、この読み込み処理はバグってます。根本的な変更が必要です。

「1回目の読み込みの10000バイト目に、多バイト文字の第1バイトが来て、2回目の読み込みの1バイト目に、多バイト文字の第2バイト以降が来たら、どういう結果になるか?」を考えてみて下さい。

例えば、全角文字の「あ」は、UTF8では16進で「E3、81、82」の3バイトになります。「あ」を3335文字送ると「E3、81、82」が3335回繰り返した、10005バイトのデータが送られます。

読み込みは10000バイト単位で繰り返すので、1ブロック目のデータは
00001:E3
00002:81
00003:82
00004:E3
  |
09997:E3
09998:81
09999:82
10000:E3 ☆
になり、2ブロック目のデータは
10001:81 ☆
10002:82 ☆
10003:E3
10004:81
10005:82
になります。

すると、System.Text.Encoding.UTF8.GetStringは、上記の☆が付いたデータを、正しく「あ」にエンコードできません。

つまり、貴方が書いたプログラムは「10000バイト単位に、他バイト文字が文字化けし、正しく受信できない」と言うバグがあるのです。

ですので、System.Text.Encoding.UTF8.GetStringで文字コードのエンコードを行う際は「全部のデータを取得し終わってから、最後に1回だけ、一気に行う必要」があります。

それが不可能であれば「改行コードなど、絶対に多バイト文字じゃない文字で区切ってreadする」、つまり、「1行ごとに読み込みを繰り返す」などの処理が必要でしょう。

ともかく、バグってて使い物にならないので、上司から指摘されて恥をかく前に、全面的に作り直しましょう。
    • good
    • 0
この回答へのお礼

お恥ずかしいソースにも関わらず、丁寧なご説明、ありがとうございました。

教えていただいたとおり、文字コードのエンコードは、全部のデータを取得し終わってから行うよう修正しました。

しかし、Formなら正しく動くのですが、Serviceにすると正しく動きません。

LNetworkStream.DataAvailable が、FormだとTrueの所が、
サービスだとFalseと判定して、データの途中で読み込みを終了してしまいます。

下記が、データを取得部分のソースです。

Do
LNetworkStream = objClient.GetStream()

Redim LucData(100000)

'受信データの読み出し
LsLen = LNetworkStream.Read(LucData, 0, LucData.Length)

Redim Preserve LucTotalBytes(LsTotalLen + LsLen)
'データを連結する
For LiCnt = 0 To LsLen - 1
LucTotalBytes(LsTotalLen + LiCnt) = LucData(LiCnt)
Next

'全バイト数
LsTotalLen = LsTotalLen + LsLen

Loop Until LNetworkStream.DataAvailable = False

サービスだとNetworkStream の DataAvailable では、
終了を正しく判定できないのでしょうか。

何かご存知のことがあれば、ご教授いただければ幸いです。
何度も申し訳ありませんが、よろしくお願い致します。

お礼日時:2008/03/13 10:57

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!