題名:ネットワークについて

五郎の入り口に戻る

日付:2000/7/1

 この文章について | ネットワークの動作概要 | Physical Layer:物理層 | Media Access Control SubLayer | Logical Link Sublayer | Network Layer | Transport Layer


Transport Layer-TCP

Transmission Control Protocol(TCP)

TCPは信頼性のある(つまりAck-再送によるエラー処理をちゃんと行なう)双方向のバイト単位でのデータ通信サービスを提供するものあであり、以下のような特徴を持っている。

・TCPはデータをバイト単位のデータ列として扱う。実際に送信を行なうときには、Segmentという単位に分割して送信する。この分割する場所を決めるのはTCPであって、TCPを使って通信を行なおう、というユーザー(もしくはユーザーが書いたアプリケーション)ではない。

・Segmentの境界-大きさは変化する。だからデータを中継するときに、200バイトのデータが二つ送られてきたのを一つの400バイトのデータとして送るのもありである。

・Data Link Layerのところで説明したSliding Window Machanismと同様のアルゴリズムを用いるが、ウィンドウの大きさはバイト単位で決められる。

そしてこの大きさは動的に変化する。何を元に変化させるかというと、データの受けとりてからAckとともに送信されてくる「受け捕り手が受け取ることのできるバイト数」情報による。これについては後述する。

 

さて、このTCPのヘッダーは以下の通りとなっている。

Source Port(16ビット):

Destination Port(16ビット): これらはUDPで説明したのと同じく、プロセスを識別するための番号である。しかし同じ名前で同じ長さ(16ビット)であるからといって、UDPとまったく同じ番号が使われるとは限らない。

Sequence Number(32bits):Segment中の一番最初のバイトデータのシーケンス番号

Acknowledgement Number(32bits):次に受け取るだろうと思っているバイトの番号。つまり受け取り手側はこの番号のデータまでは受け取ったことを意味する。

Flags(6bits):このフィールドには1ビットのフラグが6っつはいっている。

Urgent Pointer (URG):前述したUrgent Pointerにはいっているデータが有功かどうかのフラグ。0であればUrgent Pointerにデータがセットされていても無視される。

Acknowledgement Valid(ACK bit) :Acknowledge number フィールドが有効なときにセットされる。実際問題として、このフィールドが0であるのは、コネクション確立時くらい、、ということなのだが。

Reset(RST):なんらかのエラーによって通信ができなくなった場合、通信を迅速に中断するのに使われる。つまり

「これ以上送るデータはねえよ」

となって、双方平和のうちに通信を終了する場合ではない。このフラグがたったSegmentを受け取った場合、通信を中断し、アプリケーションに対してその旨通知する。

Push(PSH):送り手または受け捕り手のバッファにあるデータをアプリケーションに渡す。このビットをセットするのは、TCPではなくそれを利用するアプリケーションである。ではなぜこれをしたいと思うか。

TCPはデータを送る際にSegmentに分割して送ると書いた。でもってこの分割の仕方はTCPに任せられていて、アプリケーションが関与するところではない。さて、「アプリケーションがデータを送り終わった」と思ったが、TCPは「おれは200バイト単位で情報を送るんだ。しかしまだ115バイトしかバッファにためていない」と思った場合どうなるか。TCPは基本的に「アプリケーションはどんどん情報を送ってくる」と勝手に思い込んでいる。したがってこの場合にはアプリケーション側から明示的に「おしまいだよん」と教えてやる必要がある。

したがってバッファを空にする、ため、このビットをセットしたSegementを送るわけだ。実際これを忘れると通信はそこでいつまでもとまってしまう。

Synchnonization (SYN):コネクション確立時に使われる。詳細は後述。

Finish(FIN):コネクション終了時に使われる。詳細は同じく後述。

Flow Control Window(16bits):受信側のウィンドウのサイズ。Ackにはこの情報がセットされて送られてくる。送信側はここで指定されたWindow Sizeを超えてデータを送信することはできない。またもし受信側でデータが受信できない場合には、このフィールドを0にしてAckを返信する。

Checksum(16bits):TCPヘッダデータのチェックサム

urgent pointer(16bits):Urgent Dataというからには緊急性を要するデータのなのだろうか。これがある場合には、このフィールドはSequence Numberから何バイト目にそのデータがあるかを示す。

ではこの「緊急性を要するデータ」とは何のことだろうか。具体的にはいま処理しているものをすっかり忘れてもこれを読めということだ。ではどんな場合にこのデータを送りたくなるかといえば、例えばリアルタイムで通信しているときに、通信を強制終了させるような場合である。

通信といってもTCP/IPで送っているのだから、そのままでは受け捕り手はバッファの先頭からのんびりとデータを読んで処理していく。ところが「とにかく終れ」というから強制終了というのであり、この場合はそんな悠長なことをやられてはかなわない。というわけで、強制終了を示すデータを送るときには、このUrgenet Dataにその位置を設定しておき、受け捕り手は

「おっといけねえ。急ぎのデータがあるわい」

と思い、指定された位置を見ると「強制終了」とあるので、それまで読み取り、バッファに止めておいたデータはすっかり忘れて通信を終了させるわけだ。

 

Options(長さは可変):送受信双方において同意するべき、あれこれのオプション。例えばMaximum Segment Size Optionというものがある。データを送る際には(エラーが少なくと仮定しての話しだが)細かいSegmentをたくさんおくるよりも、少数の大きなSegmentを送信したほうが効率がよい。

なるほど、と思いメモリがいっぱいあるような大きな機器は巨大なSegmentを送ろうとする。ところが、データの送り先はそんなにメモリの余裕のない機器かもしれない。となると巨大Segmentを送られても処理ができなくなる。そのような場合にはあらかじめこのオプションを指定しておき、

「エラーがなくてSegmentを大きくするけど、最大はここまでね」

と上限を設定しておくわけである。

Padding(可変):TCPヘッダのサイズが32ビットの整数バイトなるようにする詰め物

Data offset(4bits):TCPヘッダの中に32-bitのワードがいくつあるか、というデータ。どこがヘッダーの終わりで、どこがデータの始まりかを識別するために使用。

 

さて、こうやってざっとTCPヘッダの内容をみてくると、(仮にDLCの内容を覚えている人がいたとしての話しだが)妙なことに気がつくかもしれない。WindowsSizeを示すフィールドは16ビットだが、Sequence noはその倍の長さ(数が倍というわけではない)の32ビットもあるのだ。実に2^16乗=65536倍である。ところがDLCのところでは

「Sliding Windowのシーケンス番号は、ウィンドウサイズの2倍は必要だ」

と書いた(と思う)となるとこの差異はなんなんだ。

 

DLCとTransport layerの違いについて、は冒頭にまとめた。その中で、「古いパケットがとんでもないタイミングで届くかもしれない」とかいている。つまりウインドウサイズに比べシーケンス番号があまり大きくないと、頻繁にシーケンス番号が再利用されることになる。さて、再利用を始めたときに、それ依然のパケットが届けられるとどうなるか?正規のデータとしてうけとってしまうかもしれない。このためシーケンス番号を十分に大きくとれるようにしている。また後述するコネクション確立のところで、シーケンス番号をどこから始めるか、を設定できるようにしている。

なぜいつも0から始めないか。たとえば比較的短いデータを、短いインターバルで何度も送るとしよう。すると

0からスタート。1000まで使用

0からスタート。900まで使用

 

ということを繰り返すと、比較的小さいシーケンス番号を何度も短い間隔で再利用し

てしまうことになる。

さらにIPには「ネットワーク内でどれだけ生きていていよいか」というフィールドが存在しているのもこの問題を避けるためである。

 

さて、かのようなデータをもつTCPではどのようにコネクションの確立・終了を行なうか。また通信を行なっている最中はどのような制御を行なうかを次に見てみる。

 

・コネクション確立・終了

TCPは"3-way handshake"なる方法を用いてコネクションを確立する。これは双方がデータを送る準備をし、かつ相手が準備ができていることを知ること。また使用するシーケンス番号を通知し会うために行われる。具体的な手順は以下の通り。

 

1)TCP Aは最初のシーケンス番号を選ぶ。(ここではSeqAとする)でもってBに以下のフィールドをセットしたデータを送る。

SYN = 1, ACK = 0, Sequence Number = SeqA

2)データを受け取ったTCP Bでは、SYN=1なので「ああ。コネクションを確立しようとしているのだな」と思う。そしてAがどこからシーケンス番号を始めようとしているかを知る。

さて、じぶんでも最初のシーケンス番号を選び(SeqBとする)Aに以下のフィールドをセットしたデータを送る。

SYN=1, ACK = 1, Sequence Number = SeqB, Acknowledge Number = SeqA+1

3)さて、次にデータをうけとるのはAである。Bがどこからシーケンス番号を始めようとしているかをしり、Acknowledgeとして以下のデータを送る。

SYN=0, ACK = 1, Sequence Number = SeqA+1, Acknowledge Nubmer = SeqB+1

4)これで両方が相手が使用するシーケンス番号を確認できたので、めでたくデータ通信開始。

 

さて、これとは逆にコネクションをきるときである。こちらはなかなか面倒である。

基本的にはFINビットを1にしたデータをおくることで「もうおしまいだよん」と相手に知らせることになる。これで話しは簡単かと思えばそうではない。コネクションを解消するためには、双方が「お互いもう送るデータがない」ことを知る必要があるのだ。こうした合意なしにコネクションを落としてしまうと、データ伝送経路中にあるかもしれないパケットが失われてしまうことになる。

 

ではこれがなぜ難しいって?途中の通信回線が信頼できないからだ。この問題はTwo Army Problemといわれて、次のようなモデルで説明される。

 

谷があり、谷底に白軍がいるとしよう。そして谷の両側のがけの上には両方に青軍がいる。

白軍はがけのどちらかの青軍を攻撃すれば、勝つ事ができる。逆に両側のがけにいる青軍が同時に攻撃を行なえば、白軍を打ち負かすことができる。つまりがけの両側にいる青軍は攻撃時間をお互いに確認する必要があるわけだ。

 

同時に「もしもーし」と話しができる無線でもあれば話しは簡単。ところが使えるのは途中で殺されてしまうかもしれない伝令しかないとしよう。この場合、はたしてちゃんと攻撃時間を調整し、合意することができるか?もし確実に合意ができた、と確認できなければ攻撃するのは自殺行為だ。がけの反対側にいる味方が同時に攻撃し

てくれなければ白軍の反撃を食らってこちらが全滅の憂き目にあう。

 

片側の青軍A師団が「10時に攻撃開始」と伝令を送る。反対側にいる青軍B師団が

「10時攻撃開始。了解」

とAckを返す。さて攻撃は10時に開始されるだろうか?

私がB師団の師団長だったら攻撃は開始しない。なぜなら自分が返したAckをA師団が受け取ったと確認できないからだ。もしA師団が

「たしかに10時と伝えたとは思うが、確認がとれないから攻撃は延期」

などと言い出したら、単独で攻撃を開始してしまい、その結果は恐るべきものになるだろう。

 

 

では先ほどの3 way handshakeのように、「10時攻撃開始を了解した、という内容を受け取った」とA師団からB師団にAckの伝令を送ったらどうだろう。

しかしこれでも状況は改善されない。B師団がちゃんとそのAckのAckを受け取ったかどうかA師団ではしる由がないからだ。もし伝令がどこかに消えてしまい、B師団がそのAckのAckを受け取れなかったら、さきほどと状況はかわらない。B師団は攻撃を開始しないだろう。となればA師団単独で攻撃を開始してしまう危険性が残り、以下同文である。

 

かくのごとく、どうやったところで相手が攻撃開始時期について確認した、と確信をいだくことはできない。証明は簡単だ。仮になんらかの方法によって双方が攻撃開始時刻について相手が知っている、と確信を持てたとしよう。すると最後にメッセージを運ぶ伝令が存在することになる。

もしこの伝令が不必要なものであれば、そんなものはなくしてしまえばよい。というわけで必要かくべからざる伝令だけが残る。ところが設問の定義により、伝令は途中でやられてしまうかもしれない。必要かくべからざる伝令がやられてしまう可能性がある、ということは、当初の「お互いが確信を持てる」という前提が間違っていることを意味する。(つまり背理法だ)

 

この例の「同時に攻撃」を「同時にコネクションを解除」に置き換えてみればよい。状況としては同値である。つまり双方が「コネクションを解除することについて相手も同意した」と確信することはできない。したがって現実的には一定時間待つという方法が用いられる。

 

TCP Congestion Control

さて、これまた前述したように、TCPではCongestionの面倒もみてやらなければならない。特にConnectionlessのネットワーク層のサービスを利用している場合、データの混雑を検知してそれに対処するのはTransport 層の重要な役割となる。

 

ではCongestionを起こす要因はなにか。

1.現在のWindow size。Window sizeが大きいということは、多くのデータが送られ

れる、ということであり、混雑の要因となりやすい。

2.再送タイマーのタイミング。もし再送タイマーが短すぎると、やたらとパケットが単に遅れているだけで失われていないのに再送信が起こり混雑はますます悪化することになる。

 

それぞれの要因について以下に述べる。

 

・再送タイマー

この値が短すぎると、必要以上に再送信をしてしまって問題がある、ということは前述した。では長すぎるとどうなるかといえば、今度は必要以上に再送信を待ってしまうことになる。つまりどっかでデータが失われているのにいつまでもぼんやりと

「なーんで返事がこないのかなー。もっとまってみるかなー」

とやってしまうわけだ。

 

長すぎても短すぎても問題になる、ということであれば何が適正なのだろうか。理想的にはRound Trip Time(RTT)つまりデータがお送られて返ってきくるまでの間の時間に近ければよい。(どちらにしてもこれだけの時間がかかるわけだから)しかしながらこのRTTは動的に変化するので(経路は変わるかもしれないし、途中のネットでの遅れも一定ではない)一たん値を設定してはいおしまい、という事はできない。ではどうするか。

 

Segmentを送りだしたときにタイマーをスタートし、Ackを受け取ったところでタイマーをストップさせる。これで、RTTが計測できたわけだが、これをそのまま使うわけにはいかない。なぜかといえば、RTTは動的にあれこれ変動するからだ。たまたま一つのSegmentを送るときに時間がかかったかもしれないが、平均すればもっと短い時間ですむかもしれない。

 

というわけで、Smoothed RTT(SRTT)-つまり平準化したRTTを以下の式で定義する。

 

SRTT=(1-α)×RTTNow + α×SRTTOld

 

つまり加重平均をとるわけだ。αの値は0.8から0.9の間で使用される。つまり今計測されたばかりの値よりも、前の値にずっと重きをおくわけだ。このことにより、刻々変化するRTTにふりまわされる、ということがなくなる。

 

こうしてもとまるSRTTはRound Trip Timeの推定値だから、再送信のタイマーの値は若干長めに設定する必要がある。TCPはRTTの分散(どのていどばらつきがあるか)のデータも計算しており、実際には両者を加味して再送信タイマーの値を設定することになる。

 

Slow Start TCP

Window Sizeを大きくすると一度にどんとデータが送れるから効率がいいといえばいいのだが、Congestionの原因にもなりかねない。物事すべからく適切な水準で保つ、ということを覚えなくてはいけない。そしてこの「適切なWindowサイズ」というのもネットワークの状況に応じ、あれこれ変わる。ではどうするか。

 

TCPはCongestion Windowなるものを保持し、Congestionを念頭においた場合にどういうWindow Sizeをとるべきか、という値を持っている。これはFlow ControlのためのWindowとは別に決まり、実際には両Windowサイズのうち、小さいものが使用される。

 

さて、このCOngestion Windowのサイズは以下のように変化する。

 

1.Congestionが発生しない限りにおいて、Windowsサイズを増大しつづける。ただしゆっくりと。ゆっくりと増やしていけば、その結果何が起こるかはNetworkの状態を監視していればわかる。つまりフィードバックがかけられるわけだ。これを急にどんと増やしてしまうと何がおこるかはだいたい想像がつく。具体的には、送ったSegmentに対して、Ackを受け取ると、これはCongestionなしにちゃんとデータが送られた知るしと思い、Congestion Windowのサイズを示すcwndという値を増やす。

 

2.Congestionが発生した場合は、急激にWindow サイズを減らす。この場合はのんびりとしている場合ではない。とにかくCongestionを解消するのが第一優先だ。ではCongestionをどうやって検知するかといえば、Ackが返ってこない場合をCongestionの発生とみなすのである。そしてcwndの値を1にする。そして現在のcwndの値をssthreshという変数に保存する。これからはまたAckを受け取るたびにcwndの値を増す。ssthreshの値にcwndが到達すると、その増加させるペースを落とす。

 

これで終わり?

さて、以上で本講義でカバーした内容はだいたい記述できたと思う。講義ではこの後に期末試験があった。この試験は試験時間が3時間半ほどあり、問題・回答用紙が数十枚あったと記憶している。まさか3時間半もの時間を全部使う事はあるまい、と思っていたのだが、実際には最初から最後までくるったように解答用紙をめくり、何かを書き込んでいた。

試験がおわってげっそりしていると、スイス人の友達もげんなりしていた。「おれはこんな試験はみたことがない。これに近いものすら聞いたことがない」と言っていたが。

私としてはここで「当初の予定は書き上げたもんね」といっておしまいにすることもできる。実際この文書はもう一年以上にわたって(その間の休止期間も長いのだが)書き続けている。そろそろ一区切りつけるためにここまでとりあえずの完成とし、題名一覧に乗せることにした。

しかしながら私にとっても読んでくれる奇特な人にとっても足りない部分が山ほどあることに気がついてはいる。各レイヤーの原理原則はだいぶ書いたと思うのだが、「実生活で目にする呪文」の解説が大幅に欠けている。

したがってまずはそれらの呪文について随時それらについて調べた内容を追記していくことにしよう。

 さて、まずは仮にネットワークのセットアップを誰かがやってくれる、もしくはマニュアルを読んで中身をなにも理解せずにすませたとしても目にせざるを得ない呪文、URLから書いていく。

 次の章


注釈