【初心者わかりやすく】TCP通信の確立と終了シーケンス

 

TCP は、データを送り始める前にコネクションを確立し、データを送り終えたらコネクションを終了します。どちらも、TCP ヘッダのフラグ(SYN、ACK、FIN)を立てたパケットのやり取りで行います。

この記事では、コネクションの確立(3-way handshake)と終了のシーケンスを、Wireshark で見た TCP フラグと一緒に確認します。

この記事でわかること
  1. 3-way handshake(SYN、SYN/ACK、ACK)でコネクションを確立する流れ
  2. FIN と ACK でコネクションを終了する流れ(3回の場合と4回の場合)
  3. Wireshark で TCP フラグを確認する方法

TCPのフラグ

TCP ヘッダには、そのパケットの役割を示すフラグがあります。コネクションの確立と終了で使うのは次の3つです。Wireshark では、TCP ヘッダの Flags を開くと、どのフラグが立っているか(Set)を確認できます。

フラグ意味使う場面
SYN(Synchronize)コネクションを始めたい。シーケンス番号の初期値を相手に伝えるコネクションの確立
ACK(Acknowledgment)相手から受け取ったことを確認する。ACK 番号が有効になるコネクションの確立後は、すべてのパケットで立つ
FIN(Finish)送るデータがもうないので、コネクションを閉じたいコネクションの終了

TCPコネクション開始時

TCP では、データを送受信する前にコネクションを確立します。3回のやり取りで確立するので、3-way handshake(スリーウェイハンドシェイク)と呼ばれます。

クライアントサーバ3-way handshakeSYNFlags: 0x002(SYN)SYN/ACKFlags: 0x012(SYN, ACK)ACKFlags: 0x010(ACK)
図1 3-way handshake

クライアントが SYN を送る

まず、通信を始めたい側(クライアント)が、SYN フラグを立てたパケットを送ります。「TCP 通信を始めたい」という合図です。

サーバが SYN/ACK を返す

サーバは、クライアントからの SYN を受け入れる場合、ACK フラグを立てて返します。また、サーバ側からもクライアントへの通信を始めるため、SYN フラグも立てる必要があります。この2つを1つのパケットにまとめ、SYN/ACK として返します。

クライアントが ACK を返す

クライアントは、サーバからの SYN を受け入れる場合、ACK フラグを立てて返します。この3回のやり取りで、コネクションが確立され、データを送れるようになります。

画面イメージについて

この記事の画面は、実際の画面ではなく、検証時のパケットキャプチャから見るポイントを抜き出して描いたイメージです。バージョンによって、項目の並びや表記は変わります。

パケットキャプチャ(Wireshark) 3-way handshake の TCP フラグ画面イメージ
1▾ Flags: 0x002 (SYN)    000. .... .... = Reserved: Not set    ...0 .... .... = Nonce: Not set    .... 0... .... = Congestion Window Reduced (CWR): Not set    .... .0.. .... = ECN-Echo: Not set    .... ..0. .... = Urgent: Not set    .... ...0 .... = Acknowledgment: Not set    .... .... 0... = Push: Not set    .... .... .0.. = Reset: Not set    .... .... ..1. = Syn: Set    .... .... ...0 = Fin: Not set
2▾ Flags: 0x012 (SYN, ACK)    000. .... .... = Reserved: Not set    ...0 .... .... = Nonce: Not set    .... 0... .... = Congestion Window Reduced (CWR): Not set    .... .0.. .... = ECN-Echo: Not set    .... ..0. .... = Urgent: Not set    .... ...1 .... = Acknowledgment: Set    .... .... 0... = Push: Not set    .... .... .0.. = Reset: Not set    .... .... ..1. = Syn: Set    .... .... ...0 = Fin: Not set
3▾ Flags: 0x010 (ACK)    000. .... .... = Reserved: Not set    ...0 .... .... = Nonce: Not set    .... 0... .... = Congestion Window Reduced (CWR): Not set    .... .0.. .... = ECN-Echo: Not set    .... ..0. .... = Urgent: Not set    .... ...1 .... = Acknowledgment: Set    .... .... 0... = Push: Not set    .... .... .0.. = Reset: Not set    .... .... ..0. = Syn: Not set    .... .... ...0 = Fin: Not set
図2 SYN(上)、SYN/ACK(中)、ACK(下)のフラグ(実際の画面ではなく、表示内容のポイントを示したイメージです)
No.表示意味
1Flags: 0x002 (SYN)SYN だけが立っている。クライアントからの最初のパケット
2Flags: 0x012 (SYN, ACK)ACK と SYN の両方が立っている。サーバからの応答
3Flags: 0x010 (ACK)ACK だけが立っている。これで確立が終わる
補足

SYN を送るときには、それぞれの側がシーケンス番号の初期値を相手に伝えます。シーケンス番号と ACK 番号の増え方は、【TCP】シーケンス番号とACK番号について解説 で詳しく説明しています。

TCPコネクション終了時

コネクションを閉じるときは、閉じたい側から FIN フラグを立てて送ります。TCP は送信と受信の2つの方向を別々に閉じるので、両方の側が FIN を送り、相手がそれぞれ ACK を返して、コネクションが終わります。先に FIN を送るのはクライアントとは限らず、サーバから閉じることもあります。

RFC(RFC 9293)で示されている基本の形は、FIN と ACK を別々に送る4回のやり取りです(後述)。ただし、サーバが FIN を送るまでの間が短いと、ACK と FIN が1つのパケットにまとまり、3回のやり取りになります。この記事の検証では3回になっていたので、まずこちらを説明します。

クライアントサーバ3回のやり取りFIN/ACKFlags: 0x011(FIN, ACK)FIN/ACKFlags: 0x011(FIN, ACK)ACKFlags: 0x010(ACK)
図3 コネクションの終了(3回のやり取り)

クライアントが FIN を送る

コネクションを閉じたい側(ここではクライアント)が、FIN フラグを立てて送ります。コネクションの確立後に送るパケットには、必ず ACK フラグも立てる決まりがあるため、Wireshark では FIN/ACK(0x011)と表示されます。この ACK は、それまでに受け取ったデータへの確認応答で、「FIN と ACK の2つを送った」という意味ではありません。

サーバが FIN/ACK を返す

サーバは、クライアントからの FIN を受け取ったことを ACK で知らせます(ACK 番号は、クライアントの FIN のシーケンス番号+1)。この検証では、サーバ側のアプリケーションもすぐにコネクションを閉じたため、サーバは ACK とあわせて自分の FIN も立て、1つのパケットで送っています。

クライアントが ACK を返す

クライアントは、サーバからの FIN に ACK を返します。これでコネクションが完全に終了します。

パケットキャプチャ(Wireshark) コネクション終了時の TCP フラグ画面イメージ
1▾ Flags: 0x011 (FIN, ACK)    000. .... .... = Reserved: Not set    ...0 .... .... = Nonce: Not set    .... 0... .... = Congestion Window Reduced (CWR): Not set    .... .0.. .... = ECN-Echo: Not set    .... ..0. .... = Urgent: Not set    .... ...1 .... = Acknowledgment: Set    .... .... 0... = Push: Not set    .... .... .0.. = Reset: Not set    .... .... ..0. = Syn: Not set    .... .... ...1 = Fin: Set
2▾ Flags: 0x011 (FIN, ACK)    000. .... .... = Reserved: Not set    ...0 .... .... = Nonce: Not set    .... 0... .... = Congestion Window Reduced (CWR): Not set    .... .0.. .... = ECN-Echo: Not set    .... ..0. .... = Urgent: Not set    .... ...1 .... = Acknowledgment: Set    .... .... 0... = Push: Not set    .... .... .0.. = Reset: Not set    .... .... ..0. = Syn: Not set    .... .... ...1 = Fin: Set
3▾ Flags: 0x010 (ACK)    000. .... .... = Reserved: Not set    ...0 .... .... = Nonce: Not set    .... 0... .... = Congestion Window Reduced (CWR): Not set    .... .0.. .... = ECN-Echo: Not set    .... ..0. .... = Urgent: Not set    .... ...1 .... = Acknowledgment: Set    .... .... 0... = Push: Not set    .... .... .0.. = Reset: Not set    .... .... ..0. = Syn: Not set    .... .... ...0 = Fin: Not set
図4 クライアントの FIN/ACK(上)、サーバの FIN/ACK(中)、クライアントの ACK(下)(実際の画面ではなく、表示内容のポイントを示したイメージです)
No.表示意味
1Flags: 0x011 (FIN, ACK)クライアントの FIN。ACK も立っている
2Flags: 0x011 (FIN, ACK)サーバの、クライアントの FIN への ACK と、サーバ自身の FIN
3Flags: 0x010 (ACK)サーバの FIN への ACK

FIN と ACK を別々に送る場合

RFC の基本の形では、サーバは ACK と FIN を別々に送ります。FIN を受け取ったサーバはまず ACK を返し、自分の FIN は、サーバ側のアプリケーションがコネクションを閉じるまで送りません。そのため、アプリケーションの終了処理に時間がかかると、ACK と FIN の間があき、4回のやり取りになります。実際の通信でもよく見られる形です。

クライアントサーバ4回のやり取りFIN/ACKクライアントの FINACKFIN を受け取った確認FIN/ACKサーバの FIN(アプリの終了処理のあと)ACKFIN を受け取った確認
図5 ACK と FIN を別々に送る場合(4回のやり取り)
補足
  • 先に FIN を送った側は、最後の ACK を送ったあと、TIME_WAIT という状態で一定時間(MSL:パケットの最大生存時間の2倍)待ってから、コネクションの情報を消します。最後の ACK が届かず相手が FIN を送り直してきたときに ACK を返し直せるようにするためと、前のコネクションの遅れたパケットが新しいコネクションに混ざらないようにするためです。
  • FIN による終了のほかに、RST フラグを立てたパケットで、コネクションを強制的に切ることもあります。

まとめ

場面やり取りWireshark のフラグ
コネクションの確立SYN → SYN/ACK → ACK0x002 → 0x012 → 0x010
コネクションの終了(3回)FIN/ACK → FIN/ACK → ACK0x011 → 0x011 → 0x010
コネクションの終了(4回)FIN/ACK → ACK → FIN/ACK → ACK0x011 → 0x010 → 0x011 → 0x010

コメント

  1. LazyGrizzly より:

    とても分かりやすかったです、ありがとうございます。
    一点、TCPコネクション終了時 の冒頭で
     “閉じたい方から”
    とありますが、Server 側から能動的に閉じることも可能なのでしょうか。
    その場合は、Server -FIN> Client ➡ Client -ACK> Server で完結、でしょうか。
    大変お忙しい中お手数で誠に恐縮ながら、教えて頂けますと幸いです。
    よろしくお願い申し上げます。