なぜTCP接続が信頼できないですか?

10013 ワード

リンク:http://watter1985.iteye.com/blog/1924977
原文ここで
この文章はTCPネットワークプログラミングに関する小さな問題です.ほとんどの人はこの問題がどういうことかよく分かりません.理解したと思いましたが、先週になって、理解していないことに気づきました.
だから私はネットで専門家を検索して相談することにしました.彼らが知恵の跡を残して、それによって苦労を重ねます.このテーマに終止符を打ってほしいです.
専門家(H.Willstrand、Evegeniy Polyakov、ビルFink、Ilpo Jarvinen、and Herbert Xu)は返事をしました.ここで彼らをまとめました.
多くのLinuxのTCP実現を参考にしましたが、この問題はLinux特有のものではなく、どのようなオペレーティングシステムでも発生できます.
問題は何ですか
未知のサイズのデータをある場所から別の場所に転送する必要があります.TCP(信頼できる転送制御プロトコル)はまさに私たちが必要としているようです.以下は Linux の手帳tcp(7)が取得したオンラインヘルプ:
「TCPはip(7)層(ipv 4)にあります. 和 ipv 6)上には、2つのソケットを接続する信頼性のある、ストリームに向かっての全二重接続が確立されています.TCP保証データは、順序に従って、紛失したパケットを再送します.各パケットのチェックサムを生成し、確認して、伝送エラーをキャプチャします.
しかし、私たちは純粋にTCPを使用して送信する必要があるデータを送信する場合、常に私たちの考えに沿ってすることができず、最後の数千バイトまたは数兆バイトは永遠に到着しないことがあります.
例えば、2つのPOSIX互換オペレーティングシステムでは、以下の2つのプログラムを実行し、プログラムAはプログラムBに100万バイトのデータを送信する(プログラムここにいますで見つけることができる).
A:
1 sock = socket(AF_INET, SOCK_STREAM, 0);  
2 connect(sock, &remote, sizeof(remote));
3 write(sock, buffer, 1000000);// returns 1000000
4 close(sock);
B:
 1 int sock = socket(AF_INET, SOCK_STREAM, 0);
 2 bind(sock, &local, sizeof(local));
 3 listen(sock, 128);
 4 int client=accept(sock, &local, locallen);
 5 write(client, "220 Welcome\r
", 13); 6 int bytesRead=0, res; 7 8 for(;;) { 9 res = read(client, buffer, 4096); 10 if(res < 0) { 11 perror("read"); 12 exit(1); 13 } 14 if(!res) break; 15 bytesRead += res; 16 } 17 printf("%d
", bytesRead);
問題テスト - プログラムBが完了したら何をプリントしますか?
A) 1000000
B)   1000000     
C)       
D)       
悲しいことに、正しい答えは「D」です.しかし、どうしてこのようなことがありますか?プログラムAが報告したすべてのデータは正しく送られました.
何が起きましたか?
TCPソケットでデータを送信すると、通常のファイルへの書き込みと同様の意味を提供しません.
実際には、TCPの世界では、write()は、カーネルがあなたのデータを受け取って、カーネルが喜ぶ時にそれを転送しようとするという意味に成功しています.カーネルは、すでにパケットが送信されたと考えています.データはネットワークアダプタに渡されただけです.ネットワークアダプターは、嬉しい時に、本当にデータを送信するかもしれません.
この点から言えば、データはネットワーク上の多くのアダプターとキューを巡回して、データがリモートホストに到達するまで、受信側のカーネルは応答を送信します.もしプロセスがあれば、socketからデータを読み取ることを試みているなら、データはアプリケーションに到達します.
注意報の発信はカーネルがすでにデータを受け取ったことを意味します.データを受け取ったという意味ではありません.
はい、これらの内容は分かりましたが、なぜ上記の例では全てのデータを受け取っていないですか?
TCP/IPソケットのclose()方法を開始すると、具体的な状況によっては、カーネルはこうするかもしれない.socketをオフにし、それに関連するTCP/IP接続をオフにする.
実際には、いくつかのデータが送信待ちになっていますが、まだ確認されていません.カーネルは接続全体を閉じています.この問題はすでにメーリングリスト、ニュースグループとフォーラムで多くのスレッドを生んでいます.これらの書き込みはすぐにSOuLIGERのソケットオプションによって解決されました.次の問題だけあるようです.
有効にすると、close(2)またはshutdown(2)は、すべてのキューに並んでいるメッセージが滞留時間を超えて送信されたときに戻ります.そうしないと、呼び出しはすぐに戻ります.クローズはバックグラウンドで行われます.ソケットがexit(2)でオフになると、常にバックグラウンドに滞在します.
したがって、私たちはこのオプションを設定して、プログラムを再起動します.まだ動作していません.すべてのデータが受信されているわけではありません.
どうしてですか
この場合、RFC 1122の第4.2.2.13は、close()呼び出しの際に、掛けられた読み取り可能なデータがあれば、すぐにリセットを送信する可能性があるということを示しています.
「ホストは、"半二重"TCPクローズシーケンスを実現することができます.closeのアプリケーションプログラムを呼び出しても、接続からデータを読み取ることができません.このようなホストがTCPに掛けられたデータを読み出すときに呼び出します.」 close、またはcloseを呼び出してからまた新しいデータが到着した場合、TCPはRSTを送信してデータがなくなったことを示すべきです.
私達の例では、このようなデータは保留されています.プログラムBで「220 Welcome\r」を送信しますが、プログラムAで読み込まれたことはありません.もしまだ送っていないプログラムBなら、一番可能なのは、私達のすべてのデータが正しく到着したということです.
まずデータを読んで、それからLINGERをセットすればいいですか?
まだだめです.closeを呼んでください.私達の考え通りにはしません.すべてのデータが送信された時、接続を閉じます.
幸いにもシステム調shutdown()があります.このシステムの呼び出しはまさにこのことです.しかし、このシステムでの呼び出しだけでは足りません.shutdown()メソッドが戻った時、データが全部Bに受信されたかどうかはまだ分かりません.
私たちが必要なのはshutdownを呼び出す方法です.これはFINパッケージをプログラムBに送ることになります.プログラムBはそのsocketをオフにします.そしてプログラムAでエンドのcloseを検出できます.その後のreadは0に戻ります.
プログラムAは現在変わりました.
 1 sock = socket(AF_INET, SOCK_STREAM, 0);  
 2 connect(sock, &remote, sizeof(remote));
 3 write(sock, buffer, 1000000);// returns 1000000
 4 shutdown(sock, SHUT_WR);
 5 for(;;) {
 6    res=read(sock, buffer, 4000);
 7    if(res < 0) {
 8        perror("reading");
 9        exit(1);
10    }
11    if(!res) break;
12 }
13 close(sock);
完璧な解決策は何ですか?
HTTPプロトコルを見ると、データは一般的にその長さ情報と一緒に送信され、HTTP応答の開始に関わらず、または情報の送信中(いわゆる「ブロック分割」モード)である.
このようにするには理由があります.このようにしてこそ、受信側はすべてのデータが受信されたことを確認することができます.
上記のshutdown()技術を使って、遠端が接続をオフにしたことだけを教えてくれます.実際には、すべてのデータが正確に受信されることは保証されていません.
最善の提案は、長さ情報を送信して、遠端プログラムに主導的にすべてのデータが受信されたことを確認させることです.
もちろん、これは自分の協議を選ぶことができる時だけ役立ちます.
他に何をするべきですか?
もしあなたが"愚かなTCP/IPウォールホール"を通じてストリーミングデータを転送する必要があるなら、私は何回もしましたが、聖人の提案に従って長さ情報を持って確認することができないかもしれません.
この場合、受信側は、すべてのデータが受け入れられていることを示すために、socketをオフにするのは、良い方法ではないかもしれない.
幸い、Linuxは未確認のデータを追跡することができます.未確認のデータはioctl()SIOCOUTOを使用することができます. を取得します.この数字が0であることが分かり次第、データが少なくとも遠端のオペレーティングシステムに到達したことを確認できます.
前述の shutdown() 方法は違っていますが、SIOCOUTQはLinux特有のものらしいです.他のオペレーティングシステムの更新を歓迎します.
サンプルコードは、SIOCOUTQをどのように使用するかの例を含む.
でも、どういうことですか?もう「正しい仕事」が何回もあります.
未読のデータがない限り、星と月が協力してくれます.特定のオペレーティングシステムのバージョンを使っても、前の予想外のエラーを無視することができます.通常は仕事ができますが、彼に依存しないでください.
ブロッキングではないソケットの注意事項
通信関係の開発者が多いので、SONGGERと非ブロッキングソケットを混ぜて使いたいです.絶対にやめてください.使ってください. shutdown() 次に読みます eof 彼の代わりに来てください.もちろん適当に使ってもいいです. poll/epoll/select()
Linuxのsendfile()とsplice()システムの呼び出しについての話
注目すべきは、Linuxのシステム呼び出しはsendfile()とsplice()にとても適しています.この二つのシステムの呼び出しが戻ったら、すぐにclose()を呼び出しても問題が発生しません.この二つのシステムはファイルの送信内容を管理します.
実際にはsplice()(sendfile()はsplice()に基づいてゼロコピーを与えるので、パケットがTCPプロトコルスタックに到達した時に安全に戻ってきます.また、戻ってきたらファイルを修正しても呼び出しの挙動は変わりません.
この関数はすべてのデータが確認されるのを待たずに、データが送信されるのを待つだけです.