send関数の送信サイズ

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
Gammodler

send関数の送信サイズ

#1

投稿記事 by Gammodler » 13年前

非常に初歩的な問題で悩んでいます。
TCP/IPでWindows側サーバ、Linux自作マシンクライアントで、サーバ側から大量のデータを流し込んでいます。
send関数でlen=9210として複数回実行、WireSharkで観測したところLength=9282となり、CheckSumエラーが表示されます。
識者に聞いたところ、これはJumbo flame、うまくいくはずがないとのこと。
しかし実際には受信側の動作チェックでほとんどの場合間違いはないようです。まれに"could not read the length"というエラーが表示されます。

これを回避するため、len=900としたところ、WireShark上ではLength=1800となりました。Linux側のMTUは測定の結果1499でしたのでこの場合もCheckSumエラーになります。
複数回実行されたsend関数による送信要求をどこかでまとめてしまっているようです。
send関数による要求をTCP/IP ProtocolがMTUに相当するセグメントに分割、MSSの制約の範囲内で自動的に送信してくれるはずと単純に考えていたのでが、別の設定が必要なようです。
くだらない質問で恐縮ですがよろしくお願いいたします。

beatle
記事: 1281
登録日時: 14年前
住所: 埼玉
連絡を取る:

Re: send関数の送信サイズ

#2

投稿記事 by beatle » 13年前

詳しくない分野なので、的外れかもしれませんが・・・ 要するに、手動でMTUを調整する必要があるのかもしれません。

Gammodler

Re: send関数の送信サイズ

#3

投稿記事 by Gammodler » 13年前

beatle様
Resありがとうございます。

MTUを調べると:
送信側(Vustaマシン) :1472
受信側(Linuxマシン) :1500
これだけであとはtcp/IP ProtocolがMTU単位に分割送信してくれると思ったのですが、send関数へのLength指示をMTU以下にしないと不都合なようです。
send関数を数10μs間隔で発行すると、どこかで『つないで』しまうようで、不具合が再燃します。
Sleep(1)を入れたところ各sendを別物として扱ってくれたのか正常になりました。
しかしsend発行ループに1msを入れるわけにはいきません。問題は解決しません。
ご示唆いただけることがあればお願いいたします。

アバター
softya(ソフト屋)
副管理人
記事: 11677
登録日時: 15年前
住所: 東海地方
連絡を取る:

Re: send関数の送信サイズ

#4

投稿記事 by softya(ソフト屋) » 13年前

すごくプログラムに問題がある気がしますが、結合したと思えるパケットのWireSharkのデータを見せてもらえませんか?
by softya(ソフト屋) 方針:私は仕組み・考え方を理解して欲しいので直接的なコードを回答することはまれですので、すぐコードがほしい方はその旨をご明記下さい。私以外の方と交代したいと思います(代わりの方がいる保証は出来かねます)。

YuO
記事: 947
登録日時: 15年前
住所: 東京都世田谷区

Re: send関数の送信サイズ

#5

投稿記事 by YuO » 13年前

根本的な話ですが,受信側のプログラムで問題が発生している (データが消失する等) のですか。
それとも,Socketよりも下の層を調べていたら「問題が発生していた」のですか。
# 個人的には後者であれば,Socket層でのプログラムに影響があった場合を除いて無視しますが。

普通に考えたら,MSS値より小さなデータのsendはNagleアルゴリズムの餌食だと思いますが。
受信時に送信時のデータの区切りをそのまま使いたいのであればTCP/IPは向きません。
どうもそういうことをやろうとしているように見えます。
# Nagleアルゴリズムの遅延はRFC上は500ms,実装上は200msまで許容されるはず。

Gammodler

Re: send関数の送信サイズ

#6

投稿記事 by Gammodler » 13年前

softoya様
Resありがとうございます。

送信プログラム:
while(condition){
データ用意。数10μs
int ok = send(socket, sendStr, 1440, 0);
}

WireSharkのファイルは添付できないようなので正常/異常フレームを抜き出して掲げます:

正常ケース:
No. Time Source Destination Protocol Length Info
4342 1558.369969 192.168.1.3 192.168.1.4 TCP 1506 8824 > 4045 [PSH, ACK] Seq=349921 Ack=1 Win=66560 Len=1440 TSval=1117738 TSecr=1741420

Frame 4342: 1506 bytes on wire (12048 bits), 1506 bytes captured (12048 bits)
Ethernet II, Src: 00:0d:5e:85:5f:fa (00:0d:5e:85:5f:fa), Dst: 00:11:0c:09:80:4e (00:11:0c:09:80:4e)
Internet Protocol Version 4, Src: 192.168.1.3 (192.168.1.3), Dst: 192.168.1.4 (192.168.1.4)
Transmission Control Protocol, Src Port: 8824 (8824), Dst Port: 4045 (4045), Seq: 349921, Ack: 1, Len: 1440
Data (1440 bytes)

異常ケース:
No. Time Source Destination Protocol Length Info
4343 1558.370453 192.168.1.3 192.168.1.4 TCP 2946 8824 > 4045 [PSH, ACK] Seq=351361 Ack=1 Win=66560 [TCP CHECKSUM INCORRECT] Len=2880 TSval=1117738 TSecr=1741420

Frame 4343: 2946 bytes on wire (23568 bits), 2946 bytes captured (23568 bits)
Ethernet II, Src: 00:0d:5e:85:5f:fa (00:0d:5e:85:5f:fa), Dst: 00:11:0c:09:80:4e (00:11:0c:09:80:4e)
Internet Protocol Version 4, Src: 192.168.1.3 (192.168.1.3), Dst: 192.168.1.4 (192.168.1.4)
Transmission Control Protocol, Src Port: 8824 (8824), Dst Port: 4045 (4045), Seq: 351361, Ack: 1, Len: 2880
Data (2880 bytes)


両ケースはほぼ交互に出現します。

アバター
softya(ソフト屋)
副管理人
記事: 11677
登録日時: 15年前
住所: 東海地方
連絡を取る:

Re: send関数の送信サイズ

#7

投稿記事 by softya(ソフト屋) » 13年前

調べて見ましたが、Nagle アルゴリズムを止めるか、Nagle アルゴリズムでも大丈夫なようにするか、どちらかでしょうね。
「Linux におけるソケット機能の向上」
http://www.ibm.com/developerworks/jp/li ... /l-hisock/

あと、WireSharkの右クリックでCopyでBytes→Offset Hex Textで内容コピーが出来ると思います。
by softya(ソフト屋) 方針:私は仕組み・考え方を理解して欲しいので直接的なコードを回答することはまれですので、すぐコードがほしい方はその旨をご明記下さい。私以外の方と交代したいと思います(代わりの方がいる保証は出来かねます)。

Gammodler

Re: send関数の送信サイズ

#8

投稿記事 by Gammodler » 13年前

Yuo様
Resありがとうございます。

受信側プログラム:

コード:

while(1){
  strcpy("rcvStr, "");
  rcvSize = 0;
  while(rcvSize < 1440){
    tmp = recv(socket, rcvStr + rcvSize, 1440 - rcvSize, 0);	// Blocking Mode
    if ((tmp < 1440) && tmp)
      printf("Packet Devided/n");
    rcvSize += tmp;
  }
  文字列化
  data = strtoul(regStr, NULL, 16);    // Hexに復元
}
 受信データをすべて同じ長さに揃え、Hexデータに復元しているだけです。

エラーはdataを使用した後の結果の動作で判断しています。
 "could no read////"エラーはTCP/IPのデバイスドライバが発しているようです。出たからと言って必ずしも誤動作するわけではありません。
 Packet分割は10回に1回程度起きます。このときcould not エラーが発生しやすいようです。

 受信側は受け身、今回の問題は送信側にあると思っていたですが、先ほどsoftya様からもNagleアルゴリズムについての指摘がありました。
 検討してみます。

Gammodler

Re: send関数の送信サイズ

#9

投稿記事 by Gammodler » 13年前

softoya様 YuO様

 ご示唆いただいたようにNagle Algorithmをとめることで改善しました。:
int flag = 1;
ret = setsockopt( socket, IPPROTO_TCP, TCP_NODELAY, (char *)&flag, sizeof(flag) );
を送信サーバであるWindows側に設定、これでエラーは減ったのですが根絶には至りません。
len=1440の倍2880のフレームがかなりの頻度で出現します。

 回避策として
1.send()発行の合間にwaitをいれる。・・・・およそ数100μsのwaitでフレームの”連結”は防止できました。
2.送信バッファを1MTUのサイズにしてしまう。
sock_buf_size = 1472; // 送信側MTU
ret = setsockopt( socket, SOL_SOCKET, SO_SNDBUF, (char*)&sock_buf_size, sizeof(sock_buf_size) );
で”連結”は防止できました。
sock_buf_size = 1472 * 1.9程度は差し支えありませんが、これを超えると問題が再燃します。

 時間短縮になると思われる2を採用しようと思っているのですが、この措置は妥当でしょうか?。Nagle Algorithmが元凶、これの排除ですべて解決とはいかないことが腑に落ちません。

 ご示唆いただけることがあればお願いいたします。

YuO
記事: 947
登録日時: 15年前
住所: 東京都世田谷区

Re: send関数の送信サイズ

#10

投稿記事 by YuO » 13年前

Gammodler さんが書きました: 時間短縮になると思われる2を採用しようと思っているのですが、この措置は妥当でしょうか?。Nagle Algorithmが元凶、これの排除ですべて解決とはいかないことが腑に落ちません。
Nagleアルゴリズム云々の前に,TCPを使う以上,送信データサイズと受信データサイズに関しては,合計値が一致する以上の期待は寄せられません。
受信側に空きがあれば,送信バッファのデータの一部分を送信する,というのも許されます。

前回も書いたのですが,TCPはデータの区切りが保持されるプロトコルではありません。
故に,データの区切りはアプリケーション側が用意する必要があります。
スレッド中に,何度もパケット分割や連結といった,本来気にならないようなことが書かれているのでそちらが気にかかりました。
sendした時のデータ境界をrecvでも確実に保持したい,といった期待を誤ってしているのであれば,その期待を捨て去るのが先にすべきことだと思います。


何を問題視しているのか,明確に答えが出ていないので再度聞きます。
  • 受信側のプログラムで問題が発生している (データが消失するといった重大な事項) のですか。
  • それとも,Socketよりも下の層を調べていたら「問題が発生していた」のですか。
  • それとも,その他の問題が発生したのですか。

Gammodler

Re: send関数の送信サイズ

#11

投稿記事 by Gammodler » 13年前

YuO様
再Resありがとうございます。

現在時点では下の層などでデータ化け等の事態は発生していません。WireSharkによる観測でも問題はありません。つまり解決しているとも言えます。しかし妥当性に不安があります。

送受信段階での”データの区切り”は求めてはいます。ただし、送受信量が一致するという原則が”区切り”となるものでTCP/IPに特段の機能を期待しているわけではありません。

お書きになった:
”Nagleアルゴリズム云々の前に,TCPを使う以上,送信データサイズと受信データサイズに関しては,合計値が一致する以上の期待は寄せられません。”

これで十分です。
1回のsend関数による送信量と受信側での受信量が一致していれば、それ以上のものは求めません。テストした結果、一致しないこともあることが分かりました。そこで受信側で不足が発見された場合は、不足分を要求するようにしました。それが前掲のプログラムです。これにより送受信量を一致させられることが分かりました。
受信側は次の処理にかかれます。

処理は簡単で受信側による送信要求はほぼ連続的に数百回行われます。

当初送信側はlen=9210でsend関数を呼んでいたのですが、これはWireSharkでJumbo Flameとなることが分かったのでlen=1440に設定しました。
しかしこれでも二つの送信単位が連結してしまう現象が観測されました。それでNagle Algorithmが疑われる状況となったのです。

送信側のBufferをMTU近傍に設定してしまえば現象的はWireShark上でのエラーは回避できているようなのですが。妥当性に不安があります。

アバター
softya(ソフト屋)
副管理人
記事: 11677
登録日時: 15年前
住所: 東海地方
連絡を取る:

Re: send関数の送信サイズ

#12

投稿記事 by softya(ソフト屋) » 13年前

Gammodler さんが書きました: 現在時点では下の層などでデータ化け等の事態は発生していません。WireSharkによる観測でも問題はありません。つまり解決しているとも言えます。しかし妥当性に不安があります。
送受信段階での”データの区切り”は求めてはいます。ただし、送受信量が一致するという原則が”区切り”となるものでTCP/IPに特段の機能を期待しているわけではありません。
YuOさんが書いているのは、ストリームの区切りを示すデータを入れて受信側で自分でデータを分割しろって話だと思います。
つまり、MTUやらいろんな値が想定外でもNagle Algorithmが働いてもちゃんと動く受信側のコードをまず作る必要があるよって話じゃないでしょうか。
by softya(ソフト屋) 方針:私は仕組み・考え方を理解して欲しいので直接的なコードを回答することはまれですので、すぐコードがほしい方はその旨をご明記下さい。私以外の方と交代したいと思います(代わりの方がいる保証は出来かねます)。

YuO
記事: 947
登録日時: 15年前
住所: 東京都世田谷区

Re: send関数の送信サイズ

#13

投稿記事 by YuO » 13年前

Gammodler さんが書きました:送受信段階での”データの区切り”は求めてはいます。ただし、送受信量が一致するという原則が”区切り”となるものでTCP/IPに特段の機能を期待しているわけではありません。
えーっと,TCP/IPというもの自体を勉強したのでしょうか……。
TCP/IPをよく知らない人がよくある間違いに陥っているように思います。
# プロトコル自体を学ぶ必要はないですが,特性についての勉強はしないといけません。
Gammodler さんが書きました:”Nagleアルゴリズム云々の前に,TCPを使う以上,送信データサイズと受信データサイズに関しては,合計値が一致する以上の期待は寄せられません。”
これで十分です。
1回のsend関数による送信量と受信側での受信量が一致していれば、それ以上のものは求めません。テストした結果、一致しないこともあることが分かりました。そこで受信側で不足が発見された場合は、不足分を要求するようにしました。それが前掲のプログラムです。これにより送受信量を一致させられることが分かりました。
合計値というのはSocketにおける通信全体,つまりはconnect/acceptからclosesocketするまでの合計です。
# まぁ,双方ともsendしていない状況がしばらく続いていて,かつ双方がACKを全て返していて,かつ相手方が全てのACKを受け取った状態でも成立しますが。
sendの1回の呼び出しが1つのデータパケットになることを想定したプロトコル自体が「TCP/IPの上で動くプロトコルとして異常」で「間違い」なのです。

データの区切りは,例えば最初に何octetのデータなのかを明記するなどのアプリケーションプロトコル側で用意する必要があります。
プロトコルの定義自体にサイズを持たせても良いでしょう (固定サイズのデータの送信など)。
受信側は,データの区切りをストリームから認識して,自分で分離する必要があります。
# もちろん,区切りはサイズでなく,データに含まれないoctet列でも可能。例えば\0とか。
Gammodler さんが書きました:しかしこれでも二つの送信単位が連結してしまう現象が観測されました。それでNagle Algorithmが疑われる状況となったのです。
根本が違います。TCP/IPは「送信単位」という考え自体が存在しない,という前提を肝に銘じるべきです。

Gammodler

Re: send関数の送信サイズ

#14

投稿記事 by Gammodler » 13年前

YuO様
-------------------------------------------------------------------------------------------------------------------------------
合計値というのはSocketにおける通信全体,つまりはconnect/acceptからclosesocketするまでの合計です。
# まぁ,双方ともsendしていない状況がしばらく続いていて,かつ双方がACKを全て返していて,かつ相手方が全てのACKを受け取った状態でも成立しますが。
sendの1回の呼び出しが1つのデータパケットになることを想定したプロトコル自体が「TCP/IPの上で動くプロトコルとして異常」で「間違い」なのです。
-------------------------------------------------------------------------------------------------------------------------------
これの理解ができていませんでした。
区切りとして頼っていたのは数(=9)です。1回のsend関数発行で約束事になっているrcvSize = 9 * n(整数)が到着しないと数が合うまで再送を要求、一致したところで次の処理にかかるようにしていました。これでも動作はしていたのですが、安全ではないことが分かりました。
終端コード'\0'などで区切る方式に変更いたします。

YuO様 softoya様
TCP/IP Protocolの基本的理解を欠いていたのが、お手数をかけてしまった理由です。もう一度勉強いたします。
お導き下さり感謝いたしております。

閉鎖

“C言語何でも質問掲示板” へ戻る