こんにちはゲストさん。会員登録(無料)して質問・回答してみよう!

締切り済みの質問

Winsockでの送受信についての質問

本日はじめてOKwaveを利用させていただきます、Nimameです。
以後よろしくお願いします。

本日はwinsockの送受信について質問させていただきたく、投稿しました。

現在winsockを利用したS/Cのネットワークプログラムを組んでいるのですが、
送受信の時、同PC内だとうまくいき、外部PCからだとうまくいかずに困っています。

送受信の際(recv, send)の後にSleep(10)を入れるとうまくいくことから

・パケットが最後まで送信しきれていない
・パケットが最後まで受信しきれていない

の以上が原因かと考えています。

そこでサイズ分最後まで送受信をする関数を用意したのですが、
これがどうにもうまく働いていないようでやはりうまくいきません。


--------------------------------------------------------
// 最後まで送りきる
int Send(SOCKET s, char *buf, int len)
{
int endsize;
int r;
char* sendptr = buf; // 送信する先頭アドレスを取得

// 確実に全てのパケットを送信する
while(endsize < len)
{
r = send(s, sendptr, len - endsize, 0);

// 送信結果がエラーなら終了
if( r <= 0 ) return r;

sendptr += r;
endsize+= r;
}

sendptr = NULL;

return endsize;
}
--------------------------------------------------------
// 最後まで受信する
int Recv(SOCKET s, char *buf, int len)
{
// 受信データアドレスの設定
char* recvptr = buf;
int endsize= 0;

// 受信データを1つぶん読み込むまでループする
while( endsize < len )
{
// 未受信データアドレスに実際にデータを読み込む
int r = recv(s, recvptr, len-endsize, 0);

// エラーだったら終了する
if( r <= 0 ) return r;

recvptr += r;
endsize+= r;
}

strext(buf, buf, 0, len);

recvptr = NULL;

return endsize;
}

--------------------------------------------------------

以上なのですが、おかしな点や、改善点などありましたら
お教えいただけたら幸です。

投稿日時 - 2009-01-20 00:57:42

QNo.4644709

すぐに回答ほしいです

このQ&Aは役に立ちましたか?

18人が「このQ&Aが役に立った」と投票しています

回答(14)

ANo.14

遅くなりました。

>互いにWindowsXPですが。
SPが違うとかいうレベルでしょうか。。。
いずれにしても今回とはあまり関連性が
見えてこないですね;

>まったく同じ環境、構造体です。
であれば、パディングの問題でもなさそうですが、
通信手段について、4バイトのヘッダがあると
おっしゃっていますが、下で既に出ている以外の
構造体を使用されているということでしょうか?

あとは、通信経路に少し特殊な処理系があり、
ネットワークバイトオーダの変換などをしてないとかでしょうか。。
サイズなどの数値を送る場合にバイトオーダの変換はされてますか?

# 実際に何が起こっているのかが
# 把握できていないのでこれ以上だと
# 何でも指摘してやってみないと
# どうしようも無いかもしれません。。。;

投稿日時 - 2009-02-03 16:01:57

ANo.13

> char text[32]; です。
であれば、
>>格納されているデータはぶつ切りになる
は起こりません。構造体の中にtextの
サイズも内包されますので、
sizeof(Data)で問題ありません。

>>外部PCからだと
外部PCはサーバ側で使用してる環境と
まったく同じなのでしょうか?

念のための確認ですが、
送受信側の構造体は同じものを使っていて、
同じ環境でコンパイルされているのですよね??

投稿日時 - 2009-01-23 14:24:49

補足

遅れて申し訳ありません。

>>外部PCはサーバ側で使用してる環境と
まったくおなじではありません。
互いにWindowsXPですが。

>>送受信側の構造体は同じものを使っていて、
>>同じ環境でコンパイルされているのですよね??
まったく同じ環境、構造体です。
アライメントについても同様です。

投稿日時 - 2009-01-29 11:42:12

ANo.12

> struct Data{
> int i;
> short char text;
> }pData;
>strcpy(pData.text, "HELLO");
short char ??って型がよく分かりませんが、
上記であればそもそも、送信側のtextに領域がありません。
文字列などを格納する領域が明らかに不正でしょう。

>Send(socket, (char *)&pData, sizeof(Data));
さらに、これでは構造体分のサイズしか送られません。
最近の環境ですと、intが4byte + short であれば2byte.
charであれば1byte分のサイズのみしか送られず、
格納されているデータはぶつ切りになるでしょう。

もしも、textがポインタであるならば、
それ以前の問題ですが。。。

投稿日時 - 2009-01-22 10:47:26

補足

>short char text;
誤字を指摘されつつまたも;
short char text[32]; です。

>Send(socket, (char *)&pData, sizeof(Data));
>さらに、これでは構造体分のサイズしか送られません。
そうなのですか?
以前はこの方法で無事送受信でき、
ローカル環境であれば無事に動作することから、
sizeof(Data)に特に問題がないようには思えますが・・・。
問題点と改善があればおねがいします。

投稿日時 - 2009-01-22 19:02:31

お礼

*追加
short char って・・・。
すみません。charの間違いです。

投稿日時 - 2009-01-22 19:05:43

ANo.11

> ヘッダー(4byte)は無事受信、その後のデータ受信にて
ヘッダの4byteってその後のデータのサイズということでしょうか?

> printf("%s\n", buf);
最初の投稿で載せられているコードを使っているのだと思いますが
実際にはこのbufはどのように取得された物なのでしょうか?
# 単純に文字終端入れてないだけとかじゃ。。。?

例えば、以下のようになっていませんか?

> int Send(SOCKET s, char *buf, int len)
char Hoge[] = "Hogehoge";
Send( sock, Hoge, strlen(Hoge));
~~~~~~~~~~~~
> int Recv(SOCKET s, char *buf, int len)
char buf[200];
Recv( sock, buf, sizeof(buf));
この場合受信後の格納先であるbufには'\0'終端が無いはずなので、
そのままprintfなどで表示すると、バッファーオバーランします。

投稿日時 - 2009-01-21 16:12:43

補足

ヘッダー4byteはその後受信するデータタイプを格納しています。
それによって受信サイズをきめ、受信しております。

データ本体の受信には構造体を利用しています。

struct Data
{
int i;
short char text;
}pData;

strcpy(pData.text, "HELLO");
Send(socket, (char *)&pData, sizeof(Data));

-----------------------------------------------

Recv(socket, (char *)&pData, sizeof(Data));

printf("%s\n", pData->text);

投稿日時 - 2009-01-21 18:56:38

ANo.10

映し出しミスだと思いますが、Send関数のendsize変数を初期化する際0を代入していないのは大丈夫ですか?

投稿日時 - 2009-01-21 03:26:24

お礼

おはようございます。

映し出しミスのようです;
以後誤字に気をつけたいと思います。
御指摘、ありがとうございます。

投稿日時 - 2009-01-21 04:42:14

ANo.9

> WSAGetLastError()は非同期型にしているためWSAEWOULDBLOCKを返しているようです。
> 一応取得するようにはしましたが、以前変化はないようです。
WSAEWOULDBLOCK を戻しているのがわかったなら、
WSAEWOULDBLOCK に対して適切な処理を実装する必要がありますよ。

例えば、recv()可能なパケットが届くまで待ってみるとか。
例えば、timeout処理を作ってみるとか。
等です。

投稿日時 - 2009-01-21 00:15:58

補足

おはようございます。今朝も寒いです。

昨日(本日)追加でかきこんだ分が反映されていなかったようで。

内部で接続ではWSAEWOULDBLOCKが発生したのですが、
外部の方に接続をしてもらったところWSAECONNRESETが発生しているようです。

始めの1度の通信(CからSへの送信)以降におきているようですが・・。

一応クライアントでは特に接続先を変えるようなアクションはしていません。

投稿日時 - 2009-01-21 04:37:46

ANo.8

nonBloking モードで通信をしているようですが、
通信の内容が、遅れて届くのは、TCP 通信によるDELAY(遅延)
が原因だったりしませんか?

TCP通信では、転送速度を最適化するために、ネーグルアルゴリズム
と言うのを採用していますから、データ転送には、DELAYが発生したりします。

もし、転送速度よりも、応答速度を優先するのでしたら、ネーグルアルゴリズム
の無効化も試してみては如何でしょう。

他には、recv()関数が、SOCKET_ERRORを戻した場合には、
WSAGetLastError()を使って、エラー処理しましょう。

多分ですが、recv()関数が、SOCKET_ERRORを戻した場合に、
WSAGetLastError()が何を戻しているのか、デバッグ表示してみると、
解決のヒントがあるのではないかと思います。

投稿日時 - 2009-01-20 23:23:28

補足

ご返答ありがとうございます。

WSAGetLastError()は非同期型にしているためWSAEWOULDBLOCKを返しているようです。
一応取得するようにはしましたが、以前変化はないようです。

ネーグルアルゴリズムの無効化ですか。
いろいろと調べてみます。

投稿日時 - 2009-01-20 23:57:49

ANo.7

何度もすみません。。

> send関数も同様に、正常切断に対しては 0 、
> 異常切断に対しては -1 のいずれかを返却します。
と書きましたが上記は誤りです。
sendは非同期の場合0を許容しているようですね。。。
Winsock Programing QAでダウンロード可能な
リファレンス及び、MSDNに記載がありました。
# http://msdn.microsoft.com/en-us/library/ms740149(VS.85).aspx
# http://www.sockets.com/winsock2.htm#Docs

リファレンスには、送信したバイト数、
またはSOCKET_ERRORを返すと記述されていました。
# なので 0を返す記述はありませんでしたので、
# 0 は送信したバイト数に含まれる?ものと思われます。

>No3さん
大変、失礼しました。

投稿日時 - 2009-01-20 15:04:54

ANo.6

もうひとつ一番重要かも。。。
この通信のプロトコルは何ですか?
できれば、ソケット生成のコードなど、
もう少し全体的にコードを載せると、
早く解決できるかもしれません。

投稿日時 - 2009-01-20 14:00:34

お礼

通信のプロトコルにはTCPを利用しています。

ソケット生成のコードですね。

-------------------------------------------------
// サーバー(自身)
int Init()
{
// Winsockの設定
WSADATA data;
WSAStartup(MAKEWORD(2,0), &data);

// ソケットの生成
srvSocket = socket(AF_INET, SOCK_STREAM, 0);
if (srvSocket == INVALID_SOCKET)
{
printf("socket failed\n");
return 1;
}

// Addr構造体のセット
srvAddr.sin_family = AF_INET;
srvAddr.sin_port = htons(PORT);
srvAddr.sin_addr.S_un.S_addr = INADDR_ANY;
dstAddrSize = sizeof(dstAddr);

// ソケットのバインド
bind(srvSocket, (struct sockaddr *) &srvAddr, sizeof(srvAddr));

// 接続の許可
listen(srvSocket, 1);

// 正常終了
return 0;
}

接続してきたクライアントに対してはacceptした後、そのsocketに対し、

u_long val=1;
ioctlsocket(socket, FIONBIO, &val);


//------------------------------------------------------
// クライアント
if(connect(dstSocket, (struct sockaddr *) &dstAddr, sizeof(dstAddr))) return -1;

投稿日時 - 2009-01-20 18:37:19

ANo.5

> sendは送信時に「ネットが詰まってて送れない時」には
> 「0バイト送信した」で0で返って来る。
> recvも受信時に「ネットが詰まっててまだデータが来ない時」
> には「0バイト受信した」で0で返って来る。
そんな事は無いと思いますが。。。
recv関数は、接続が正しくシャットダウンを
実行した場合に 0 となります。それ以外のエラーは
(タイムアウトも含めて)-1となるはずです。
send関数も同様に、正常切断に対しては 0 、異常切断に対しては -1 のいずれかを返却します。このとき WSAGetLastError() の返却値は、上記の recv() のときと同様のエラーが返されます。
# これはPOSIXでもWINSOCKでも広域エラー領域に対する設定値が
# 異なるだけで動作的には同じはずですが。。。

> これがどうにもうまく働いていないようで
どううまく動かないのでしょう?
送っているデータは文字列ですが?
数値などを含むバイナリですか?

参考URL:http://www.kt.rim.or.jp/~ksk/wskfaq-ja/articles/bsd-compatibility.html

投稿日時 - 2009-01-20 13:57:02

補足

>>どううまく動かないのでしょう?
ヘッダーとデータを分けて受信しているのですが
ヘッダー(4byte)は無事受信、その後のデータ受信にて
大量の記号?ですか?文字化けのようなものが受信されてしまいます。
*printf("%s\n", buf);

投稿日時 - 2009-01-20 20:57:09

ANo.4

追記。

てゆーか、自分でタイムアウト制御するくらいなら、sendとrecvでタイムアウト制御して貰った方が楽だな。

現状は「タイムアウト時間が設定されてないか、極端に短いのが原因で、外部と送受信した場合だけ、極端に短い時間でタイムアウトして返って来て、sendやrecvが0を返して来る」のだと思う。

ソケットを繋ぐ際に「タイムアウトは3分で」とか、ちゃんとしたタイムアウト時間を指定してから繋げば、現状の「戻り値が0なら0でリターン」でも大丈夫だと思われる(0が返されたらタイムアウト、って事)

投稿日時 - 2009-01-20 12:46:01

お礼

参考になるソースの提示、ありがとうございます。

>>sendやrecvが0を返して来る
戻り値に0を指定しているのが問題かどうか。
今から調べてみます。

投稿日時 - 2009-01-20 18:34:15

ANo.3

送信部分
>// 送信結果がエラーなら終了
>if( r <= 0 ) return r;

受信部分
>// エラーだったら終了する
>if( r <= 0 ) return r;

の「0ならエラーでリターン」はやってはいけない。

sendは送信時に「ネットが詰まってて送れない時」には「0バイト送信した」で0で返って来る。でも、まだ送るべきデータが残ってるんだから「return r」しちゃ駄目だ。

recvも受信時に「ネットが詰まっててまだデータが来ない時」には「0バイト受信した」で0で返って来る。でも、まだ受けるべきデータを受け切ってないんだから「return r」しちゃ駄目だ。

どちらも「マイナスが返って来たらreturn rしても良いが、0が返って来たら、続きの送受信を繰り返す」と言う処理が必要。

更に「0が延々と返り続ける場合は、ハードウェアによるウォッチドックタイマーを用いてタイムアウトになったらエラーにする」と言う処理も必要だろう。もちろん、0でもマイナスでもない値が来た場合は、タイマーをリセットして、タイムアウトしないようにしないとならない。

//ウォッチドッグタイマーが「指定の時間を過ぎたら非0」になる変数
extern volatile int timeout;

// 最後まで送りきる
int Send(SOCKET s, char *buf, int len)
{
int endsize;
int r;
int t=0; // タイマーを起動したかのフラグ
//ウォッチドッグタイマーの停止とリセットをここで行う
timeout = 0;
char* sendptr = buf; // 送信する先頭アドレスを取得

// 確実に全てのパケットを送信する
while(endsize < len)
{
r = send(s, sendptr, len - endsize, 0);

// 送信結果がエラーなら終了
if( r < 0 ) return r;

if( r == 0 ) {
if (!t) {
timeout = 0;
//ウォッチドッグタイマーのセットをここで行う
t++;
sleep(適当な待ち時間);
} else {
if (timeout) {
//ウォッチドッグタイマーの停止とリセットをここで行う
return ERR_TIMEOUT;
}
sleep(適当な待ち時間);
}
} else {
if (t) {
t=0; //1バイトでも送信したならフラグリセット
//ウォッチドッグタイマーの停止とリセットをここで行う
}
sendptr += r;
endsize+= r;
}
}

sendptr = NULL;

return endsize;
}
--------------------------------------------------------
// 最後まで受信する
int Recv(SOCKET s, char *buf, int len)
{
// 受信データアドレスの設定
char* recvptr = buf;
int endsize= 0;
int t=0; // タイマーを起動したかのフラグ
//ウォッチドッグタイマーの停止とリセットをここで行う
timeout = 0;

// 受信データを1つぶん読み込むまでループする
while( endsize < len )
{
// 未受信データアドレスに実際にデータを読み込む
int r = recv(s, recvptr, len-endsize, 0);

// エラーだったら終了する
if( r < 0 ) return r;

if( r == 0 ) {
if (!t) {
timeout = 0;
//ウォッチドッグタイマーのセットをここで行う
t++;
sleep(適当な待ち時間);
} else {
if (timeout) {
//ウォッチドッグタイマーの停止とリセットをここで行う
return ERR_TIMEOUT;
}
sleep(適当な待ち時間);
}
} else {
if (t) {
t=0; //1バイトでも受信したならフラグリセット
//ウォッチドッグタイマーの停止とリセットをここで行う
}
recvptr += r;
endsize+= r;
}
}

strext(buf, buf, 0, len);

recvptr = NULL;

return endsize;
}

投稿日時 - 2009-01-20 12:40:17

ANo.2

VOT

回答がないようなので、とりあえず。

>・パケットが最後まで送信しきれていない
>・パケットが最後まで受信しきれていない
>の以上が原因かと考えています。

違うと思いますよ。
生成している socket の属性をどうしているのか、TCPならconnect()/bind()はちゃんと行っているのか、非常に疑わしいのですが、質問の内容からして socket() の基本的な使い方から、まだ理解できていないように見受けられます。
このレベルなら、すぐにコンパイルして動かせるサンプルコードはいくらでも転がってますので、まずそれらを参照してみてはいかがでしょう。

参考URL:http://www.geekpage.jp/programming/winsock/

投稿日時 - 2009-01-20 12:35:20

お礼

通信にはTCPを利用し、connect/bindももちろん行っています。
参考に上げていただいたサイト様のサンプルは一通り確認はしてあり、
簡易なチャットプログラムなどは開発済みです。

ただ今回は通信頻度が高いせいか、データがうまく送受信されないので
困り、質問させていただきました。

Sendの後にSleepをある程度入れることで解消できはするのですが・・・

投稿日時 - 2009-01-20 18:28:32

ANo.1

send,receive を使っている所から、接続型の通信と思います。
普通の手順ですと、以下のようになります。
(1)送信側がconnectして、通信開始を相手に伝える。
(2)受信側はacceptで、送信側の開始要求を受け付ける。
(3)送受信する
(4)送信側は通信の終了を知らせる合図を送る。
(5)受信側は通信の終了を知らせる合図を受けたら受信を止める。
この「通信終了の合図」ですが、どのような取り決めにして
いますか?これがプロトコールなのですが、私でしたらsocketを
閉じてしまうことで、EOFを伝えます。受信側は切断を検知するので、
EOFを知ることができます。(FTPのデータ送受信なども同じ方式)

時間とかで通信を打ち切るのはチョット疑問ですね。
それにデータサイズが分かっているなら非接続型通信でも出来る
ように思います。こちらの方式も検討されましたか?

投稿日時 - 2009-01-20 12:29:40

お礼

手順はそのとおりですね。
終了の合図にはshutdown(), closesocket()を使用しています。

リアルタイムでの通信を行いますので、非接続型は検討してないです。
ご返答ありがとうございました。

投稿日時 - 2009-01-20 18:23:03

あなたにオススメの質問