電子・情報工学系/システム情報工学研究科CS専攻 新城 靖 <yas@is.tsukuba.ac.jp>
このページは、次の URL にあります。
http://www.coins.tsukuba.ac.jp/~syspro/2006/No8.html
あるいは、次のページから手繰っていくこともできます。
http://www.coins.tsukuba.ac.jp/~syspro/2006/
http://www.coins.tsukuba.ac.jp/~yas/
IP 層では、アドレスとしては、IP アドレスだけを使う。UDP/IP は、IP 層の 機能をほとんどそのままの形で利用可能にしたものである。違いは、ポート番 号を指定できることと、チェックサムでデータの内容を検査できることなどで ある。
UDP/IP でも、クライアント側のアドレスは、OSに任せ、自らは指 定しないことが多い(指定することもできる)。
UDP/IP でも、通信パタンによる
クライアント・サーバ・モデル
に基づくことが多い。
図? UDP/IPによるクライアント・サーバ型の通信
telnet は、TCP/IP の汎用のクライアント(文字列の送受信用)として使える。 しかし、UDP/IP 用のクライアントとしては使えない。
1:
2: /*
3: echo-client-udp.c -- 文字列を送受信するクライアント(UDP/IP版)
4: ~yas/syspro/ipc/echo-client-udp.c
5: Created on: 1997/06/16 21:22:26
6: */
7: #include <stdio.h>
8: #include <stdlib.h> /* exit() */
9: #include <sys/types.h> /* socket() */
10: #include <sys/socket.h> /* socket() */
11: #include <netinet/in.h> /* struct sockaddr_in */
12: #include <netdb.h> /* getaddrinfo() */
13: #include <string.h> /* strlen() */
14:
15: extern void echo_client_udp( char *server, int portno );
16: extern int udp_port_nobind();
17: extern int sockaddr_in_init( struct sockaddr_in *addr, int addrlen,
18: char *hostname, int portno );
19: extern void sockaddr_print( struct sockaddr *addrp, socklen_t addr_len );
20: extern void sockname_print( int s );
21:
22: main( int argc, char *argv[] )
23: {
24: char *server ;
25: int portno ;
26: if( argc != 3 )
27: {
28: fprintf( stdout,"Usage: %% %s host port\n",argv[0] );
29: exit( -1 );
30: }
31: server = argv[1] ;
32: portno = strtol( argv[2],0,10 );
33: echo_client_udp( server, portno );
34: }
35:
main の部分は、TCP/IP 版の echo サービスのクライアント とほとんど同じである。
main() 関数は、コマンドラインの引数を調べて、echo_client() を読んでい る。strtol() で、文字列で与えられたポート番号を、int に変換している。
36: #define BUFFERSIZE 1024
37:
38: void
39: echo_client_udp( char *server, int portno )
40: {
41: int sock ;
42: int slen,scount,rcount ;
43: char sbuf[BUFFERSIZE];
44: char rbuf[BUFFERSIZE];
45: int i ;
46: socklen_t fromlen ;
47: struct sockaddr_in to; struct sockaddr_storage from ;
48:
49: sock = udp_port_nobind();
50: if( sock<0 )
51: exit( -1 );
52: printf("my port is "); sockname_print( sock ); printf("\n");
53:
54: if( sockaddr_in_init( &to, sizeof(to), server, portno )<0 )
55: {
56: perror("sockaddr_in_init");
57: exit( -1 );
58: }
59: printf("server is ");
60: sockaddr_print( (struct sockaddr *)&to, sizeof(to) ); printf("\n");
61:
62: printf("==> "); fflush(stdout);
63: while( fgets(sbuf,BUFFERSIZE,stdin) )
64: {
65: slen = strlen( sbuf );
66: printf("sending [%s] (%d bytes) to ", sbuf, slen );
67: sockaddr_print( (struct sockaddr *)&to, sizeof(to) ); printf("\n");
68:
69: scount = sendto( sock, sbuf, slen, 0,
70: (struct sockaddr *)&to, sizeof(to) );
71: if( scount != slen )
72: {
73: perror("sendto()");
74: exit( 1 );
75: }
76: printf("after sendo(), my port is "); sockname_print( sock ); printf("\n");
77:
78: fromlen = sizeof( from );
79: rcount = recvfrom( sock, rbuf, BUFFERSIZE, 0,
80: (struct sockaddr *)&from, &fromlen);
81: if( rcount < 0 )
82: {
83: perror("recvfrom()");
84: exit( 1 );
85: }
86: rbuf[rcount] = 0 ;
87: printf("received %d bytes [%s] from ",rcount, rbuf );
88: sockaddr_print( (struct sockaddr *)&from, fromlen ); printf("\n");
89:
90: printf("==> "); fflush(stdout);
91: }
92: printf("\n");
93:
94: close( sock );
95: }
96:
echo_client_udp() では、udp_port_nobind() という関数を呼び出している。
この結果、UDP/IP の通信用ポートが作られ、通信可能なファイル記述子が返される。
ただし、サーバとの間には、通信路は確保されない。このファイル記述子は、
標準入出力(0,1,2)や open() システム・コールの結果と同じもので、不要に
なったら close() で解放する。しかし、この状態では、write() や read()
システムコールは使えない。その代りに、sendto() や recvfrom() システム
コールを使う。
クライアント側では、bind() でアドレス(IPアドレスとポート番号)を設定し ないことが多い。通信に使う前には、アドレスは固定されていない。通信に使 うと、その瞬間に固定され、close() するまで有効である。(クライアント側 でも、bind() でアドレスを固定することもできるが、一般的ではない。)
sockaddr_in_init() は、構造体 to に送り先のアドレス(IPアドレスとポート 番号)をセットしている。
送るデータは、TCP/IP版の同じく文字列である。UDP/IP の通信では、データ の区切りは保存されるので、行末の区切りをつけずに構造体をそのまま送るも のも多い。
sendto() システムコールで、データを送信している。送り先は、第5引数に指 定されている。TCP/IP とは異なり、データの区切りは保存される。また、一 度に全データが送られるので、TCP/IP とは異なり、全デー タを送り出すためのループは不要である。ただし、一度に送ることができるデー タの大きさには上限がある。
途中、階層の通信媒体の上限を越えた場合、「フラグメント化」が行われ、実 際のデータとしては分割されることがある。たとえば、イーサネットでは、 1500バイトを越えた場合、フラグメント化される。フラグメント化されたデー タは、受信側のプロセスに送られる前に組み立てられる。
データを受け取るには、recvfrom() システムコールを使っている。この時、 データの送り手のアドレス(IPアドレスとポート番号)が&from番 地に保存される。普通は、sendto() と同じアドレスから返ってくるが、 違うアドレスから返ってくることもある。
recvfrom() では、最後に終端の 0 を付けてくれないので、自分で 0 を付け ている。
97: int
98: udp_port_nobind()
99: {
100: int s ;
101: if( (s = socket(PF_INET, SOCK_DGRAM, 0)) < 0 )
102: {
103: perror("socket");
104: return( -1 );
105: }
106: return( s );
107: }
108:
udp_port_nobind() は、クライアント側の UDP/IP のポートを作る関数である。 内部では、ソケットを socket() システムコールで作成しているだけである。 PF_INET と SOCK_DGRAMの組み合わせなので、 UDP を使うことを意味する。 socket() の引数で、PF_INET の変りに、AF_INET と書いてもよい。ここでは、 Protocol を選んでいるので、PF_ が正しいが、実際には、PF_INET と AF_INET は同じであり、また、多くのテキストで混在されて使われいる。
UDP/IP でも、connect() システムコールで接続先を固定する方法もあるが、 一般的ではない。接続先を固定した場合は、sendto() や recvfrom() ではな く、write() やread() を使って通信することもできる。その場合も、UDP の 性質は保たれるので、write() したデータのパケットが失われた時には、再転 送は行われない。
109: int
110: sockaddr_in_init( struct sockaddr_in *addr, int addrlen,
111: char *hostname, int portno )
112: {
113: struct addrinfo hints, *ai;
114: int err ;
115:
116: if( addrlen < sizeof(struct sockaddr_in) )
117: {
118: fprintf(stderr,"sockaddr_in, not enough space (%d) > (%d)\n",
119: addrlen, sizeof(struct sockaddr_in) );
120: return( -1 );
121: }
122: memset( &hints, 0, sizeof(hints) );
123: hints.ai_family = AF_INET ; /* IPv4 */
124: if( (err = getaddrinfo( hostname, NULL, &hints, &ai )) )
125: {
126: fprintf(stderr,"unknown host %s (%s)\n",hostname,
127: gai_strerror(err) );
128: return( -1 );
129: }
130: if( ai->ai_addrlen > addrlen )
131: {
132: fprintf(stderr,"sockaddr too large (%d) > (%d)\n",
133: ai->ai_addrlen,sizeof(addr) );
134: freeaddrinfo( ai );
135: return( -1 );
136: }
137: memcpy( addr, ai->ai_addr, ai->ai_addrlen );
138: addr->sin_port = htons( portno );
139: freeaddrinfo( ai );
140:
141: return( 0 );
142: }
sockaddr_in_init() は、
TCP/IPのechoクライアントで用いたものと同じものである。
UDP/IP の場合も、TCP/IP と同様に、アドレスの指定には、sockaddr_in 構造 体を使う。sendto() システムコールのマニュアルには、sockaddr 構造体を使 うようにと書かれているが、UDP/IP (IPv4) では、そのサブクラス(オブジェ クト指向用語)であるsockaddr_in を使う。この場合、IP アドレスとポート番 号が埋められている必要がある。その他に先頭に、sockaddr_in であることを 示す定数 AF_INET を置く。 getaddrinfo() は、ホスト名を IP アドレスし、さらに、先頭に定数 AF_INET を設定している。ポート番号は、第2引数が NULL なので、0 を指定している。 この例では、引数で与えられたポート番号を htons() を介してバイトオーダ を調整してから設定している。
143:
144: void
145: sockaddr_print( struct sockaddr *addrp, socklen_t addr_len )
146: {
147: char host[BUFFERSIZE] ;
148: char port[BUFFERSIZE] ;
149: if( getnameinfo(addrp, addr_len, host, sizeof(host),
150: port, sizeof(port), NI_NUMERICHOST|NI_NUMERICSERV)<0 )
151: return;
152: printf("%s:%s", host, port );
153: }
154:
155: void
156: sockname_print( int s )
157: {
158: struct sockaddr_storage addr ;
159: socklen_t len ;
160: len = sizeof( addr );
161: if( getsockname( s, (struct sockaddr *)&addr, &len )< 0 )
162: {
163: perror("getsockname");
164: exit( -1 );
165: }
166: sockaddr_print( (struct sockaddr *)&addr,len );
167: }
sockaddr_print() は、
echo-server-nofork-fdopen.c
のものと同じである。
getaddrinfo() ライブラリ関数により、ホスト名とポート番号の文字列表現を
得ている。文字列といっても、NI_NUMERICHOST|NI_NUMERICSERV というフラグ
を指定しているので、結果は "123.4.5.6" や "80" のような数字の並びによ
る表記になる。
sockname_print() は、自分自身のポートのアドレスを表示する関数である。 アドレスは、getsockname() で得られる。なお、UDP/IP では、TCP/IP とは異 なり、getpeername() で通信相手を調べることは、この場合できない。UDP/IP では、通信相手は、recvfrom() の引数で知ることができる。 sockname_print() は、 echo-server-nofork-fdopen.c の tcp_peeraddr_print() の getpeername() を、getsockname() に変えたものである。getsockname() は、自分自身の名前(TCP/IP では、IP アドレスとポート番号)を得るためのシステムコールである。
注意:メッセージの送り先としては、 echo-server-udp.c を用いている。 標準の 7 番ポートで動作しているものは、うまく動作しない。
実行例:
% cp ~yas/syspro/ipc/echo-client-udp.c .このプログラムは、コマンドラインから2の引数をとる。第1引数は、ホスト 名、第2引数は、ポート番号である。このプログラムは、標準入力から得られ た行を、その指定されたホスト上のポート番号で動作しているサーバに対して 送る。サーバは、 同じものを送り返してくるので、それを受け取る。% make echo-client-udp
cc echo-client-udp.c -o echo-client-udp % ./echo-client-udp azalea20.coins.tsukuba.ac.jp 1231
my port is 0.0.0.0:0 server is 130.158.86.40:1231 ==> hello
sending [hello ] (6 bytes) to 130.158.86.40:1231 after sendo(), my port is 0.0.0.0:53397 received 6 bytes [hello ] from 130.158.86.40:1231 ==> world!
sending [world! ] (7 bytes) to 130.158.86.40:1231 after sendo(), my port is 0.0.0.0:53397 received 7 bytes [world! ] from 130.158.86.40:1231 ==> ^D % ./echo-client-udp azalea20.coins.tsukuba.ac.jp 1231
my port is 0.0.0.0:0 server is 130.158.86.40:1231 ==> aaa
sending [aaa ] (4 bytes) to 130.158.86.40:1231 after sendo(), my port is 0.0.0.0:53398 received 4 bytes [aaa ] from 130.158.86.40:1231 ==> bbb
sending [bbb ] (4 bytes) to 130.158.86.40:1231 after sendo(), my port is 0.0.0.0:53398 received 4 bytes [bbb ] from 130.158.86.40:1231 ==> ^D %
![]()
1:
2: /*
3: echo-server-udp.c -- 文字列を送受信するサーバ(UDP/IP版)
4: ~yas/syspro/ipc/echo-server-udp.c
5: Created on: 1997/06/16 21:22:26
6: */
7:
8: #include <stdio.h>
9: #include <stdlib.h> /* exit() */
10: #include <sys/types.h> /* socket() */
11: #include <sys/socket.h> /* socket() */
12: #include <netinet/in.h> /* struct sockaddr_in */
13: #include <netdb.h> /* getaddrinfo() */
14: #include <string.h> /* memset() */
15:
16: extern void echo_server_udp( int portno );
17: extern int udp_port_bind( int portno );
18: extern void sockaddr_print( struct sockaddr *addrp, socklen_t addr_len );
19: extern void sockname_print( int s );
20:
21: main( int argc, char *argv[] )
22: {
23: int portno ;
24: if( argc >= 3 )
25: {
26: fprintf( stdout,"Usage: %s [portno]\n",argv[0] );
27: exit( -1 );
28: }
29: if( argc == 2 )
30: portno = strtol( argv[1],0,10 );
31: else
32: portno = getuid();
33: echo_server_udp( portno );
34: }
35:
TCP版のecho サービスのサーバ
と同様に、
引数として、ポート番号を取る。ポート番号が与えられなければ、そのプロセ
スの UID (User ID) から生成する。UID は、個人個人を識別するための番号
(16ビット程度、システムによっては 32 ビット)である。この課題では、(1台
のコンピュータでは)個人ごとに別々の UID を使う必要がある。上のように
UID から生成する方法は、一般的ではない。(筑波大学情報学類のシステムで
はうまく働く。)
36: #define BUFFERSIZE 1024
37:
38: void
39: echo_server_udp( int portno )
40: {
41: int s, rcount, scount;
42: struct sockaddr_storage addr ;
43: socklen_t addrlen ;
44: char buffer[BUFFERSIZE];
45:
46: s = udp_port_bind( portno );
47: if( s<0 )
48: exit( -1 );
49: printf("my port is "); sockname_print( s ); printf("\n");
50:
51: while( 1 )
52: {
53: addrlen = sizeof( addr );
54: rcount = recvfrom( s, buffer, BUFFERSIZE, 0,
55: (struct sockaddr *)&addr, &addrlen );
56: if( rcount < 0 )
57: {
58: perror("recvfrom()");
59: exit( 1 );
60: }
61: buffer[rcount] = 0 ;
62: printf("received %d bytes [%s] from ",rcount, buffer );
63: sockaddr_print( (struct sockaddr *)&addr,addrlen ); printf("\n");
64:
65: printf("sending back [%s] (%d bytes) to ", buffer, rcount );
66: sockaddr_print( (struct sockaddr *)&addr,addrlen ); printf("\n");
67: scount=sendto( s, buffer, rcount, 0, (struct sockaddr *)&addr, addrlen );
68: if( scount!= rcount )
69: {
70: perror("sendto()");
71: exit( 1 );
72: }
73: }
74: }
75:
udp_port_bind() は、引数で与えられたポート番号を使ってUDP/IP のポート
を作成し、そのファイル記述子(ソケット)を返す。sockname_print() は、そ
のアドレス(IPアドレスとポート番号)を表示する。
TCP/IP とは異なり、このポートは、そのまま通信に使うことができる。
逆に、accept() は使うことはできない。
サーバのプログラムの特徴は、内部に無限ループを持っていることである。 サーバは、普通の状態では、終了しない。
このサーバは、fork() しない。サービスの内容が簡単であり、sendto() シス テムコールでブロックすることもないからである。サービスが重たい時には、 他のクライアントからの処理を並列に進めるために、子プロセスを作ること可 能である。
76: int
77: udp_port_bind( int portno )
78: {
79: struct sockaddr_in addr ;
80: int addr_len ;
81: int s ;
82:
83: if( (s = socket(PF_INET, SOCK_DGRAM, 0)) < 0 )
84: {
85: perror("socket");
86: return( -1 );
87: }
88:
89: memset( &addr, 0, sizeof(addr) );
90: addr.sin_family = AF_INET ;
91: addr.sin_addr.s_addr = INADDR_ANY ;
92: addr.sin_port = htons( portno );
93:
94: if( bind(s,(struct sockaddr *)&addr,sizeof(addr)) < 0 )
95: {
96: perror("bind");
97: fprintf(stderr,"port number %d is already used. wait a moment or kill another program.\n", portno );
98: return( -1 );
99: }
100: return( s );
101: }
...
104: sockaddr_print( struct sockaddr *addrp, socklen_t addr_len )
...
115: sockname_print( int s )
udp_port_bind() は、サーバ用に、ポート番号を固定した
UDP/IP のポートを作成する。
クライアント側との違いは、
bind() システムコールを使っている点にある。
bind() システムコールの引数は、 TCP版のecho サービスのサーバ と同じである。 ソケットが作成できたら、bind() システムコールで、サーバ側で利用するア ドレス(IPアドレスとポート番号)を設定する。IP アドレスは、普通は、 INADDR_ANY を指定する。複数の IP アドレスがある時には、どれに要求が来 ても受け付ける。特定の IP アドレスを指定すると、そのアドレスに来た要求 だけを受け付けるようになる。
ポート番号は、引数で与えられたものを、htons() でネットワーク・バイトオー ダに変換して与える。
listen() システムコールは、UDP/IP では使われない。 要求、すなわち、データが大量に送られ、サーバが処理できない時には、(普 通は古いものから順に)捨てられる。
実行例。
サーバは、終了しないので、最後に、^C を押して、割り込みを掛け て終了させる。
注意:全員が同じコンピュータでポート番号 1231 を使うとプログラムが動かないことがある。
% cp ~yas/syspro/ipc/echo-server-udp.c .サーバ側では、bind() をつかっているので、最初からポート番号が固定され ていることがわかる。% make echo-server-udp
cc echo-server-udp.c -o echo-server-udp % ./echo-server-udp 1231
my port is 0.0.0.0:1231 received 6 bytes [hello ] from 130.158.86.50:53397 sending back [hello ] (6 bytes) to 130.158.86.50:53397 received 7 bytes [world! ] from 130.158.86.50:53397 sending back [world! ] (7 bytes) to 130.158.86.50:53397 received 4 bytes [aaa ] from 130.158.86.50:53398 sending back [aaa ] (4 bytes) to 130.158.86.50:53398 received 4 bytes [bbb ] from 130.158.86.50:53398 sending back [bbb ] (4 bytes) to 130.158.86.50:53398 ^C %
![]()
ヒント:DatagramSocket() を使う。
UDP/IP では、一度に送ることができるデータの大きさにには、実装上の制限 が付いている。これがいくつかを調べるプログラムを作りなさい。
UDP/IP のデータを中継するようなプログラムを作りなさい。 単方向だけでなく、双方向で中継するようにしなさい。
このようなプログラムの例として、udprelay と呼ばれるプログラムがある。
UDP/IP のサーバ、または、中継プログラムで、クライアントの IP アドレス によってアクセスを許したりエラーを発生させたりしなさい。