こんにちは、 @kz_morita です。
以前から読んでいる マスタリング TCP / IP の 6章を読んだので,内容をまとめます.
目次
6 章は,トランスポート層のプロトコルである TCP
と UDP
についての話でした.
主に以下のような内容です.
- トランスポート層の役割
- ポート番号
- UDP
- TCP
トランスポート層の役割
インターネット層で,ホストまでデータ届けますが,その上でアプリケーションにデータを届けるのがトランスポート層の役割となります. どのアプリケーションにデータを渡すかを指定するために,ポート番号で識別します.
アプリケーションは,UNIX の場合だと サーバー上で動いているデーモンのことで,それあてにデータを届けます.( httpd
とか,sshd
とか)
UNIX デーモンには,inetd (インターネットデーモン) というスーパーデーモンがあり,それがサービスを受けると, forkし,sshd などを exec しますが,その際にport 番号が,22 だったら,sshd , 80 なら,httpd などといった形で振り分けます.
トランスポート層のプロトコルには,TCP と UDP があり,TCPは信頼性が高く,UDPはリアルタイム性を重視する場合や,(マルチキャスト,ブロードキャストに使われます.
TCPやUDPはを利用して通信するときはソケットという API が使われていて,アプリケーションはソケットを利用して通信相手のIPアドレスやポート番号を設定したりデータの送信や受信の要求をします.
ポート番号
ポート番号も,MACアドレスやIPアドレスと同様,通信相手を識別するためのアドレスで,以下のような役割になります.
- MACアドレス
- データリンク内のコンピュータを識別するもの
- IPアドレス
- TCP / IPネットワーク上のホストやルーターを識別するためのもの
- ポート番号
- 同一のコンピュータ内で通信を行っているプログラムを識別するためのもの
TCP / IP
, UDP / IP
による通信では,「宛先IPアドレス」「送信元IPアドレス」「宛先ポート番号」「送信元ポート番号」「プロトコル番号」の組で通信を識別します.
例えば,同じホストの別ブラウザで,同じサイトを別々に開いたりしたときにも,それぞれの通信を識別する必要がありますが,このときは各ブラウザごとに異なる 送信元ポート番号
が割り振られ,それにより識別することになります.
ポート番号は, 0 ~ 1023
までが Well-Known ポートと呼ばれる定義済みの数値で,
1024 ~ 49151
までは,自由に使っても良いポート番号とされています.
先程書いたように,通信は プロトコル番号でも区別されるため,トランスポートプロトコルの TCP と UDP の間で同じポート番号を別の目的で使用することもできます.(例えば,8080 番を TCP では用途 A に,UDP では用途 B にといった使い方ができる.)
Well-Know ポートはこちらから https://www.iana.org/assignments/port-numbers
UDP
UDP は,User Datagram Protocol の頭文字です. 輻輳の回避や,パケットの再送,到着順の保証など複雑なことは何も行わない,コネクションレスで,シンプルなプロトコルとなっています.
単純な処理のため高速に動くので以下のような用途で用いられることが多いです.
- パケット数が少ない通信 (DNS, SNMP)
- 動画,音声といったマルチメディア (リアルタイム性)
- LANなどの特定ネットワーク限定の通信
- ブロードキャスト,マルチキャスト
TCP
TCP は Transmission Control Protocol の略で,通信の制御をするプロトコルです.
以下のような通信の制御を行う仕組みがあります.
- シーケンス番号と確認応答
- 再送タイムアウト
- コネクション管理
- データ分割
- ウィンドウ制御と再送制御
- フロー制御
- 輻輳制御
- その他の利用効率を高める仕組み
シーケンス番号と確認応答
TCP では,分割されたパケットにシーケンス番号が振られ,それを用いて順序保証や確認応答などの仕組みを実装しています.
- 確認応答 = ACK
- 否定確認応答 = NACK
となり,データが送られたときに受信側にデータが正常に届いたことを送信側におくる応答が ACK
で,届かなかった場合に明示的に送られるのが NACK
です.
ACK には,次のデータのシーケンス番号が書かれているので,送信側はそれを確認して,次のパケットを送信します.
再送タイムアウト
TCPの通信では ACK が帰ってくるのまで,どのくらいの長さを待つかというタイムアウト時間の設定します. このタイムアウトを過ぎたらパケットを再送することになります.
タイムアウト時間の設定は,RTT (Round Trip Time) と呼ばれる往復時間と,その RTT の分散 (ジッタ) を計測して,それらの合計よりもやや大きな値を設定します. 最初の通信のパケットは,BSD系の UNIX , Windows などでは,初期値は 6 秒程度に設定されているらしいです.
データの再送までのタイムアウトは,指数関数的に増やしていって無駄に再送しないようにしています.
コネクション管理
TCPでは,スリーウェイハンドシェイクと呼ばれる,3 つのパケットでコネクションを張る方式が取られています.
3つのパケットは以下のとおりです.
- SYN: コネクション確立要求
- ACK SYN: SYNに対する確認応答 (ACK) と,コネクション確立要求 (SYN)
- ACK: SYNに対する確認応答
接続開始側をホストA とし,接続される側を ホストB とした場合,以下のように接続とコネクションの破棄をします.
( A ) ( B )
| ---- SYN ----> |
| <-- ACK SYN -- |
| ---- ACK ----> |
| (接続) |
| ... |
| .. |
| . |
| ---- FIN ----> |
| <--- ACK ----- |
| |
| <--- FIN ----- |
| ---- ACK ----> |
| (切断) |
データ分割
TCPはコネクション確立時に,データのセグメントサイズを決めます.(MSS: Maximum Segment Size) 理想的には,IP で分割処理されない最大のデータ長にすると,効率が良いです.
MSS はスリーウェイハンドシェイク時にホスト間で決められ,TCPヘッダに,MSS オプションをつけて自分のインターフェースにあったMSSをお互いに通知し,その小さい方の値がMSSとなります.
ウィンドウ制御と再送制御
TCPでは,ウィンドウという概念をとりいれて,ある程度のセグメントを連続で遅れるようにしています. 通信に時間がかかるようなホスト間において,相手の 確認応答 を待ってから,次のセグメントを送るとするとかなり効率が悪くなります.
そこで複数のセグメントを一定のサイズ (ウィンドウサイズという) ごとに,確認応答を待たずに連続で送信できるようにしています.
これを実現するためには,送受信側に Buffer を設けて ウィンドウサイズ分の複数セグメントに対して並列で確認応答をします. (このような手法は,スライディングウィンドウ制御と言われる方式)
ウィンドウ制御を入れると確認応答がパケットロスしても,ある程度は次の確認応答パケットで確認応答がされます.1つめのシーケンス番号の確認応答のパケットがロスしても,2つめシーケンス番号の確認応答によるパケット番号が送信側にとどけば,それで1つめも届いたことが保証できるといった具合です.
ウィンドウサイズが大きいときにデータが失われると失われたシーケンス番号の確認応答が連続して送られてきます.3回同じシーケンス番号の確認応答が送られてきた場合には,パケットが消失したとして,再送するようなしくみになっています.この仕組みはタイムアウトよりも速く再送されるため効率が良いです.
フロー制御
フロー制御によって,TCPパケットの流れる量を制御します. 受信側が忙しくて,パケットを取りこぼしてしまった場合に特別な制御を入れないとひたすら送信側は,パケットロスと思って再送し続けてしまいます. これでは無駄なパケットをひたすら送信し続けてしまうので,送信側が受信側の能力に応じてパケット送信量を調整します.
TCPヘッダに,ウィンドウサイズを通知するためのフィールドがあり,受信側がこのフィールドに,受信可能なバッファサイズをいれて送ることで実現します.(このサイズが大きれば大きいほど高スループットでの通信が可能)
送信側は,時々ウィンドウブローブとよばれる 1オクテットだけのデータを送り,最新のウィンドウサイズを受信側から取得する仕組みがあります.
輻輳制御
他の通信でも共有しているネットワークに大きな負荷をかけないための制御です.
いきなり,大きなウィンドウサイズで通信を始めると,ネットワークに多くのパケットが流れてしまい負荷が大きくなってしまいます. そのため,TCP では,スロースタートアルゴリズムというものを採用しています.
輻輳ウィンドウという,ネットワーク通信量を制御するためのウィンドウを定義し,最初は,輻輳ウィンドウのサイズを 1MSS に定義して, 確認応答のたびに 1 ずつ大きくします. そして,輻輳ウィンドウと送信相手のウィンドウサイズの小さい方の値以下になるようなウィンドウサイズでパケットを送信します.
輻輳ウィンドウが大きくなると,それに比例して指数関数的に輻輳ウィンドウは増えていきます.最初は,1 で,ACKが返ってきたら 2 に増やし,次は 2つ ACKが帰ってくるので,4になる...といった具合です.
このようにネットワークトラフィックが急激に増加するのを防ぐために,スロースタート閾値を導入してこれを超えた場合には,以下の分だけ輻輳ウィンドウが増えるようにします.(線形に増える)
$$ \frac{1セグメントのオクテット数}{輻輳ウィンドウ(オクテット)} \times 1セグメントのオクテット数 $$
閾値は,最初の通信では設定されておらず,タイムアウトが発生したらその時の輻輳ウィンドウサイズの半分を設定するようになっています.
全体の動きとして,TCPは通信開始後には徐々にスループットを向上させ,ネットワークが混雑すると急激にスループットを低下させた後,また徐々にスループットをあげていくようになります.
利用効率を高める仕組み
他にもネットワークの利用効率を高めるための仕組みがいくつかあります.
Nagle アルゴリズム
データ量が少ないときに送信を遅らせることで利用効率を上げる方法です. 以下のどちらにも当てはまらない場合に,送信を遅らせます.
- すべての送信済みデータが確認応答されている
- MSS のデータを送信できる場合
遅延確認応答
データを受信したてのホストは小さなウィンドウサイズを返す可能性があるため,確認応答を遅延させてまとめて複数パケット確認応答をする仕組みです.
- 2 * MSS のデータを受信するまで確認応答をしない. (OS によっては,データによらず2パケットとしているものもある)
- そうでない場合は,確認応答を最大で 0.5秒 遅延させる (多くのOSでは,0.2秒程度)
つまり,大雑把に言うと2パケットごとに確認応答を行うような仕組みになっていて,パケット数が奇数のときの最後の端数は,0.2 秒程度待っても次のリクエストが来なければ端数としてACKを返すようなものです.
ピギーバック
確認応答とレスポンスを一つのパケットにつめて送る方式です.HTTP の Response などと,ACK を同じパケット詰めるといった方式です.方式はアプリケーションプロトコルによります.
まとめ
今回は,トランスポートプロトコルである,TCP / UDP について簡単にまとめました.
記事には記載しませんでしたが書籍の方には,ヘッダフォーマットなども書かれているためネットワークプログラミングをする際には,参照すると役に立つかと思います.