情報処理IIb(n) vol.4 慶應義塾大学 村井純 今日やること コンピュータコミュニケーションの基礎 – トランスポート層 • TCP – 輻輳制御 – 状態遷移 コンピュータコミュニケーションのモデル – プロキシサーバ TCP (Transmission Control Protocol) 異なるホストのアプリケーション間に信頼性の ある通信を提供 IPが提供する通信に信頼性を与える • • • • • コネクション型通信 タイムアウトと再転送 エラー検出とエラー訂正 フロー制御 順序の再構成 – データ転送に関するインターフェイスを提供 輻輳制御ルール1:返事で判断 返事が早い→空いてる→沢山送る 輻輳制御ルール2:混んだら小さく 返事が遅い→混んでる→少し送る 輻輳制御ルール2:混んだら小さく 返事が無い→混んでる→少し送る 輻輳制御ルール3:空いても急ぐな 返事が早い→空いてる→ゆっくり送る バイト数/日 サンフランシスコ→藤沢(バイト数/日) TCP/UDP利用別グラフ 14000000000 12000000000 10000000000 8000000000 6000000000 4000000000 2000000000 0 UDP TCP 1 2 3 4 5 月 6 7 インターネットが遅い原因 返事が遅い – 相手がのろい – 途中がのろい 相手がのろい場合 – 相手が過負荷 • アクセスが多すぎる • 力がなさ過ぎる 途中がのろい場合 – どこかの待ち行列であふれている TCPヘッダ Src Port Dst Port Sequence Number Acknowledgement Number Offset Reserved Flag TCP Checksum Option (if any) Data Window Size Urgent Pointer Padding Flags CODE FlagsはTCPがコネクション制御に利用 6つのflagsがある – – – – – – URG: Urgent Pointerが有効 ACK: Acknowledge Numberが有効 PSH: セグメントをすぐに上位層へ渡す RST: エラーによる強制的なクローズ SYN: コネクションセットアップの同期をとる FIN: コネクションを終了する TCPの状態遷移図 CLOSED Passive Open Recv SYN send SYN,ACK SYN_RCVD Active Open LISTEN Send SYN Recv RST Send SYN SYN_SENT Recv SYN Recv SYN,ACK Send ACK Recv ACK ESTABLISHED データ転送状態 コネクション開始 送信側 SEQ=812 受信側 SYN SEQ=812 No ACK SEQ=123 SYN SEQ=123 ACK=813 SEQ=813 ACK=124 SEQ=123 SEQ=813 ACK=124 ACK=813 3way Handshake コネクションのセットアップ – ホスト1、ホスト2間で • ホスト1はSYN Flagのついたパケットを送る • SYNを受けたホスト2は相手との同期を取るために SYN Flagのついたパケットを送る。この時いっしょ に受け取ったSYNに対するACK Flagもつける • ホスト2からSYNを受け取ったホスト1はACKを返す – SYN と ACKが相乗り • Piggy Back コネクションの終了 コネクションの終了はFIN Flagによって行う コネクションの終了は一方ごとにできる FIN SEQ=457 SEQ=789 ACK=458 FIN SEQ=790 SEQ=458 ACK=791 データの転送 Sequence NumberとAcknowledge Numberによって、デー タの順番を保証 ACKが帰ってきたら次のデータを送る ACKが帰ってこなかったら前のデータを再転送 SEQ=231 DATA SEQ=456 ACK=232 SEQ=232 DATA データ転送(続き) いちいちACKを待つと効率が悪い 推測を元にある程度先送りした方が良い だが、先送りしすぎるとACKがなかなか帰ってこ ない 幅を決めなければならない Congestion Control(輻輳制御) 輻輳(Congestion) – 中間ノードがデータグラムの過負荷によって 遅延が生じた状態. 輻輳を解決するために – ウィンドウコントロール – スロースタート ウィンドウコントロール 4 先送りする幅を決める仕組み 送るパケットを制限する SEQ=238 SEQ=270 SEQ=302 SEQ=334 ACK=335 4 スロー・スタート ネットワークに送り出されるパケットの通信速度を 他方のエンドから返される確認応答の通信速度 を同期させるためのもの 送り手のTCPに別のウィンドウを追加する。 – 輻輳ウィンドウ(cwnd) 新しいコネクションが確立 輻輳ウィンドウをセグメント1つに初期化 ACKが受け取られるたびに、輻輳ウィンドウは1セ グメントずつ増やされる スロー・スタート animation cwnd=1 1:513(512)ack1, win4096 1 ack513, win8192 cwnd=2 3 4 cwnd=3 6 7 cwnd=4 cwnd=5 2 513:1025(512)ack1, win4096 1025:1537(512)ack1, win4096 ack1025, win8192 1537:2049(512)ack1, win4096 5 2049:2561(512)ack1, win4096 ack1537, win8192 8 ack2049, win8192 9 まとめ IPはホストホスト間の通信まで トランスポート層はポート番号によってアプ リケーション間の通信を実現 UDPはIPの提供する品質の通信 TCPはいろいろな機能を用いることで、信 頼性のある通信をアプリケーション間に提 供する 代返 代わりに返事をすること – 授業 当事者がその場に入れないときに使う クライアントがサーバに到達する代わり代 理に問い合わせる Proxy Server Proxy Server 本物のサービスの代わりに、その代理の サービスを提供するサーバ – WWW Proxy Server Proxy Client 本物server Socket Programming2 慶応大学政策・メディア研究科 今泉英明 [email protected] 先週の課題について 20人が提出(49%) – 締め切りを過ぎても再提出可能(出さないよりは大幅 にまし) 寄せられた問題点 – – – – – バッファサイズが1024bytesじゃ少ない。 void main()はint main()にした方がいい。 サーバのソケットから読み出すべき。 他人の名前でメールできてしまう。 Subjectが書けるようになっていない 課題の問題点 サーバとうまくおしゃべりできていない – – – – Read()がブロックしてしまう サーバからしゃべりだしたりしたとき サーバから1024bytes以上送られてきたとき 以下の順序に依存しているのが原因 1. 2. 3. 4. 標準入力から読む(read()でブロック) ソケットに書き込む ソケットから読む(read()でブロック) 標準出力に書き込む 今日の目標 ソケットプログラミングの基礎を学ぶ – 非同期多重入出力 • 複数のディスクリプタを同時に利用しよう! – TCPを用いるサーバを書こう • UNIXプログラミングの勉強 – プロセスについて 非同期多重入出力 読み込み準備の整ったファイルディスクリ プタから読みたい 書き込み準備の整ったファイルディスクリ プタから読みたい とにかく複数のディスクリプタから非同期 に読み書きがしたい Select()システムコール 複数のディスクリプタを非同期に扱うため のシステムコール – ファイルディスクリプタの集合を渡して、読み 出し/書き込み可能なファイルディスクリプタを 教えてもらう – 一定のタイムアウトも指定できる fd_set ファイルディスクリプタの集合を保持するための ビットマップ 以下のような10bitのビットマップにファイルディ スクリプタ0, 4, 8という集合を表現させると 0 1 2 3 4 5 67 8 9 0 1 2 3 4 5 67 8 9 0 0 0 0 0 0 0 0 0 0 1 0 0 0 1 0 0 0 1 0 10bitsあると10個のファイルディスクリプタの集合を 表現することができる •FreeBSD3.2, Solaris 2.6では1024bits •BSD/OS3.1では256bits fd_setの操作 以下のインターフェースを利用して操作 fd_set fds; FD_ZERO(&fds); FD_SET(fd, &fds); FD_ISSET(fd, &fds); FD_CLR(fd, &fds); /*fdsをゼロに初期化*/ /*fdsにfd番目のビットを上げる*/ /*fdsにfd番目のビットが設定さ れているか調べる*/ /*fdsからfd番目のbitを落とす*/ Select() システムコール int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); •Selectは0から(nfds-1)までのファイルディスクリプタを検査 -最大の値のディスクリプタ+1を設定 ・readfds/writefdsは読み込み/書き込み可能かを調べたい ディスクリプタの集合 ・exceptfdsはとりあえず気にしない(詳しくは参考文献21章) ・どれかのディスクリプタが読み/書き可能になった場合に戻る ・それ以外はタイマの指定に依存 ・timeoutはタイムアウトするまでのタイマ値(秒、マイクロ秒) timeval構造体 longである秒とマイクロ秒の二つのメンバ タイマの値を指定できる long tv_sec long tv_usec タイマの設定 Selectは以下の3つのタイマの指定が可能 1. NULLを指定 - タイムアウトしない 2. Timeval構造体を指定 - 指定された一定時間でタイムアウト 3. 0秒0マイクロ秒に指定したTimeval構造体を指定 - ファイルディスクリプタを検査した後すぐにタイムアウト Selectの手順1 ファイルディスクリプタ0, 4, 8という集合のうちどれ が読み出し可能になったか知りたい場合 int nfds; 0 1 2 3 4 fd_set fds; 0 0 0 0 0 FD_ZERO(&fds); 1 0 0 0 0 FD_SET(0, &fds); 1 0 0 0 1 FD_SET(4, &fds); FD_SET(8, &fds); 1 0 0 0 1 nfds = 8 + 1; select(nfds, &fds, NULL, NULL, NULL); 読み出し可能なビットが立つ 1 0 0 0 0 5 0 0 0 0 67 0 0 0 0 0 0 0 0 8 0 0 0 1 9 0 0 0 0 0 0 0 1 0 Selectの手順2 Selectを実行した後の処理 select(nfds, &fds, NULL, NULL, NULL); 読み出し可能なビットが立つ if(FD_ISSET(0, &fds){ 0 1 2 3 4 5 67 8 9 read(0, ….); 1 0 0 0 0 0 0 0 1 0 } if(FD_ISSET(4, &fds){ read(4, ….); } if(FD_ISSET(8, &fds){ read(8, …..); } 実際に変更 別紙参照 ここまでのまとめ ネットワークプログラミングをするにはソケットを 利用する ネットワークに流す複数バイトのデータ型 (long,short)はネットワークバイトオーダにあわ せる TCPでコネクションを張るとbyte streamが流れ るパイプが張れる – その上に流れるデータはそれを扱うプログラムが担う 複数のディスクリプタを非同期に扱うには select()システムコールを利用する。 サーバ側を書いてみよう サーバも同様にTCPのコネクションが張れると単 なるディスクリプタ 違いは、みんなからの接続要求を待つためのソ ケットを開く – 自分でソケットにアドレスをbindしなければならない – 接続要求を保持するためにListen()を呼んでキュー を作成 – 要求にaccept()して、それぞれのクライアントと通信 するためのディスクリプタを作成 あとは一緒 システムコールの流れ TCPクライアント TCPサーバ socket() socket() connect() bind() listen() write() accept() read() read() write() close() EOFの通知 read() close() プログラムの流れ 1. 2. 3. 4. 5. 6. 7. Sockaddr_in構造体に、サーバのIPアドレス とポート番号を設定 ソケットを開く 1のアドレスをソケットにbind() Listen(接続待機)でキューを作成 接続要求に対してAccept() おしゃべり Close() 初期状態 クライアント プロセス Port A サーバ プロセス Port B Port C ホストA IP Address: xx.xx.xx.xx. ホストB IP Address: xx.xx.xx.xx. Socketを開いた状態 Socketを開く クライアント プロセス Port A サーバ プロセス Port B Port C ホストA IP Address: xx.xx.xx.xx. ホストB IP Address: xx.xx.xx.xx. bindした状態 Proto LocalAdddress TCP *.A クライアント プロセス ForeignAddress *.* Port A State Closed サーバ プロセス Port B Port C ホストA IP Address: xx.xx.xx.xx. ホストB IP Address: xx.xx.xx.xx. Listen() Proto LocalAdddress TCP *.A クライアント プロセス ForeignAddress *.* Port A State Listen サーバ プロセス Port B Port C ホストA IP Address: xx.xx.xx.xx. ホストB IP Address: xx.xx.xx.xx. Listen()拡大 Proto LocalAdddress TCP *.A Port A Port B Port C ForeignAddress *.* サーバ プロセス 3Way Hand Shakeを済ました接続 要求やSYNを保持するための キュー ホストB IP Address: xx.xx.xx.xx. State Listen クライアントがConnect() Proto LocalAdddress TCP *.A ForeignAddress State *.* SYN_RCVD Connect() SYN クライアント プロセス Port X Port A サーバ プロセス Port B Port C ホストA IP Address: yy.yy.yy.yy ホストB IP Address: xx.xx.xx.xx. キューの拡大 Proto LocalAdddress TCP *.A Port A Port B Port C ForeignAddress State *.* SYN_RCVD サーバ プロセス 3Way Hand Shakeを済ました接続 要求やSYNをacceptされるまで保 持するためのキュー ホストB IP Address: xx.xx.xx.xx. 埋まり 空き Accept() Proto LocalAdddress TCP *.A TCP xx.xx.xx.xx.A ForeignAddress State *.* Listen yy.yy.yy.yy.X Establish Connect() クライアント プロセス Port X Port A サーバ プロセス Port B Port C ホストA IP Address: yy.yy.yy.yy ホストB IP Address: xx.xx.xx.xx. 新しいシステムコール bind() listen() accept() bind() int bind(int s, struct sockaddr *name, int namelen); – ソケットに名前(アドレス)を付ける – アドレスにINADDR_ANYを指定した場合は、自分の 持つすべてのアドレスに対しての要求を受け付ける • *.port-number • そのサービスを提供するアドレスを制限できる – 開いたソケットのステートはclosed 同じポート番号を利用する 2つのサーバ あるホストAはxx.xx.xx.xxとyy.yy.yy.yyの二つのアドレ スを持つ – サーバAのソケットをxx.xx.xx.xx, port 10001にbind – サーバBのソケットをyy.yy.yy.yy, port 10001にbind – サーバCのソケットをINADDR_ANY, port 12345にbind socketの状態は Proto LocalAdddress TCP xx.xx.xx.xx.10001 TCP yy.yy.yy.yy.10001 TCP *.12345 ForeignAddress *.* *.* *.* State closed closed closed port 10001へのクライアントの要求は、 目的アドレスによって、異なるサーバが答えることになる 絵にすると、、、 サーバA Port A Port A Port B サーバB Port C Port C Address: xx.xx.xx.xx. Port B サーバC Address: zz.zz.zz.zz ホストB アドレス毎に独立したポート表を持つ listen() int listen(int s, int backlog); – s は ソケットディスクリプタ – backlogはキューの長さ(backlog分の要求 を保持できる) • 接続要求は、accept()されるまでbacklog長の キューに保持される • キューがあふれると、その要求は無視 キューの拡大 Proto LocalAdddress TCP *.A ForeignAddress State *.* SYN_RCVD サーバ プロセス Port A Port B backlog=4のキュー Port C ホストB IP Address: xx.xx.xx.xx. 埋まり 空き accept() int accept(int s, struct sockaddr *client, int *namelen) – キューで待っている接続要求を取り出して、そのクラ イアントと通信するためのディスクリプタを作成して、 返す – clientにはクライアントのsocketのアドレス、 namelenにはclientのサイズ Accept() Proto LocalAdddress TCP *.A TCP xx.xx.xx.xx.A ForeignAddress State *.* Listen yy.yy.yy.yy.X Establish Connect() クライアント プロセス Port X Port A サーバ プロセス Port B Port C ホストA IP Address: yy.yy.yy.yy ホストB IP Address: xx.xx.xx.xx. accept()の後 accept()で返ってきたファイルディスクリプ タを利用してクライアントとしゃべる 要求待機用のソケットもselectで扱える – listenしているsocketが読み込み可能なとき はaccept可能なとき selectをもちいることで、多くのクライアント の要求に答えることができる サーバの気持ちになってみよう そんなたくさんのソケットを扱ったり、めん どくさいことはしたくない 沢山のクライアントが接続してきたら、もう 大変 猫の手も借りたいわ! えーい、クローンだ。 forkシステムコール – int fork(); – 自分とまったく同じ複製プロセスを作るシステ ムコール – 0が帰ってきたら自分は子プロセス – 正の整数が帰ってきたら自分はfork()を呼び 出した親プロセス – -1は失敗 処理の流れ 接続要求があったら、acceptして,とりあえ ずfork(); – 子プロセスだったら、サービスの提供開始 – 親プロセスだったら、接続要求待ち シグナル は来週 まとめ TCPサーバのプログラムは、 – – – – Socketを開く socketにアドレス(Ipaddress, port 番号)をbind listen()でキューを作成 accept()でそれぞれのクライアントへのディスクリプタ を作成 – おしゃべり – 最後にclose() 今週の課題 fork()を利用せずに、select()のみを利用 して、echoサーバを作りなさい。 – listenのディスクリプタもaccept()でもらった ディスクリプタもselectで非同期多重 – Cの勉強なので、がんばってね。 – 完全でなくても出してください。 締め切り 10月25日午後11時59分59秒 いつもどおりSOIのシステムで提出
© Copyright 2024 ExpyDoc