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

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

このページは、次の URL にあります。
http://www.coins.tsukuba.ac.jp/~syspro/2007/No7_files/echo-server-fork.html
あるいは、次のページから手繰っていくこともできます。
http://www.coins.tsukuba.ac.jp/~syspro/2007/
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:	#include <stdio.h>
   7:	#include <stdlib.h>     /* exit() */
   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:	#include <string.h>     /* strlen() */
  15:	
  16:	extern  void echo_server( int portno );
  17:	extern  void echo_reply( int com );
  18:	extern  void delete_zombie(void);
  19:	extern  void print_my_host_port( int portno );
  20:	extern  void tcp_peeraddr_print( int com );
  21:	extern  void sockaddr_print( struct sockaddr *addrp, socklen_t addr_len );
  22:	extern  tcp_acc_port( int portno );
  23:	extern  int fdopen_sock( int sock, FILE **inp, FILE **outp );
  24:	
  25:	main( int argc, char *argv[] )
  26:	{
  27:	    int portno ;
  28:	        if( argc >= 3 )
  29:	        {
  30:	            fprintf( stdout,"Usage: %s [portno] \n",argv[0] );
  31:	            exit( -1 );
  32:	        }
  33:	        if( argc == 2 )
  34:	            portno = strtol( argv[1],0,10 );
  35:	        else
  36:	            portno = getuid();
  37:	        echo_server( portno );
  38:	}
  39:	
main() は、 echo-server-nofork-fdopen.c と同じである。
  40:	void
  41:	echo_server( int portno )
  42:	{
  43:	    int acc,com ;
  44:	    pid_t child_pid ;
  45:	        acc = tcp_acc_port( portno );
  46:	        if( acc<0 )
  47:	            exit( -1 );
  48:	        print_my_host_port( portno );
  49:	        while( 1 )
  50:	        {
  51:	            delete_zombie();
  52:	            if( (com = accept( acc,0,0 )) < 0 )
  53:	            {
  54:	                perror("accept");
  55:	                exit( -1 );
  56:	            }
  57:	            tcp_peeraddr_print( com );
  58:	            if( (child_pid=fork()) > 0 ) /* parent */
  59:	            {
  60:	                close( com );
  61:	            }
  62:	            else if( child_pid == 0 ) /* child */
  63:	            {
  64:	                close( acc );
  65:	                echo_reply( com );
  66:	                exit( 0 );
  67:	            }
  68:	            else
  69:	            {
  70:	                perror("fork");
  71:	                exit( -1 );
  72:	            }  
  73:	        }
  74:	}
  75:	

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

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

  76:	void
  77:	delete_zombie()
  78:	{
  79:	    pid_t pid ;
  80:	    while( (pid=wait4(-1,0,WNOHANG,0)) >0 )
  81:	    {
  82:	        printf("[%d] zombi %d deleted.\n",getpid(),pid );
  83:	        continue;
  84:	    }
  85:	}
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 21 20:12:41 2006
 --- echo-server-fork-fdopen.c   Sun May 21 20:55:59 2006
 ***************
 *** 1,8 ****
 - 
   /*
 !       echo-server-nofork-fdopen.c -- (fork)
 !       ~yas/syspro/ipc/echo-server-nofork-fdopen.c
 !       Created on 2004/05/09 19:08:47
   */
   #include <stdio.h>
   #include <stdlib.h>   /* exit() */
 --- 1,7 ----
   /*
 !       echo-server-fork-fdopen.c -- (fork)
 !       ~yas/syspro/ipc/echo-server-fork-fdopen.c
 !       Created on 2004/05/09 19:57:07
   */
   #include <stdio.h>
   #include <stdlib.h>   /* exit() */
 ***************
 *** 16,21 ****
 --- 15,21 ----

   extern        void echo_server( int portno );
   extern        void echo_reply( int com );
 + extern        void delete_zombie(void);
   extern        void print_my_host_port( int portno );
   extern        void tcp_peeraddr_print( int com );
   extern        void sockaddr_print( struct sockaddr *addrp, socklen_t addr_len );
 ***************
 *** 41,62 ****
   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 );
	 }
   }

   #define       BUFFERSIZE      1024

   void
 --- 41,89 ----
   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 );
 !           }  
	 }
   }

 + void
 + delete_zombie()
 + {
 +     pid_t pid ;
 +     while( (pid=wait4(-1,0,WNOHANG,0)) >0 )
 +     {
 +       printf("[%d] zombi %d deleted.\n",getpid(),pid );
 +       continue;
 +     }
 + }
 + 
   #define       BUFFERSIZE      1024

   void
 % 

実行例。

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

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

% ./echo-server-fork-fdopen 1231 [←]
run telnet azalea20.coins.tsukuba.ac.jp 1231 
[17559] connection (fd==4) from 130.158.86.40:55958
[17561] received (fd==4) 5 bytes, [012
]
[17559] connection (fd==4) from 130.158.86.50:52792
[17562] received (fd==4) 5 bytes, [abc
]
[17562] received (fd==4) 5 bytes, [def
]
[17562] connection (fd==4) closed.
[17561] received (fd==4) 5 bytes, [345
]
[17561] connection (fd==4) closed.
^C
% []
クライアント側(その1)。
% telnet azalea20.coins.tsukuba.ac.jp 1231  [←]
Trying 130.158.86.40...
Connected to azalea20.coins.tsukuba.ac.jp.
Escape character is '^]'.
012[←]
012
345[←]
345
^]
telnet> quit[←]
Connection closed.
% []
クライアント側(その2)。
% telnet azalea20.coins.tsukuba.ac.jp 1231  [←]
Trying 130.158.86.40...
Connected to azalea20.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 コマンドで見ると、ゾンビの状態(STAT) は、Z と表示される。

% ps uxw | egrep echo [←]
yas  17618   0.0 -0.0        0      0  p4  Z+    1Jan70   0:00.00 (echo-server-fork)
yas  17616   0.0 -0.0    36640    348  p4  S+    9:19PM   0:00.01 ./echo-server-fork-fdopen 1231
yas  17619   0.0 -0.0        0      0  p4  Z+    1Jan70   0:00.00 (echo-server-fork)
yas  17626   0.0 -0.0     8776      8  p5  R+    9:21PM   0:00.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.
ゾンビが回収された後の ps コマンドの結果:
% ps uxw | egrep echo [←]
yas  17631   0.0 -0.0     8776      8  p5  R+    9:23PM   0:00.00 egrep echo
yas  17616   0.0 -0.0    36640    360  p4  S+    9:19PM   0:00.01 ./echo-server-fork-fdopen 1231
yas  17629   0.0 -0.0    36800    156  p4  S+    9:23PM   0:00.00 ./echo-server-fork-fdopen 1231
% []


Last updated: 2007/05/29 18:08:29
Yasushi Shinjo / <yas@is.tsukuba.ac.jp>