システムプログラム(第7週): 受け取った文字列をそのまま返すサーバ(fork無し版)

                                       筑波大学 システム情報工学研究科 
                                       コンピュータサイエンス専攻, 電子・情報工学系
                                       新城 靖
                                       <yas@is.tsukuba.ac.jp>

このページは、次の URL にあります。
http://www.coins.tsukuba.ac.jp/~syspro/2009/No7_files/echo-server-nofork-fdopen.html
あるいは、次のページから手繰っていくこともできます。
http://www.coins.tsukuba.ac.jp/~syspro/2009/
http://www.coins.tsukuba.ac.jp/~yas/

echo-server-nofork-fdopen.c

TCP/IP のポート番号 7 (echo) では、受け取ったデータをそのまま返すサー ビスを提供している。以下は、これと同じような機能を提供するサーバである。 複数の接続先(クライアント)の要求を同時に処理するために、クライアント ごとに fork() システム・コールで専用の子プロセスを作る。

print_my_host_port()

print_my_host_port() は、telnet で接続する時のヒントを表示する。
  84:	void
  85:	print_my_host_port( int portno )
  86:	{
  87:	    char hostname[100] ;
  88:	        gethostname( hostname,sizeof(hostname) );
  89:	        hostname[99] = 0 ;
  90:	        printf("run telnet %s %d \n",hostname, portno );
  91:	}
  92:	
gethostname() システムコールで自分自身のホスト名を取り出している。(正 式には、gethostname() の結果とインターネット的なホスト名(IPアドレスと 対応している))が一致して異ないことがある。

tcp_peeraddr_print()

tcp_peeraddr_print() は、通信相手(peer)のアドレス(TCP/IPの場合、IPアド レスとポート番号)を表示する。
  93:	void
  94:	tcp_peeraddr_print( int com )
  95:	{
  96:	    struct sockaddr_storage addr ;
  97:	    socklen_t addr_len ; /* MacOSX: __uint32_t */
  98:	        addr_len = sizeof( addr );
  99:	        if( getpeername( com, (struct sockaddr *)&addr, &addr_len  )<0 )
 100:	        {
 101:	            perror("tcp_peeraddr_print");
 102:	            return;
 103:	        }
 104:	        printf("[%d] connection (fd==%d) from ",getpid(),com );
 105:	        sockaddr_print( (struct sockaddr *)&addr, addr_len );
 106:	        printf("\n");
 107:	}
 108:	
通信相手のアドレス(IPアドレスとポート番号)は、getpeername() システムコー ルで得られる。得たアドレスを、sockaddr_print() に渡して表示している。

sockaddr_print()

sockaddr_print() は、引数で与えられた struct sockaddr に含まれる IP アドレスとポート番号を画面に表示する関数である。
 109:	void
 110:	sockaddr_print( struct sockaddr *addrp, socklen_t addr_len )
 111:	{
 112:	    char host[BUFFERSIZE] ;
 113:	    char port[BUFFERSIZE] ;
 114:	        if( getnameinfo(addrp, addr_len, host, sizeof(host),
 115:	                        port, sizeof(port), NI_NUMERICHOST|NI_NUMERICSERV)<0 )
 116:	            return;
 117:	        printf("%s:%s", host, port );
 118:	}
 119:	
IP アドレスは、IPv4 では、32 ビット(int)であり、 ポート番号は、16 ビット(sort)である。 ここでは、getnameinfo() ライブラリ関数を用いてホスト名とポート番号の 文字列表現に変換している。この時、NUMERIC と指定しいるので、 IPv4 では、ドット「.」で区切られた10進数4つになる。

getnameinfo() が使われている部分では、以前は、gethostbyaddr() が使われ ていた。

tcp_acc_port()

tcp_acc_port() は、 通信路の開設 の仕事のうち、サーバ側で接続要求受付用ポートを作る関数である。
 120:	tcp_acc_port( int portno )
 121:	{
 122:	    struct sockaddr_in addr ;
 123:	    int addr_len ;
 124:	    int s ;
 125:	
 126:	        if( (s = socket(PF_INET, SOCK_STREAM, 0)) < 0 )
 127:	        {
 128:	            perror("socket");
 129:	            return( -1 );
 130:	        }
 131:	
 132:	        memset( &addr, 0, sizeof(addr) );
 133:	        addr.sin_family = AF_INET ;
 134:	        addr.sin_addr.s_addr = INADDR_ANY ;
 135:	        addr.sin_port = htons( portno );
 136:	
 137:	        if( bind(s,(struct sockaddr *)&addr,sizeof(addr)) < 0 )
 138:	        {
 139:	            perror("bind");
 140:	            fprintf(stderr,"port number %d is already used. wait a moment or kill another program.\n", portno );
 141:	            return( -1 );
 142:	        }
 143:	        if( listen( s, 5 ) < 0 )
 144:	        {
 145:	            perror("listen");
 146:	            close( s );
 147:	            return( -1 );
 148:	        }
 149:	        return( s );
 150:	}
 151:	
まず、クライアント側と同様に、ソケットを、socket() システムコールで作成している。 PF_INET と SOCK_STREAMの組み合わせ なので、TCP を使うことを意味する。

socket() の引数で、PF_INET の変りに、AF_INET と書いてもよい。ここでは、 Protocol を選んでいるので、PF_ が正しいが、実際には、PF_INET と AF_INET は同じであり、また、多くのテキストで混在されて使われいる。

ソケットが作成できたら、bind() システムコールで、サーバ側で利用するア ドレス(IPアドレスとポート番号)を設定する。IP アドレスは、IPv4 では普通、 INADDR_ANY を指定する。複数の IP アドレスがある時には、どれに要求が来 ても受け付ける。特定の IP アドレスを指定すると、そのアドレスに来た要求 だけを受け付けるようになる。

ポート番号は、引数で与えられたものを、htons() でネットワーク・バイトオー ダに変換して与える。

次に、listen() システムコールにより、要求受け付けを開始する。第2引数 は、最大何個のクライアントを接続要求待ちで待たせるか(待ち行列の長さ) を指定する。 重たいサーバを設計する時には、キューの長さを調節する。Apache (WWW サー バ) などでは、500 程度になっていることがある。

注意:このプログラムには 複数のクライアントに対してサービスを同時に提供できない という問題がある。

bind() する addr を、getaddrinfo() で調べる流儀(IPv6風)もある。この場 合、getaddrinfo()の第一引数には、NULL を入れ、hints.ai_flags には AI_PASSIVE を設定する。

fdopen_sock()

fdopen_sock() は、TCP/IP による通信を、fprintf(), fgets(), fread() 等で 行えるようにする関数である。
 152:	int
 153:	fdopen_sock( int sock, FILE **inp, FILE **outp )
 154:	{
 155:	    int sock2 ;
 156:	        if( (sock2=dup(sock)) < 0 )
 157:	        {
 158:	            return( -1 );
 159:	        }
 160:	        if( (*inp = fdopen( sock2, "r" )) == NULL )
 161:	        {
 162:	            close( sock2 );
 163:	            return( -1 );
 164:	        }
 165:	        if( (*outp = fdopen( sock, "w" )) == NULL )
 166:	        {
 167:	            fclose( *inp );
 168:	            *inp = 0 ;
 169:	            return( -1 );
 170:	        }
 171:	        setvbuf(*outp, (char *)NULL, _IONBF, 0);
 172:	        return( 0 );
 173:	}
この関数は、 クライアント側 とまったく同じである。
Last updated: 2009/06/08 19:47:38
Yasushi Shinjo / <yas@is.tsukuba.ac.jp>