TCP

TCPは"Transmission Control Protocol"の略で、トランスポート層のプロトコルの一つです。TCPは、信頼性の高い通信を提供するために設計されており、以下のような特徴を持っています。

  • コネクション指向: 通信を開始する前に、接続を確立する必要があります。これにより、データの送受信が確実に行われます。

  • 信頼性: データの順序が保証され、パケットの損失や重複が検出され、再送信が行われます。

TCPの概要

それではTCPの概要について確認しましょう。

TCPでは情報は連続して入出力されるということで、UDPにおける『データグラム』とは違う『ストリーム(stream)』という概念で扱われます。 ストリームは、データの連続的な流れを表し、TCPはこのストリームを確実に送受信するための機能を提供します。

  • ストリームには順序(最初に送信されたデータが最初に届く)を保証する必要があります。

  • ストリームは、データの断片化を行わず、連続したデータとして扱います。つまり、アプリケーション層でのデータの分割や結合は必要ありません。

    • 分割は必要であればTCPの層が行うため、アプリケーション側では考えなくて良いということです。

  • ストリームは、データの到達確認を行い、パケットの損失や重複を検出し、再送信を行います。これにより、信頼性の高い通信が実現されます。

順序の保証

TCPにおいて、送出されるデータは順序が保証されるようになっています。 これは、断片化が発生する際に『この断片はストリームの何番目』という情報を付与して送信されるためです。

受信側はこの情報を利用して、受信したデータを正しい順序で再構築します。

信頼性の保証

TCPは、信頼性の高い通信を提供するために、以下のような機能を持っています。

  • 再送信: パケットが損失した場合、TCPはそのパケットを再送信します。これにより、データの完全性が保証されます。

  • フロー制御: TCPは、送信側と受信側のバッファサイズを考慮して、データの送信速度を調整します。これにより、受信側が処理できる範囲内でデータが送信され、輻輳を防ぎます。

コネクションの確立と終了

信頼性のひとつとしても考えられますが、どこから通信が開始され、どこで終了するのかが明確にしておかないといけません。 そのために、接続(コネクション)の確立と終了の際に、3ウェイハンドシェイクと呼ばれる手順が用いられます。

接続時の確立フロー

        sequenceDiagram
    participant Client
    participant Server

    Client->>Server: SYN (接続要求)
    Server->>Client: SYN-ACK (接続承認)
    Client->>Server: ACK (接続完了)
    

接続の確立の前にある意味確認とも言える『接続要求』の信号を送信します、これをSYNと呼びます。 受け取った相手側は双方向通信を行うため、ACKと呼ばれる『承認』の信号と共にSYNも返します(SYN-ACK)。 受け取った側(すなわち本来の接続要求側)も、ACKを返すということになります。 これにより双方から通信の承認が下り、双方向回線が開くということになります。

接続終了のフロー

        sequenceDiagram
    participant Client
    participant Server

    Client->>Server: FIN (接続終了要求)
    Server->>Client: ACK (接続終了承認)
    Server->>Client: FIN (接続終了要求)
    Client->>Server: ACK (接続終了承認)
    

接続を終了する際には、FINと呼ばれる『ここで通信は終わります』に相当する信号を送信します。これは『接続終了要求』の意味を持ちます。 こちらでも、受け取った側からACK(承認)と共にFINを返します。 送信元もこれに応じてACKを返すことで、双方のFINが成立して切断となります。

もちろん実際にはFINの処理が行われる前に『強制的に回線が切られる』などの問題も起こりえますが、正常な終了という意味ではこの流れとなっています。

フロー制御

フロー制御は、TCPの重要な機能の一つであり、送信側と受信側の間でデータの流れを調整する役割を果たします。これにより、受信側が処理できる範囲内でデータが送信され、輻輳を防ぎます。

TCPでは、ウィンドウサイズという概念を用いてフロー制御を実現しています。ウィンドウサイズは、送信側が一度に送信できるデータの量を制限するもので、受信側のバッファサイズに基づいて動的に調整されます。

具体的には、受信側は自分のバッファの空き状況を送信側に通知し、送信側はその情報をもとにデータの送信速度を調整します。これにより、受信側がオーバーフローすることなく、スムーズなデータ転送が実現されます。

この部分をいかに効率的に行うかというのが輻輳制御(ふくそうせいぎょ)というものです。 輻輳制御のアルゴリズムなどまではこの授業では取り扱いません。

TCPのヘッダー構造

以下の図は、基本となるTCPのヘッダー構造です。

        classDiagram
    class TCPHeader {
        +Source Port(送信元ポート番号): 16bit
        +Destination Port(宛先ポート番号): 16bit
        +Sequence Number(シーケンス番号): 32bit
        +Acknowledgment Number(確認応答番号): 32bit
        +Data Offset(データオフセット): 4bit
        +Reserved(予約領域): 6bit
        +Flags(コントロールフラグ): 6bit
        +Window Size(ウィンドウサイズ): 16bit
        +Checksum(チェックサム): 16bit
        +Urgent Pointer(緊急ポインタ): 16bit
    }
    

それぞれを簡単に説明します。

  • Source Port(送信元ポート番号): 送信元のアプリケーションが使用するポート番号です。

  • Destination Port(宛先ポート番号): 宛先のアプリケーションが使用するポート番号です。

  • Sequence Number(シーケンス番号): 送信されたデータの順序を識別するための番号です。これにより、受信側はデータの順序を正しく再構築できます。

  • Acknowledgment Number(確認応答番号): 受信側が受け取ったデータの最後のシーケンス番号を示します。これにより、送信側はどのデータが受信されたかを確認できます。

  • Data Offset(データオフセット): ヘッダーの長さを示すフィールドで、ヘッダーのサイズを4の倍数で表します。

    • ここだけは少しややこしくて、4バイトを1つのかたまりと考えてn倍という形で表現します。

    • 標準的なTCPヘッダは20バイトのため、ここの値は(20/4で)5です。

    • オプションがある場合は32ビット単位のパディングで入力するキマリのため、必ず4の倍数です。

  • Reserved(予約領域): 将来の拡張のために予約されたビットです。

  • Flags(コントロールフラグ): TCPの制御フラグを示すビットフィールドで、接続の確立や終了、データの確認応答などの状態を示します。

  • Window Size(ウィンドウサイズ): 受信側のバッファサイズを示し、送信側が一度に送信できるデータの量を制限します。

  • Checksum(チェックサム): データの整合性を確認するためのフィールドで、送信されたデータに対するチェックサムが含まれます。

  • Urgent Pointer(緊急ポインタ): 緊急データの位置を示すためのフィールドで、緊急データが含まれている場合に使用されます[1]

シーケンス番号により、上位レイヤーから送られたデータが分割されている状態において『どの位置か』がわかる仕組みとなっています。 そして確認応答番号により『受信側がどのデータを受け取ったのか』がわかる仕組みとなっています[2]

再送について

TCPにおいては、送信側が『受信側がどこまで受信できているか』を確認応答番号で把握します。 そのため、受信側が確認応答を返さない場合、送信側はそのデータが失われたと判断し、再送信を行います。

再送が発生するまでの時間(いわゆる『タイムアウト』)をどうするかはOSによる違いがあります。 一般的なUNIXでは、初期のタイムアウトを0.5秒単位で制御しており、タイムアウトは0.5秒の整数倍とされていることが多いです。ただし最初は多め(6秒程度)に設定されていることが多いです。

再送しても確認応答が戻らない場合は、繰り返し送信していきますが、タイムアウトが倍々となっていきます(0.5秒→1秒→2秒→4秒…)。さらにタイムアウトが増えていくことで、最終的には『再送をやめる』となります。 回線の正常な切断が行われなければ、いずれ再送が起きなくなり、回線は切れた状態となります。

ウィンドウサイズとバースト送信

ひとつパケットを送るたびにACKを待っていると非常に効率が悪いため、実際にはある程度まとめて送信を行っています。 このまとめて送る際に重要となるのがウィンドウサイズです。 確認応答パケットにあるウィンドウサイズを利用して、TCPパケットの分割された状態(セグメント、分割されたIPパケットとざっくり考えておくとよい)をまとめて送信します。 このウィンドウサイズは、受信側のバッファサイズに基づいて動的に調整されます。送信側は、ウィンドウサイズを考慮して、同時に送信できるデータの量を制限します。

この場合の確認応答は『受信の完了したデータの最後のシーケンス番号(+1)』となります。 もし途中のシーケンスが損失した場合は多少問題が発生します。

  • ACKにより足りないシーケンス番号が返されるため、それ以降の再送となります。

  • そもそもACKが帰ってこない場合は、まとめて送り直しということになります。

TCPの利用例

TCPは、信頼性が重要なアプリケーションで広く使用されています。以下は、TCPの利用例です。

  • ウェブブラウジング: HTTPやHTTPSプロトコルは、TCPを基盤として動作します。ウェブページのデータが正確に順序通りに送信されることが求められます。ただし最近はQUICというプロトコルに置き換えられる状況が出ています(HTTP/3)。

  • 電子メール: SMTP、POP3、IMAPなどの電子メールプロトコルは、TCPを使用して信頼性の高いメールの送受信を実現します。

  • ファイル転送: FTP[3](File Transfer Protocol)やSFTP(Secure File Transfer Protocol)などのファイル転送プロトコルは、TCPを使用してファイルの正確な転送を行います。