エンジニアHubPowered by エン転職

若手Webエンジニアのための情報メディア

TCP/IPをわかりやすく - 通信プロトコルの基礎知識を図解で学ぼう

現在のインターネットを支える技術であるTCP/IPについて、基礎となるプロトコル群と、TCPの基本機能を丸田一輝さん、 中山悠さんに解説していただきました。

TCP/IPメインカット

今からおよそ50年前、パケット交換方式による世界初のコンピュータネットワークであるARPANETが構築されました。それ以来、TCP/IP(Transmission Control Protocol / Internet Protocol)は通信を実現する基盤技術として使われ続けています。今ではパソコンに限らず、スマートフォンやゲーム機、センサー、最近では自動車など、無線通信機能を持ったさまざまな端末も含めコンピュータネットワークは構成されています。

その中でも「通信の信頼性を確保する」役割を担っているTCPは、その性質上、多くの機能を備えています。加えて、時代とともに進化するアプリケーションの要求に合わせて常に進化を続けています。本稿では、現在のインターネットを支える技術であるTCP/IPについて、基礎となるプロトコル群と、TCPの基本機能について解説します。

プロトコルスタック

コンピュータネットワークは非常に身近な存在である一方で、たくさんの通信プロトコルで支えられる非常に複雑なシステムです。プロトコルは、もともと複数の人間で作業を実行するための取り決めという意味で使われていた言葉ですが、コンピュータネットワークの発展に伴い、ソフトウェア間の通信規約を指すためにも用いられるようになりました。異なるベンダー製品間の通信を実現するために、通信プロトコルは不可欠なものです。

コンピュータは、通信を実現するための一連のプロトコル群を一つのモジュールとして実装しています。このモジュールは、プロトコルスタックと呼ばれたり、プロトコルスイートと呼ばれたりします。プロトコルスタックの実装はOSにより異なりますが、OSI(Open System Interconnection)参照モデルを用いて統一的に表現することができます。

プロトコルを階層化することによって、ソフトウェアの開発者はその階層が担う役割に特化した機能のみ開発すれば良いことになります。これにより、実装の容易化とともに責任の区分を明確にすることができるのです。

OSI参照モデルは7つの階層(レイヤ)で構築され、階層が低いほどハードウェアよりの、階層が高いほどソフトウェアよりの処理を行います。各階層の名称と主な機能は以下のようにまとめられます。

  • 第7層:アプリケーション層
    • それぞれのアプリケーションの中で通信に関係するプロトコルを定める
  • 第6層:プレゼンテーション層
    • アプリケーション固有のデータフォーマットをネットワーク共通のフォーマットに変換
  • 第5層:セッション層
    • アプリケーションレベルで送受信間における通信の接続を管理
  • 第4層:トランスポート層
    • コネクションを確立・切断し、アプリケーションの要求に応じた方法でデータを転送
  • 第3層:ネットワーク層
    • アドレスの管理や経路の選択を行い、宛先までデータを届ける
  • 第2層:データリンク層
    • 物理的に接続された2つの機器間での通信を提供
  • 第1層:物理層
    • 情報ビット列を電気ないしは光の信号に変換し、物理媒体を通して情報伝送

TCP/IPも同様に階層型プロトコルの概念としてモデル化されていますが、OSI参照モデルとはやや異なります。両者の対応関係と、該当するプロトコルの例を以下に示します。

TCP/IP参考図版001

OSI参照モデルにおけるセッション層以上は、TCP/IPではアプリケーション層に属しており、ほとんどの機能は個々のアプリケーションによって実装されます。実際にはほとんどのアプリケーションはOSI参照モデルではなく、TCP/IPに準拠しています。

OSI参照モデルは機能別にプロトコル階層をモデル化しているのに対し、TCP/IPは実装や実用性を主眼にしてモデル化されていることから、現在までに広く普及してきた、という背景があります。

TCPとは

TCPの基本スペックは、インターネット技術の標準仕様群であるRFC(Request For Comments)793に規定されています。TCPは「コネクション型」と分類され、送受信を行う機器間で通信の開始と終了を確認します。データ転送時には、送信側はデータを送信し、受信側はそれに対する確認応答であるACK(Acknowledgement)を返すことによって、両端のホスト間でデータが届いたかどうかを確認し合いながら確実にデータ転送を行います。

通常、データの送信単位にはデータ部とヘッダ部があり、ヘッダは各階層ごとにフォーマットが規定されています。TCPでは、両ホストはトランスポート層のヘッダのみを参照し、通信のやりとりを行います。またTCPではこの送信単位のことを「セグメント」と呼びます。

TCPのヘッダには、「セグメントが何番目まで送られたか」を示すシーケンス番号や、「何番目まで受け取りました/次は何番目が欲しい」ということを示す確認応答番号、「受信可能なセグメント量」を示すウィンドウサイズ等のフィールドが用意されています。以下の図の例では、1000bytesごとにデータを送信し、シーケンス番号が更新されていく様子を示しています。

シーケンス番号の更新イメージ

TCPは、通信する両者が同時にデータを送受信可能である全二重通信を提供しますが、ネットワークの状況によっては宛先へ到着するセグメントの順序が入れ替わったり、消失してしまったりすることがあります。

この問題を解消するために、TCPはシーケンス番号によるセグメントの順序制御や、消失したセグメントの再送制御、そしてネットワークの混雑(輻輳、ふくそうと言います)をできるだけ回避するような送信セグメント量の制御、といった機能を持っており、エンドツーエンド間における信頼性の高い通信を実現しているのです。

また、同じトランスポート層プロトコルとしてUDP(User Datagram Protocol)があります。UDPは通信の品質よりもリアルタイム性を重視したプロトコルです。TCPとUDPがそれぞれ備える機能を下の表にまとめます。この図からもわかるように、UDPは非常にシンプルである一方、TCPは多くの機能を備えていることがわかります。以降では、TCPの基本機能を解説します。

TCPとUTP比較

コネクション管理

コネクションの確立:3ウェイハンドシェイク

TCPでは通信を開始する前に、通信相手との間でコネクションを確立します。TCPは全二重の通信を提供するプロトコルなので、送受信側の双方からの接続確立要求を行う必要があります。コネクションの確立は下記の手順で行われます。

  1. 送信側から確立要求としてSYN(コネクションの確立要求)フラグの有効化されたTCPパケットを送信
  2. 受信側はこれに対するACKと、同時に受信側からの接続確立要求として同TCPヘッダのSYNフラグを有効化して送信(SYN+ACK)
  3. 送信側が受信側からのSYNに対するACKパケットを送信

このように3回の送信でコネクションの確立が成立するため、3ウェイハンドシェイクと呼ばれます。この流れを以下に示します。

ウェイハンドシェイク図版

コネクションの切断:ハーフクローズ

コネクションの終了は片方ずつ行います。これは、TCPのデータ転送が全二重通信にて行われている場合、一方のコネクションが先にデータ転送を完了したとしても、もう一方のデータ転送が継続している可能性があるためです。

したがって、コネクション切断が完了するまでには4つのパケットがやりとりされることになります。このプロセスをハーフクローズと呼びます。コネクション切断は、FINに対するACKパケットを返送した後に、一定のタイムアウト期間を待って行われます。双方の切断が完了すれば、TCPによるコネクションの終了となります。

  1. 送信側は最初にFIN(コネクション終了要求)を送信
  2. 受信側はACKを送信し、続けてFINを送信
  3. 送信側はFINを受信して最後のACKを送った後、一定時間待ってコネクション終了

この様子を以下に示します。

ハーフクローズ図版

フロー制御・ウィンドウ制御

フロー(流量)制御

受信側の機器は、データを処理するために一時的に記憶する領域として「バッファ」を備えています。これは機器のCPU等の処理速度も含めてスペックに依存します。受信側では、受け取ったデータをいったん受信バッファに溜めて届け先となるアプリケーションに引き渡します。

受信側が受信バッファよりも大きなデータを受信してしまうと、そのデータを取りこぼし、データの消失と見なされてしまい、送信側に無駄な再送をさせてしまうことになります。このことから、ネットワークの輻輳以前にまず通信機器としての許容量を把握し転送量を調整しなければなりません。そこで受信側は送信側に受信可能なデータサイズをACKとともに通知することで、送信側がセグメントの送信量を調整できるようになっているのです。これを「フロー制御」と言います。

ここで、TCPはデータ転送に「ウィンドウ」という概念を取り入れています。送信側は通知されたウィンドウサイズ分のセグメントを一度に送信できるという仕組みになっています。TCPのヘッダにはウィンドウサイズを通知するためのフィールド(rwnd)が定義されており、受信側は、受信可能なバッファ量をこのフィールドに格納してACKを返答します。送受信側のやりとりとしては、以下のイメージです。

フロー制御図版

ウィンドウ制御

TCPの送信側では、一度に転送可能なデータ量を表すパラメータであるウィンドウサイズ(swnd)が定義されており、これを増減させながら、未送信のデータを転送します。このウィンドウ制御には「スライディングウィンドウ」という考え方が用いられています。

下の図を用いて説明します。ウィンドウ内にあるセグメントはACKを待たずに送信することができるので、図中(a)において、4つのセグメント(2、3、4、5)は一度に送信され、ACKの受信を待っている、という状態です。

次に、(b)の状態に移行します。例えば、スロースタートというフェーズでは、2番のセグメントに対するACKを受信すると、ウィンドウは1つ右へスライドすると同時にその大きさが1つ拡張されます。そして送信待ちであった6番と7番のセグメントが送信されます。このようにして、ACKが到着するたびにセグメント数を増加させながら送出していきます。

ウィンドウ制御図版

ウィンドウをスライドさせる際に増減させるウィンドウの数(ウィンドウサイズ)は輻輳の状態等によってさまざまなアルゴリズムによって決められます。このウィンドウサイズは輻輳ウィンドウサイズ(cwnd)として定義されており、以降にて説明するさまざまな輻輳制御アルゴリズムによって能動的に決定されます。ただし、受信側の許容量を超えて送ることはできず、通知されるrwnd値がcwndよりも小さい場合には、rwndが優先されることになります。

TCPでは、「フロー制御」、「ウィンドウ制御」、「輻輳制御」というさまざまな方法によってウィンドウサイズを決定します。それらの関係をここで簡単に整理しましょう。

まず、「ウィンドウ制御」が上位概念に相当し、スライディングウィンドウ方式に基づいてデータの転送を行う、というものです。送信ウィンドウサイズswndを決定するための方法が「フロー制御」と「輻輳制御」となります。

フロー制御は受信側から通知される受け入れ可能なウィンドウサイズrwndに基づいて送信ウィンドウサイズswndを決定するものです。輻輳制御は、ネットワークの輻輳をできるだけ抑えつつ、かつ効率よくデータを転送することを目的とし輻輳ウィンドウサイズcwndを決定するものです。 rwndがcwndよりも小さければ、rwndを優先的に採用します。

輻輳制御・再送制御

ネットワークでは、その中でやりとりされているデータの総量や混雑状況を全く予測することができません。ただ、ネットワークに大量のデータを送り続けると、どこかの中継点でデータが溢れ、パンク状態になってしまうことは明らかです。この状態が輻輳です。

TCPの輻輳制御アルゴリズムは、観測可能なある情報に基づき転送量の制御を行います。その基本的な手掛かりの一つはセグメントの消失です。基本的な制御方法としては、セグメントが消失していないときはネットワークが空いていると判断し転送量を上げ、反対にセグメントが消失したときにはネットワークが混雑していると判断し転送量を下げます。このように TCPでは転送量の上げ下げを繰り返しながら通信を行います。つまり、セグメント消失が起きる=通信経路がパンク状態にある、と判断しているのです。

基本的な輻輳制御では、輻輳ウィンドウサイズcwndを「スロースタート」「輻輳回避」「高速リカバリ」といったアルゴリズムを使い分けることにより制御します。以降では、TCPの中でも重要な機能の一つである再送制御と併せてそれぞれのアルゴリズムの動作を解説します。

スロースタート

それぞれのアプリケーションが通信開始とともに大量のデータを送信し始めると、すぐに輻輳が起こってしまうことが予想されます。これを防ぐため、通信開始時には「スロースタート」と呼ばれるアルゴリズムに従ってデータを送信します。送信側はまず輻輳ウィンドウサイズcwndを1セグメントサイズ(mss)に設定して送信し、それに対するACKを受け取るとcwndを1セグメント増加させます。

cwnd = cwnd + mss

下の図でも示すように、送信側はACKを1つ受け取るごとに2つのセグメントを送信できることになります。セグメントの送信からACKを受信するまでの往復遅延時間(RTT:Round Trip Time)ごとに見た場合、cwndは指数関数的に増加します。そして受信側から通知されたウィンドウサイズrwndもしくは予め定められた最大値に達した後はそれらの値となります。ここでセグメントの消失を検出して再送を行った場合は、cwndを1とし再度スロースタートから始めます。

スロースタート

輻輳回避

スロースタートは指数関数的に輻輳ウィンドウを拡大するため、時間の経過とともにデータ転送量が大幅に増えていきます。セグメントの消失後、スロースタートによってセグメントを送り続けると、再び輻輳が起こる可能性があります。これを防ぐための改良法として輻輳回避アルゴリズムが考案されました。再送が起きた時点でのcwndの半分の値をスロースタート閾値として設定し、cwndが当該閾値に達したとき、ACKを受け取る毎の輻輳ウィンドウの増加分を緩やかにします。このときのcwndの更新式は

cwnd = cwnd + mss/cwnd

となります。これにより、下の図に示すように、ウィンドウサイズはRTTごとに1セグメントサイズずつ線形に増加していく形となり、輻輳が発生したときのウィンドウサイズまで徐々に転送量を上げていくことになります。

輻輳回避

再送制御

セグメントまたはACKがネットワーク中で消失した場合、そのセグメントは再送されなければなりません。再送は、通信の信頼性を提供する最も重要な機能です。同時に、転送効率も考慮する必要があります。セグメントの消失は、

  1. 再送タイマーがタイムアウトした場合
  2. 重複するACKが一定数以上届いた場合

の2つのケースで判断され、それらを契機としてセグメントの再送が行われます。以降は、それぞれのケースでの再送制御を解説します。

A. 再送タイマーによるタイムアウト制御

データの消失を検出する一つの手段は、「送信側にACKが届いたか否か」です。これを判断するために、タイマーを用います。セグメント送信後、当該セグメントに対応するタイマーをセットし、一定時間待ってもACKが返信されない場合、つまり、タイムアウトとなった場合に、送信側は同じセグメントを送信します。

再送タイマー

再送タイマーはセグメントが送出される度にセットされますが、このとき、適切な再送タイムアウト値(RTO:Retransmission Time Out)を決定することが重要となります。RTOが長すぎると、セグメントの消失が起こったとしても余計に待つことになるため転送効率の低下を招き、逆に短すぎると、正確にACKが返ってきているにも関わらずそれを待たずに不要な再送を行ってしまいます。

ここでRTTが一つの重要な指標となりますが、ネットワークの混み具合や経路の長さによってその値は大きく変動します。そこでACKの受信ごとに観測されるRTTを基に、RTOを随時更新していくことでRTTの変化に動的に対応できるようにしています。

B. 重複ACKの利用:高速再転送アルゴリズム

セグメント消失の判断にタイムアウトまで待つのは、余計な時間をかけている、ということでもあり、転送効率の低下を招く一因と考えられます。そこで、より効率的な再送制御としてACKを利用する方法が考案されました。

セグメントが消失した場合、受信側では期待するシーケンス番号とは異なるデータを受信することになります。このような場合、受信側は期待するセグメントを受け取るまでそのセグメントを要求するACKを送り続け、送信側は同じシーケンス番号を要求するACKを受信し続けることになります。これを重複ACKと言い、高速再転送(Fast Retransmit)アルゴリズムではこの特徴を利用します。

送信側は、受信したACKに続いてさらに同じものを3回連続して受信すると、当該セグメントは消失したと判断し、タイムアウトを待たずに再送処理を行います。このアルゴリズムはタイムアウトによる再送制御よりも高速に動作することから、高速再転送と呼ばれます。なぜ重複ACKの数が3回かというと、1回や2回の重複受信の段階においては、単にセグメントの順序が入れ替わっているだけ、という可能性もあるためです。

重複ACKの利用

高速リカバリ

輻輳回避は、輻輳を検出した後のセグメント送信量を少しずつ増加させるようにして再度の輻輳を起きにくくする工夫でした。一方、輻輳を検出して再送を行った直後、毎回スロースタートから再開するのでは転送効率が必ずしも良いとは言えません。

そこで今度は、再送後の輻輳ウィンドウcwndを小さくし過ぎないように改良することで転送効率を向上させる、という工夫が考案されました。高速リカバリは、輻輳の程度が小さい場合に有効と考えられることから、重複ACKの受信を契機とする高速再転送と組み合わせて用いられます。再送後であってもcwndを小さくし過ぎず、ある程度転送量を維持できるようなウィンドウ制御を行います。

具体的には、高速再転送が行われた後に、輻輳ウィンドウサイズをcwnd/2+3とした値に設定し、以降重複ACKであっても受け取るごとに輻輳ウィンドウを1ずつ増加していきます。そこで未送信のセグメントがあれば送信します。再送セグメントに対するACKを受信すると、輻輳回避フェーズへと移ります。

これまでに説明した輻輳回避アルゴリズムと再送制御が複合的に動作したときの輻輳ウィンドウcwndの変化の例を以下に示します。

高速リカバリ

このように、TCPの輻輳制御アルゴリズムは「輻輳が起きない限界近くまで転送量を上げる」動作と、「輻輳検出後の再開時にできるだけ転送量を下げ過ぎないようにする」動作を繰り返すことによって、ネットワークの混雑状況に柔軟に対応しながら高信頼かつ効率の良いデータ伝送を実現しています。

近年のTCP輻輳制御アルゴリズム

TCPは歴史のあるプロトコルですが、今なお改良、進化が続いています。中でも特に多くの改良が行われてきたのが輻輳制御アルゴリズムであり、Googleが2016年9月に新しいアルゴリズムであるTCP BBR(Bottleneck Bandwidth and Round-trip propagation time)を発表するなど、近年でもトピックが更新されています。

そもそも輻輳制御アルゴリズムとは、輻輳ウィンドウサイズをどのように調節するか、に関する方法です。より効率の良いデータ転送を実現するため、技術の進歩やネットワーク環境の変化に合わせながら、これまで多くのアルゴリズムが開発されてきました。

これまでに開発された輻輳制御アルゴリズムは、大きく3つのタイプに分類されます。すなわち、輻輳の指標としてパケットロスを用いるLoss-based、遅延を用いるDelay-based、その両方を用いるHybridです。これまでに開発されたアルゴリズムの名称を以下の年表にまとめます。

アルゴリズム名称年表

これまでに説明してきたアルゴリズムはLoss-basedであり、ネットワークの混雑度合の指標としてパケット廃棄数を用います。初期のTCP Renoでは高速リカバリのみが採用されていました。次に登場したTCP NewRenoでは高速再転送に加え、高速リカバリ段階において複数のセグメント消失が生じた際の改良がなされています。NewRenoは長い間にわたって標準的なアルゴリズムとして広く利用されてきました。

Loss-basedタイプで近年主流となっているアルゴリズムとしてCUBICがあります。これはLinux 2.6.19以降で標準搭載されています。CUBICは現在のインターネットで一般的である広帯域・高遅延環境(ロングファットパイプと呼ばれます)に対する適正が高く、また既存アルゴリズムとの親和性が高いなど、多くの優れた特長があります。

Delay-basedアルゴリズムでは、輻輳状態の指標としてRTTを利用します。TCP Vegasとして1990年代半ばに導入されました。これはつまりRTTが大きくなるほど、ネットワークが混雑していると判断して輻輳ウィンドウサイズを調整します。これは、ルータ等のネットワーク機器のメモリ上での転送待ち時間は、そこにどれだけデータが蓄積されているかによって大きく変動する、という性質に基づいています。

ルータ等から送出されるデータの転送レートは一定なので、ネットワークの混雑により単位時間あたりの入力データ量が大きくなれば、メモリ上に蓄積されるデータ量が増え、新しく到着した新しく流入したパケットを転送するまでの時間が増加してしまいます。そこで、RTTが増大した際には輻輳ウィンドウサイズを抑え、混雑を解消させようとするのがDelay-basedアルゴリズムなのです。

ただし、インターネットのようなさまざまな通信が混在する環境においては、RTTが増大するとすぐに輻輳ウィンドウサイズを減少させてしまうDelay-basedアルゴリズムは、Loss-basedアルゴリズムに駆逐されやすかった、という課題もありました。

そして、近年開発された重要なDelay-basedアルゴリズムがBBRです。BBRは、Google社が2016年9月に発表して以降、Linuxカーネル4.9以降で利用可能となっています。またYouTubeやGoogle Cloud Platform等でも用いられるなど、その利用が広がり注目を集めています。

BBRの基本的な考え方は、Loss-basedアルゴリズムにおけるパケットロスによる輻輳検知では遅すぎる、というものです。その代わりに、パケットがバッファに蓄積され始める直前、つまりネットワークの帯域はフルに活用しつつ、バッファ遅延を発生させない、という状態を理想的な状況とします。

この理想的な状況を目指すためにスループットとRTTを常にモニタリングし、データ送出量とRTTの関係を把握しながらデータ送信速度を調節することで、ネットワークが処理可能な範囲内での最大スループットを出すことを目指します。

BBRは、多くの場合において適切な輻輳制御が可能であることが確認されています。ただし、BBRはまだ比較的新しい輻輳制御アルゴリズムであるため、今後さまざまな検証や改良が加えられていくと考えられます。

まとめ

本記事は、TCP/IPの概要と、通信の信頼性を提供するのに重要な役割を担うTCPを中心に、基本的な機能を解説しました。また本記事は、筆者たちの著書『TCP技術入門――進化を続ける基本プロトコル』の内容を基に執筆しています。詳しくはこちらの書籍にも書いてありますので、ご興味のある方はぜひご参照ください。本稿の内容をもとに、読者の方々がTCP/IPへの理解を深めていただければ幸いです。

丸田 一輝 (まるた・かずき) @marutakazuki

丸田一輝さん
千葉大学大学院工学研究院・助教。博士(工学)。無線通信、主にアレーアンテナ信号処理の研究に従事。2017年度電子情報通信学会論文賞、RCS研究会最優秀貢献賞等。

中山 悠 (なかやま・ゆう)

中山 悠さん
東京農工大学工学研究院・准教授。モバイルコンピューティング、低遅延ネットワーク,IoT等の研究に取り組む。博士(情報理工学)。平成29年度東京大学大学院情報理工学系研究科長賞等。

関連記事

編集:中薗 昴