広告
広告
https://www.7key.jp/nw/tcpip/tcp/window.html#buffer
TCP の基本は「セグメントを一つ送信し、ACKパケットを一つ受け取る」この繰り返しにより信頼性を確保することでした(詳しくは確認応答と再送制御を参照下さい)。確かに一つセグメントを送信するごとに、「受け取りましたよー。次を送って下さい。」と返事が来れば、かなりの信頼性が確保できるでしょう。ただこの方法ですと、送信側はセグメントを一つ送るたびに、確認応答が返って来るのを待たなければなりませんので、通信効率が悪すぎます(Figure:WINDOW-01)。
ここで簡単に通信効率を上げるために思いつくのは、「一つのセグメントサイズを大きくする」ということですが、セグメントの大きさはデータリンク層のデータサイズ(MTU)に依存します。ですからセグメントを大きくすると言うことは簡単ではありません。そこでTCP では、送信側はセグメントを次から次へと連続して送り、受信側はまとめてACKパケットにて応答をする、という方法がとられています(Figure:WINDOW-02)。
この方法により、データの送信効率は上がるのですが一つ問題点も出てきます。受信側にしてみれば、次から次へとセグメントが(一方的に)送られてくることになります。もしかしたらアプリケーションの処理が追いつかないかもしれませんし、そもそも準備が出来ていないかもしれません。この方法を実現したい場合、どうしても受信側にて一時的にデータを蓄えておく場所が必要となってきます。その場所のことをバッファと呼びます。バッファは処理する速さの異なる相手とデータをやり取りする場合に、一時的にデータを溜める貯水槽の役割を果たします。送信側は受信側のバッファがいっぱいになるまではどんどんとデータを送信することができますし、受信側(アプリケーション)にしてみても自分の都合次第でバッファからデータを取り出せば良くなります。これは受信側ホストにあるメモリ領域と言うことで受信バッファとも呼ばれています。対して、送信時にアプリケーションから受け取った際にデータを一時保管するためのメモリ領域を送信バッファと呼びます。ただ、バッファはやり取りをするためのデータ全体を蓄えておけるほどの容量は持っていませんので、処理済みの不要データは順番に捨てられることとなっています。
https://www.7key.jp/nw/tcpip/tcp/window.html#window
バッファが理解できればウィンドウの理解は簡単です。ウィンドウとは、バッファ内でデータの読み書きを行ったり、格納してあるデータの管理を行ったりする領域のことです。「バッファに開けられた作業用の窓」と言った感覚でしょうか。ウィンドウの範囲内であれば、一時的にデータを保存しておくことができ、データの損失を防ぐことができます(ちなみにバッファのサイズが足らず、データがあふれ出てしまうことをバッファオーバフローと言います)。
そして、ウィンドウの大きさのことをウィンドウサイズと呼びます。受信側ホストは送信側ホストに、ウィンドウサイズを前もって教えておきます(ここで言うウィンドウサイズはあくまで初期値であり、通信の最中にこのサイズは変化します)。もちろん前もってとはスリーウェイハンドシェイクの際にです。思い出してください。TCP ヘッダにはウィンドウサイズという項目がありましたよね(下表)。
発信元ポート番号 (16ビット) |
宛先ポート番号 (16ビット) |
|||||||
シーケンス番号 (32ビット) |
||||||||
確認応答番号 (32ビット) |
||||||||
ヘッダ長 (4ビット) |
予約ビット (6ビット) |
フラグ (6ビット) |
ウィンドウサイズ (16ビット) |
|||||
U R G |
A C K |
P S H |
P S T |
S Y N |
F I N |
|||
チェックサム (16ビット) |
緊急ポインタ (16ビット) |
|||||||
(オプション) | ||||||||
データ |
送信側ホストは最初の「SYNパケット」の際に、シーケンス番号や MSS と一緒に自分のウィンドウサイズを受信側ホストに通知します。それを受け取った受信側ホストは、「SYN+ACK」パケットで返事をするのですが、その際に自分のウィンドウサイズも併せて送信側ホストに通知します。このような手順でお互いのウィンドウサイズが分かるのです。送信側ホストは受信側ホストのウィンドウサイズが分かれば、そのサイズまでのデータであれば確認応答(ACKパケット)を待つ必要がありません。次から次へとセグメントを送信することができます。
https://www.7key.jp/nw/tcpip/tcp/window.html#slidingwindow
さて、ウィンドウについて一通りの説明が終わったところで実際の動きを見てみましょう。まずは受信ウィンドウから。ある程度データがまとまった時点で、TCP は上位層に受信ウィンドウのデータを渡します。渡し終わるとウィンドウ内のデータは不要となりますので破棄されます。ただ、単純に消したりウィンドウ内のデータを動かしたりするのではなく、受信バッファ上のウィンドウ領域を動かします(Figure:WINDOW-04)。
このことにより、データの破棄と新しい領域の確保が同時にできると言うわけです。この手法のことをスライディングウィンドウと呼びます。バッファはスライドに対応しやすいよう両端が繋がれた輪のようになっているため、図5のように端までウィンドウが移動しても問題ありません。
https://www.7key.jp/nw/tcpip/tcp/window.html#tcpf
同じように、送信バッファでもスライディングウィンドウが使われています。ただ、送信側は受信側のように単純ではありません。受信側の処理能力を考えずセグメントを送信しても、処理しきれなかったデータは捨てられてしまいます。このために送信側では、受信ウィンドウの空きサイズに合わせて自らのウィンドウサイズを変化させ、相手が確実にセグメントを受け取ったことを確認してからウィンドウをスライドさせる、といった動きになります。具体的に見てみましょう。ACKパケットにより次はどこから送信すべきか、つまりセグメントを受け取ったと言うことが分かるのは確認応答と再送制御で説明しました。問題なのは受信ウィンドウのサイズですが、実はこちらもACKパケットの中に盛り込まれています。上記図4を見てもらえれば分かると思いますが、「ウィンドウサイズ」と言う項目がありますよね。スリーウェイハンドシェイクの際は、お互いの初期ウィンドウサイズを通知し合うのに使われましたが、ACKパケットの中では現在の受信ウィンドウの空き領域が入ります。これを見て送信側は一度に送信するセグメントの数、つまりは送信ウィンドウサイズを随時変化させ、受信側のオーバーフローを防いでいるのです。この方法を TCP のフロー制御と言います。では、受信側のウィンドウが一時的にいっぱいになったらどうするのでしょうか。この場合、受信側はウィンドウサイズを「0(ゼロ)」にした ACKパケットを送信側に返します。すると送信側は受信側の状態が分かり、セグメントの発信を中断します。このような状態のことをゼロウィンドウ状態と言います。もちろん、このままでは通信が止まったままとなりますので、受信ウィンドウに空きができたら受信側から改めてACKパケットを送信し、新しいウィンドウサイズを通知することになっています。
広告