VS2005のVC++にてTCPサーバの実験を行っています。
動作としては10バイトのデータをサーバから送信するとクライアントから10バイトの
データが送信される動作です。
送信はボタンを押す事によって開始され、受信が完了するまでボタンは押せないようにし
てます。
色々検索して見よう見まねで作ってるので変な部分があるかもしれません。
部分的に省略させていただきますが、送信は実際に送信された数を見て、全部送信される
まで繰り返す。
受信はウィンドウメッセージが来た時点で処理を行っています。
これである程度間隔を空けてボタンを押しデータを確認すると問題なくデータが受信され
てますが
素早く連続してボタンを押すと2回目の受信データが期待値と違ってます。
データは、送受信共0,55,AA,55,AA,55,AA,55,EB,FC(HEX)が正解ですが異常が発生すると
CC,CC,CC,55,AA,55,AA,55,EB,FCなどと必ずCChを受信します。
この時のWSAGetLastError()を確認するとWSAEWOULDBLOCKとなっています。
色々対処方を検索してますが、なかなか解決策を見つけられなく困っています。
以下、抜粋したソースコードです。
宜しくお願いします。
#define WM_LAN_MSG WM_USER + 2 // LANに関するメッセージ
afx_msg LRESULT Lan_Action(WPARAM wParam, LPARAM lParam);
char rcv_buf[10]; // 受信バッファ
char send_buf[10]; // 送信バッファ
int down_count; // 受信カウントガウン変数
int get_number; // 受信数を入れる変数
int read_number = 0; // 受信したデータを入れるポインタをオフセットさせる
時の変数
SCKET target_socket // 送信先
LRESULT CLAN_MDlg::Lan_Action(WPARAM wParam, LPARAM lParam){
switch(lParam){
case FD_ACCEPT: // 接続
// 接続に関する処理は省略
break;
case FD_WRITE: // 送信が完了した
break;
case FD_READ: // データが来た
if(down_count == 0){ // down_countが0
なら最初の送信開始である
down_count = 10; // 受信する回数をセットす
る
read_number = 0; // ポインタのオフセット値
はクリア
}
get_number = recv((SOCKET)wParam,&rcv_buf[0] +
read_number,down_count,0);
if(get_number ==SOCKET_ERROR){
if(WSAGetLastError() == WSAEWOULDBLOCK){
// ???
}
}
down_count -= get_number;
// 受信すべき値から今回受信した値を引く
read_number += get_number;
// 受信し残しがある場合、入れるバッファの位置が変わるので計算しておく
if(down_count == 0) m_button1.EnableWindow(TRUE);
// 送信ボタンを押せるようにする
break;
case FD_CLOSE:
closesocket((SOCKET)wParam); // 接続してきた
クライアントソケットを破棄する
}
return 0;
}
// 送信動作
// 引数:送信する相手
bool CLAN_MDlg::Write_Action(SOCKET target_socket){
int get_number; // 実際に送信した値が入る
int send_number = 10; // 残送信数を入れる変数
int offset_number = 0; // 全部送信されなかった場合送信バッファをオフセット
させる変数
WORD loop_count = 0;
m_button1.EnableWindow(FALSE); // データ送信ボタンは押せないよう
にする
while(loop_count != 0xFFFF){ // 全部送信するまでFFFFh回チャレン
ジするが、ダメだったら諦める
loop_count++;
get_number = send(target_socket,send_buf[0] +
offset_number,send_number,0);
if(get_number == SOCKET_ERROR){ // エラーチェック
}
offset_number += get_number;
if(offset_number != 10) send_number = 10 - offset_number;
//目標送信数と実際の送信数を比較
else return true;
}
return false;
}
訂正です。
>この時のWSAGetLastError()を確認するとWSAEWOULDBLOCKとなっています。
これ間違ってました。
実際はrecv関数のは戻り値はデータ化けしてる時もSOCKET_ERRORの値は返してません。
チラッとしか見てないけど
rcv_buf[10];
しか領域取ってないのにそれ以上の場所にアクセスしてるんではないだろうか?
down_count -= get_number;
これって必ずジャスト0になる?
出ないと
if(down_count == 0){
に入らないと思うけど・・・
そうなると
rcv_buf[0] + read_number
でオーバーするんじゃないだろか?
違ったらごめん
switchさんへ
>しか領域取ってないのにそれ以上の場所にアクセスしてるんではないだろうか?
クライアントの構成はXportというLAN接続モジュールと16bitマイコンがUARTで接続され
てるのですが、その信号を取り出してRS232Cレベルに変換してPCに接続しRS232C送受信ソ
フトでデータのやり取りを確認しました。
その結果サーバ側から10バイト送信、クライアントから10バイト送信の順やデータ量には
問題ありませんでした。
念のため、これから配列を多めに取って実験してみます。
クライアント側だけでなく、サーバ側でも、どんなデータがやりとりされたかを調べて
みてはいかがでしょう? 実際にゴミデータを受信してしまっているかもしれませんし。
有名どころではWireshark(旧Ethereal)というソフトでネットワークの送受信データを
見ることができます。
以下、参考になるかどうか怪しいですが、
> これである程度間隔を空けてボタンを押しデータを確認すると問題なくデータが
> 受信されてますが
> 素早く連続してボタンを押すと2回目の受信データが期待値と違ってます。
素早く連続して押した場合、10+10バイト、と分けて送受信されるのではなく、
20バイトまとめて送信または受信されていたりしませんか?
20バイトまとめて受信された場合、FD_READが2回発生するわけではなかったハズ
ですので、2回目を受信するタイミングを逸するような気が…… (うろ覚え)
もしくは、20バイトまとめて送信されたことにクライアントが対応できずゴミを
返しているとか……
あと、
> get_number = send(target_socket,send_buf[0] +
> offset_number,send_number,0);
第2引数は、&send_buf[0] + offset_numberのtypoだと思われますが、実際の
ソースではどうなっているでしょうか?
yoh2さんへ
>素早く連続して押した場合、10+10バイト、と分けて送受信されるのではなく、
>20バイトまとめて送信または受信されていたりしませんか?
サーバ側は10バイト受信するまで次の10バイトを送信できないような仕組みにしてるので
連続20バイトの送信をしてる事はありませんでした。
そこでアドバイスにあったWiresharkを使用しデータ転送部のログを見たら
正常な時
サーバ 00 55 aa 55 aa 55 aa 55 eb fc
クライアント 00 55 aa 55 aa 55 aa 55 eb fc
でしたが、取得したデータが化けてる時
クライアント 00 55 aa 55
クライアント aa 55 aa 55 eb fc
と2回に分けて?(すいません。知識が乏しくて間違った解釈かもしれませんんが)
となってました。
そこでクライアントのXportというLANデバイスの送信タイミングに関する設定で
ある程度データがまとまったら送信する(マイコンからのデータが途切れたら)
としたらデータが分割される事もなく現在の所100%正常なデータが受信できるように
なりました。
データが分割されていてもちゃんと受信できるようにしなきゃマズイだろという突っ込み
が出てきそうですが...
TCP ってそういうものなので、対処しておかないとまずいだろうね。
実験機で変更できた設定が本番機ではできなかったりすることがあるんで。
send の呼び出し1回が network 上の1パケットになるとは限らん。
network 上の1パケットを receive 1回で受け取るとも限らん。
send/receive のバイト数や呼び出しタイミングをアプリ側で制約すると
性能が出なくなる/不必要にネットワークを混雑させることがある。注意すべし。
解決済にチェックが入っていますが、
> データが分割されていてもちゃんと受信できるようにしなきゃマズイだろという突っ込み
> が出てきそうですが...
この突っ込みの一環としてひとつ。
提示されたソースを見る限りでは、余分に分割されたパターンには問題なく対処できそうなんですが。
グローバル変数の方の(受信で使っている方の)down_countやget_numberを変なところでいじって
いたりしませんか?
yoh2さんへ
プロジェクト内で検索して確認してみましたがdown_countは受信の一番最初に10の値を
入れているのと、recv関数で取得した値を引く以外に何もしてないです。
get_numberもrecv関数の戻り値を入れている以外に何もしてないです。
もう少しデータに異常が出たときの変数値や挙動を探ってみます。
私の大間違えが発覚しました。
他の実験用変数を
LRESULT CLAN_MDlg::Lan_Action(WPARAM wParam, LPARAM lParam){
直下に宣言していて、間違えてその変数に受信したデータを入れてました。
ローカル変数に入れて、分けた状態で受信すればおかしくて当たり前でした。
お手数をおかけして申し訳ありません。