Open12

データ送受信

syysyy

Webサーバーにメッセージを送信するにはOSのプロトコル・スタックに依頼する。

データ送受信をするコンピュータ間でデータの通り道(パイプ)を作成し、そこにデータを入れて相手にデータを渡す。
パイプの出入り口をソケットという。

流れ

  1. ソケット作成フェーズ
  2. 接続フェーズ
  3. 送受信フェーズ
  4. 切断フェーズ

↓細かく書くと

  1. サーバー側でソケットを作成
  2. クライアントがパイプをつなぎに来るのを待つ
  3. クライアント側でソケットを作成
  4. サーバーに向かってパイプを繋げる
  5. パイプが繋がったらデータ送受信開始
  6. データ送受信が完了したらパイプを外す
  7. パイプが外せたらソケットを削除
syysyy

ソケット作成フェーズ

Socketライブラリのsocket関数を呼び出してソケットを作成する。
ソケットが作成されると、ディスクリプタが渡される。
ディスクリプタ = ソケットの識別子。ソケットを使う際に使用する

接続フェーズ

クライアント側で作成したソケットをサーバー側のソケットに接続するようプロトコル・スタックに依頼を出す。
connect関数にディスクリプタ, サーバーのIP, ポート番号を渡す。
IPアドレスで相手のコンピュータがわかり、ポート番号で相手のソケットが決まる。

なお、クライアントがソケットを作成する際にもソケットにポート番号が割り当てられる。
クライアントのポート番号はサーバー接続時に通知される。

送受信フェーズ

相手のソケットと接続できたら、Socketライブラリのwrite関数を使ってデータを送ることができる。
(ブラウザが作成したHTTPリクエスト・メッセージなど)

write関数ではディスクリプタと送信データを指定する。
送信データは相手のサーバーに送られ、サーバーが何らかの処理をしてレスポンスを送る。
クライアントはメッセージを受信するためにSocketライブラリのread関数を実行する。
read関数には受信バッファ(受信したメッセージを格納するメモリのこと)を指定する。
受信バッファにメッセージが入れば送受信完了。

切断フェーズ

送受信が終わったら切断する。
切断にはSocketライブラリのclose関数を実行する。これでソケットやパイプが削除される。
クライアント/サーバーのどちらから切断するかはプロトコルによる。

HTTPプロトコルではサーバー側から切断する。
サーバーがレスポンスを送信完了すると、close関数を実行して切断する。
サーバーが切断したことがクライアントに伝わり、クライアントのソケットが切断フェーズに入る。
ブラウザがread関数を実行した際に切断されたことを通知する。
ブラウザは切断フェーズに入る。

syysyy

ソケット
通信相手のIPアドレス, ポート番号, 通信動作状況などの制御情報を持つ概念的なもの。

プロトコルスタックは制御情報を参照して動作する。

ディスクリプタ経由でソケットを参照する。

制御情報
・TCPヘッダーに書き込まれる情報
クライアントとサーバーが状況を伝え合うのに必要な情報。接続, 送受信, 切断の各フェーズで何が必要かTCPプロトコルで決まっている。TCPヘッダーに記入され、パケットの先頭に付与される。
・ソケットに記入する情報
プロトコルスタックの動作管理に必要な情報。通信相手からもらった情報(ポート番号など)をソケット(メモリ)上に記録する。プロトコルスタックごとに必要な情報が変わる。

syysyy

TCPヘッダー
20byte ~
パケットの先頭に付与される。接続フェーズなど本体データを送らずに経路情報だけを送ることもある。

送信元ポート番号 : 16bit
宛先ポート番号 : 16bit
シーケンス番号 : 32bit。送信するデータの何byte目から始まるのか受信側に伝えるための情報
ACK番号 : 32bit。何byte目まで受信したか送信側に伝えるための情報
データオフセット : 4bit。データ部分がどこから始まるかを表す
コントロールビット : 6bit。ACK, PSH, RST, SYN, FIN
ウィンドウ : 16bit。受信側から送信側にウィンドウサイズを伝えるための情報
チェックサム. : 16bit。データが正しく送信されたか確認するのに使う
オプション : 拡張性のために用意されている。可変長。

syysyy

接続動作

Socketライブラリのconnect関数で接続する

connect(ディスクリプタ, IPアドレス, ポート番号)

接続用のTCPヘッダーを作成し、コントロールビットのSYNフィールドを1にする。
このTCPヘッダーを送信する。

サーバーは、TCPヘッダーを受け取り、宛先ポート番号を取得して該当するLISTEN状態のソケットを探す。
ソケットがあれば状態を接続中に更新し、レスポンス用のTCPヘッダーを作成する。
TCPヘッダーのコントロールビットのSYNフィールドとACKフィールドを1にして返信する。

クライアントは、返ってきたパケットを見て接続が成功したかを確認する。(コントロールビットのSYNフィールドが1になっているか)
成功していたらサーバーのIPやポート番号をソケットに記入し、ソケットを接続完了状態にする。
そしてパケットを受け取ったことをサーバーに通知するためにACKビットを1にしたTCPヘッダーを作成してサーバーに送る。

これで接続フェーズが完了し、データ送受信できるようになる。
この時クライアントとサーバーの間にできた目に見えないパイプをコネクションという。

syysyy

データ送受信

Socketライブラリのwrite関数でデータを送信する。
write関数の内部でプロトコルスタックにデータ送信依頼をしている。
write関数を実行した直後にデータが送信されるわけではなく、プロトコルスタックの送信用バッファーに蓄えられる。
一定時間経つか、一定量のデータが溜まったら送信する。
(ネットワークの利用効率を上げるため。細かくデータを送るとネットワークに負担がかかり過ぎてしまう)

どのくらいデータを貯めるかは、MTU, MSSの値を元に動的に判断する。

MTU

1つのパケットで送信できるデータ量。イーサネットだと1500byte
IPヘッダー + TCPヘッダー + データ の範囲

Maximum Transmission Unit

MSS

MTUからヘッダーのサイズを引いた、実際に送信できるデータサイズのこと。

IPヘッダー + TCPヘッダー + データ の内、IPヘッダーとTCPヘッダーを除いた本体データの部分

Maximum Segment Size

溜まったデータがMSSを超えるか、MSSに近くなってから送信すると効率よく送信できる。
もしくは、一定時間(ms)を超えると送信する。

MSSに近づくまで貯めればネットワーク効率は良くなるが、送信するまでに時間がかかってしまう。
細かく送信するとネットワーク効率が悪くなる。
= どちらを優先するかはTCPプロトコルでは決まっておらず、プロトコルスタックの実装に依存する。

アプリケーション側の都合で決めたいケースもあるので、アプリケーションから指定ができるようになっている。

syysyy

データ分割
MSSを超えるサイズのデータを送信する場合は、送信バッファーに入っているデータを分割して送信する。

syysyy

届いたことを連絡するACK
シーケンス番号とACK番号でデータ漏れがないか確認できる。

TCPは確実に相手にデータが届ける。もし送信に失敗していたら再送する必要がある。
送信側は、送信したデータが受信側に届いたかどうか判断するために確認する。

TCPはデータを分割する際に、その断片が通信開始から何バイト目かを覚えておく。
データを送信する際に、何バイト目から始まるデータを送るのかTCPヘッダーのシーケンス番号に記入する。

↓データ分割して何バイト目から始まるのか覚えておく
[1byte~][1460byte~][2921byte~]

受信側は、1460と記載されたシーケンス番号が届く前に2921と書かれたシーケンス番号のパケットが届くと途中のパケットがロストしたことがわかる。

受信側はパケットを受信した際に、何バイトまで受信したかを計算し、TCPヘッダーのACK番号にその値を記載する。(コントロールビットのACKビットとは違う)
= 受信したことを伝えることを受信確認応答という

シーケンス番号を1から始めると推測されて攻撃される可能性があるため、接続フェーズ時のSYNパケットを送る際に、シーケンス番号の初期値をシーケンス番号に入れて送信する。

syysyy

シーケンス番号を踏まえた流れ

接続フェーズ
c -> s: SYNパケットを送信。シーケンス番号に初期値を入れておく
s -> c: ACKパケットを送信。シーケンス番号に初期値を入れておく
c -> s: ACKパケットを送信。
送受信フェーズ
c -> s: httpリクエスト送る。
s -> c: TCPヘッダーのACK番号に届いたbyte数を書いて送信

s -> s: httpレスポンスを送る
c -> s: TCPヘッダーのACK番号に届いたbyte数を書いて送信

送信確認が取れるまで送信したデータを送信バッファに補完しておく。
一定時間待っても送信確認が取れなかった場合、データを再送する。

syysyy

データ喪失に対する回復処理はTCPが行う。
そのためルーターやハブはデータをロストしても回復処理は行わない。

syysyy

TCPは送信エラー時に複数回再送を行なうが、いずれもデータ送信に失敗した場合は、異常と見做してアプリケーション層にエラーを送る

syysyy

ACKのタイムアウト
いつまで確認応答を待つか。

ネットワークの混雑状況や受信者の都合によって必要な時間が変わる。

TCPはタイムアウト値を状況に合わせて動的に変更する。
ACK番号が返ってくるまでの時間を計測しておき、時間がかかっていればタイムアウトの時間を伸ばす。すぐに返って来ればタイムアウトの時間を短くする。