第6回NP資料 tcpを使った通信
Download
Report
Transcript 第6回NP資料 tcpを使った通信
第6回ネットワークプ
ログラミング
中村 修
今日のお題
講義
TCPとは?
練習1: tcpを使ったecho clientを書こう
名前の解決
練習2 : gethostbynameに慣れよう
---------休憩------------------------------- 実習:TCPでデータを送る/受け取る
TCPで
echo serverを作ろう
トランスポート層の役割
届いたデータをどのプログラムに渡す?
ポート番号 : /etc/services
アプリケーションに通信の質の選択を提供
複雑だが信頼性を保証する通信
TCP
Connection Oriented
Virtual Circuit
単純だが信頼性を保証しない通信
UDP
Connection Less
Datagram
TCP vs UDP
ネットワークを利用するアプリケーションは主に
トランスポートプロトコルとしてTCPとUDPを利用する。
TCP
•Reliable
•Sequence Control
•Flow Control
•Congestion Control
•Retransmission
•Bit-based charging
UDP
•Unreliable
•No Sequence Control
•No Flow Control
•No Congestion Control
•No Retransmission
•Fixed-rate charging
UDP
(User Datagram Protocol)
信頼性がない
データレート制御:可
身近な例
DNS
イメージ
はがき
データ
データ
データ
はがきのやりとり
S
client
データ
ストリーミング
server
C
データ
TCP
(Transmission Control Protocol)
信頼性のある通信を提供
データレート制御:不可・困難
身近な例
電子メール
WEB
イメージ
電話のやりとり
S:もしもし
C:もしもし、○○です
S:××です。あのさ、、、
server
client
SYN
SYN+ACK
ACK
データ
TCPが提供する信頼性
IPが提供する通信に信頼性を与える
コネクション型通信
タイムアウトと再転送
エラー検出とエラー訂正
フロー制御
順序の再構成
データ転送に関するインターフェイスを提供
※それでも必ずデータが届くわけではない
輻輳制御ルール1:
返事で判断
返事が早い→空いてる→沢山送る
輻輳制御ルール2:
混んだら小さく
返事が遅い→混んでる→少し送る
輻輳制御ルール3:
無くなったらしばらくたってもう一度
返事が無い→データが無くなった?→もう一度送る
輻輳制御ルール4:
空いても急ぐな
返事が早い→空いてる→ゆっくり送る
TCPヘッダ
ポート番号
シーケンス番号
あるデータを送る時、そのデータがどの要に連続しているかを
シーケンシャル(連続的)に示す番号(何番目のバイトか)
ack番号
送信者側(source)と受信者側(destination)
16bit (u_short)
送られたデータに対してackを返す場合に、どのシーケンス番号
の確認応答なのかを示す番号
確認応答したいシーケンス番号+1
フラグ
それぞれのパケットの状態を示す
その通信の最初のパケットなのか
届いたという確認なのか
もう終わりなのか
強制終了なのか
コネクション開始
送信側
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
接続が中断する状況
ネットワークの停止/ピアホストのクラッシュ
ピアアプリケーションのクラッシュ
送信側は約9分再試行の後タイムアウト
受信側はポーリングしない
Operation Timed Out
(ETIMEDOUT)/SIGPIPE/EPIPE/ENETURNREACH/EHOSTUNR
EACH
SIGPIPE/EPIPE
ピアホストのリブート
Connection Reset by Peer (ECONNRESET)
Broken Pipe (SIGPIPE/EPIPE)
⇒プログラマによる対処が必要
データの転送
Sequence NumberとAcknowledge Numberによって、デー
タの順番を保証
ACKが帰ってきたら次のデータを送る
ACKが帰ってこなかったら前のデータを再転送
SEQ=231 DATA
SEQ=456 ACK=232
SEQ=232 DATA
データ転送と効率化
いちいちACKを待つと効率が悪い
パケット毎に確認する必要があるか?
送信者
推測を元にある程度先送りした方が良い
受信者
ある程度まとめてackを送る方が良い
だが、先送りしすぎるとACKの戻りが遅れる
先送りする幅を決めなければならない
ウィンドウコントロール(受信側)
先送りする幅を決める仕組み
受信側が決める
送るパケットを制限する
SEQ=238
ACK=239
WindowSize=3
SEQ=270
SEQ=302
SEQ=334
3
ウィンドウコントロール(送信側)
ウィンドウサイズ=5
ウィンドウ
1
2
3
4
5
6
7
8
1~5までのデータを送れる
ウィンドウ
1
2
3
4
5
6
7
8
1のackが返ってきたら、
ウィンドウを2までずらす。
6まで送れるようになる。
8
2、3のackが返ってきたら、
ウィンドウを4までずらす。
8まで送れるようになる。
ウィンドウ
1
2
3
4
5
6
7
スロー・スタート
最初はなるべく小さなウィンドウサイズ
通信し開始後、ネットワークの混雑度を把握
送信するパケットの送信速度を受信者から返され
るACKの速度、頻度(間隔)に同期
ACKからネットワークの状態を推測
送り手のTCPのウィンドウが増加
輻輳ウィンドウ(cwnd)
新しいコネクションが確立
輻輳ウィンドウをセグメント1つに初期化
ACKが受け取られるたびに、輻輳ウィンドウが1セグメ
ントずつ増加
スロー・スタート
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
TCP まとめ
Transmission Control Protocol
コネクションの確立
3way-handshake/piggy-back
順序の再構成・フローコントロールなど
信頼性を提供するための仕組み
スライディングウィンドウ・スロースタート
TCP 通信の流れ
TCP Server
socket()
TCP client
bind()
socket()
listen()
connect()
write()
establish
accept()
data
read()
data
write()
end
read()
read()
close()
close()
練習1:tcpでechoクライアント
TCPでechoクライアントを書いて見ましょう。
答えは以下にあり
/home/kaizaki/osamuNP/6/6-1.c
練習1: ポイント
sock_fd = socket(AF_INET, SOCK_STREAM, 0);
sin.sin_family = AF_INET;
sin.sin_port = htons(SERV_PORT);
inet_aton(SERV_ADDR,&sin.sin_addr);
connect(sock_fd, (struct sockaddr *)&sin, sizeof(sin));
fgets(buf,sizeof(buf),stdin);
write(sock_fd, buf, sizeof(buf));
readlen = read(sock_fd, buf, sizeof(buf));
printf("%s\n",buf);
補足:データサイズについて
read(),write()の際の
データサイズと、TCP
のデータ転送の際の
データサイズは異なる
プロセス
プロセス
TCP
TCP
名前の解決:www.sfc.keio.ac.jpにはどう
やってたどりつく?
ユーザ
IPアドレスから通信相
手まではたどり着ける
www.sfc.keio.ac.jpの
IPアドレスって何?
名前解決の例
•コンピューターは数字を理解する
www.sfc.keio.ac.jp
が見たいんだけど…
•一方人間は長い数字よりも
名前のほうが記憶しやすい
名前解決の必要性
ユーザ
133.27.4.212のサーバに
アクセスしてください
ネームサーバ
WEBサーバ
133.27.4.212
名前とアドレス
通信先は通信前に特定しなければならない
ホストは一意のIPアドレスで識別される
ホストは一意の名前で識別される(ユーザーの便宜のた
め)
名前
可変長
人間が記憶しやすい
ホストの物理的な位置と無関係
アドレス
固定長
計算機が処理しやすい
ルーティングによる制限がある
名前空間と名前解決
名前空間
使用可能な名前の定義
平面
or 階層
名前解決のメカニズム
名前(ホスト名)から対応する値(IPアドレス)を見
つけるメカニズム
ネームサーバ
名前解決のひとつの実現方法
インターネット上でもっとも使われている
簡単な例
user
www.sfc.keio.ac.jp
www.sfc.keio.ac.jp
ネーム
サーバ
Web ブラウザ
133.27.4.212
133.27.4.212
Webサーバ
名前から値への
対応情報を提供
ではプログラマはどうすればいい?
プログラムの中で
名前 -> IPアドレスの変換を行えばよい
struct hostent *
gethostbyname(const char *name);
練習2: gethostbynameに慣れよう
第一引数にホスト名前を入力すると、
echo clientとして機能する。
答えは以下にあり
/home/kaizaki/osamuNP/6/6-2.c
練習2:ポイント
#include <netdb.h>
int main (int argc, char *argv[] ) {
int sock_fd;
struct sockaddr_in sin;
char buf[BUF_SIZE];
int readlen;
struct hostent *hp;
hp = gethostbyname( argv[1] ); /* add */
sock_fd = socket(AF_INET, SOCK_STREAM, 0);
sin.sin_family = AF_INET;
sin.sin_port = htons(SERV_PORT);
sin.sin_addr = *(struct in_addr *)hp->h_addr;
}
実習
TCPを用いたechoサーバを作ろう。
第一引数にホスト名を指定しよう
第二引数にポート番号を指定しよう
→次のページからに必要となる初出関数の説明
TCP 通信の流れ(おさらい)
TCP Server
socket()
TCP client
bind()
socket()
listen()
connect()
write()
establish
accept()
data
read()
data
write()
end
read()
read()
close()
close()
Socketを開いた状態
Socketを開く
クライアント
プロセス
Port A
サーバ
プロセス
Port B
Port C
ホストA
IP Address: xx.xx.xx.xx.
ホストB
IP Address: xx.xx.xx.xx.
bind()システムコール
int bind(int s, const struct sockaddr *addr, int addrlen)
用意したsokcetのアドレスを実際にsocketと結びつける
IPアドレスとTCP/UDPのポート番号の組
開いたソケットのステートはclosed
実際のコードでは…
bind(listenfd, (struct sockaddr *) &servaddr,
sizeof(servaddr)
成功なら0、エラーなら-1の返り値
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()システムコール
int listen(int s, int backlog)
用意したsocketを待ち受け準備状態にする
ソケットの状態をCLOSEDからLISTENへ
backlogはキューの長さ(backlog分の要求を保持できる)
accept()されるまでbacklog分のキューに保持
キューがあふれると、その要求は無視
実際のコードでは…
listen(listenfd, LISTENQ)
成功なら0、エラーなら-1の返り値
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.
accept()システムコール
int accept(int s, strucet sockaddr *addr, int *addrlen)
キューで待っている接続要求を取り出して、そのクライアント
と通信するためのディスクリプタを作成して、返す
clientにはクライアントのsocketのアドレス、namelenには
clientのサイズ
実際のコードでは…
clientにはクライアントのsocketのアドレス、namelenにはclient
のサイズ
accept(listenfd, (struct sockaddr *) &cliaddr, &clilen)
成功なら0、エラーなら-1の返り値
accept()
Proto LocalAdddress
TCP
xx.xx.xx.xx.A
ForeignAddress
State
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()
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.
使い方例
int socket_fd, accept_fd;
int client_addrlen;
int readlen;
struct sockaddr_in server, client;
socket_fd = socket(AF_INET,SOCK_STREAM, 0);
bind(socket_fd, (struct sockaddr *)&server, sizeof(struct sockaddr_in));
listen(socket_fd, 5);
memset((void *)&client, 0, sizeof(client));
client_addrlen = sizeof(client)
accept_fd = accept(socket_fd,(struct sockaddr *)&client, (int *)&client_addrlen);
---(read/writeなどの処理)---close(accept_fd);
close(socket_fd);
}
実習:それでは作ってみましょう!
TCPを用いたechoサーバを作ろう。
第一引数にポート名を指定しよう
TCPを用いたechoクライアントを作ろう。
第一引数にホスト名を指定しよう
第二引数にポート名を指定しよう