TCPって何なのか
TCPってよく聞くし、GoのAPIからdockerのMySQLコンテナに接続するときに@tcp(hostname:port>/db
みたいなの書いたような覚えがあるけど、全然何なのか理解していないので調べてまとめてみました。
TCPは、Transmission Control Protocolの略で、TCP/IPプロトコルの中のトランスポート層に属するプロトコルです。
役割
アプリケーション層のHTTPなどのプロトコルはWeb開発をしているとかなり意識することが多いですが、TCPは全く意識したことがないのでまず役割が分かりません。
TCPの最大の役割は通信に信頼性を持たせることのようです。
この役割を果たすためにTCPは大きく以下の2つのことを行っています。
- コネクションの確立
- 通信の制御
コネクションの確立
TCPでは、通信を始める前にパケットが相手に届くかを事前に確認します。
この手法を3ウェイハンドシェイクというみたいです。
この3ウェイハンドシェイクが終わってから、上位レイヤーのデータのやり取り(HTTPなど)が開始されます。イメージ的には、アプリケーション層で言うプリフライトリクエスト的なものなのかなと思いました。
以下で手順を説明します。
SYNというフラグとACKというフラグでコネクションの応答を確認します。
クライアント側はまず、SYNフラグを送信して、通信開始を通達します。
※ SYNは最初に送信するホストという意味でのクライアント
サーバー側はACKフラグとSYNフラグを送信して、送られてきたSYNフラグに対する応答とサーバー側からクライアント側にも通信開始を通達します。
※ SYNは最初に受信するホストという意味でのサーバー
クライアントはACKフラグを送信して、サーバーから送られてきたSYNフラグに対して応答します。
1~3の手順でコネクションの確立がされます(手順が3つなので3ウェイらしいです)
通信の制御
次に通信の制御についてです。
大きく以下の2つの機能があります。
- ウィンドウ制御
- フロー制御
TCPではデータが送られてきた際に、コネクションの確立で説明したのと同じような形で、受け取ったことをACKフラグを送信して応答します。
これによって、データが届かなかったときに気付くことができます。
しかし、データをパケット単位で送信するたびにACKを返していては効率が悪いので、一定量のデータを受信するまでACKを返さなくて良いような仕組みが送信側、受信側双方にあります。
ウィンドウ制御
送信側で一定量のデータを送信するまでACKを待たずにデータを送信するようにする仕組みをウィンドウ制御と言います。
TCPヘッダーにあるWindow
というフィールドとWindow Scale
というオプションから計算されるウィンドウサイズで応答を待たずに送信する量を指定します。
3ウェイハンドシェイクの際に受信側からウィンドウサイズを伝えられます。
スライディングウィンドウ
このようにACKを待たずにデータを送信する仕組みはありますが、当たり前ですがウィンドウサイズまでデータを送信した後はACKを待たなければいけません。
そこでスライディングウィンドウという仕組みがあります。
スライディングウィンドウという仕組みでは、受信側はデータを受信する度にACKを送ります。送信側はACKを受信するとそのACKに対応するウィンドウサイズ分を元のウィンドウサイズからスライドさせて送ります。こうして実際にはACKを待たずに連続的にデータを送信し続けることができます。
フロー制御
受信側は受信側で、複数先からデータを受信しているなどのことがあるかもしれません。そのため3ウェイハンドシェイクの際のウィンドウサイズより処理できるデータ量が少なくなることがあります。
このような場合、受信側はウィンドウサイズを小さくしたり、また処理できるデータ量が増えたときにはウィンドウサイズを大きくして送信側に送ります。
受信側のこの機能をフロー制御と言います。
スライディングウィンドウはフロー制御の実装で、スライディングウィンドウでACKを送る代わりに受信側はWindow
フィールドを使って送信側に再度ウィンドウサイズを指定します。
このようにメモリバッファに余裕があるときは窓を開け、余裕が無くなってきたら調整することで通信を効率化します。
その他
コネクションの正常終了
TCPのコネクションは片方ずつ切断することになっていて(ハーフクローズ)、どちらから切断するかはソケットAPIの使い方次第だそうです。
コネクションの確立時と同じような形で、コネクションを切断したい方はFINフラグを送信し、受信側はFINフラグとACKフラグを送信し、再度ACKで応答して切断されます。
Portについて
TCPやUDPで通信するときは、プロセスやスレッド単位で通信が行われます。
そのためプロセス同士が通信する際、各プロセスにはポート番号というものが割り振られます。プロセスやスレッドはこのポート番号を目印にして、どのアプリケーションとどのアプリケーションが通信をとっているのかを判別します。
パケットキャプチャしてTCP通信を見てみる
中身がキャプチャできるようにTLSではないhttp://
で始まるサイトを対象にパケットキャプチャしました。
内容は以下の通りです。ポートの80番で絞り込んでいます。
IPアドレスが192.168.10.101
となっているのがクライアント(自分)で、158.199.134.50
となっているのがサーバーです。
HTTPでGETリクエストを送る前にTCPの通信が3つ確認できます。これが3ウェイハンドシェイクです。
内容を見て分かる通り、クライアントからサーバーにまずSYNを送って、サーバーはクライアントにSYNとACKを送り、最後にクライアントがサーバーにACKを送っています。
その後、GETリクエストをHTTPで送ってから、レスポンスが帰ってくるまでにサーバーからクライアントに対して5回のTCP通信が起きており、クライアントからサーバーに対して3回のTCP通信が起きています。
最後にHTTPでレスポンスがきて、それに対してもACKが返されています。
レスポンスのパケットキャプチャを見てみると、5つのTCPセグメントで5134bytesのデータを送ったと書いています。
TCPヘッダーにもWindowがあるのが確認できます。