fork()による複数のクライアントに対するサービスの同時提供

システム・プログラム

                                       電子・情報工学系
                                       新城 靖
                                       <yas@is.tsukuba.ac.jp>

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

■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()している時にクライアントが connect()した所

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

図2 サーバで accept()からreturnした所。fork() する直前。

図2 サーバで accept()からreturnした所。fork() する直前。

図3サーバで親がcomをclose()、子が acc を close() した所。

図3サーバで親がcomをclose()、子が acc を close() した所。

図4 複数のクライアントが接続した時

図4 複数のクライアントが接続した時

■ゾンビ・プロセス

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.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: 2004/05/10 11:46:56
Yasushi Shinjo / <yas@is.tsukuba.ac.jp>