プロセス間通信、TCP/IP(2)

システム・プログラムI

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

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

■復習

■rdaytime-client

TCP/IP のポート番号 13 では、時刻を返すサービスを提供している。

----------------------------------------------------------------------
% egrep day /etc/services [←]
daytime         13/tcp
daytime         13/udp
% telnet adonis1 13 [←]
Trying 130.158.86.1...
Connected to adonis1.
Escape character is '^]'.
Mon Jun  8 19:37:03 1998
Connection closed by foreign host.
% []
----------------------------------------------------------------------

----------------------------------------------------------------------
   1:	
   2:	/*
   3:	        rdaytime-client.c -- 遠隔のホストの日付を調べる
   4:	        ~yas/syspro1/ipc/rdaytime-client.c
   5:	        $Header: /home/lab2/OS/yas/syspro1-1998/ipc/RCS/rdaytime-client.c,v 1.6 1998/06/08 12:13:46 yas Exp $
   6:	        Start: 1995/09/08 21:04:33
   7:	*/
   8:	#include <stdio.h>
   9:	#include <sys/types.h>  /* socket() */
  10:	#include <sys/socket.h> /* socket() */
  11:	#include <netinet/in.h> /* struct sockaddr_in */
  12:	#include <netdb.h>      /* gethostbyname() */
  13:	
  14:	extern  int tcp_connect( char *hostname, int portno );
  15:	extern  void rdaytime_client( char *hostname, int portno );
  16:	
  17:	main( int argc, char *argv[] )
  18:	{
  19:	        if( argc != 3 )
  20:	        {
  21:	            fprintf( stdout,"Usage: %s host port\n",argv[0] );
  22:	            exit( -1 );
  23:	        }
  24:	        rdaytime_client( argv[1],atoi(argv[2]) );
  25:	}
  26:	
  27:	void rdaytime_client( char *hostname, int portno )
  28:	{
  29:	    int s ;
  30:	    int rcount ;
  31:	    char buf[BUFSIZ];
  32:	
  33:	        s = tcp_connect( hostname, portno );
  34:	        if( s<0 )
  35:	            exit( -1 );
  36:	        while( (rcount=read(s,buf,BUFSIZ)) > 0 )
  37:	        {
  38:	            if( write(1,buf,rcount) != rcount )
  39:	            {
  40:	                 perror("write");
  41:	                 exit( 1 );
  42:	            }
  43:	        }
  44:	        close( s );
  45:	}
  46:	
  47:	int tcp_connect( char *hostname, int portno )
  48:	{
  49:	    struct hostent *hostent ;
  50:	    struct sockaddr_in addr ;
  51:	    int addr_len ;
  52:	    int s ;
  53:	
  54:	        addr.sin_family = AF_INET ;
  55:	        if( (hostent = gethostbyname( hostname )) == NULL )
  56:	        {
  57:	            fprintf(stderr,"unknown host %s\n",hostname );
  58:	            return( -1 );
  59:	        }
  60:	        bcopy( hostent->h_addr, &addr.sin_addr, hostent->h_length );
  61:	        addr.sin_port = htons( portno );
  62:	        if( (s = socket(AF_INET, SOCK_STREAM, 0)) < 0 )
  63:	        {
  64:	            perror("socket");
  65:	            return( -1 );
  66:	        }
  67:	        if( connect(s, &addr, sizeof(addr)) < 0 )
  68:	        {
  69:	            perror( hostname );
  70:	            close( s );
  71:	            return( -1 );
  72:	        }
  73:	        return( s );
  74:	}
----------------------------------------------------------------------

実行例。

----------------------------------------------------------------------
% ./rdaytime-client adonis1 13 [←]
Mon Jun  8 19:40:16 1998
% []
----------------------------------------------------------------------

■rdaytime-server

TCP/IP のポート番号 13 で提供されている、時刻を返すサービスと同等の機 能をもつサービスを他のポート番号で提供することができる。

----------------------------------------------------------------------
   1:	
   2:	/*
   3:	        rdaytime-server.c -- 日付を返すサーバ
   4:	        ~yas/syspro1/ipc/rdaytime-server.c
   5:	        $Header: /home/lab2/OS/yas/syspro1-1998/ipc/RCS/rdaytime-server.c,v 1.5 1998/06/08 11:36:59 yas Exp $
   6:	        Start: 1995/09/08 21:04:33
   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:	#include <time.h>       /* time() */
  13:	
  14:	
  15:	extern  rdaytime_server( int portno );
  16:	extern  rdaytime_reply( int com );
  17:	extern  void print_host_port( int portno );
  18:	extern  void tcp_peeraddr_print( int com );
  19:	extern  tcp_acc_port( int portno );
  20:	extern  int writen( int fd, char *buf, int nbytes );
  21:	
  22:	main( int argc, char *argv[] )
  23:	{
  24:	    int portno ;
  25:	        if( argc >= 3 )
  26:	        {
  27:	            fprintf( stdout,"Usage: %s host port\n",argv[0] );
  28:	            exit( -1 );
  29:	        }
  30:	        if( argc == 2 )
  31:	            portno = atoi( argv[1] );
  32:	        else
  33:	            portno = getuid();
  34:	        rdaytime_server( portno );
  35:	}
  36:	
  37:	rdaytime_server( int portno )
  38:	{
  39:	    int acc,com ;
  40:	    int rcount ;
  41:	        acc = tcp_acc_port( portno );
  42:	        if( acc<0 )
  43:	            exit( -1 );
  44:	        print_host_port( portno );
  45:	        while( 1 )
  46:	        {
  47:	            if( (com = accept( acc,0,0 )) < 0 )
  48:	            {
  49:	                perror("accept");
  50:	                exit( -1 );
  51:	            }
  52:	            tcp_peeraddr_print( com );
  53:	            rdaytime_reply( com );
  54:	        }
  55:	}
  56:	
  57:	rdaytime_reply( int com )
  58:	{
  59:	    time_t t ;
  60:	    char *p ;
  61:	    int len ;
  62:	
  63:	        t = time( 0 );
  64:	        p = ctime( &t );        /* gettimeofday() if BSD */
  65:	        len = strlen( p );
  66:	        if( writen( com, p, len ) != len )
  67:	        {
  68:	            perror("write");
  69:	            exit( -1 );
  70:	        }
  71:	        close( com );
  72:	}
  73:	
  74:	void print_host_port( int portno )
  75:	{
  76:	    char hostname[100] ;
  77:	        gethostname( hostname,sizeof(hostname) );
  78:	        hostname[99] = 0 ;
  79:	        printf("run telnet %s %d \n",hostname, portno );
  80:	}
  81:	
  82:	void tcp_peeraddr_print( int com )
  83:	{
  84:	    struct sockaddr_in addr ;
  85:	    int addr_len ;
  86:	    union {
  87:	        int i ;
  88:	        unsigned char byte[4] ;
  89:	    } x ;
  90:	        addr_len = sizeof( addr );
  91:	        if( getpeername( com, &addr, &addr_len  )<0 )
  92:	        {
  93:	            perror("print_peeraddr");
  94:	        }
  95:	        x.i = addr.sin_addr.s_addr ;
  96:	        printf("[%d,%d] connection from %d.%d.%d.%d:%d\n",getpid(),com,
  97:	               x.byte[0],x.byte[1],x.byte[2],x.byte[3],
  98:	               ntohs( addr.sin_port ));
  99:	}
 100:	
 101:	tcp_acc_port( int portno )
 102:	{
 103:	    struct hostent *hostent ;
 104:	    struct sockaddr_in addr ;
 105:	    int addr_len ;
 106:	    int s ;
 107:	
 108:	        if( (s = socket(AF_INET, SOCK_STREAM, 0)) < 0 )
 109:	        {
 110:	            perror("socket");
 111:	            return( -1 );
 112:	        }
 113:	
 114:	        addr.sin_family = AF_INET ;
 115:	        addr.sin_addr.s_addr = INADDR_ANY ;
 116:	        addr.sin_port = htons( portno );
 117:	
 118:	        if( bind(s,&addr,sizeof(addr)) < 0 )
 119:	        {
 120:	            perror( "bind" );
 121:	            fprintf(stderr,"port number %d is already used. wait a moment or kill another program.\n", portno );
 122:	            return( -1 );
 123:	        }
 124:	        listen( s, 5 );
 125:	        return( s );
 126:	}
 127:	
 128:	/* See: UNIX Network Programming, SS.6.6, Utility routines */
 129:	
 130:	int writen( int fd, char *buf, int nbytes )
 131:	{
 132:	    register int         nleft, nwritten ;
 133:	
 134:	        nleft = nbytes ;
 135:	        while( nleft > 0 )
 136:	        {
 137:	            nwritten = write( fd, buf, nleft );
 138:	            if( nwritten <= 0 )
 139:	                return( nwritten );
 140:	            nleft -= nwritten ;
 141:	            buf  += nwritten ;
 142:	        }
 143:	        return( nbytes - nleft );
 144:	}
----------------------------------------------------------------------

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

----------------------------------------------------------------------
% ./rdaytime-server  [←]
run telnet adonis1 1231 
[13405,4] connection from 130.158.86.1:11108
[13405,4] connection from 130.158.86.1:11109
[13405,4] connection from 130.158.86.1:11110
[13405,4] connection from 130.158.86.1:11111
^C
% []
----------------------------------------------------------------------
実行例。クライアント側。telnet か rdaytime-client を使う。

----------------------------------------------------------------------
% telnet adonis1 1231 [←]
Trying 130.158.86.1...
Connected to adonis1.
Escape character is '^]'.
Mon Jun  8 20:07:10 1998
Connection closed by foreign host.
% telnet adonis1 1231 [←]
Trying 130.158.86.1...
Connected to adonis1.
Escape character is '^]'.
Mon Jun  8 20:07:16 1998
Connection closed by foreign host.
% ./rdaytime-client adonis1 1231 [←]
Mon Jun  8 20:07:19 1998
% ./rdaytime-client adonis1 1231 [←]
Mon Jun  8 20:07:20 1998
% []
----------------------------------------------------------------------

■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() で、あ るオプションを使うとよい。

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

ソフトウェア割込みについては、 来週 話します。

★練習問題51 pipe() と TCP/IPのストリーム(1)

pipe() の代りに、TCP/IP のストリームを使って、異なるホスト上のプロセス を接続して、フィルタの処理を行わせてみなさい。

ヒント:例題 pipe-rw.cを tcp_connect() やtcp_acc_port() を使って書き直すとよい。 ただし、別のホストで。fork() は、しなくてもよいはず。

TCP/IP なら本来双方向で使える。しかし、この練習問題だと単方向だけを使 う。こうすると、パイプと同じような使い方ができる。

★練習問題52 pipe() と TCP/IPのストリーム(2)

練習問題51 で、3つ以上のプロセスを接続できるようにしなさい。

ヒント: ★練習問題40 3個のプロセスをパイプで結ぶ相当。 プログラムは、1つではなく、最初用、中間用、最後用の3つを作る方法があ る。中間用のものは、うまく作ると、何個でもはさみ込めるようにできるはず。 fork() は、しなくてもよいはず。

★練習問題53 pipe() と TCP/IPのストリーム(3)

練習問題52 で、中間のプログラムとして、外部のフィルタ・プログラムを実行でき るようにしなさい。

ヒント:dup(), dup2(), close() などで、標準入出力を切り替えて、 execve() などで、プログラムを実行する。

たとえば、シェルに次のように打ち込むことを考える。

% command1 | command2 | command3
この時、シェルは、fork() しながら2つのパイプで3つのプロセスを結び、 execve() など command1, command2, command3 を実行する。この問題では、 パイプではなくTCP/IP 結ぶ。そして、close(), dup(), close() して、 execve() でcommand1, command2, command3 を実行する。fork() は、しなく てもよいはず。

★練習問題54 IPアドレスをホスト名に逆変換する

上の tcp_peeraddr_print() は、IP アドレスを数字で表示していた。 これを、ホスト名で表示するようにしなさい。 それには、gethostbyaddr()を利用するとよい。

普通は、ホスト名からIPアドレスを調べる手続き gethostbyname() が使われ る。gethostbyaddr() は、その逆を行う手続きである。

★練習問題55 localhost

telnet で接続先として localhost (127.0.0.1) を指定するとどうなるか。 localhost は、どのホストからも自分自信を指す名前である。

■課題提出方法


↑[もどる] [課題提出方法] ←[6月2日] ・[6月9日] →[6月16日]
Last updated: 1998/06/18 01:20:30
Yasushi Shinjo / <yas@is.tsukuba.ac.jp>