echoサービスのサーバ(fork)

システム・プログラム

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

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

■echo-server-fork

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

----------------------------------------------------------------------
   1:	
   2:	/*
   3:	        echo-server-fork.c -- 受け取った文字列をそのまま返すサーバ(fork版)
   4:	        ~yas/syspro1/ipc/echo-server-fork.c
   5:	        $Header: /home/lab2/OS/yas/syspro1-1998/ipc/RCS/echo-server-fork.c,v 1.5 1997/06/09 21:28:27 yas Exp $
   6:	        Start: 1997/06/09 19:46:40
   7:	*/
   8:	#include <stdio.h>
   9:	#include <sys/types.h>  /* socket(), time() */
  10:	#include <sys/socket.h> /* socket() */
  11:	#include <netinet/in.h> /* struct sockaddr_in */
  12:	
  13:	extern  void echo_server( int portno );
  14:	extern  void echo_reply( int com );
  15:	extern  void print_host_port( int portno );
  16:	extern  void tcp_peeraddr_print( int com );
  17:	extern  tcp_acc_port( int portno );
  18:	extern  int writen( int fd, char *buf, int nbytes );
  19:	
  20:	main( int argc, char *argv[] )
  21:	{
  22:	    int portno ;
  23:	        if( argc >= 3 )
  24:	        {
  25:	            fprintf( stdout,"Usage: %s host port\n",argv[0] );
  26:	            exit( -1 );
  27:	        }
  28:	        if( argc == 2 )
  29:	            portno = atoi( argv[1] );
  30:	        else
  31:	            portno = getuid();
  32:	        echo_server( portno );
  33:	}
  34:	
  35:	void echo_server( int portno )
  36:	{
  37:	    int acc,com ;
  38:	    pid_t child_pid ;
  39:	        acc = tcp_acc_port( portno );
  40:	        if( acc<0 )
  41:	            exit( -1 );
  42:	        print_host_port( portno );
  43:	        while( 1 )
  44:	        {
  45:	            if( (com = accept( acc,0,0 )) < 0 )
  46:	            {
  47:	                perror("accept");
  48:	                exit( -1 );
  49:	            }
  50:	            tcp_peeraddr_print( com );
  51:	            if( (child_pid=fork()) > 0 ) /* parent */
  52:	            {
  53:	                close( com );
  54:	            }
  55:	            else if( child_pid == 0 )
  56:	            {
  57:	                close( acc );
  58:	                echo_reply( com );
  59:	                printf("[%d,%d] connection closed.\n",getpid(),com );
  60:	                close( com );
  61:	                exit( 0 );
  62:	            }
  63:	        }
  64:	}
  65:	
  66:	void echo_reply( int com )
  67:	{
  68:	    char buf[BUFSIZ] ;
  69:	    int rcount ;
  70:	    int wcount ;
  71:	
  72:	        while( (rcount=read(com,buf,BUFSIZ)) > 0 )
  73:	        {
  74:	            if( (wcount=writen(com,buf,rcount))!= rcount )
  75:	            {
  76:	                 perror("write");
  77:	                 exit( 1 );
  78:	            }
  79:	            printf("[%d,%d] ",getpid(),com );
  80:	            fflush( stdout );
  81:	            write( 1, buf, rcount );
  82:	        }
  83:	}
<以下省略>
  85:	void print_host_port( int portno )
...
  93:	void tcp_peeraddr_print( int com )
...
 112:	tcp_acc_port( int portno )
...
 141:	int writen( int fd, char *buf, int nbytes )
----------------------------------------------------------------------

実行例。

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


----------------------------------------------------------------------
% ./echo-server-select [←]
run telnet adonis1 1231 
[1701,4] connection from 130.158.86.1:20822
[1701,4] 012
[1701,5] connection from 130.158.86.11:1479
[1701,5] abc
[1701,5] def
[1701,5] connection closed.
[1701,4] 345
[1701,4] connection closed.
^C
% []
----------------------------------------------------------------------
クライアント側(その1)。
----------------------------------------------------------------------
% telnet adonis1 1231 [←]
Trying 130.158.86.1...
Connected to adonis1.
Escape character is '^]'.
012[←]
012
345[←]
345
^]
telnet> quit[←]
Connection closed.
% []
----------------------------------------------------------------------
クライアント側(その2)。
----------------------------------------------------------------------
% % telnet adonis1 1231 [←]
Trying 130.158.86.1...
Connected to adonis1.
Escape character is '^]'.
abc[←]
abc
def[←]
def
^]
telnet> quit[←]
Connection closed.
% []
----------------------------------------------------------------------

■ゾンビ・プロセス

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

ps -l コマンドで見ると、ゾンビの状態(S) は、Z と表示される。 CMD は、defunct (故人となった、消滅した)と表示される。

% ps -lu $USER [←]
  F S   UID   PID  PPID  C PRI NI  P    SZ:RSS      WCHAN TTY     TIME CMD
 b0 S  1231 13105 13104  0  39 20  *   618:228   802737f0 pts/2   0:02 tcsh 
 90 Z  1231 13614 13609  0   0 -   -     - -            - -       0:00 <defunct>
 b0 S  1231 13609 13105  0  60 20  *   359:44    8079a958 pts/2   0:00 echo-serv 
 b0 S  1231 13076 13075  1  39 20  *   614:221   802737f0 pts/1   0:01 tcsh 
 b0 R  1231 13623 13076  3  61 20  0   397:79           - pts/1   0:00 ps 
 90 Z  1231 13617 13609  0   0 -   -     - -            - -       0:00 <defunct>
 b0 T  1231 13142 13076  0  60 20  *  2229:1052         - pts/1   0:15 emacs 
% []

■練習問題

★練習問題50 ゾンビの消去

echo-server-fork.c では、ゾンビ・プロセスがどんどん残ってしまう。これ を残らないようにしなさい。

ヒント:accept() で止まる前に、wait() する方法がある。ただし、単純に wait() すると、一つのクライアントが終了するまで他のクライアントが接続 できなくなり、せっかく fork() した意味がなくなってしまう。よって、子プ ロセスが終了したときだけ、wait() したい。それには、waitpid() や wait3(), wait4() で、WNOHANG オプションを使い、終了したプロセスが存在 する間、それらを全て待ち消去すればよい。

ヒント:子プロセスの終了を、 ソフトウェア割り込み (SIGCHLD)で知る方法もある。複数の子プロセスが終了しても、割り込みは1 回しか起こらないことがあることに注意しなさい。accept() システムコール がエラーで戻って来ることがあることにも注意しなさい。


↑[もどる] ←[5月08日] ・[5月15日] [課題]
Last updated: 2000/05/14 18:57:58
Yasushi Shinjo / <yas@is.tsukuba.ac.jp>