システムプログラム(第7週): fork()による複数のクライアントに対するサービスの同時提供


電子・情報工学系/システム情報工学研究科CS専攻
新城 靖
<yas@is.tsukuba.ac.jp>

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

echo-server-fork-fdopen.c

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

   1:	/*
   2:	        echo-server-fork-fdopen.c -- 受け取った文字列をそのまま返すサーバ(fork版)
   3:	        ~yas/syspro/ipc/echo-server-fork-fdopen.c
   4:	        Created on 2004/05/09 19:57:07
   5:	*/
   6:	#define _USE_BSD        /* wait4() */
   7:	#include <stdio.h>
   8:	#include <sys/types.h>  /* socket(), wait4() */
   9:	#include <sys/socket.h> /* socket() */
  10:	#include <netinet/in.h> /* struct sockaddr_in */
  11:	#include <sys/resource.h> /* wait4() */
  12:	#include <sys/wait.h>   /* wait4() */
  13:	#include <netdb.h>      /* getnameinfo() */
  14:	
  15:	extern  void echo_server( int portno );
  16:	extern  void echo_reply( int com );
  17:	extern  void print_my_host_port( int portno );
  18:	extern  void tcp_peeraddr_print( int com );
  19:	extern  void sockaddr_print( struct sockaddr *addrp, int addr_len );
  20:	extern  tcp_acc_port( int portno );
  21:	extern  void delete_zombie(void);
  22:	extern  int fdopen_sock( int sock, FILE **inp, FILE **outp );
  23:	
  24:	main( int argc, char *argv[] )
  25:	{
  26:	    int portno ;
  27:	        if( argc >= 3 )
  28:	        {
  29:	            fprintf( stdout,"Usage: %s [portno] \n",argv[0] );
  30:	            exit( -1 );
  31:	        }
  32:	        if( argc == 2 )
  33:	            portno = strtol( argv[1],0,10 );
  34:	        else
  35:	            portno = getuid();
  36:	        echo_server( portno );
  37:	}
  38:	
main() は、 echo-server-nofork-fdopen.c と同じである。
  39:	void
  40:	echo_server( int portno )
  41:	{
  42:	    int acc,com ;
  43:	    pid_t child_pid ;
  44:	        acc = tcp_acc_port( portno );
  45:	        if( acc<0 )
  46:	            exit( -1 );
  47:	        print_my_host_port( portno );
  48:	        while( 1 )
  49:	        {
  50:	            delete_zombie();
  51:	            if( (com = accept( acc,0,0 )) < 0 )
  52:	            {
  53:	                perror("accept");
  54:	                exit( -1 );
  55:	            }
  56:	            tcp_peeraddr_print( com );
  57:	            if( (child_pid=fork()) > 0 ) /* parent */
  58:	            {
  59:	                close( com );
  60:	            }
  61:	            else if( child_pid == 0 ) /* child */
  62:	            {
  63:	                close( acc );
  64:	                echo_reply( com );
  65:	                exit( 0 );
  66:	            }
  67:	            else
  68:	            {
  69:	                perror("fork");
  70:	                exit( -1 );
  71:	            }  
  72:	        }
  73:	}

delete_zombie() は、ゾンビ・プロセス(後述)を消去するものである。 (改善の余地がある。) echo サービスとしての処理は、fork() システムコールで分身を作り、子プロ セス側で行う。親プロセスは、すぐにaccept() に戻る。

getpid() は、自分自身の PID (Process ID (identifier)) を返すシステムコー ルである。PID (pid_t) は、16 ビット(システムによっては32ビット)の整数 である。実行結果で printf() の表示に、PID が表示されているが、同じではないことに 注意しなさい。

 167:	void
 168:	delete_zombie()
 169:	{
 170:	    pid_t pid ;
 171:	    while( (pid=wait4(-1,0,WNOHANG,0)) >0 )
 172:	    {
 173:	        printf("[%d] zombi %d deleted.\n",getpid(),pid );
 174:	        continue;
 175:	    }
 176:	}
wait4() システムコールを使って、終了した子プロセスをwait してあげてい る。ただし、WNOHANG オプションを付けてあるので、終了した子プロセスがい なければ、待たずに返ってくる。

他の関数は、 echo-server-nofork-fdopen.c と同じである。

 % diff -c echo-server-nofork-fdopen.c echo-server-fork-fdopen.c
 *** echo-server-nofork-fdopen.c Sun May  9 23:19:57 2004
 --- echo-server-fork-fdopen.c   Mon May 10 11:44:38 2004
 ***************
 *** 1,8 ****
 - 
   /*
 !       echo-server-nofork-fdopen.c -- 受け取った文字列をそのまま返すサーバ(fork無し版)
 !       ~yas/syspro/ipc/echo-server-nofork-fdopen.c
 !       Created on 2004/05/09 19:08:47
   */
   #define _USE_BSD      /* wait4() */
   #include <stdio.h>
 --- 1,7 ----
   /*
 !       echo-server-fork-fdopen.c -- 受け取った文字列をそのまま返すサーバ(fork版)
 !       ~yas/syspro/ipc/echo-server-fork-fdopen.c
 !       Created on 2004/05/09 19:57:07
   */
   #define _USE_BSD      /* wait4() */
   #include <stdio.h>
 ***************
 *** 19,24 ****
 --- 18,24 ----
   extern        void tcp_peeraddr_print( int com );
   extern  void sockaddr_print( struct sockaddr *addrp, int addr_len );
   extern        tcp_acc_port( int portno );
 + extern        void delete_zombie(void);
   extern        int fdopen_sock( int sock, FILE **inp, FILE **outp );

   main( int argc, char *argv[] )
 ***************
 *** 40,58 ****
   echo_server( int portno )
   {
       int acc,com ;
	 acc = tcp_acc_port( portno );
	 if( acc<0 )
	     exit( -1 );
	 print_my_host_port( portno );
	 while( 1 )
	 {
	     if( (com = accept( acc,0,0 )) < 0 )
	     {
		 perror("accept");
		 exit( -1 );
	     }
	     tcp_peeraddr_print( com );
 !           echo_reply( com );
	 }
   }

 --- 40,74 ----
   echo_server( int portno )
   {
       int acc,com ;
 +     pid_t child_pid ;
	 acc = tcp_acc_port( portno );
	 if( acc<0 )
	     exit( -1 );
	 print_my_host_port( portno );
	 while( 1 )
	 {
 +           delete_zombie();
	     if( (com = accept( acc,0,0 )) < 0 )
	     {
		 perror("accept");
		 exit( -1 );
	     }
	     tcp_peeraddr_print( com );
 !           if( (child_pid=fork()) > 0 ) /* parent */
 !           {
 !               close( com );
 !           }
 !           else if( child_pid == 0 ) /* child */
 !           {
 !               close( acc );
 !               echo_reply( com );
 !               exit( 0 );
 !           }
 !           else
 !           {
 !               perror("fork");
 !               exit( -1 );
 !           }  
	 }
   }

 ***************
 *** 149,154 ****
 --- 165,181 ----
	     return( -1 );
	 }
	 return( s );
 + }
 + 
 + void
 + delete_zombie()
 + {
 +     pid_t pid ;
 +     while( (pid=wait4(-1,0,WNOHANG,0)) >0 )
 +     {
 +       printf("[%d] zombi %d deleted.\n",getpid(),pid );
 +       continue;
 +     }
   }

   int
 % 

実行例。

サーバ側。 サーバは、終了しないので、最後に、^C を押して、割り込みを掛け て終了させる。

注意:全員がポート番号 1231 を使うとプログラムが動かないことがある。


% ./echo-server-fork-fdopen 1231 [←]
run telnet adonis9.coins.tsukuba.ac.jp 1231 
[6907] connection (fd==4) from 130.158.86.28:47332
[6908] read(4,,) 5 bytes, [012
]
[6907] connection (fd==4) from 130.158.86.29:42126
[6910] read(4,,) 5 bytes, [abc
]
[6910] read(4,,) 5 bytes, [def
]
[6910] connection (fd==4) closed.
[6908] read(4,,) 5 bytes, [345
]
[6908] connection (fd==4) closed.
クライアント側(その1)。
% telnet adonis9.coins.tsukuba.ac.jp 1231 [←]
Trying 130.158.86.29...
Connected to adonis9.coins.tsukuba.ac.jp.
Escape character is '^]'.
012[←]
012
345[←]
345
^]
telnet> quit[←]
Connection closed.
% []
クライアント側(その2)。
% telnet adonis9.coins.tsukuba.ac.jp 1231 [←]
Trying 130.158.86.29...
Connected to adonis9.coins.tsukuba.ac.jp.
Escape character is '^]'.
abc[←]
abc
def[←]
def
^]
telnet> quit[←]
Connection closed.
% []

図1 サーバが accept() で接続要求を待っている

図1 サーバが accept() で接続要求を待っている

図2 サーバが accept()している時にクライアントが connect()した所

図2 サーバが accept()している時にクライアントが connect()した所

図3 accept() の結果、通信用ポート com が作られる

図3 accept() の結果、通信用ポート com が作られる

図4 fork() して、親子に別れる。

図4 fork() して、親子に別れる。

図5 親は、com を close()、子は acc を close() する

図5 親は、com を close()、子は acc を close() する

図6 親は、再び accept() で待ち、子は、特定のクライアントに対して read()/write() する。

図6 親は、再び accept() で待ち、子は、特定のクライアントに対して read()/write() する。

図7 サーバが別のクライアントから接続要求を受け付ける

図7 サーバが別のクライアントから接続要求を受け付ける

図8 accept() の結果、通信用ポート com が作られる

図8 accept() の結果、通信用ポート com が作られる

図9 fork() して、親子に別れる。

図9 fork() して、親子に別れる。

図10 親は、com を close()、子は acc を close() する

図10 親は、com を close()、子は acc を close() する

図11 親は、再び accept() で待ち、子は、特定のクライアントに対して read()/write() する。

図11 親は、再び accept() で待ち、子は、特定のクライアントに対して read()/write() する。

ゾンビ・プロセス

exit(2) システム・コールで終了したり、ソフトウェア割り込み(kill(2))で 強制終了させれたプロセスは、親プロセスが wait() するまで、形だけのこっ ている。このようなプロセスは、ゾンビ(Zombie)と呼ばれる。

ps -l コマンドで見ると、ゾンビの状態(S) は、Z と表示される。

% ps aux | egrep echo [←]
yas       6765  0.0  0.2  1752  684 pts/5    S    22:09   0:00 less echo-server-
yas       6907  0.0  0.1  1364  396 pts/1    S    22:51   0:00 ./echo-server-for
yas       6908  0.0  0.0     0    0 pts/1    Z    22:51   0:00 [echo-server-for 
yas       6910  0.0  0.0     0    0 pts/1    Z    22:51   0:00 [echo-server-for 
yas       6913  0.0  0.1  1448  444 pts/4    R    22:53   0:00 egrep echo
% []
echo-server-fork-fdopen.c では、 delete_zombie() でゾンビを消そうとはしている。しかし、accept() で止まっ ている状態なので、どこかのクライアントから接続要求が来ない限りは、 delete_zombie() が呼ばれないので、ゾンビとして残っている。

この状態で接続要求が来ると、ゾンビが回収される。

% ./echo-server-fork-fdopen 1231 [←]
run telnet adonis9.coins.tsukuba.ac.jp 1231 
[6907] connection (fd==4) from 130.158.86.28:47332
[6908] read(4,,) 5 bytes, [012
]
[6907] connection (fd==4) from 130.158.86.29:42126
[6910] read(4,,) 5 bytes, [abc
]
[6910] read(4,,) 5 bytes, [def
]
[6910] connection (fd==4) closed.
[6908] read(4,,) 5 bytes, [345
]
[6908] connection (fd==4) closed.
[6907] connection (fd==4) from 127.0.0.1:42127
[6907] zombi 6910 deleted.
[6907] zombi 6908 deleted.


Last updated: 2005/06/05 20:39:31
Yasushi Shinjo / <yas@is.tsukuba.ac.jp>