筑波大学 システム情報工学研究科 コンピュータサイエンス専攻, 電子・情報工学系 新城 靖 <yas@is.tsukuba.ac.jp>
このページは、次の URL にあります。
http://www.coins.tsukuba.ac.jp/~syspro/2010/No7_files/echo-server-nofork-fdopen.html
あるいは、次のページから手繰っていくこともできます。
http://www.coins.tsukuba.ac.jp/~syspro/2010/
http://www.coins.tsukuba.ac.jp/~yas/
87: void 88: print_my_host_port( int portno ) 89: { 90: char hostname[100] ; 91: gethostname( hostname,sizeof(hostname) ); 92: hostname[99] = 0 ; 93: printf("run telnet %s(v6) %d \n",hostname, portno ); 94: }gethostname() システムコールで自分自身のホスト名を取り出している。(正 式には、gethostname() の結果とインターネット的なホスト名(IPアドレスと 対応している))が一致して異ないことがある。
coins では、2010年6月現在、IPv4 を主に使い、普通のホスト名ではIPv4 の IPアドレスが返されるようにっている。IPv6 のIPアドレスは、ホスト名の末尾 に v6 を付けた名前で引けるようなっている。たとえば、 cosmos10.coins.tsukuba.ac.jp なら、IPv4 の IP アドレス、 cosmos10v6.coins.tsukuba.ac.jp なら、IPv6 の IP アドレスが引けるように なっている。
一般的には、同じホスト名で IPv4 と IPv6 の両方のアドレスを登録すること もできる。移行の過程では、片方のアドレスで接続を試みて時間切れを起こし た後に別のアドレスで接続を試みるとしう処理が入るために、応答時間が増大 することがある。
95: 96: void 97: tcp_peeraddr_print( int com ) 98: { 99: struct sockaddr_storage addr ; 100: socklen_t addr_len ; /* MacOSX: __uint32_t */ 101: addr_len = sizeof( addr ); 102: if( getpeername( com, (struct sockaddr *)&addr, &addr_len )<0 ) 103: { 104: perror("tcp_peeraddr_print"); 105: return; 106: } 107: printf("[%d] connection (fd==%d) from ",getpid(),com ); 108: sockaddr_print( (struct sockaddr *)&addr, addr_len ); 109: printf("\n"); 110: } 111:通信相手のアドレス(IPアドレスとポート番号)は、getpeername() システムコー ルで得られる。得たアドレスを、sockaddr_print() に渡して表示している。
112: void 113: sockaddr_print( struct sockaddr *addrp, socklen_t addr_len ) 114: { 115: char host[BUFFERSIZE] ; 116: char port[BUFFERSIZE] ; 117: if( getnameinfo(addrp, addr_len, host, sizeof(host), 118: port, sizeof(port), NI_NUMERICHOST|NI_NUMERICSERV)<0 ) 119: return; 120: if( addrp->sa_family == PF_INET ) 121: printf("%s:%s", host, port ); 122: else 123: printf("[%s]:%s", host, port ); 124: } 125:IP アドレスは、IPv4 では、32 ビット(int)であり、 ポート番号は、16 ビット(sort)である。 ここでは、getnameinfo() ライブラリ関数を用いてホスト名とポート番号の 文字列表現に変換している。この時、NUMERIC と指定しいるので、 IPv4 では、ドット「.」で区切られた10進数4つになる。
getnameinfo() が使われている部分では、以前は、gethostbyaddr() が使われ ていた。
126: #define PORTNO_BUFSIZE 30 127: 128: int 129: tcp_acc_port( int portno, int ip_version ) 130: { 131: struct addrinfo hints, *ai; 132: char portno_str[PORTNO_BUFSIZE]; 133: int err, s, on, pf; 134: 135: switch( ip_version ) 136: { 137: case 4: 138: pf = PF_INET; 139: break; 140: case 6: 141: pf = PF_INET6; 142: break; 143: default: 144: fprintf(stderr,"bad IP version: %d. 4 or 6 is allowed.\n", 145: ip_version ); 146: goto error0; 147: } 148: snprintf( portno_str,sizeof(portno_str),"%d",portno ); 149: memset( &hints, 0, sizeof(hints) ); 150: ai = NULL; 151: hints.ai_family = pf ; 152: hints.ai_flags = AI_PASSIVE; 153: hints.ai_socktype = SOCK_STREAM ; 154: if( (err = getaddrinfo( NULL, portno_str, &hints, &ai )) ) 155: { 156: fprintf(stderr,"bad portno %d? (%s)\n",portno, 157: gai_strerror(err) ); 158: goto error0; 159: } 160: if( (s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol)) < 0 ) 161: { 162: perror("socket"); 163: goto error1; 164: } 165: 166: #ifdef IPV6_V6ONLY 167: if( ai->ai_family == PF_INET6 ) 168: { 169: on = 1; 170: if( setsockopt(s,IPPROTO_IPV6, IPV6_V6ONLY,&on,sizeof(on)) < 0 ) 171: { 172: perror("setsockopt(,,IPV6_V6ONLY)"); 173: goto error1; 174: } 175: } 176: #endif /*IPV6_V6ONLY*/ 177: 178: if( bind(s,ai->ai_addr,ai->ai_addrlen) < 0 ) 179: { 180: perror("bind"); 181: fprintf(stderr,"port number %d can be already used. wait a moment or kill another program.\n", portno ); 182: goto error2; 183: } 184: on = 1; 185: if( setsockopt( s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on) ) < 0 ) 186: { 187: perror("setsockopt(,,SO_REUSEADDR)"); 188: goto error2; 189: } 190: if( listen( s, 5 ) < 0 ) 191: { 192: perror("listen"); 193: goto error2; 194: } 195: freeaddrinfo( ai ); 196: return( s ); 197: 198: error2: 199: close( s ); 200: error1: 201: freeaddrinfo( ai ); 202: error0: 203: return( -1 ); 204: } 205:
この関数の引数は、ポート番号(整数値)と IP のバージョン(4か6、整数値)を取る。 まず、IPのバージョンに応じて、Protocol Family (PF_) を選択している。 socket() の引数で、PF_INET/PF_INET6 の変りに、AF_INET/AF_INET6 と書いて もよい。ここでは、Protocol を選んでいるので、PF_ が正しいが、実際には、 PF_INET と AF_INET は同じであり、また、多くのテキストで混在されて使われ いる。
次に、getaddrinfo() を使って、socket() システム・コール、および、 bind() システム・コールに渡すためのパラメタを整えている。 hints.ai_family には、 PF_INET か PF_INET6 を設定している。 hints.ai_socktype に SOCK_STREAM を設定している。これにより、TCP を意味 する。ポート番号は、引数で与えられたもの(を、文字列に変換したもの)を指 定している。
hints.ai_flags には、AI_PASSIVE を指定し、 getaddrinfo() の第一引数とし て NULL を指定している。複数の IP アドレスがある時には、どれに要求が来 ても受け付ける。特定の IP アドレスを指定すると、そのアドレスに来た要求 だけを受け付けるようになる。IPv4 の時代には、IPアドレスとして INADDR_ANY という定数を指定することが一般的であった。
次に、クライアント側と同様に、ソケットを、socket() システムコールで作成 している。 PF_INET と SOCK_STREAMの組み合わせ または、 PF_INET6 と SOCK_STREAMの組み合わせ なので、TCP を使うことを意味する。
IPv6 の場合、setsockopt() で IPV6_V6ONLY というオプションを有効にしてい る。これで、IPv6 が指定された時には、IPv6 のみで接続要求を受け付ける。 このオプションをつけないと、IPv4射影アドレス(IPv4-mapped address)でも 要求を受け付けることになる。意図せずこのアドレスでもサービスを提供する とセキュリティ上の問題が生じることがある。
ソケットが作成できたら、bind() システムコールで、サーバ側で利用するア ドレス(IPアドレスとポート番号)を設定する。IP アドレスとポート番号は、 getaddrinfo() で返されたものを使っている。
次に、setsockopt() で、SO_REUSEADDR オプションを設定している。これによ り、同じ IP アドレスとポート番号で(連続して)サービスを提供することがや りやすくなる。ただし、可能性は低いが、前のプロセスと新しいプロセスで、 通信内容が混じってしまう危険性がある。
次に、listen() システムコールにより、要求受け付けを開始する。第2引数 は、最大何個のクライアントを接続要求待ちで待たせるか(待ち行列の長さ) を指定する。 重たいサーバを設計する時には、キューの長さを調節する。Apache (WWW サー バ) などでは、500 程度になっていることがある。
注意:このプログラムには 複数のクライアントに対してサービスを同時に提供できない という問題がある。