TCP の三方ハンドシェイクと四方ウェーブの高頻度面接の質問(2023 年最新版)を図解で解説

皆さんこんにちは。最近、TCP の 3 ウェイ ハンドシェイクと 4 ウェイ ハンドシェイクの面接質問のバージョンを再編成しました (2023 年最新版)。

-----

TCP が何度私を苦しめたとしても、私は今でも TCP を初恋のように扱います。

巨大、巨大、巨大、長いアウトライン、始めましょう! はじめましょう!

写真

画像


TCP の基本的な理解

TCPヘッダー形式とは何ですか?

まず TCP ヘッダーの形式を見てみましょう。ラベルの色はこの記事に関連するフィールドを示しており、他のフィールドについては詳しく説明しません。

写真

TCPヘッダー形式

シリアル番号:コンピュータが生成する乱数で、接続確立時に初期値として使用され、SYNパケットを通じて受信ホストに送信され、データ送信ごとに「データバイト数」のサイズが変化します。 「蓄積」されています。ネットワークパケット障害の問題を解決するために使用されます。

確認応答番号: 次に「期待される」受信データのシーケンス番号を指し、送信者はこの確認応答を受信すると、このシーケンス番号以前のデータは正常に受信されたとみなすことができます。パケットロスの問題を解決するために使用されます。

制御ビット:

  • ACK : このビットがセットされる 1 と 、「確認応答」フィールドが有効になり、TCP では、最初の接続確立時のSYN パケットを除いて、このビットをセットする必要があると 規定しています1 。

  • RST : このビットがセットされている 1 場合は、TCP 接続で例外が発生し、接続を強制的に切断する必要があることを意味します。

  • SYN : このビットが に設定されている 1 場合、接続が確立されることが期待されていることを意味し、シリアル番号の初期値がその「シリアル番号」フィールドに設定されます。

  • FIN : このビットが設定されている 1 場合は、今後データが送信されず、接続が切断されることが予想されることを意味します。通信が終了して切断したい場合、通信の両側のホストは FIN ビット 1 を持つ TCP セグメントを相互に交換できます。

#なぜ TCP プロトコルが必要なのでしょうか? TCP はどの層で機能しますか?

IP この層は「信頼性が低く」、ネットワーク パケットの配信、ネットワーク パケットの順序どおりの配信、およびネットワーク パケット内のデータの完全性を保証しません。

写真

OSI参照モデルとTCP/IPの関係

ネットワーク データ パケットの信頼性を保証する必要がある場合は、上位層 (トランスポート層) TCP プロトコルが責任を負う必要があります。

TCP はトランスポート層で動作する信頼性の高いデータ送信サービスであるため、受信側で受信したネットワーク パケットが損傷がなく、ギャップなく、冗長性がなく、順序が正しいことを保証できます。

#TCPとは何ですか?

TCP は、コネクション指向で信頼性の高いバイト ストリーム ベースのトランスポート層通信プロトコルです。

写真

画像

  • 接続指向: 接続は「1 対 1」である必要があります。UDP プロトコルのように、1 つのホストが同時に複数のホストにメッセージを送信することはできません。つまり、1 対多は実現できません。 ;

  • 信頼性: ネットワーク リンクでどのようなリンク変更が発生しても、TCP はメッセージが受信側に到達できることを保証します。

  • バイト ストリーム: ユーザー メッセージが TCP プロトコルを通じて送信される場合、メッセージはオペレーティング システムによって複数の TCP メッセージに「グループ化」される場合があります。受信側プログラムが「メッセージ境界」を知らない場合、有効なメッセージを読み取ることができません。メッセージ。そして、TCP メッセージは「順序付け」されています。「前の」TCP メッセージが受信されていない場合、後続の TCP メッセージを先に受信したとしても、それをアプリケーション層にスローして処理することはできません。同時に、「繰り返し」TCPパケットは自動的に破棄されます。

#TCP 接続とは何ですか?

RFC 793 で「接続」がどのように定義されているかを見てみましょう。

接続: 上で説明した信頼性とフロー制御メカニズムでは、TCP がデータ ストリームごとに特定のステータス情報を初期化して維持する必要があります。ソケット、シーケンス番号、ウィンドウ サイズなどのこの情報の組み合わせは、接続と呼ばれます。

簡単に言うと、信頼性とフロー制御の維持を確保するために使用されるいくつかの状態情報であり、ソケット、シーケンス番号、ウィンドウ サイズなどのこの情報の組み合わせを接続と呼びます。

写真

画像

したがって、TCP 接続を確立するには、クライアントとサーバーが上記の 3 つの情報について合意に達する必要があることがわかります。

  • ソケット: IP アドレスとポート番号で構成されます

  • シリアル番号: 順序がずれている問題などを解決するために使用されます。

  • ウィンドウ サイズ: フロー制御に使用されます

#TCP 接続を一意に決定するにはどうすればよいですか?

TCP クアドルプルは接続を一意に決定できます。クアドルプルには次のものが含まれます。

  • 送信元アドレス

  • 送信元ポート

  • 宛先アドレス

  • 宛先ポート

写真

TCPクワッド

送信元アドレス フィールドと宛先アドレス フィールド (32 ビット) は IP ヘッダー内にあり、IP プロトコルを通じて他のホストにメッセージを送信するために使用されます。

送信元ポートと宛先ポートのフィールド (16 ビット) は TCP ヘッダー内にあり、その機能はメッセージをどのプロセスに送信すべきかを TCP プロトコルに指示することです。

ポートでリッスンしている IP サーバーがあります。TCP 接続の最大数はどれくらいですか?

サーバーは通常、ローカル ポートでリッスンし、クライアントの接続要求を待ちます。

したがって、クライアントのIPとポートは可変となり、理論値の計算式は次のようになります。

写真

画像

IPv4 の場合、クライアント IP の数は最大で の 2 累乗 32 、クライアント ポートの数は最大で の累乗です 2 。 16 つまり、単一サーバー上の TCP 接続の最大数はほぼ の 2 累乗 です48 。

もちろん、サーバー上の同時 TCP 接続の最大数は理論上の上限には遠く及ばず、次の要因の影響を受けます。

  • ファイル記述子の制限

    、各 TCP 接続はファイルです。ファイル記述子がいっぱいの場合、開いているファイルが多すぎます。Linux では、オープン ファイル記述子の数に 3 つの制限が課されます。

    • システム レベル: 現在のシステムで表示できる最大数 cat /proc/sys/fs/file-max 。

    • ユーザー レベル: ユーザーが cat /etc/security/limits.conf 表示して開くことができる最大数を指定します。

    • プロセス レベル: 単一プロセスが表示できる最大数 cat /proc/sys/fs/nr_open 。

  • メモリ制限: 各 TCP 接続は一定量のメモリを占有します。オペレーティング システムのメモリには制限があり、メモリ リソースがいっぱいになると OOM が発生します。

#UDP と TCP の違いは何ですか? さまざまなアプリケーション シナリオにはどのようなものがありますか?

UDP は複雑な制御メカニズムを提供せず、IP を使用して「コネクションレス」通信サービスを提供します。

UDP プロトコルは実際には非常に単純で、ヘッダーは 8 1 バイト (64 ビット) のみで、UDP ヘッダーの形式は次のとおりです。

写真

UDPヘッダー形式

  • 宛先ポートと送信元ポート: 主に、メッセージをどのプロセスに送信する必要があるかを UDP プロトコルに指示します。

  • パケット長: このフィールドには、UDP ヘッダーの長さとデータの長さの合計が格納されます。

  • チェックサム: チェックサムは、ネットワーク送信中に破損した UDP パケットの受信を防ぐために、信頼性の高い UDP ヘッダーとデータを提供するように設計されています。

TCP と UDP の違い:

1.接続する

  • TCP はコネクション指向のトランスポート層プロトコルであり、データを送信する前に接続を確立する必要があります。

  • UDP は接続を必要とせず、すぐにデータを送信します。

2. サービスオブジェクト

  • TCP は 1 対 1 の 2 ポイント サービスです。つまり、接続には 2 つのエンドポイントしかありません。

  • UDP は、1 対 1、1 対多、および多対多の対話型通信をサポートします。

3. 信頼性

  • TCP はデータを確実に配信し、データはエラー、損失、重複なしに順番に到着します。

  • UDP はベスト エフォート型の配信であり、信頼性の高いデータ配信を保証するものではありません。ただし、QUIC プロトコルなど、UDP 伝送プロトコルに基づいた信頼性の高い伝送プロトコルを実装することはできます。

4. 輻輳制御、フロー制御

  • TCP には、データ送信のセキュリティを確保するための輻輳制御およびフロー制御メカニズムがあります。

  • UDP はそうではなく、ネットワークが非常に混雑している場合でも、UDP の送信速度には影響しません。

5. 初期オーバーヘッド

  • TCPヘッダーの長さは長く、一定のオーバーヘッドが発生しますが、「オプション」フィールドを使用しない場合はヘッダーは1バイトですが、「 20 オプション」フィールドを使用するとさらに長くなります。

  • UDP ヘッダーはわずか 8 バイトで固定されているため、オーバーヘッドは小さくなります。

6. 送信方法

  • TCP は境界のないストリーミングですが、連続性と信頼性が保証されています。

  • UDP はパケットごとに送信され、境界がありますが、パケットの損失や乱れが発生する可能性があります。

7. さまざまなシャード

  • TCP データのサイズが MSS サイズより大きい場合、トランスポート層でフラグメント化されます。ターゲット ホストは、データを受信した後、トランスポート層でも TCP データ パケットを組み立てます。途中でフラグメントが失われた場合は、それのみが残ります。失われたフラグメントを送信する必要があります。

  • UDP のデータサイズが MTU サイズより大きい場合、IP 層で断片化され、ターゲットホストは受信後、IP 層でデータを組み立ててトランスポート層に送信します。

TCP および UDP アプリケーションのシナリオ:

TCP は接続指向であり、信頼性の高いデータ配信を保証できるため、次の用途によく使用されます。

  • FTP ファイル転送。

  • HTTP / HTTPS;

UDP はコネクションレス指向であるため、いつでもデータを送信でき、UDP 自体の処理がシンプルで効率的であるため、次の用途によく使用されます。

  • DNS など、合計パッケージ サイズが小さい通信 SNMP 。

  • ビデオ、オーディオ、その他のマルチメディア通信。

  • ブロードキャスト通信。

UDP ヘッダーには「ヘッダー長」フィールドがないのに、TCP ヘッダーには「ヘッダー長」フィールドがあるのはなぜですか?

その理由は、TCP には可変長の「オプション」フィールドがあるのに対し、UDP ヘッダーの長さは変わらないためであり UDP ヘッダーの長さを記録するための追加フィールドは必要ありません。

UDP ヘッダーには「パケット長」フィールドがあるのに、TCP ヘッダーには「パケット長」フィールドがないのはなぜですか?

まず、TCP がペイロード データ長を計算する方法について説明します。

写真

画像

このうち、IPヘッダフォーマットでは、IPの全長とIPヘッダの長さが分かります。TCP ヘッダーの長さは TCP ヘッダー フォーマットでわかっているため、TCP データの長さを取得できます。

このとき、皆さんは「UDPもIP層をベースにしているのに、UDPのデータ長もこの計算式で計算できるのでは?なぜ『パケット長』があるの?」と驚きました。

この質問をすると、UDPの「パケット長」が冗長であるとつくづく感じます。

多くの情報を確認しましたが、さらに信頼できる記述が 2 つあると思います。

  • 最初のステートメント: ネットワーク デバイスのハードウェアの設計と処理の都合上、ヘッダーの長さは 4 バイトの整数倍である必要があるためです。UDP の「パケット長」フィールドが削除された場合、UDP ヘッダーの長さはバイトの整数 4 倍ではないため、これは、UDP ヘッダーの長さがバイトの整数倍であるという事実を補足するためのものである可能性があると思います 4 。パケット長」フィールドが追加されました。

  • 2 番目のステートメント: 今日の UDP プロトコルは IP プロトコルに基づいて開発されていますが、当時はそうではなかった可能性があります。独自のメッセージ長やヘッダー長を提供しない他のネットワーク層プロトコルに依存している可能性があるため、UDP メッセージ ヘッダーは計算には length. フィールドが必要です。

#TCP と UDP は同じポートを使用できますか?

答え:はい

データリンク層では、LAN 上のホストが MAC アドレスを通じて検出されます。インターネット層では、ネットワーク内で相互接続されたホストまたはルーターを見つけるために IP アドレスが使用されます。トランスポート層では、同じコンピュータ上で同時に通信している異なるアプリケーションを識別するために、ポートによるアドレス指定が必要です。

したがって、トランスポート層の「ポート番号」の機能は、同じホスト上の異なるアプリケーションのデータ パケットを区別することです。

トランスポート層には、TCP と UDP という 2 つのトランスポート プロトコルがあり、カーネル内の 2 つの完全に独立したソフトウェア モジュールです。

ホストがデータ パケットを受信すると、IP パケット ヘッダーの「プロトコル番号」フィールドでデータ パケットが TCP/UDP であることがわかり、どのモジュール (TCP/UDP) を TCP/UDP に送信するかを決定できます。この情報に基づいてモジュールが決定され、パケットは「ポート番号」に基づいてどのアプリケーションに送信されて処理されます。

写真

画像

したがって、TCP/UDPのそれぞれのポート番号も独立しており、例えばTCPのポート番号は80、UDPのポート番号も80というように、両者が競合することはありません。

ポートに関して議論できる知識ポイントはまだたくさんありますが、たとえば、次の問題も関係する可能性があります。

  • 複数の TCP サービス プロセスを同時に同じポートにバインドできますか?

  • TCP サービス プロセスを再起動すると、「アドレスは使用中です」エラー メッセージが表示されるのはなぜですか? そしてそれを避けるにはどうすればよいでしょうか?

  • クライアントのポートは再利用できますか?

  • クライアント TCP 接続の TIME_WAIT 状態が多すぎると、ポート リソースが枯渇し、新しい接続を確立できなくなりますか?

#TCP接続の確立

#TCP スリーウェイ ハンドシェイク プロセスとは何ですか?

TCP は接続指向のプロトコルであるため、TCP を使用する前に接続を確立する必要があり、接続は 3 ウェイ ハンドシェイクを通じて確立されます3 ウェイ ハンドシェイクのプロセスは次のとおりです。

写真

TCP スリーウェイ ハンドシェイク

  • 最初は、クライアントとサーバーの両方が の CLOSE 状態です。まず、サーバーは特定のポートをアクティブにリッスンし、次の LISTEN 状態になります。

写真

最初のメッセージ - SYN メッセージ

  • クライアントはシーケンス番号 ( client_isn) をランダムに初期化し、このシーケンス番号を TCP ヘッダーの「シーケンス番号」フィールドに配置し、  メッセージを示す SYN フラグの位置 を設定します。次に、最初の SYN メッセージがサーバーに送信され、サーバーへの接続が開始されたことを示します。メッセージにはアプリケーション層データは含まれず、クライアントはその後の状態になります  。1SYNSYN-SENT

写真

2 番目のメッセージ - SYN + ACK メッセージ

  • サーバーがクライアントからメッセージを受信し た後SYN 、サーバーはまずシリアル番号をランダムに初期化し ( server_isn)、このシリアル番号を TCP ヘッダーの「シリアル番号」フィールドに入力し、次に TCP ヘッダーの「確認応答番号」フィールドに入力します。 TCP ヘッダーを指定 client_isn + 1し、  その位置に SYN と マークを付けます最後に、メッセージがクライアントに送信されます。メッセージにはアプリケーション層のデータは含まれません。その後、サーバーは状態になります  。ACK1SYN-RCVD

写真

3 番目のメッセージ - ACK メッセージ

  • クライアントはサーバーからメッセージを受信した後、最後の応答メッセージでサーバーに応答する必要があります。まず、応答メッセージ ACK の TCP ヘッダー フラグの位置は であり 1 、次に「確認応答番号」フィールドに入力します server_isn + 1 。そして最後にメッセージをサーバー側に送信します。今回はメッセージがクライアントからサーバーにデータを運ぶことができ、クライアントはその状態になります ESTABLISHED 。

  • クライアントからの応答メッセージを受信すると、サーバーも ESTABLISHED この状態になります。

上記のプロセスから、 3 回目のハンドシェイクではデータを運ぶことができますが、最初の 2 回のハンドシェイクではデータを運ぶことができないことがわかります。これはインタビューでもよく聞かれる質問です。

3 ウェイ ハンドシェイクが完了し、双方が ESTABLISHED その状態になると、接続が確立され、クライアントとサーバーは相互にデータを送信できるようになります。

#Linux システムで TCP ステータスを確認するにはどうすればよいですか?

TCPの接続状態を確認するには、Linuxのコマンドで確認できます netstat -napt 。

写真

TCP接続ステータスの表示

#なぜ三者握手なのか?2、4回じゃないですか?

あなたがよく答える答えは、「スリーウェイ ハンドシェイクにより、両方の当事者が受信と送信の能力を確保できるからです」であると思います。

この答えは問題ありませんが、この答えは一方的なものであり、主な理由を述べていません。

以前に TCP 接続が何であるかを知っていました。

  • 信頼性とフロー制御の維持を確保するために特定の状態情報が使用され、 ソケット、シーケンス番号、ウィンドウ サイズなどのこの情報の組み合わせを接続と呼びます。

したがって、重要なのは、なぜ 3 ウェイ ハンドシェイクによってソケット、シリアル番号、ウィンドウ サイズが初期化され、TCP 接続が確立できるのかということです。

次に、3 ウェイ ハンドシェイクの理由を 3 つの側面から分析します。

  • 過去の接続の繰り返しの初期化を防ぐことができるのは 3 ウェイ ハンドシェイクのみです (主な理由)

  • スリーウェイ ハンドシェイクにより、双方の初期シリアル番号を同期できます

  • 3 ウェイ ハンドシェイクによりリソースの無駄を回避できます

理由 1: 歴史的なつながりを避ける

RFC 793 で指摘されているように、TCP 接続で 3 ウェイ ハンドシェイクが使用される主な理由を見てみましょう。

スリーウェイ ハンドシェイクの主な理由は、古い重複した接続開始によって混乱が生じるのを防ぐことです。

簡単に言うと、スリーウェイ ハンドシェイクの主な理由は、古い重複した接続の初期化によって引き起こされる混乱を防ぐことです。

クライアントが最初に SYN (seq = 90) メッセージを送信し、その後クライアントがクラッシュするというシナリオを考えてみましょう。さらに、SYN メッセージはネットワークによってブロックされ、サーバーはそれを受信しません。その後、クライアントが再起動した後、接続はサーバーに対して再確立され、SYN (seq = 100) メッセージが送信されます (注意! これは SYN の再送信ではありません。再送信された SYN のシーケンス番号は同じです)。

3 ウェイ ハンドシェイクが過去の接続をどのようにブロックするかを確認してください。

写真

歴史的なつながりを避けるための 3 ウェイ ハンドシェイク

クライアントは接続を確立するために複数の SYN メッセージ (すべて同じ 4 タプル) を継続的に送信します。ネットワークが混雑している場合:

  • SYN + ACK 「古い SYN メッセージ」は「最新の SYN」メッセージよりも早くサーバーに到着し、サーバーはクライアントにメッセージを返します 。このメッセージの確認番号は 91 (90+1) です。

  • それを受信した後、クライアントは、受信すると予想される確認番号が 90 + 1 ではなく 100 + 1 である必要があることがわかり、RST メッセージで応答します。

  • サーバーは RST メッセージを受信すると、接続を解放します。

  • 最新の SYN がサーバーに到着すると、通常、クライアントとサーバーは 3 ウェイ ハンドシェイクを完了できます。

前述の「古い SYN メッセージ」は履歴接続と呼ばれます。TCP が接続の確立に 3 ウェイ ハンドシェイクを使用する主な理由は、「履歴接続」による接続の初期化を防ぐためです

ヒント

多くの人は、サーバーが RST メッセージを受信する前に「新しい SYN メッセージ」を受信した場合、つまり、サーバーがクライアント メッセージを受信する順序が「古い SYN メッセージ」 -> 「新しい SYN メッセージ」の場合、何が起こるのかと尋ねます。この時に起こりますか?

サーバーが初めて SYN メッセージを受信したとき、つまり「古い SYN メッセージ」を受信したとき、サーバーは SYN + ACK クライアントにメッセージを返信します。このメッセージの確認番号は 91 (90+1) です。

その後、再度「新しい SYN メッセージ」を受信すると、チャレンジ Ack メッセージがクライアントに返されます。この ACK メッセージは、「新しい SYN メッセージ」の受信を確認するものではなく、最後の ACK 確認番号である 91 ( 90+1)。したがって、クライアントがこの ACK メッセージを受信すると、受信する予定の確認番号は 91 ではなく 101 であることがわかり、RST メッセージで応答します。

2 ハンドシェイク接続の場合、履歴接続はブロックできません では、なぜ TCP 2 ハンドシェイクで履歴接続を防ぐことができないのでしょうか?

まず結論だけを言っておきます。主な理由は、2 つのハンドシェイクの場合、サーバーには履歴接続を防ぐためのクライアントの中間状態が存在しないため、サーバーが履歴接続を確立する可能性があり、リソースの無駄が発生するためです

考えてみてください。2 回のハンドシェイクの場合、サーバーは SYN メッセージを受信した後に ESTABLISHED 状態に入ります。これは、この時点で相手にデータを送信できることを意味しますが、クライアントはまだ ESTABLISHED 状態になっていないとします。この 2 回目は履歴接続です。クライアントがこの接続が履歴接続であると判断した場合、接続を切断するための RST メッセージが返されます。サーバーは最初のハンドシェイク中に ESTABLISHED 状態になり、データを送信できるようになりますただし、これが過去の接続であることは認識されていないため、RST メッセージを受信した後にのみ切断されます。

写真

2 回のハンドシェイクでは歴史的な接続を防ぐことはできません

TCP 接続を確立するために 2 回のハンドシェイクが使用される場合、サーバーはクライアントにデータを送信する前に履歴接続をブロックしないため、サーバーは履歴接続を確立して無駄にデータを送信することになり、サーバー リソースが浪費されることがわかります。

したがって、この現象を解決するには、サーバーがデータを送信する前、つまり接続が確立される前に、リソースを無駄にしないように履歴接続をブロックすることが最善であり、この機能を実現するには 3 回のハンドシェイクが必要です

したがって、TCP が接続を確立するために 3 ウェイ ハンドシェイクを使用する主な理由は、「履歴接続」によって接続が初期化されるのを防ぐためです。

ヒント

「クライアントは 3 ウェイ ハンドシェイク (ACK メッセージ) を送信した後にデータを送信できますが、この時点ではパッシブ側はまだ syn_received 状態にあります。ACK が失われた場合、クライアントによって送信されたデータは無駄になりますか?」という質問がありました。

いいえ、サーバーがまだ syn_received 状態にあり、クライアントから送信されたデータを受信して​​いる場合でも、接続を確立してデータ パケットを正常に受信できます。これは、データ メッセージに ACK 識別ビットと、2 回目のハンドシェイクの受信を確認する確認番号が含まれているためです。以下に示すように:

写真

画像

したがって、サーバーはこのデータ パケットを受信すると、正常に接続を確立でき、その後、このデータ パケットを正常に受信できます。

理由 2: 双方の初期シーケンス番号を同期する

TCP プロトコルの双方は、「シリアル番号」を維持する必要があります。シリアル番号は、信頼性の高い送信を実現するための重要な要素です。その機能は次のとおりです。

  • 受信者は重複データを削除できます。

  • 受信機は、シーケンス番号に従ってデータ パケットを順番に受信できます。

  • 送信されたデータ パケットのどれが相手によって受信されたかを識別できます (ACK メッセージ内のシーケンス番号によってわかります)。

 TCP 接続ではシーケンス番号が非常に重要な役割を果たしていることがわかります。そのため、クライアントが「最初のシーケンス番号」を含むメッセージを送信すると、サーバーはクライアントの SYN メッセージが次のとおりであることを示す応答メッセージをSYN 送り返す 必要があります。 ACKクライアントがそれを正常に受信した場合、サーバーが「初期シーケンス番号」をクライアントに送信するときに、クライアントからの応答を取得する必要があります。このようにして、両方の当事者の初期シーケンス番号を確実に取得できます。確実に同期することができます。

写真

4回の握手と3回の握手

実際には、4 ウェイ ハンドシェイクは双方の初期化シリアル番号を確実に同期できますが、2 番目と 3 番目のステップを 1 つのステップに最適化できるため、「3 ウェイ ハンドシェイク」になります。

2 回のハンドシェイクでは、一方の当事者の初期シリアル番号が他方の当事者によって正常に受信されることのみが保証されますが、両方の当事者の初期シリアル番号が確認されて受信されることを保証する方法はありません。

理由 3: リソースの無駄を避ける

「ハンドシェイクが 2 回」しかない場合、クライアントが送信した SYN メッセージが ネットワーク内でブロックされ、クライアントがACK メッセージを受信しなかった場合、メッセージが再送信されます SYN 。3回目のハンドシェイクがないため、サーバーはクライアントがメッセージを受信したかどうかを知りません。はメッセージを受信しました。応答 ACK メッセージなので、サーバーはメッセージを受信するたびに、 SYN アクティブに接続を確立することしかできません。どうなりますか?

クライアントによって送信された SYN メッセージが ネットワーク内でブロックされ、メッセージが複数回送信された場合、サーバーはSYN リクエストの受信後に複数の冗長な無効なリンクを確立し、リソースの不必要な浪費が発生します。

写真

2 回のハンドシェイクはリソースの無駄になります

つまり、2 回のハンドシェイクによってメッセージが保持されると、サーバーは無駄な接続要求 SYN メッセージを繰り返し受け入れ、その結果、リソースが繰り返し割り当てられることになります。

ヒント

多くの人が、2 つのハンドシェイクではコンテキスト情報に基づいて syn 履歴メッセージも破棄できないのかと疑問に思います。

ここでの 2 回のハンドシェイクは、「3 回目のハンドシェイクがないため、サーバーは、クライアントが ACK 自ら送信した接続を確立するための確認メッセージを受信したかどうかを知らないため、メッセージを受信するたびに、 SYN 能動的にメッセージを受信することしかできない」という前提に基づいています。接続を確立します。」

もちろん、3 ウェイ ハンドシェイクのように実装する必要があります。コンテキストに応じて syn 履歴メッセージを破棄することも可能です。2 ウェイ ハンドシェイクの具体的な実装は存在しないため、任意の推測が可能です。

まとめ

TCP が接続を確立するとき、スリーウェイ ハンドシェイクにより、過去の接続の確立が防止され、両側の不要なリソース オーバーヘッドが削減され、双方がシーケンス番号を同期的に初期化できるようになりますシーケンス番号により、データ パケットが繰り返されず、破棄され、順番に送信されることが保証されます。

「双方向ハンドシェイク」と「四方向ハンドシェイク」を使用しない理由:

  • 「2 つのハンドシェイク」: 過去の接続の確立を防ぐことはできず、双方でリソースの無駄が発生し、双方のシリアル番号を確実に同期することができません。

  • 「4 ウェイ ハンドシェイク」: 3 ウェイ ハンドシェイクは、信頼性の高い接続を確立するための理論上の最小値であるため、それ以上の通信時間を費やす必要はありません。

#TCP 接続が確立されるたびに初期化シーケンス番号を変える必要があるのはなぜですか?

主な理由は 2 つあります。

  • 同じ 4 タプル (メイン アスペクト) を使用した次の接続で履歴メッセージが受信されないようにするため。

  • セキュリティ上の理由から、ハッカーによって偽造された同じシーケンス番号を持つ TCP メッセージは相手側で受信されません。

次に、1つ目のポイントについて詳しく説明します。

接続が確立されるたびに、クライアントとサーバーの初期化シーケンス番号が 0 から始まると仮定します。

写真

画像

プロセスは次のとおりです。

  • クライアントとサーバーは TCP 接続を確立します。クライアントが送信したデータ パケットはネットワークによってブロックされ、データ パケットはタイムアウトして再送信されます。このとき、サーバー デバイスの電源がオフになり、再起動され、接続が確立されます。クライアントとの間で以前に確立されていたメッセージが失われるため、クライアントのデータ パケットを受信するときに RST メッセージが送信されます。

  • その直後、クライアントは前の接続と同じ 4 タプルを使用してサーバーとの接続を確立しました。

  • 新しい接続が確立された後、前の接続でネットワークによってブロックされたデータ パケットがサーバーに到着します。データ パケットのシーケンス番号はたまたまサーバーの受信ウィンドウ内にあるため、データ パケットは正常に受信されます。これにより、データの混乱が生じます。

接続が確立されるたびにクライアントとサーバーの初期化シーケンス番号が同じである場合、同じ 4 タプルを使用した次の接続で履歴メッセージが受信されやすくなることがわかります

接続が確立されるたびにクライアントとサーバーの初期化シーケンス番号が「異なる」場合は、履歴メッセージのシーケンス番号が相手側の受信ウィンドウに「入っていない」可能性が高いため、履歴メッセージが送信されるのを回避できます。次の図のように、広範囲に渡ります。

写真

画像

逆に、接続が確立されるたびにクライアントとサーバーの初期化シーケンス番号が「同じ」である場合は、履歴メッセージのシーケンス番号が単にメッセージの受信ウィンドウ内で「発生」する可能性が高くなります。接続が正常に受信されました。

したがって、各初期化シーケンス番号は大幅に異なり、同じ 4 タプルを使用した次の接続で履歴メッセージが受信されるのを防ぐことができます。これは、完全に回避されるわけではないことに注意してください (シーケンス番号が変更されるため)。したがって、履歴メッセージを判断するにはタイムスタンプ メカニズムを使用する必要があります)

#最初のシーケンス番号 ISN はどのようにランダムに生成されますか?

開始は ISN 時計に基づいて行われ、4 マイクロ秒ごとに +1 され、1 回転には 4.55 時間かかります。

RFC793 では、初期化シーケンス番号 ISN のランダム生成アルゴリズムについて言及しています: ISN = M + F (localhost、localport、remotehost、remoteport)。

  • M 4 マイクロ秒ごとに 1 ずつ増加するタイマーです。

  • F これは、送信元 IP、宛先 IP、送信元ポート、宛先ポートに基づいてランダムな値を生成するハッシュ アルゴリズムです。ハッシュ アルゴリズムが外部から簡単に計算されないようにするには、MD5 アルゴリズムを使用することをお勧めします。

乱数は時計タイマーに基づいて増加することがわかり、同じ初期化シーケンス番号をランダムに生成することは基本的に不可能です。

#IP層は断片化されるのに、なぜTCP層にMSSが必要なのでしょうか?

まずはMTUとMSSについて理解しましょう

写真

MTU と MSS

  • MTU: ネットワーク パケットの最大長。通常、 1500 イーサネットではバイト単位。

  • MSS: IP ヘッダーと TCP ヘッダーを削除した後のネットワーク パケットに収容できる TCP データの最大長。

TCP メッセージ全体 (ヘッダー + データ) がフラグメント化のために IP 層に渡された場合、何が起こるでしょうか?

IP 層 MTU に送信するサイズ (TCP ヘッダー + TCP データ) を超えるデータがある場合、IP 層はデータをいくつかの部分に断片化して、各断片が MTU より小さくなるようにします。IP データグラムは断片化された後、ターゲット ホストの IP 層によって再構築され、上位の TCP トランスポート層に渡されます。

これは順序どおりに見えますが、これには隠れた危険があり、IP フラグメントが失われると、IP メッセージ全体のすべてのフラグメントを再送信する必要があります

IP 層自体にはタイムアウト再送信メカニズムがないため、タイムアウトと再送信を担当するのはトランスポート層の TCP です。

IP フラグメントが失われると、受信者の IP 層は完全な TCP メッセージ (ヘッダー + データ) を組み立てることができず、データ メッセージを TCP 層に送信できません。そのため、受信者は送信者に対する ACK に応答しません。 ACK 確認メッセージを長期間受信しなかった場合、タイムアウト再送信がトリガーされ、「TCP メッセージ全体 (ヘッダー + データ)」が再送信されます。

したがって、IP 層による断片化された送信は非常に非効率であることがわかります。

したがって、最高の伝送パフォーマンスを達成するために、TCP プロトコルは通常、接続を確立するときに双方の MSS 値をネゴシエートします。TCP 層は、データが MSS を超えていることを検出すると、まずデータを断片化します。それによって形成される IP パケットのサイズは MTU を超えることはなく、当然のことながら IP フラグメント化の必要はありません。

写真

ハンドシェイクフェーズ中にMSSをネゴシエートする

TCP 層のフラグメンテーション後、TCP フラグメントが失われた場合、すべてのフラグメントを再送信するのではなく、MSS が再送信の単位として使用されるため、再送信の効率が大幅に向上します。

#最初の握手が失われた場合はどうなりますか?

クライアントがサーバーとの TCP 接続を確立したい場合、最初に SYN メッセージが送信され、その後この SYN_SENT 状態に入ります。

その後、クライアントがサーバーからの SYN-ACK メッセージの受信 (2 回目のハンドシェイク) に失敗した場合、「タイムアウト再送信」メカニズムがトリガーされて SYN メッセージが再送信され、再送信された SYN メッセージのシリアル番号はすべて同じです

オペレーティング システムのバージョンによってタイムアウトが異なり、1 秒のものと 3 秒のものがあります。このタイムアウトはカーネルにハードコードされています。変更したい場合はカーネルを再コンパイルする必要があり、面倒です。

クライアントが 1 秒経過してもサーバーから SYN-ACK メッセージを受信しない場合、クライアントは SYN メッセージを再送信します。何回再送信されますか?

Linux では、クライアントの SYN メッセージの最大再送信回数は tcp_syn_retriesカーネル パラメータによって制御されます。このパラメータはカスタマイズ可能で、通常のデフォルト値は 5 です。

# cat /proc/sys/net/ipv4/tcp_syn_retries
5

通常、最初のタイムアウト再送信は 1 秒後、2 回目のタイムアウト再送信は 2 秒後、3 回目のタイムアウト再送信は 4 秒後、4 回目のタイムアウト再送信は 8 秒後、2 回目のタイムアウト再送信は 8 秒後です。 16 秒の再送信タイムアウトが経過した後。そうです、各タイムアウトは前回の 2 倍の長さです

5 回目の時間外再送信後、32 秒間待機し続けますが、それでもサーバーが ACK で応答しない場合、クライアントは SYN パケットを送信しなくなり、TCP 接続を切断します。

したがって、合計の所要時間は 1+2+4+8+16+32=63 秒、約 1 分となります。

たとえば、tcp_syn_retries パラメータ値が 3 であると仮定すると、クライアントの SYN パケットがネットワーク内で常に失われると、次のプロセスが発生します。

写真

画像

具体的なプロセス:

  • クライアントが時間外に SYN メッセージを 3 回再送信すると、tcp_syn_retries が 3 であるため、再送信の最大数に達しているため、サーバーがまだ受信できない場合は、しばらく待ちます (時間は前回のタイムアウト時間の 2 倍)。 2 回目のハンドシェイク (SYN-ACK メッセージ) 後、クライアントは切断されます。

第二波が負けたらどうなるのでしょうか?

サーバーはクライアントの最初のウェーブを受信すると、ACK 確認メッセージを送り返し、この時点でサーバーの接続は状態になります CLOSE_WAIT 。

また、ACK メッセージは再送信されないことについても前述しましたので、サーバーの 2 番目の波が失われた場合、クライアントはタイムアウト再送信メカニズムをトリガーし、サーバーの最初の波を受信するまで FIN メッセージを再送信します。再送信の回数。

たとえば、tcp_orphan_retries パラメーターの値が 2 であると仮定すると、2 番目のウェーブが常に失われる場合、発生するプロセスは次のとおりです。

写真

画像

具体的なプロセス:

  • クライアントが時間外に 2 回 FIN メッセージを再送信すると、tcp_orphan_retries が 2 であるため、再送信の最大数に達しているため、サーバーがまだ失敗する場合は、一定時間待機します (この時間は前回のタイムアウト時間の 2 倍です)。 2 番目の波 (ACK メッセージ) を受信すると、クライアントは切断されます。

ここで、クライアントが 2 番目のウェーブを受信したとき、つまりサーバーから送信された ACK メッセージを受信した後、クライアントは状態になります。この状態では、サーバーが 3 番目のウェーブを送信するまで待つ必要があります FIN_WAIT2 。 、つまりサービス端末の FIN メッセージです。

close 関数によって閉じられたコネクションは、データの送受信ができなくなるため、FIN_WAIT2 その状態が長く続くことはなく、 tcp_fin_timeout この状態の継続時間が制御されます (デフォルト値は 60 秒)。

これは、close 呼び出しによって閉じられた接続の場合、60 秒経過しても FIN メッセージが受信されない場合、以下に示すように、クライアント (アクティブな終了側) の接続が直接閉じられることを意味します。

写真

画像

ただし、アクティブなクロージング パーティがシャットダウン関数を使用して接続を閉じ、送信方向のみがクローズされ、受信方向はクローズされないことを指定する場合、アクティブなクロージング パーティは引き続きデータを受信できることを意味することに注意してください。

このとき、アクティブなクロージング側が第 3 波を受信して​​いない場合、アクティブなクロージング側のコネクションは常に 状態になります FIN_WAIT2 (tcp_fin_timeout シャットダウンによりクローズされたコネクションは制御できません)。以下に示すように:

写真

画像

第3波が失われたらどうなるでしょうか?

サーバー(受動的なクロージング側)がクライアント(能動的なクロージング側)からFINメッセージを受信すると、カーネルは自動的にACKを返信し、同時にコネクションが確立された状態となり、その名の通り、応答を待つことを意味します CLOSE_WAIT 。アプリケーションプロセスが close 関数を呼び出して接続を閉じます。

現時点では、カーネルにはプロセスに代わって接続を閉じる権限がないため、プロセスは積極的に close 関数を呼び出して、サーバーが FIN メッセージを送信するようにトリガーする必要があります。

サーバーが CLOSE_WAIT 状態にあり、close 関数が呼び出されると、カーネルは FIN メッセージを送信し、同時に接続は LAST_ACK 状態に入り、クライアントが接続が閉じられたことを確認する ACK を返すのを待ちます。

ACKが長時間受信されない場合、サーバーはFINメッセージを再送信しますが、再送信回数は tcp_orphan_retriesパラメータによって制御されます。これは、再送信されたFINメッセージの再送信回数の制御方法と同じです。クライアントによって。

たとえば、  tcp_orphan_retries = 3 と仮定すると、第 3 波が常に失われる場合、発生するプロセスは次のとおりです。

写真

画像

具体的なプロセス:

  • サーバーが第 3 ウェーブ メッセージを 3 回再送信すると、tcp_orphan_retries が 3 であるため、再送信の最大数に達するため、サーバーはさらに一定時間待機します (時間は最後のタイムアウトの 2 倍です)。 4 番目の波 (ACK メッセージ) の受信に失敗すると、サーバーは切断されます。

  • クライアントは close 関数によって接続を閉じるため、FIN_WAIT_2 状態には時間制限があり、tcp_fin_timeout 時間内にサーバーからの 3 番目のウェーブ (FIN メッセージ) を受信できない場合、クライアントは切断されます。

#第4波 は負けましたが、どうなるでしょうか?

クライアントはサーバーから 3 回目の振りの FIN メッセージを受信すると、ACK メッセージ、つまり 4 回目の振りを返し、この時点でクライアント接続が状態になります TIME_WAIT 。

Linux システムでは、TIME_WAIT 状態は 2MSL 継続してからシャットダウン状態になります。

その後、サーバー (受動的終了側) が ACK メッセージを受信しない前は、まだ LAST_ACK 状態にあります。

4 番目の波の ACK メッセージがサーバーに届かない場合、サーバーは FIN メッセージを再送信しますが、再送信の回数は tcp_orphan_retries 上記で紹介したパラメーターによって引き続き制御されます。

たとえば、tcp_orphan_retries が 2 であると仮定すると、4 回目のウェーブが常に失われる場合、発生するプロセスは次のとおりです。

写真

画像

具体的なプロセス:

  • サーバーが wave メッセージを 3 回目に再送信して 2 に達すると、tcp_orphan_retries が 2 であるため、再送信の最大回数に達するため、しばらく待機します (時間は最後のタイムアウトの 2 倍です)。クライアントが 4 回目に手を振る (ACK メッセージ) と、サーバーは切断されます。

  • 3 番目の波を受信した後、クライアントは TIME_WAIT 状態に入り、2MSL の継続時間でタイマーを開始します。途中で再び 3 番目の波 (FIN メッセージ) を受信すると、タイマーをリセットし、2MSL 後、クライアントは切断されます。

#TIME_WAIT の待ち時間が 2MSL なのはなぜですか?

MSL これは、最大セグメント存続時間、つまりメッセージの最大生存時間です。これは、メッセージがネットワーク上に存在する最長時間です。この時間を過ぎると、メッセージは破棄されます。TCP メッセージは IP プロトコルに基づいており、  TTL IP ヘッダーには、IP データグラムが通過できるルートの最大数を示すフィールドがあり、この値は、処理を行うルーターを通過するたびに 1 ずつ減ります。この値が 0 の場合、データグラムは破棄され、送信元ホストに通知するために ICMP メッセージが送信されます。

MSL と TTL の違い: MSL の単位は時間ですが、TTL はルーティング ホップの数です。したがって、 パケットが自然に破棄されたことを保証するには、MSL は TTL が 0 を消費する時間以上である必要があります。

通常、TTL の値は 64 で、Linux は MSL を 30 秒に設定します。これは、Linux は、データ パケットが 64 個のルーターを通過する時間が 30 秒を超えないと判断し、30 秒を超えた場合、パケットが 30 秒を超えないとみなします。ネットワーク上から消えてしまいました

TIME_WAIT は MSL の 2 倍待ちます。より合理的な説明は、ネットワーク内に送信者からのデータ パケットがある可能性があります。送信者からのこれらのデータ パケットが受信者によって処理されると、受信者は相手に応答を送信します。前後に待つ必要があり、時間は 2 倍になります

たとえば、パッシブ クロージング パーティが切断に対する最後の ACK メッセージを受信しなかった場合、タイムアウトがトリガーされてメッセージ FIN が再送信され一方のパーティは FIN. MSL を受信した後、パッシブ クロージング パーティに ACK を再送信します。

2MSL の継続時間は、 実際にはパケットが少なくとも 1 回失われることを許容するのと同等であることがわかります たとえば、ACK が 1 つの MSL で失われた場合、パッシブ パーティによって再送信された FIN は 2 番目の MSL に到着し、TIME_WAIT 状態の接続でこれを処理できます。

なぜ 4 または 8 MSL 期間ではないのでしょうか? パケット損失率が 1% の不良ネットワークを想像してみてください。2 回連続してパケット損失が発生する確率は 10,000 分の 1 にすぎません。この確率は小さすぎます。問題を解決するよりも無視した方がコスト効率が高くなります。

2MSL 時間は、クライアントが FIN を受信した後、ACK を送信した時点から開始しますTIME-WAIT 時間以内の場合、クライアントの ACK がサーバーに送信されず、クライアントがサーバーによって再送信された FIN メッセージを受信するため、2MSL 時間がリタイムされ ます

Linux システムでは、 2MSL デフォルトは 60 秒であるため、1 は 秒MSL です 30 。Linux システムは、60 秒の固定時間 TIME_WAIT 状態に留まります

Linux カーネル コードで定義されている名前は TCP_TIMEWAIT_LEN です。

#define TCP_TIMEWAIT_LEN (60*HZ) /* how long to wait to destroy TIME-WAIT 
                                    state, about 60 seconds  */

TIME_WAIT の長さを変更する場合は、Linux カーネル コードの TCP_TIMEWAIT_LEN の値を変更し、Linux カーネルを再コンパイルすることしかできません。

なぜ TIME_WAIT 状態が必要なのでしょうか?

接続の終了を積極的に開始した当事者のみが TIME-WAIT ステータスを持ちます。

TIME-WAIT 状態が必要になるのは主に次の 2 つの理由です。

  • 過去の接続のデータが、同じ 4 タプルの後続の接続で誤って受信されるのを防ぎます。

  • 「受動的に接続を閉じる」側が正しく閉じることができることを確認します。

理由 1: 履歴接続のデータが、同じ 4 タプル グループの後続の接続で誤って受信されるのを防ぐため。

この理由をよりよく理解するために、まずシーケンス番号 (SEQ) と初期シーケンス番号 (ISN) について理解しましょう。

  • シーケンス番号はTCP のヘッダー フィールドであり、TCP 送信者から TCP 受信者へのデータ フローのバイトを識別します。TCP はバイト ストリームの信頼できるプロトコルであるため、メッセージの順序と信頼性を保証するために、TCP それぞれ各送信方向のバイトには番号が割り当てられ、送信成功後の確認や損失後の再送信を容易にし、受信側で障害がないことを保証します。シーケンス番号は 32 ビットの符号なし数値であるため、 4G に達すると 0 にループバックします

  • 初期シーケンス番号。TCP が接続を確立すると、クライアントとサーバーの両方が初期シーケンス番号を生成します。これは、各接続が異なる初期シーケンス番号を持つようにするためにクロックに基づいて生成される乱数です。初期化シーケンス番号は 32 ビットのカウンターとみなすことができ、カウンターの値は 4 マイクロ秒ごとに 1 ずつ増加し、1 サイクルは 4.55 時間かかります

全員分のパッケージを入手しました。下の図の Seq はシリアル番号で、赤いボックスはそれぞれクライアントとサーバーによって生成された初期シリアル番号です。

写真

TCPパケットキャプチャ図

前回の記事でわかるように、シリアル番号と初期化シリアル番号は無限に増加するわけではなく、初期値にラップするため、シリアル番号だけではデータの新旧を判断できません

TIME-WAIT に待ち時間がないか、待ち時間が短すぎると仮定すると、遅延したデータ パケットが到着した後はどうなりますか?

写真

TIME-WAIT 時間が短すぎるため、古い接続からのデータ パケットが受信されています

上に示すように:

  • 接続を閉じる前にサーバーから送信された SEQ = 301 メッセージがネットワークによって遅延されました。

  • 次に、サーバーは同じ 4 タプルを使用して新しい接続を再度開き、以前に遅延した接続が SEQ = 301 クライアントに到着します。データ パケットのシーケンス番号はクライアントの受信ウィンドウ内にあるため、クライアントは通常このデータ パケットを受信します。ただし、このデータ パケットは以前の接続の名残であり、データの混乱などの重大な問題を引き起こします。

履歴接続内のデータが、同じクアッドプルを使用した後続の接続で誤って受信されるのを防ぐために、TCP は長時間継続する TIME_WAIT 状態を設計しました。この時間は、両方向のデータ パケットが破棄されるのに十分な時間 です2MSL 。データ パケットはネットワーク内で自然に消えますが、再び現れるデータ パケットは新しく確立された接続によって生成される必要があります。

理由 2: 「接続を受動的に閉じる」側が正しく閉じられることを確認する

RFC 793 は、TIME-WAIT のもう 1 つの重要な役割を次のように指摘しています。

TIME-WAIT - リモート TCP が接続終了要求の確認応答を確実に受信するために十分な時間が経過するまで待機することを表します。

言い換えれば、TIME-WAIT の役割は、受動的クロージング側が最終 ACK を確実に受信できるように十分な時間を待機し、それによってパッシブなクロージング側が正常に閉じるのを支援することです。

クライアント (アクティブなクロージング パーティ) の最後の ACK メッセージ (4 番目のウェーブ) がネットワーク内で失われた場合、TCP の信頼性原則に従って、サーバー (パッシブなクロージング パーティ) は FIN メッセージを再送信します。

クライアントが TIME_WAIT 状態ではなく、最後の ACK メッセージを送信した後に直接 CLOSE 状態に入ったと仮定します。ACK メッセージが失われた場合、サーバーは FIN メッセージを再送信し、この時点でクライアントは CLOSE 状態に入っています。クローズ状態では、サーバーから再送されたFINメッセージを受信後、RSTメッセージが返されます。

写真

TIME-WAIT 時間が短すぎるため、接続が適切に閉じられることが保証されません

サーバーはこの RST を受信し、それをエラー (ピアによる接続のリセット) として解釈します。これは、信頼性の高いプロトコルの正常な終了ではありません。

これを防ぐために、クライアントはサーバーが ACK を受信できるよう十分な時間待機する必要があります。サーバーが ACK を受信しない場合、TCP 再送信メカニズムがトリガーされ、サーバーは FIN を再送信します。行きと帰りに正確に 2 MSL かかります。

写真

TIME-WAIT 時間は正常であり、接続が正常に閉じられることを保証します。

クライアントがサーバーによって再送信された FIN メッセージを受信すると、TIME_WAIT 状態の待ち時間は 2MSL にリセットされます。

#TIME_WAIT が多すぎるとどのような危険がありますか?

TIME-WAIT 状態が多すぎると、主に 2 つの危険があります。

  • 1 つ目は、ファイル記述子、メモリ リソース、CPU リソース、スレッド リソースなどのシステム リソースを占有することです。

  • 2 つ目はポート リソースを占有することです。ポート リソースにも制限があります。通常、オープンできるポートは であり 32768~61000、 net.ipv4.ip_local_port_rangeその範囲はパラメータで指定することもできます。

クライアントとサーバーで TIME_WAIT が多すぎると、さまざまな影響が生じます。

クライアント (接続の終了を積極的に開始する側) の TIME_WAIT ステータスが多すぎて、すべてのポート リソースを占有している場合、同じ「宛先 IP + 宛先 PORT」を持つサーバーへの接続を開始できませんが、使用されているポートはまだ使用できます。引き続き別のサーバーへの接続を開始してください。詳細については、私の記事「クライアント ポートは再利用できますか?」を参照してください。(新しいウィンドウが開きます)

そのため、クライアント(接続を開始する側)が同じ「宛先IP + 宛先ポート」でサーバーに接続を確立する場合、クライアントのTIME_WAIT状態での接続が多すぎると、ポートのリソースによって制限されてしまいます。すべてのポートがリソースを占有している場合、同じ「宛先 IP + 宛先ポート」を持つサーバーとの接続を確立することはできなくなります。

ただし、このシナリオでも、異なるサーバーが接続されている限り、ポートは再利用できるため、クライアントは引き続き他のサーバーへの接続を開始できます。これは、カーネルが接続を見つけるときに、接続が 4 タプル (送信元 IP、送信元ポート、宛先 IP、宛先ポート)情報が含まれており、クライアント ポートが同じであるため、接続の競合は発生しません。

サーバー (接続の終了を開始する側) の TIME_WAIT ステータスが大きすぎる場合、サーバーは 1 つのポートのみをリッスンし、4 つのポートが一意に TCP 接続を決定するため、ポート リソースが制限されることはありません。理論的にはサーバー 多くの接続を確立できますが、TCP 接続が多すぎると、ファイル記述子、メモリ リソース、CPU リソース、スレッド リソースなどのシステム リソースが占有されます。

#TIME_WAIT を最適化するには?

TIME-WAIT を最適化するいくつかの方法を次に示します。どの方法にも長所と短所があります。

  • net.ipv4.tcp_tw_reuse および net.ipv4.tcp_timestamps オプションをオンにします。

  • net.ipv4.tcp_max_tw_buckets

  • SO_LINGER がプログラムで使用されており、アプリケーションは強制的に RST を使用して終了されます。

方法 1: net.ipv4.tcp_tw_reuse および tcp_timestamps

次の Linux カーネル パラメータを有効にすると、TIME_WAIT のソケットを新しい接続に再利用できます

注意すべき点の 1 つは、tcp_tw_reuse 関数はクライアント (接続イニシエーター) でのみ使用できるということです。この関数がオンになっているため、connect() 関数が呼び出されると、カーネルは time_wait ステータスが more の接続をランダムに見つけます。新しい接続には 1 秒未満。再利用します。

net.ipv4.tcp_tw_reuse = 1

このオプションを使用するには、TCP タイムスタンプのサポートを有効にするという前提条件もあります。

net.ipv4.tcp_timestamps=1(默认即为 1)

このタイムスタンプのフィールドは TCP ヘッダーの「オプション」にあり、タイムスタンプを表す合計 8 バイトで構成され、そのうち最初の 4 バイトのフィールドはデータ パケットの送信時間を節約するために使用されます。 2 番目の 4 バイト フィールドは、受信側によって送信された最新の到着データの時刻を格納するために使用されます。

2MSL タイムスタンプの導入により、タイムスタンプの有効期限が切れると重複したデータ パケットが自然に破棄されるため、前述の問題は なくなりました。

方法 2: net.ipv4.tcp_max_tw_buckets

この値のデフォルトは 18000 です。システムの TIME_WAIT 接続がこの値を超えると、システムはその後の TIME_WAIT 接続ステータスをリセットします。この方法はより暴力的です。

方法 3: プログラムで SO_LINGER を使用する

ソケット オプションを設定することで、close を呼び出して接続を閉じる動作を設定できます。

struct linger so_linger;
so_linger.l_onoff = 1;
so_linger.l_linger = 0;
setsockopt(s, SOL_SOCKET, SO_LINGER, &so_linger,sizeof(so_linger));

l_onoffこれがゼロでなく、値が 0 の場合l_linger、呼び出し後close、フラグが直ちにピアに送信されRST、TCP 接続は 4 つのウェーブをスキップするため、TIME_WAIT状態をスキップして直接終了します。

しかし、これは州を越えるTIME_WAIT可能性をもたらしますが、非常に危険な行為であり、推奨する価値はありません。

先ほど紹介した方法はどれも TIME_WAIT国家を超越しようとするもので、実は良くありません。TIME_WAIT 状態は少し長く続くため非常に不親切に見えますが、面倒なことを避けるように設計されています。

『UNIX ネットワーク プログラミング』という本には、「TIME_WAIT は私たちの友達であり、私たちを助けてくれます。この状態を避けようとするのではなく、理解してください。」と書かれています

サーバーが TIME_WAIT 状態での接続が多すぎることを避けたい場合は、積極的に切断せず、クライアントを切断させ、どこにでも分散しているクライアントに TIME_WAIT を負担させるべきです

#サーバー上に大量の TIME_WAIT 状態が表示される理由は何ですか?

まず第一に、TIME_WAIT 状態は当事者が積極的に接続を閉じるときに発生する状態であることを知っておく必要があります。そのため、サーバーに TIME_WAIT 状態の TCP 接続が多数ある場合、それはサーバーが多くの TCP 接続を積極的に切断していることを意味します。接続。

問題は、どのような状況でサーバーが積極的に切断されるかということです。

  • 最初のシナリオ: HTTP は長い接続を使用しません

  • 2 番目のシナリオ: HTTP の長い接続タイムアウト

  • 3 番目のシナリオ: HTTP 永続接続リクエストの数が上限に達した

次にそれぞれを紹介します。

最初のシナリオ: HTTP は長い接続を使用しません

まず、HTTP 長時間接続 (キープアライブ) メカニズムがどのようにオンになるかを見てみましょう。

HTTP/1.0 ではデフォルトでオフになっています。ブラウザがキープアライブをオンにしたい場合は、それをリクエスト ヘッダーに追加する必要があります。

Connection: Keep-Alive

次に、サーバーがリクエストを受信して​​応答すると、そのリクエストも応答ヘッダーに追加されます。

Connection: Keep-Alive

これにより、TCP 接続は中断されず、接続されたままになります。クライアントが別のリクエストを送信するときは、同じ TCP 接続を使用します。これは、クライアントまたはサーバー側が切断を開始するまで続きます。

HTTP/1.1 以降、キープアライブはデフォルトで有効になり、現在ではほとんどのブラウザがデフォルトで HTTP/1.1 を使用するため、キープアライブはデフォルトで有効になっています。クライアントとサーバーが合意に達すると、長時間の接続が確立されます。

HTTP キープアライブをオフにしたい場合は、 HTTP リクエストまたはレスポンスのヘッダーに Connection:close 情報を追加する必要があります。つまり、クライアントまたはサーバーのいずれかの HTTP ヘッダーに情報がある限り Connection:close 、HTTP長い接続機構は使用できません

HTTP 長い接続メカニズムを閉じた後、各リクエストは次のプロセスを経る必要があります: TCP の確立 -> リクエスト リソース -> 応答リソース -> 接続の解放。このメソッドは、次の図に示すように HTTP 短い接続になります 

写真

HTTP ショート接続

いずれかの当事者の HTTP ヘッダーに情報がある限り Connection:close 、HTTP 長時間接続メカニズムは使用できないため、HTTP リクエスト/処理が完了すると接続が閉じられることは以前にわかっています。

この時点でクライアントまたはサーバーが積極的に接続を閉じるのかという疑問が生じます。

RFC 文書では、誰が接続を閉じるのかは明確ではありませんが、要求と応答の双方が積極的に TCP 接続を閉じることができます。

ただし、ほとんどの Web サービスの実装によれば、どちらの側が HTTP キープアライブを無効にしても、サーバーは積極的に接続を閉じ、この時点で TIME_WAIT 状態の接続がサーバー上に表示されます。

クライアントは HTTP キープアライブを無効にし、サーバーは HTTP キープアライブを有効にしました。これを積極的に閉じるのは誰ですか?

クライアントが HTTP キープアライブを無効にすると、HTTP リクエストのヘッダーに Connection:close 情報が含まれ、サーバーは HTTP レスポンスの送信後に接続をアクティブに閉じます。

なぜこのように設計されているのでしょうか? HTTP はリクエスト-レスポンス モデルであり、イニシエーターは常にクライアントです。HTTP キープアライブの本来の目的は、クライアントからの後続のリクエストに対して接続を再利用することです。ある HTTP リクエストのリクエスト ヘッダーにconnection:close 情報を定義すると、応答モデルの 場合、この接続を再利用しなくなるのはサーバー側だけであるため、HTTP 要求と応答のサイクルの「終了」時に接続を閉じるのが合理的です。

HTTP キープアライブはクライアントで有効になっており、HTTP キープアライブはサーバーで無効になっています。これを積極的に閉じるのは誰ですか?

クライアントが HTTP キープアライブを有効にし、サーバーが HTTP キープアライブを無効にすると、サーバーは HTTP 応答の送信後に接続をアクティブに閉じます。

なぜこのように設計されているのでしょうか? サーバーが接続をアクティブに閉じる場合、接続を解放するために close() を 1 回呼び出すだけで済み、残りの作業はカーネル TCP スタックによって直接処理されます。プロセス全体で syscall は 1 つだけです。クライアントを閉じる必要がある場合は、サーバーを閉じる必要があります。最後の応答を書き込んだ後、ソケットを読み取り可能なキューに入れ、select / epoll を呼び出してイベントを待ちます。その後、read() を 1 回呼び出して、接続が完了したことを確認します。これは 2 つの syscall ともう 1 つのユーザー モード プログラムが閉じられ、アクティベーションの実行が完了し、ソケットの保持時間が長くなります。

したがって、サーバー上に多数の TIME_WAIT 状態の接続がある場合、クライアントとサーバーの両方が HTTP キープアライブを有効にしているかどうかを確認できます。これは、どちらかが HTTP キープアライブを有効にしていないため、サーバーがHTTP の処理を​​終了する リクエストの後、接続はアクティブに閉じられますが、このときサーバー上には TIME_WAIT 状態の接続が大量に表示されます。

このシナリオの場合、解決策も非常にシンプルで、クライアントとサーバーの両方で HTTP キープアライブ メカニズムをオンにします。

2 番目のシナリオ: HTTP の長い接続タイムアウト

HTTP ロング接続の特徴は、どちらかの側が明示的に切断を提案しない限り、TCP 接続ステータスが維持されることです。

HTTP ロング接続では、同じ TCP 接続上で複数の HTTP 要求/応答を送受信できるため、接続の確立と解放のオーバーヘッドが回避されます。

写真

画像

学生の中には、HTTP の長い接続が使用されている場合、クライアントが HTTP リクエストを完了すると新しいリクエストは開始されないのではないかと疑問に思う人もいるかもしれませんが、TCP 接続が常に占有されているのはリソースの無駄ではないでしょうか。

そうです。リソースの無駄を避けるために、Web サービス ソフトウェアは通常、nginx が提供する keepalive_timeout パラメーターなど、HTTP 長時間接続のタイムアウト時間を指定するパラメーターを提供します。

HTTP 長い接続のタイムアウトが 60 秒に設定されていると仮定すると、nginx は「タイマー」を開始します。クライアントが最後の HTTP リクエストの完了後 60 秒以内に新しいリクエストを開始しない場合、タイマー時間が到着すると、nginx はコールバック関数をトリガーして接続を閉じると、TIME_WAIT 状態の接続がサーバー上に表示されます

写真

HTTP の長い接続タイムアウト

サーバー上で TIME_WAIT 状態の接続が多数発生した場合、TCP 接続を確立した後、多数のクライアントが長時間データを送信しない場合は、HTTP 接続が原因でサーバーが積極的に接続を閉じている可能性が高くなります。長い接続タイムアウト。TIME_WAIT 状態で多数の接続が生成されます。

ネットワークの問題により、クライアントから送信されたデータがサーバーで受信されず、HTTP 長時間接続のタイムアウトが発生していないかなど、ネットワークの問題を確認できます。

3 番目のシナリオ: HTTP 永続接続リクエストの数が上限に達した

通常、Web サーバーには、長い HTTP 接続で処理できるリクエストの最大数を定義するパラメータがあり、最大制限を超えると、接続はアクティブに閉じられます。

たとえば、nginx の keepalive_requests パラメータは、HTTP 長い接続が確立された後、nginx がこの接続にカウンターを設定して、この HTTP 長い接続上で受信および処理されたクライアント リクエストの数を記録することを意味します。このパラメータで設定された最大値に達すると、nginx は長い接続を積極的に閉じ、その時点で TIME_WAIT 状態の接続がサーバー上に表示されます。

keepalive_requests パラメータのデフォルト値は 100 で、各 HTTP ロング接続で最大 100 リクエストしか実行できないことを意味します。QPS (1 秒あたりのリクエスト数) がそれほど高くない場合、このパラメータはほとんどの人に無視されることがよくあります。デフォルト値の 100 で十分です。

ただし、10,000 QPS を超える、または 30,000、50,000、さらにはそれ以上など、比較的高い QPS を持つ一部のシナリオでは、keepalive_requests パラメーター値が 100 の場合、この時点で nginx は非常に頻繁に接続を閉じ、その後サーバーは多数の TIME_WAIT 状態が表示されます

このシナリオの場合、解決策も非常に簡単で、nginx の keepalive_requests パラメーターを増やすだけです。

サーバー内で多数の CLOSE_WAIT 状態が発生する理由は何ですか?

CLOSE_WAIT 状態は「受動的終了者」のみが使用でき、「受動的終了者」が close 関数を呼び出して接続を終了しない場合、FIN メッセージを送信できないため、CLOSE_WAIT 状態の接続は送信できません。 LAST_ACK 状態に変更されます。

したがって、サーバー上で CLOSE_WAIT 状態の接続が大量に表示される場合は、サーバー プログラムが接続を閉じるための close 関数を呼び出していないことを意味します

では、サーバー側プログラムが接続を閉じる close 関数の呼び出しに失敗する原因はどのような状況にあるのでしょうか? この時点では、通常、コードのトラブルシューティングが必要になります。

まず、通常の TCP サーバーのプロセスを分析してみましょう。

  1. サーバーソケットを作成し、バインディングポートをバインドし、リスニングポートをリッスンします。

  2. サーバーソケットをepollに登録します

  3. epoll_wait は接続の到着を待ちます。接続が到着したら、accpet を呼び出して、接続されているソケットを取得します。

  4. 接続されたソケットを epoll に登録します

  5. epoll_wait はイベントが発生するのを待ちます

  6. 相手の接続が切断された場合、close を呼び出します。

サーバーが close 関数を呼び出さない理由としては、次のことが考えられます。

最初の理由: ステップ 2 が実行されておらず、サーバー ソケットが epoll に登録されていないため、新しい接続が到着したときに、サーバーはこのイベントを認識できず、接続されたソケットを取得できないため、サーバーは自然に接続を確立する可能性がありません。ソケット上で close 関数を呼び出します。

ただし、これが原因である可能性は比較的低く、この種の明らかなコード ロジックのバグは、読み取りビューの初期段階で発見されることがあります。

2 番目の理由: ステップ 3 が実行されませんでした。新しい接続が到着したときに、接続のソケットを取得するために accpet が呼び出されませんでした。その結果、多数のクライアントがアクティブに切断されたとき、サーバーは呼び出しを行う機会がありませんでした。これらのソケットの close 関数により、サーバー側で多数の接続が CLOSE_WAIT 状態になります。

これは、コードが特定のロジックでスタックしているか、サーバーが accpet 関数を実行する前の早い段階で例外がスローされているために発生する可能性があります。

3 番目の理由: ステップ 4 が実行されませんでした。accpet を通じて接続されたソケットを取得した後、epoll に登録されませんでした。その結果、その後 FIN メッセージを受信したときに、サーバーはこのイベントを感知できませんでした。イベントを検出できるようになったら、close 関数を呼び出します。

これは、サーバー コードが特定のロジックでスタックしているか、接続されたソケットを epoll に登録する前に事前に例外をスローしているために発生する可能性があります。close_wait 問題の解決に関する他の人の実践的な記事を以前に見たことがあります。興味がある場合は、「不健全な Netty コードによる大量の CLOSE_WAIT 接続の原因の分析」を参照してください。

4 番目の理由: ステップ 6 が実行されませんでした。クライアントが接続を閉じたことが判明したときに、サーバーは閉じる関数を実行しませんでした。これは、コードが処理を見逃したか、コードが実行前に特定のロジックでスタックしたことが考えられます。デッドロックなどのクローズ関数。

サーバー上で CLOSE_WAIT 状態の接続が大量に発生する場合、通常はコードの問題であることがわかります。現時点では、特定のコードを段階的に調査して特定する必要があります。分析の主な方向は次のとおりですサーバーが close を呼び出さなかった理由

#接続は確立されているが、クライアントが突然失敗した場合はどうなりますか?

クライアント障害とは、クライアントのホストがダウンしているか、停電が発生しているシナリオを指します。この場合、サーバーがクライアントにデータを送信しないと、サーバーはクライアントのダウンタイムを認識することがなくなり、サーバーの TCP 接続は常にシステム リソースを占有した状態になります ESTABLISH 。

この状況を回避するために、TCP にはキープアライブ メカニズムが備わっています。このメカニズムの原理は次のとおりです。

期間を定義します。この期間中、接続関連のアクティビティがない場合は、TCP キープアライブ メカニズムが有効になります。一定の間隔ごとに、検出メッセージが送信されます。検出メッセージには、ほとんどデータが含まれません。応答がない場合は、複数の連続した検出メッセージに対して「エラー」メッセージが受信された場合、現在の TCP 接続は切断されているとみなされ、システム カーネルは上位層アプリケーションにエラー メッセージを通知します。

Linux カーネルには、キープアライブ時間、キープアライブ検出の数、およびキープアライブ検出の時間間隔を設定するための対応するパラメーターがあります。デフォルト値は次のとおりです。

net.ipv4.tcp_keepalive_time=7200
net.ipv4.tcp_keepalive_intvl=75  
net.ipv4.tcp_keepalive_probes=9
  • tcp_keepalive_time=7200: キープアライブ時間が 7200 秒 (2 時間) であることを示します。つまり、2 時間以内に接続関連のアクティビティがない場合、キープアライブ メカニズムがアクティブ化されます。

  • tcp_keepalive_intvl=75: 各検出の間隔が 75 秒であることを示します。

  • tcp_keepalive_probes=9: 9 回応答なしを検出した後、相手に到達できないとみなされ、この接続が中断されることを示します。

つまり、Linux システムでは、「無効な」接続を見つけるのに少なくとも 2 時間 11 分 15 秒かかります。

写真

画像

アプリケーションが TCP キープアライブ メカニズムを使用したい場合は、 SO_KEEPALIVE ソケット インターフェイスを介してオプションを設定する必要があり、設定されていない場合、TCP キープアライブ メカニズムは使用できないことに注意してください。

TCP キープアライブが有効になっている場合は、次の状況を考慮する必要があります。

  • まず、ピア プログラムは正常に動作しています。TCP キープアライブ プローブ メッセージがピアに送信されると、ピアは正常に応答するため、  TCP キープアライブ時間がリセットされ、次の TCP キープアライブ時間が到着するまで待機します。

  • 次に、ピア ホストがクラッシュして再起動します。TCP キープアライブ プローブ メッセージがピアに送信されると、ピアは応答できますが、接続に関する有効な情報がないため、RST パケットが生成され、TCP 接続がリセットされたことがすぐにわかります。 。

  • 3 番目は、ピア ホストがダウンしていることです (これはプロセス クラッシュではないことに注意してください。プロセスがクラッシュした後、オペレーティング システムはプロセス リソースを再利用するときに FIN メッセージを送信しますが、ホストのダウンタイムは認識されないため、TCP キープアライブが発生します。 (相手にホストのダウンタイムがあるかどうかを検出するため) メカニズムが必要であるか、他の理由で相手のパケットが到達できない場合。TCP キープアライブ検出メッセージがピアに送信されると、応答がなく、数回連続してキープアライブ検出の数に達すると、TCP は TCP 接続が切断されたことを報告します

TCP キープアライブ メカニズムの検出時間は少し長いですが、アプリケーション層でハートビート メカニズムを独自に実装できます。

たとえば、Web サービス ソフトウェアは通常、  keepalive_timeout HTTP 長時間接続のタイムアウト期間を指定するパラメータを提供します。HTTP 永続接続のタイムアウト期間が 60 秒に設定されている場合、Web サービス ソフトウェアはタイマーを開始します。クライアントが HTTP リクエストの完了後 60 秒以内に新しいリクエストを開始しない場合、タイマーは一度期限切れになります。到着すると、コールバック関数がトリガーされて接続が解放されます。

写真

Webサービスのハートビートの仕組み

#接続は確立されているが、サーバー プロセスがクラッシュした場合はどうなりますか?

TCP 接続情報はカーネルによって維持されるため、サーバー プロセスがクラッシュすると、カーネルはプロセスのすべての TCP 接続リソースを再利用する必要があるため、カーネルは最初の waving FIN メッセージを送信し、後続の waving プロセスもカーネルの完了にはプロセスの参加は必要ないため、サーバー プロセスが終了した場合でも、クライアントとの TCP 4 ウェーブ プロセスを完了できます。

私自身、プロセスのクラッシュをシミュレートするために kill -9 を使用して実験を行ったところ、プロセスを強制終了した後、サーバーが FIN メッセージを送信し、クライアントに 4 回手を振ることがわかりました。


ソケットプログラミング

TCP 用にソケットをプログラムするにはどうすればよいですか?

写真

クライアントとサーバーは TCP プロトコルに基づいて動作します

  • サーバーとクライアントが初期化され socket、ファイル記述子が取得されます。

  • bindソケットを指定された IP アドレスとポートにバインドするためにサーバーによって呼び出されます 。

  • 監視するためにサーバーによって呼び出されます listen

  • サーバーによって呼び出され accept、クライアントの接続を待機します。

  • クライアントは、 connectサーバーのアドレスとポートへの接続リクエストを開始するために呼び出します。

  • サーバーは accept 送信に使用されるファイル記述子を返します socket 。

  • クライアントは write データの書き込みを呼び出し、サーバーは read データの読み取りを呼び出します。

  • クライアントが切断すると、 が呼び出され close、サーバーが read データを読み取ると、 が読み取られます EOF。データが処理された後、サーバーは close接続が閉じられたことを示すために を呼び出します。

ここで注意する必要があるのは、サーバーが を呼び出したときに accept 、接続が成功すると、接続が完了したソケットが返され、後でデータを送信するために使用されることです。

したがって、待機ソケットと実際にデータ送信に使用されるソケットは「2 つ」のソケットとなり、1 つは待機ソケット、もう 1 つは完了接続ソケットと呼ばれます。

接続が正常に確立されると、ファイル ストリームへの書き込みと同様に、両者が読み取りおよび書き込み関数を通じてデータの読み取りと書き込みを開始します。

リスニング時のパラメータ バックログの意味は何ですか?

Linux カーネルでは 2 つのキューが維持されます。

  • 準接続キュー (SYN キュー): SYN 接続確立要求を受信し、SYN_RCVD 状態にあります。

  • フル接続キュー (Accpet キュー): TCP 3 ウェイ ハンドシェイク プロセスが完了し、ESTABLISHED 状態になっています。

写真

SYNキューとAccpetキュー

int listen (int socketfd, int backlog)
  • パラメータ 1、socketfd は、socketfd ファイル記述子です。

  • パラメーター 2 のバックログ。このパラメーターは過去のバージョンでいくつかの変更があります。

初期の Linux カーネルのバックログには、未処理のキュー サイズである SYN キュー サイズがあります。

Linux カーネル 2.2 以降では、バックログが受け入れキュー、つまり接続の確立を完了したキューの長さになるため、現在ではバックログが受け入れキューであると考えるのが一般的です。

ただし、上限はカーネル パラメーター somaxconn のサイズです。これは、accpet キューの長さ = min(backlog, somaxconn) を意味します。

スリーウェイ ハンドシェイクのどの段階で受け入れが行われますか?

まず、クライアントがサーバーに接続するときに何が送信されるかを見てみましょう。

写真

ソケットのスリーウェイハンドシェイク

  • クライアントのプロトコル スタックは SYN パケットをサーバーに送信し、現在シーケンス番号 client_isn を送信していることをサーバーに伝え、クライアントは SYN_SENT 状態に入ります。

  • このパケットを受信したサーバーのプロトコル スタックは、クライアントに ACK を返します。応答の値は client_isn+1 で、SYN パケット client_isn の確認を示します。同時に、サーバーは SYN パケットも送信して、それを通知します。クライアント 現在の送信シーケンス番号はserver_isnであり、サーバーはSYN_RCVD状態に入ります。

  • クライアント プロトコル スタックが ACK を受信すると、アプリケーション プログラムは connect 呼び出しから戻り、クライアントとサーバー間の一方向接続が正常に確立され、クライアントのステータスが ESTABLISHED になったことを示します。プロトコルスタックはサーバーのSYNパケットにも応答します。データはserver_isn+1です。

  • ACK 応答パケットがサーバーに到着すると、サーバーの TCP 接続は ESTABLISHED 状態になり、同時にサーバーのプロトコル スタックはブロッキング コールを返します。このとき、サーバーからサーバーへの一方向の接続は確立されません accept 。クライアントも正常に確立されます。この時点で、クライアントとサーバー間の両方向の接続が正常に確立されます。

上記の説明プロセスから、クライアント接続が正常に返されたのは 2 回目のハンドシェイクの後であり、サーバー受け入れが正常に返されたのは 3 ウェイ ハンドシェイクが成功した後であることがわかります

クライアントが close を呼び出した場合、接続を切断するプロセスは何ですか?

クライアントが積極的に を呼び出すと close何が起こるか見てみましょう。

写真

クライアントがクローズプロセスを呼び出す

  • クライアントによって呼び出されると close、クライアントに送信するデータがないことが示され、サーバーに FIN メッセージが送信され、FIN_WAIT_1 状態に入ります。

  • サーバーが FIN メッセージを受信すると、TCP プロトコル スタックは FIN パケットの受信バッファにファイルの終わり文字を挿入します EOF 。アプリケーションは read 呼び出しによってこの FIN パケットを認識できます。これは、 キューに入れられた他の受信データの後に配置されEOF ます。これは、EOF は接続上で追加のデータが到着しないことを意味するため、サーバーがこの例外状況を処理する必要があることを意味します。この時点で、サーバーは CLOSE_WAIT 状態に入ります。

  • その後、データが処理されると、データは自然に読み取られる EOFため、ソケットを閉じるよう呼び出します。 close これにより、サーバーは FIN パケットを送信し、LAST_ACK 状態になります。

  • クライアントはサーバーから FIN パケットを受信し、ACK 確認パケットをサーバーに送信し、この時点で TIME_WAIT 状態に入ります。

  • ACK 確認パケットを受信した後、サーバーは最終的な CLOSE 状態に入ります。

  • 時間が経過すると、クライアント 2MSL も CLOSE 状態に入ります。

accept なしで TCP 接続を確立できますか?

答え:はい

accpet システムコールは、TCP スリーウェイハンドシェイクプロセスには関与せず、TCP フルコネクションキューから接続が確立されたソケットを取り出すことのみを担当します。ユーザー層は、accpet システムを通じて接続が確立されたソケットを取得します。呼び出し、ソケットの読み書きが可能です。

写真

準結合キューと完全結合キュー

TCP 接続はリッスンせずに確立できますか?

答え:はい

クライアント自体が接続して接続を形成することも ( TCP 自己接続)、2 つのクライアントが互いにリクエストを送信して同時に接続を確立することもできます ( TCP が同時にオープンします)。これらの両方の状況には 1 つの点があります。 common、つまりサーバーは関係しません。つまり、リッスンせずに TCP 接続を確立できます。

おすすめ

転載: blog.csdn.net/liuxing__jacker/article/details/132026056