ネットワークプログラミング 第7回「ネットワークとプログラミング(2)」 2008年秋学期 Rodney Van Meter
授業Webページ SFC-SFShttps://vu.sfc.keio.ac.jp/sfc- sfs/ 課題・授業資料などの情報を掲示します 本講義を「MY時間割(仮)」へ登録してください 課題・授業資料などの情報を掲示します 課題は毎回こちらに提出!! 今日の課題締め切り 6/9(月)23:59分まで!
今期の授業スケジュール(予定) 第1回:イントロダクション 第2回:C言語の基礎~関数・変数・Makefile 第5回: file I/O・ネットワークとプログラミング(1) () 第6回:ネットワークとプログラミング(1) () 第7回:ネットワークとプログラミング(2) (11/11) 第8回:ネットワークとプログラミング(3) () 第9回:応用ネットワークプログラミング(1) () 第10回:応用ネットワークプログラミング(1) () 第11回:応用ネットワークプログラミング(2) () 第12回:ミニプロ実習 () 第13回:ミニプロ最終発表()
今日のお題 講義 実習:TCP-echo-server作成 ネットワークプログラミング基本手順 TCPを使ったプログラミング UDP-echo-client UDP-echo-server TCPを使ったプログラミング TCP-echo-client gethostbyname() connect() 実習:TCP-echo-server作成
UDPプログラミング
UDP (User Datagram Protocol) server client 信頼性がない データレート制御:可 身近な例 DNS ストリーミング イメージ はがきのやりとり データ データ データ データ データ S C はがき
Datagram example (UDP) Server socket() Client bind() socket() recvfrom() bind() Block until Data from client sendto() Data (request) Process request Data (reply) sendto() recvfrom()
ソケット(Socket) プロセス プロセス プロセス間通信を行う為のデータの出入り口 プロセスからはファイルディスクプリタを用いてアクセス プロセスにとってはプロセス間通信もファイル入出力も同じインタ ーフェイス プロセス プロセス socket socket
socket()システムコール int socket(int family, int type, int proto) AF_INET IPv4プロトコル AF_INET6 IPv6プロトコル AF_LOCAL UNIX Domain Socket AF_ROUTE 経路制御ソケット Typeにはソケットのタイプ(以下のどれか) SOCK_STREAM ストリームソケット SOCK_DGRAM データグラムソケット SOCK_RAW rawソケット Protoにはrawソケット以外、通常0
socket()システムコール 返り値 実際のコードでは… 成功: ソケットディスクリプタが返る 失敗: -1が返る ソケットディスクリプタはファイルディスクリプタの友達 実際のコードでは… listenfd = socket(AF_INET, SOCK_STREAM, 0) AF_INETの場合の利用されるIPv4の上位層 SOCK_STREAM TCP SOCK_DGRAM UDP SOCK_RAW なし
socket int socket(int domain, int type, int protocol); (例) int sd; sd = socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP) AF_UNIX , SOCK_STREAM,IPPROTO_TCP SOCK_RAW,IPPROTO_ICMP
bind int bind(int sockfd,struct sockaddr *addr,int addrlen); (例) struct sockaddr_in cl_addr; memset((void *)&cl_addr, 0, sizeof(cl_addr)); cl_addr.sin_family = AF_INET; cl_addr.sin_port = htons(0); cl_addr.sin_addr.s_addr = htonl(INADDR_ANY); /* local host*/ bind(sd,(struct sockaddr *)&cl_addr, sizeof(cl_addr))
サーバのソケットアドレスを準備する struct sockaddr_in sv_addr; memset((void *)&sv_addr, 0, sizeof(sv_addr)); sv_addr.sin_family = AF_INET; sv_addr.sin_port = htons(?????); inet_aton(?????, &sv_addr.sin_addr);
inet_aton() int inet_aton(const char *cp, struct in_addr *inp); アドレスを表す文字列を, ネットワークバイト順序のバイナリ値へ 「127.0.0.1 」という文字列は人間には分か りやすいが,コンピュータには分かりにくい 返り値は,指定したアドレスが正当ならば0以 外,不当なら0. 仲間 inet_ntoa()
inet_pton() int inet_pton(int af, const char *src, void *dst); Better for IPv6 仲間 inet_ntop() できれば、これを使ったほうがいい
sendto ssize_t sendto(int s, const void *msg, size_t len, int flags, const struct sockaddr *to, int tolen); (例) if (sendto(sd, (char *)&msg, sizeof(msg), 0 , (struct sockaddr *)&sv_addr, sizeof(sv_addr)) < 0) { perror("sendto"); exit(-1); }
recvfrom ssize_t recvfrom(int s, void *buf, size_t len, int flags, struct sockaddr *from, int *fromlen); (例) recvlen = recvfrom(sd, (void *)buf, 1024, 0, (struct sockaddr *)&sv_addr, &svadlen);
#include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <netdb.h> #include <stdio.h> #define BUFMAX 1024 int main(int argc, char *argv[]){ int sd; int cc, svadlen; char buf[BUFMAX] char rmsg[BUFMAX]; struct sockaddr_in cl_addr, sv_addr; /* socketの作成 */ ???? /* socketに名前をつける */ /* サーバのソケットアドレスの設定 */ /* 処理ルーチン(メッセージの送信,受信) */ return 0; } Sample code: UDP-echo-client(1/3)
/* socketの作成 */ sd = socket( AF_INET, SOCK_DGRAM, 0 ); if( sd < 0 ){ perror("socket"); exit(0); } /* socketに名前をつける */ memset((void *)&cl_addr,0, sizeof(cl_addr)); cl_addr.sin_family = AF_INET; cl_addr.sin_port = htons(0); cl_addr.sin_addr.s_addr = htonl ( INADDR_ANY ); if( bind( sd, (struct sockaddr *)&cl_addr, sizeof(cl_addr)) < 0 ){ perror("bind"); /* サーバのソケットアドレスの設定 */ memset((void *)&sv_addr,0, sizeof(sv_addr)); sv_addr.sin_family = AF_INET; sv_addr.sin_port = htons(atoi(argv[2])); inet_aton(argv[1], &sv_addr.sin_addr); svadlen = sizeof(sv_addr); /* 処理ルーチン(メッセージの送信,受信) */ ???? Sample code: UDP-echo-client(2/3)
/* 処理ルーチン(メッセージの送信,受信) */ while(1){ memset(buf, '\0', sizeof(buf)); printf("echo-client> "); fgets(buf, sizeof(buf), stdin); buf[strlen(buf)-1] = '\0'; /* send a message */ if(sendto(sofd, buf, BUFMAX, 0, (struct sockaddr *)&sv_addr, svadlen) < 0) perror("sendto"); cc = recvfrom(sofd , buf, BUFMAX, 0, (struct sockaddr *)&sv_addr, &svadl en); if( cc<0) perror("recvfrom"); printf("rmsg: %s\n", buf); printf("recieve-byte: %d\n", cc); } close(sd); return 0; Sample code: UDP-echo-client(3/3)
UDP-echo-server側 Client作成との相違点 bind() recvfrom()を最初に実行 Port番号を指定する recvfrom()を最初に実行 recvfrom()でclientがデータを受け取ると,client のソケットアドレスが勝手に格納される! recvfrom(int s, void *buf, size_t len, int flags, struct sockaddr *from, int *fromlen); *fromを用いて受信したデータをsendto()する
#include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <netdb.h> #include <stdio.h> #define BUFMAX 1024 #define PORT_NO 55555 int main(int argc, char *argv[]){ int sd; int cc, ccadlen; char rmsg[BUFMAX]; struct sockaddr_in cl_addr, sv_addr; /* socketの作成 */ ???? /* socketに名前をつける */ /* サーバのソケットアドレスの設定 */ /* 処理ルーチン(メッセージの送信,受信) */ close(sd); return 0; } Sample code: UDP-echo-server(1/3)
/* socketの作成 */ sd = socket( AF_INET, SOCK_DGRAM, 0 ); if( sd < 0 ){ perror("socket"); exit(0); } /* socketに名前をつける */ memset((void *)&sv_addr,0, sizeof(sv_addr)); sv_addr.sin_family = AF_INET; sv_addr.sin_port = htons(PORT_NO); sv_addr.sin_addr.s_addr = htonl ( INADDR_ANY ); if( bind( sd, (struct sockaddr *)&sv_addr, sizeof( sv_addr)) < 0 ){ perror("bind"); cadlen = sizeof(cl_addr); /* 処理ルーチン(メッセージの送信,受信) */ ???? close(sd); return 0; Sample code: UDP-echo-server(2/3)
/* 処理ルーチン(メッセージの送信,受信) */ while(1){ /* receive a message from clients */ cc = recvfrom(sd ,rmsg, BUFMAX, 0, (struct sockaddr *)&cl_addr, &cadlen); if( cc<0) perror("recvfrom"); printf("rmsg: %s\n", rmsg); /* send a message */ if(sendto(sofd,rmsg,strlen(rmsg),0,(struct sockaddr *)&cl_addr, cadlen) < 0){ perror("sendto"); } close(sd); return 0; Sample code: UDP-echo-server(3/3)
TCPプログラミング
TCP (Transmission Control Protocol) server client 信頼性のある通信を提供 データレート制御:不可・困難 身近な例 電子メール WEB イメージ 電話のやりとり S:もしもし C:もしもし、○○です S:××です。あのさ、、、 SYN SYN+ACK ACK データ
read()システムコール int read(int d, void *buf, size t nbytes) 引数 返り値 size_t nbytes: 1回のread()で読める最大バイト数(バ ッファの大きさ) 返り値 int: 実際に読み込んだバイト数 0より大きい: 読み込み成功 0: ファイルの最後(EOF)に到達 0より小さい: エラー
write()システムコール int write(int d, void *buf,size t nbytes) 引数 返り値 size_t nbytes: 1回のwrite()で書き込むバイト数(書き込む データの大きさ) 返り値 int: 実際に書き込んだバイト数 0より大きい: 書き込み成功 0より小さい: エラー
第5回講義の資料 open()/read()/write()/close()の例 #include <fcntl.h> #include <sys/types.h> #include <sys/uio.h> #include <unistd.h> #define BUFSIZE 1024 int main() { char buf[BUFSIZE]; int fd; int nbyte; fd = open(“test.txt”, O_RDONLY, 0); while((nbyte = read(fd, buf, BUFSIZE)) > 0) { write(1, buf, nbyte); } close(fd); exit(0);
実習 TCPを用いたechoサーバを作ろう。 →次のページからに必要となる初出関数の説明 第一引数にポート番号(X)を指定しよう 先週の課題の続き! telnet localhost X でチェックしよう Wiresharkまたはtcpdumpで見ましょう 同時に二つのconnectionをできると証明して →次のページからに必要となる初出関数の説明
TCP 通信の流れ(server) TCP Server socket() TCP client bind() socket() listen() establish connect() accept() write() data read() write() data read() read() end close() close()
Socketを開いた状態 Socketを開く クライアント プロセス サーバ プロセス Port A Port B Port C ホストA IP Address: xx.xx.xx.xx. IP Address: yy.yy.yy.yy
bind()システムコール int bind(int s, const struct sockaddr *addr, int addrlen) 用意したsocketのアドレスを実際にsocketと結びつける IPアドレスとTCP/UDPのポート番号の組 開いたソケットのステートはclosed 実際のコードでは… bind(listenfd, (struct sockaddr *) &servaddr, sizeof(servaddr) 成功なら0、エラーなら-1の返り値
bindした状態 Proto LocalAddress ForeignAddress State TCP *.A *.* Closed クライアント プロセス サーバ プロセス Port A Port B Port C ホストA ホストB IP Address: xx.xx.xx.xx. IP Address: yy.yy.yy.yy
listen()システムコール listen(listenfd, LISTENQ) int listen(int s, int backlog) 用意したsocketを待ち受け準備状態にする ソケットの状態をCLOSEDからLISTENへ backlogはキューの長さ(backlog分の要求を保持できる) accept()されるまでbacklog分のキューに保持 キューがあふれると、その要求は無視 実際のコードでは… listen(listenfd, LISTENQ) 成功なら0、エラーなら-1の返り値
Listen() Proto LocalAddress ForeignAddress State TCP *.A *.* Listen クライアント プロセス サーバ プロセス Port A Port B Port C ホストA ホストB IP Address: xx.xx.xx.xx. IP Address: yy.yy.yy.yy
accept()システムコール int accept(int s, struct sockaddr *addr, int *addrlen) キューで待っている接続要求を取り出して、そのクライ アントと通信するためのディスクリプタを作成して、返す clientにはクライアントのsocketのアドレス、namelen にはclientのサイズ 実際のコードでは… accept(listenfd, (struct sockaddr *) &cliaddr, &clilen) 成功なら新しいFD番号、エラーなら-1の返り値
accept() Proto LocalAdddress ForeignAddress State TCP xx.xx.xx.xx.A yy.yy.yy.yy.X Establish Connect() クライアント プロセス サーバ プロセス Port X Port A Port B Port C ホストA ホストB IP Address: xx.xx.xx.xx. IP Address: yy.yy.yy.yy
accept() Proto LocalAdddress ForeignAddress State TCP *.A *.* Listen TCP xx.xx.xx.xx.A yy.yy.yy.yy.X Establish Connect() クライアント プロセス サーバ プロセス Port X Port A Port B Port C ホストA ホストB IP Address: xx.xx.xx.xx. IP Address: yy.yy.yy.yy
使い方例 int socket_fd, accept_fd; int client_addrlen; int readlen; struct sockaddr_in server, client; socket_fd = socket(AF_INET,SOCK_STREAM, 0); memset((void *)&server, 0, sizeof(server)); 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 通信の流れ(client) TCP Server socket() TCP client bind() socket() listen() establish connect() accept() write() data read() write() data read() read() end close() close()
connect()システムコール 別のソケットとの接続要求 #include <sys/types.h> #include <sys/socket.h> int connect(int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen); int sockfd; (ソケット記述子) struct sockaddr *serv_addr; (プロトコル対応のアドレ ス構造体へのポインタ) socklen_t addrlen; (アドレス構造体のサイズ) 返り値 接続に成功するとゼロを返す.失敗すると-1を返す client server
課題:tcpでechoクライアント TCPでechoクライアントを書いて見ましょう 第一引数にホスト名,第2引数にポート番号を指定 自分のサーバにアクセス 次ページにポイント説明あり
gethostbyname() ホスト名からIPアドレスを得る #include <unistd.h> struct hostent gethostname(const char *name); 例 struct sockaddr_in sin; struct hostent *shost; shost = gethostbyname(argv[1]); sin.sin_addr = *(struct in_addr *)hp->h_addr;
sample #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; }
hostent構造体 struct hostent{ char *h_name; /* official name of host */ char **h_aliases; /* alias list */ int h_addrtype /* host address type */ int h_length /* length of address */ char **h_addr_list; /* list of addresses */ } #define h_addr h_addr_list[0] /* for backward compatibility
練習: ポイント 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; connect(sock_fd, (struct sockaddr *)&sin, sizeof(sin)); fgets(buf,sizeof(buf),stdin); write(sock_fd, buf, readlen); readlen = read(sock_fd, buf, sizeof(buf)); printf("%s\n",buf);
gethostname() ホスト名を取得する #include <unistd.h> int gethostname(char *name, size_t len); 返り値:成功した場合0,失敗した場合-1が返る 例: char shostname[64]; gethostname(shostname, sizeof(shostname));