読む前にこの記事のポイントを音声で確認できます。この音声は本記事をもとにAIツール(NotebookLM)で自動生成したもので、発音や言い回しが不自然な箇所や、内容の誤りが含まれる可能性があります。理解の補助としてご利用ください。
TCP は、シーケンス番号(SEQ)と確認応答番号(ACK)を使って、送ったデータが相手に届いたかを確かめながら通信します。パケットキャプチャで再送や欠落を調べるときも、この2つの番号の動きが読めることが前提になります。
この記事では、SEQ と ACK の意味を整理したあと、接続から切断までの簡単な例で、2つの番号がどう増えていくかを1つずつ確認します。
- シーケンス番号と ACK 番号が、それぞれ何を表す数字か
- SYN と FIN はデータがなくても番号を1つ使うこと
- 3-way handshake からデータ送信、切断までの SEQ と ACK の計算
シーケンス番号とACK番号とは
TCP は、送るデータを1バイトずつ番号を付けて管理します。シーケンス番号は、送るデータの先頭が何番目のバイトかを表します。ACK 番号は、次に受け取りたいバイトの番号で、「その手前までは受け取った」という意味になります。
| 番号 | 意味 | 次の値の決まり方 |
|---|---|---|
| シーケンス番号(SEQ) | 自分が送るデータの先頭バイトの番号 | 次の SEQ = 今の SEQ + 送ったデータのバイト数 |
| ACK 番号 | 相手から次に受け取りたいバイトの番号(その手前までは受け取った) | ACK = 受け取ったセグメントの SEQ + そのデータのバイト数 |
シーケンス番号は、クライアントからサーバへの方向と、サーバからクライアントへの方向で別々に数えます。クライアントの SEQ はサーバの ACK と、サーバの SEQ はクライアントの ACK と対応します。
シーケンス番号の考え方
シーケンス番号は、最初の値から、送ったデータのバイト数だけ増えていきます。たとえば、1000 バイトのデータを 100、200、300、400 バイトの4つのセグメントに分けて送る場合を考えます。
| セグメント | 送るバイト | シーケンス番号 |
|---|---|---|
| 1つ目(100 バイト) | 1〜100 番目 | 1 |
| 2つ目(200 バイト) | 101〜300 番目 | 101 |
| 3つ目(300 バイト) | 301〜600 番目 | 301 |
| 4つ目(400 バイト) | 601〜1000 番目 | 601 |
受信側は、シーケンス番号を見てセグメントを正しい順番に並べ直し、抜けているバイトがないかを確認します。
実際のシーケンス番号は 1 や 0 から始まるわけではありません。接続ごとに決まる初期シーケンス番号(ISN)から始まり、32 ビットの範囲で増えていきます。Wireshark は、初期値を 0 として数え直した相対シーケンス番号(Relative sequence numbers)を既定で表示するので、番号の増え方を追いやすくなっています。大事なのは番号がどこから始まるかではなく、どう増えていくかです。
接続から切断までの例
次の擬似的な通信を例に、シーケンス番号と ACK 番号がどう計算されるかを確認します。番号は Wireshark の相対シーケンス番号と同じく、初期値を 0 として数えます。
- クライアントからサーバへ TCP 接続を開始する
- クライアントからサーバへ 111 バイトのデータを送る
- サーバからクライアントへ 222 バイトのデータを送る
- サーバから TCP 接続を終了する
3-way handshake で接続する
クライアントは、SEQ=0 で SYN を送ります。SYN はデータを持ちませんが、シーケンス番号を1つ使います。そのため、SYN を受け取ったサーバは「0 番まで受け取った。次は 1 番がほしい」として ACK=1 を返します。サーバも自分の SEQ=0 で SYN を送るので、SYN と ACK を1つのセグメント(SYN/ACK)にまとめて送ります。
クライアントは、サーバから ACK=1 を受け取ったので、次は SEQ=1 で送ります。また、サーバの SYN(SEQ=0)を受け取ったので ACK=1 を返します。
最初の SYN には ACK フラグが立っていないので、ACK 番号は意味を持ちません。ACK 番号が使われるのは、ACK フラグが立っているセグメントだけです。
クライアントが 111 バイトを送る
クライアントは、SEQ=1、ACK=1 のまま 111 バイトのデータを送ります。直前の ACK はデータを持たず、シーケンス番号を使わないので、SEQ は 1 のままです。このデータは 1〜111 番目のバイトです。
サーバが ACK=112 で 222 バイトを送る
サーバは 111 番目まで受け取ったので、次は 112 番目がほしいという意味で ACK=112(1+111)を送ります。同時に、サーバは自分の SEQ=1 から 222 バイトのデータを送ります。
クライアントが ACK=223 を返す
クライアントは、サーバから ACK=112 を受け取ったので、次は SEQ=112 で送ります。また、サーバのデータを 1〜222 番目まで受け取ったので、ACK=223(1+222)を返します。このセグメントはデータを持たない(0 バイト)確認応答です。
サーバから切断する
サーバは、SEQ=223、ACK=112 で FIN を送ります(ACK フラグも立っているので FIN/ACK と表示されます)。FIN も SYN と同じく、データを持たずにシーケンス番号を1つ使います。クライアントは FIN を受け取ったので、ACK=224(223+1)を返します。
クライアントからも切断する
クライアントも、SEQ=112、ACK=224 で FIN/ACK を送ります。サーバは FIN を受け取ったので、SEQ=224、ACK=113(112+1)を返し、TCP 接続が終わります。
すべてのセグメントの一覧
長さは、セグメントに含まれるデータのバイト数です。
| No. | 送信元 | 種類 | SEQ | ACK | 長さ | 番号の根拠 |
|---|---|---|---|---|---|---|
| 1 | クライアント | SYN | 0 | — | 0 | 初期値 |
| 2 | サーバ | SYN/ACK | 0 | 1 | 0 | ACK=0+1(SYN の分) |
| 3 | クライアント | ACK | 1 | 1 | 0 | SEQ=0+1(SYN の分)、ACK=0+1 |
| 4 | クライアント | データ | 1 | 1 | 111 | No.3 はデータがないので SEQ は同じ |
| 5 | サーバ | データ | 1 | 112 | 222 | ACK=1+111 |
| 6 | クライアント | ACK | 112 | 223 | 0 | SEQ=1+111、ACK=1+222 |
| 7 | サーバ | FIN/ACK | 223 | 112 | 0 | SEQ=1+222 |
| 8 | クライアント | ACK | 112 | 224 | 0 | ACK=223+1(FIN の分) |
| 9 | クライアント | FIN/ACK | 112 | 224 | 0 | No.8 はデータがないので SEQ は同じ |
| 10 | サーバ | ACK | 224 | 113 | 0 | SEQ=223+1、ACK=112+1(FIN の分) |
SEQ と ACK の動きは、文章だけではつかみにくいところです。TCP SEQ/ACK Lab — シーケンス番号とACK番号の動きを触る では、ボタンを順に押すと、3-way handshake からデータ送信、切断までの SEQ と ACK の変化を1ステップずつ追えます。tcpdump 風の出力も一緒に表示されるので、実際のパケットキャプチャを読むときの練習にもなります。
まとめ
| ポイント | 内容 |
|---|---|
| シーケンス番号 | 送るデータの先頭バイトの番号。送ったバイト数だけ増える |
| ACK 番号 | 次に受け取りたいバイトの番号。受け取った SEQ + データのバイト数 |
| SYN と FIN | データを持たないが、シーケンス番号を1つ使う |
| データのない ACK | シーケンス番号を使わない(次のセグメントも同じ SEQ) |
| 番号の向き | クライアント側とサーバ側で別々に数える |
TCP の解析では、Wireshark などのパケットキャプチャソフトをよく使います。Wireshark で TCP を解析するときによく使う表示フィルタや、再送(Retransmission)や重複 ACK(Dup ACK)の見方は、次の記事を参考にしてください。
コメント