ユーザー定義型をwinsockで送るには?

解決


Tcl/Tk  2005-01-21 18:52:09  No: 119115  IP: [192.*.*.*]

只今、クライアント・サーバシステムを用いた
チャットソフトを作っておりまして、
winsockを使用して、TCP/IPでデータの送受信をしたいのですが、
送信したいデータは以下の文字列だけのユーザー定義型になっています。
Public Type DATAFILE
    DF_Command  As String * 3   '送受信用コマンド
    DF_Name     As String * 50  '送受信用ユーザー名
    DF_Address  As String * 16  '送受信用アドレス(IPアドレス)
    DF_Message  As String * 255 '送受信用データ内容
End Type

過去ログを探したところ、
ユーザー定義型をバイト配列に置き換えて送ると良いとの事でした。
そこで、その方法を試してみたのですが、
送信時にはバイト配列から復元も出来るのですが、
受信になるとバイト配列から復元出来ません。

Public Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" _
            (destination As Any, source As Any, ByVal length As Long)

Dim Byte_text() As Byte

Dim SEND_DATA   As DATAFILE
Dim GET_DATA    As DATAFILE

'-- 送信
SEND_DATA.DF_Command = "MES"
SEND_DATA.DF_Message = "test"
SEND_DATA.DF_Name = "User"
SEND_DATA.DF_Address = winsock(0).LocalIP

ReDim Byte_text(LenB(SEND_DATA))
CopyMemory Byte_text(0), SEND_DATA, LenB(SEND_DATA)
'この時に復元は可能
winsock(0).SendData Byte_text

'--受信
Private Sub winsock_DataArrival(Index As Integer, ByVal bytesTotal As Long)
    
    tcpSock(Index).GetData Byte_text, vbArray + vbByte, bytesTotal
    CopyMemory GET_DATA, Byte_text(0), bytesTotal

    Debug.Print GET_DATA.DF_Command '復元出来ず

End Sub

復元する方法等、ご指摘がありましたら、
何方か教えて頂けないでしょうか?宜しくお願い致します。

編集 削除
Tcl/Tk  2005-01-21 18:55:26  No: 119116  IP: [192.*.*.*]

なお、環境はVB6.0のSP5です。

編集 削除
abu  2005-01-21 20:27:13  No: 119117  IP: [192.*.*.*]

まず通信の問題か手法の問題化を切り分けるために以下のことを
試してください。

Dim SEND_DATA   As DATAFILE
Dim GET_DATA    As DATAFILE

SEND_DATA.DF_Command = "MES"
SEND_DATA.DF_Message = "test"
SEND_DATA.DF_Name = "User"
SEND_DATA.DF_Address = winsock(0).LocalIP

ReDim Byte_text(LenB(SEND_DATA))
CopyMemory Byte_text(0), SEND_DATA, LenB(SEND_DATA)
CopyMemory GET_DATA, Byte_text(0), LenB(SEND_DATA)

これでGET_DATAにちゃんとコピーできていれば手法的に問題ありません。
通信でちゃんと全バイトを取得できていない可能性があります。

そうでなければ手法がまずいということですね。

編集 削除
Tcl/Tk  2005-01-21 22:49:55  No: 119118  IP: [192.*.*.*]

その方法で確かめたところ、
GET_DATAの方にて値が取得出来ていたので、ちゃんとコピーは出来ている様です。
となると、やはり全バイト取得出来てないということですか・・・。

そこで、受信側のbytesTotalをデバッグしてみたのですが、
送信時のバイト数よりも1バイト多いことが解りました。
 送信側 : LenB(SEND_DATA) = 324
 受信側 : bytesTotal = 325 

これはwinsockの方で何か処理をしているためでしょうか?
また、このデバッグ結果は全バイト取得出来ているということですかね?
私の予想では、これが原因では無いかと思うのですが・・・。
どうなんでしょうか?

編集 削除
abu  2005-01-21 23:04:47  No: 119119  IP: [192.*.*.*]

送信時と受信時のバイト配列の中身を比較してみればどうでしょう?

編集 削除
Tcl/Tk  2005-01-21 23:47:18  No: 119120  IP: [192.*.*.*]

> 送信時と受信時のバイト配列の中身を比較してみればどうでしょう?
比較してみましたらほぼ同じでした。
(頭の部分等の一部は確認しましたが、最後のバイトは確認していません。

それとデバッグをしていて、ある事に気が付きました。
このプログラムは制作の途中でユーザー定義型を固定長に変えたのですが、
固定長にしてから受信時の
CopyMemory GET_DATA, Byte_text(0), bytesTotal
が実行出来なくなってしまいました。
その箇所に行くとVB諸共アラートも表示されずに落とされてしまいます。
確かkarnel32の共有違反だったと思いますが、回避方法を何方かご存じありませんか?
アラートなどの情報源が何も無いので手がつけられない状態です。

<修正:バイト数が間違ってました。>
 送信側 : LenB(SEND_DATA) = 648
 受信側 : bytesTotal = 649

編集 削除
abu  2005-01-22 00:16:23  No: 119121  IP: [192.*.*.*]

> 送信側 : LenB(SEND_DATA) = 648
> 受信側 : bytesTotal = 649

送受信で1バイト違うのはこれが原因では?
ReDim Byte_text(LenB(SEND_DATA))
Option Baseが0ならByte_textはLenB(SEND_DATA)+1の配列となります。

>その箇所に行くとVB諸共アラートも表示されずに落とされてしまいます。
受信側で648バイトしかメモリを確保していないのに649バイト書き込んでいるからでは?

編集 削除
Tcl/Tk  2005-01-22 01:03:59  No: 119122  IP: [192.*.*.*]

> 受信側で648バイトしかメモリを確保していないのに649バイト書き込んでいるからでは?
1レコードのサイズが648バイトという事は解っているので、試しに
tcpSock(Index).GetData Byte_text, vbArray + vbByte, 648
CopyMemory GET_DATA, Byte_text(0), 648
と受信時を定数で行ってみましたが、やはりVB諸共落とされます・・・。
ついでに、送信側を定数にして検証してみましたが結果は同じでした。

ちなみに、可変長型で行うとCopyMemoryの際に落とされる事は無くなるのですが、
バイト配列の値が受信時と送信時で変わってしまい値を取得する事が出来ませんでした。

編集 削除
。。。  2005-01-22 03:43:46  No: 119123  IP: [192.*.*.*]

>ReDim Byte_text(LenB(SEND_DATA))
>CopyMemory Byte_text(0), SEND_DATA, LenB(SEND_DATA)

ReDim Byte_text(Len(SEND_DATA)-1)
CopyMemory Byte_text(0), SEND_DATA, Len(SEND_DATA)

編集 削除
。。。  2005-01-22 03:48:21  No: 119124  IP: [192.*.*.*]

>1レコードのサイズが648バイトという事は解っているので
-->324

編集 削除
ねろ  2005-01-22 11:20:34  No: 119125  IP: [192.*.*.*]

送信データーが全て文字列なら、適当なデリミタを決めるか、
プロトコールを決めて、文字列で送信した方が楽では。

編集 削除
Tcl/Tk  2005-01-22 23:01:43  No: 119126  IP: [192.*.*.*]

デリミタを用いる方法は私も考えたのですが、
ユーザーにデリミタとして用いている文字列を
入力される可能性が出てくるので諦めていました。
やはりデリミタに頼る他無いのでしょうか?

> >1レコードのサイズが648バイトという事は解っているので
> -->324
Win98SEで検証した時は324バイトでしたが・・・。
OSに依存する物なんでしょうかね?

編集 削除
ねろ  2005-01-23 10:09:56  No: 119127  IP: [192.*.*.*]

>ユーザーにデリミタとして用いている文字列を
>入力される可能性が出てくるので諦めていました。
エスケープキャラクターを使います。
http://www.atmarkit.co.jp/fdotnet/basics/regex01/regex01_03.html

私は最近はタグでやっています。
<body>
<command>aaaa</command>
<name>nero</name>
<add>172.xx.x.xxx</add>
<data>Hello World</data>
</body>
こんな具合に送ります。

編集 削除
Tcl/Tk  2005-01-23 11:55:43  No: 119128  IP: [192.*.*.*]

正規表現によるエスケープキャラクターは
CやPerlをやっていたので知ってはいるのですが、
デリミタとして使うのにはちょっと戸惑いますね。
一部の人だけだとは思いますが、
入力箇所があるとエスケープキャラクターを使う衝動に狩られまして
"\t"や"\n"等が使えないかを試したくなるのです。

タグを用いる方法は良いですね。
応用としてログの保存をXMLで保存していけば、
テキストで保存するよりは管理が楽になる様な気が・・・。
(当方がXMLに関する知識が乏しいのは御察し下さい。

編集 削除
ねろ  2005-01-23 16:55:52  No: 119129  IP: [192.*.*.*]

>"\t"や"\n"等が使えないかを試したくなるのです。
"\n"をデリミタとして使用した場合は、データーとして、
"\n"を送るには、"\\n"に変換して送る事になりますよね。

タグの方法を取るとしても、エスケープキャラクターとは
言わないでしょうが、データーの中に使用する、
"<" から "&lt;"、">" から "&gt;"、"&" から "&amp;"の変換は
必要になります。

編集 削除
ひろ  2005-01-24 15:42:34  No: 119130  IP: [192.*.*.*]

固定長のByte型配列の先頭をAPIに渡したいなら、
Type DataBlockType
    data(0 To 323) As Byte
End Type

Dim dataBolck As DataBlockType

みたいに構造体で宣言してAPIに渡すようにすれば先頭アドレスが渡ります。

編集 削除
Tcl/Tk  2005-01-26 17:08:06  No: 119131  IP: [192.*.*.*]

とりあえずタグを使ってString型にて転送する事にしました。
どうもありがとうございました。

編集 削除