システム・プログラム 電子・情報工学系 新城 靖 <yas@is.tsukuba.ac.jp>
このページは、次の URL にあります。
http://www.hlla.is.tsukuba.ac.jp/~yas/coins/syspro-2001/2001-05-28
あるいは、次のページから手繰っていくこともできます。
http://www.hlla.is.tsukuba.ac.jp/~yas/coins/
http://www.is.tsukuba.ac.jp/~yas/index-j.html
---------------------------------------------------------------------- 1: 2: /* 3: echo-server-fork.c -- 受け取った文字列をそのまま返すサーバ(fork版) 4: ~yas/syspro-2001/ipc/echo-server-fork.c 5: $Header: /home/lab2/OS/yas/syspro-2001/ipc/RCS/echo-server-fork.c,v 1.9 2001/05/27 13:38:59 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 ssize_t writen(int fd, const void *vptr, size_t n); 19: extern ssize_t readline(int fd, void *vptr, size_t maxlen); 20: 21: main( int argc, char *argv[] ) 22: { 23: int portno ; 24: if( argc >= 3 ) 25: { 26: fprintf( stdout,"Usage: %s host port\n",argv[0] ); 27: exit( -1 ); 28: } 29: if( argc == 2 ) 30: portno = atoi( argv[1] ); 31: else 32: portno = getuid(); 33: echo_server( portno ); 34: } 35: 36: void 37: echo_server( int portno ) 38: { 39: int acc,com ; 40: pid_t child_pid ; 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: if( (child_pid=fork()) > 0 ) /* parent */ 54: { 55: close( com ); 56: } 57: else if( child_pid == 0 ) /* parent */ 58: { 59: close( acc ); 60: echo_reply( com ); 61: printf("[%d,%d] connection closed.\n",getpid(),com ); 62: close( com ); 63: exit( 0 ); 64: } 65: else 66: { 67: perror("fork"); 68: exit( -1 ); 69: } 70: } 71: } 72: 73: #define BUFFERSIZE 1024 74: 75: void 76: echo_reply( int com ) 77: { 78: char line[BUFFERSIZE] ; 79: int rcount ; 80: int wcount ; 81: 82: while( (rcount=readline(com,line,BUFFERSIZE)) > 0 ) 83: { 84: printf("[%d,%d] read() %d bytes, %s",getpid(),com,rcount,line ); 85: fflush( stdout ); 86: if( (wcount=writen(com,line,rcount))!= rcount ) 87: { 88: perror("write"); 89: exit( 1 ); 90: } 91: } 92: } 93: 94: void 95: print_host_port( int portno ) 96: { 97: char hostname[100] ; 98: gethostname( hostname,sizeof(hostname) ); 99: hostname[99] = 0 ; 100: printf("run telnet %s %d \n",hostname, portno ); 101: } 102: 103: void 104: tcp_peeraddr_print( int com ) 105: { 106: struct sockaddr_in addr ; 107: int addr_len ; 108: union { 109: int i ; 110: unsigned char byte[4] ; 111: } x ; 112: addr_len = sizeof( addr ); 113: if( getpeername( com, &addr, &addr_len )<0 ) 114: { 115: perror("print_peeraddr"); 116: } 117: x.i = addr.sin_addr.s_addr ; 118: printf("[%d,%d] connection from %d.%d.%d.%d:%d\n",getpid(),com, 119: x.byte[0],x.byte[1],x.byte[2],x.byte[3], 120: ntohs( addr.sin_port )); 121: } 122: 123: int 124: tcp_acc_port( int portno ) 125: { 126: struct hostent *hostent ; 127: struct sockaddr_in addr ; 128: int addr_len ; 129: int s ; 130: 131: if( (s = socket(AF_INET, SOCK_STREAM, 0)) < 0 ) 132: { 133: perror("socket"); 134: return( -1 ); 135: } 136: 137: addr.sin_family = AF_INET ; 138: addr.sin_addr.s_addr = INADDR_ANY ; 139: addr.sin_port = htons( portno ); 140: 141: if( bind(s,&addr,sizeof(addr)) < 0 ) 142: { 143: perror( "bind" ); 144: fprintf(stderr,"port number %d is already used. wait a moment or kill another program.\n", portno ); 145: return( -1 ); 146: } 147: listen( s, 5 ); 148: return( s ); 149: } <省略> 171: ssize_t /* Write "n" bytes to a descriptor. */ 172: writen(int fd, const void *vptr, size_t n) <省略> 222: ssize_t 223: readline(int fd, void *vptr, size_t maxlen) <省略> ----------------------------------------------------------------------実行例。
サーバ側。サーバは、終了しないので、最後に、^C か Del を押して、割り込みを掛けて終了させる。
クライアント側(その1)。---------------------------------------------------------------------- % ./echo-server-selectrun telnet adonis1 1231 [20764,4] connection from 130.158.86.1:23599 [20766,4] read() 5 bytes, 012 [20764,4] connection from 130.158.86.9:20199 [20767,4] read() 5 bytes, abc [20767,4] read() 5 bytes, def [20767,4] connection closed. [20766,4] read() 5 bytes, 345 [20766,4] connection closed. ^C %
----------------------------------------------------------------------
クライアント側(その2)。---------------------------------------------------------------------- % telnet adonis1 1231Trying 130.158.86.1... Connected to adonis1. Escape character is '^]'. 012
012 345
345 ^] telnet> quit
Connection closed. %
----------------------------------------------------------------------
---------------------------------------------------------------------- % % telnet adonis1 1231Trying 130.158.86.1... Connected to adonis1. Escape character is '^]'. abc
abc def
def ^] telnet> quit
Connection closed. %
----------------------------------------------------------------------
図1サーバが accept()している時にクライアントが connect()した所
図2 サーバで accept()からreturnした所。fork() する直前。
図3サーバで親がcomをclose()、子が acc を close() した所。
図4 複数のクライアントが接続した時
ps -l コマンドで見ると、ゾンビの状態(S) は、Z と表示される。
CMD は、defunct
(故人となった、消滅した)と表示される。
% ps -lu $USERF 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 %
![]()
echo-server-select.c
では、1つのプロセスで実行し
ている。
---------------------------------------------------------------------- 1: 2: /* 3: echo-server-select.c -- 受け取った文字列をそのまま返すサーバ(select版) 4: ~yas/syspro-2001/ipc/echo-server-select.c 5: $Header: /home/lab2/OS/yas/syspro-2001/ipc/RCS/echo-server-select.c,v 1.3 2001/05/27 13:42:18 yas Exp $ 6: Start: 1997/06/09 19:53:33 7: */ 8: #include <stdio.h> 9: #include <sys/types.h> /* socket(), time(), select() */ 10: #include <sys/socket.h> /* socket() */ 11: #include <netinet/in.h> /* struct sockaddr_in */ 12: #include <unistd.h> /* select() */ 13: #include <bstring.h> /* select() */ 14: #include <sys/time.h> /* select() */ 15: 16: extern void echo_server( int portno ); 17: extern void echo_reply_select( int com ); 18: extern void print_host_port( int portno ); 19: extern void tcp_peeraddr_print( int com ); 20: extern tcp_acc_port( int portno ); 21: extern ssize_t writen(int fd, const void *vptr, size_t n); 22: extern ssize_t readline(int fd, void *vptr, size_t maxlen); 23: 24: main( int argc, char *argv[] ) 25: { 26: int portno ; 27: if( argc >= 3 ) 28: { 29: fprintf( stdout,"Usage: %s host port\n",argv[0] ); 30: exit( -1 ); 31: } 32: if( argc == 2 ) 33: portno = atoi( argv[1] ); 34: else 35: portno = getuid(); 36: echo_reply_select( portno ); 37: } 38: 39: void 40: echo_reply_select( int portno ) 41: { 42: int acc,com ; 43: fd_set readfds,readfds_save ; 44: int i,n ; 45: 46: acc = tcp_acc_port( portno ); 47: if( acc<0 ) 48: exit( -1 ); 49: print_host_port( portno ); 50: 51: FD_ZERO( &readfds_save ); 52: FD_SET( acc,&readfds_save ); 53: while( 1 ) 54: { 55: readfds = readfds_save ; 56: n = select( FD_SETSIZE,&readfds,0,0,0 ); 57: if( n <= 0 ) 58: { 59: perror("select"); 60: exit( 1 ); 61: } 62: if( FD_ISSET(acc,&readfds) ) 63: { 64: FD_CLR( acc,&readfds ); 65: if( (com = accept( acc,0,0 )) < 0 ) 66: { 67: perror("accept"); 68: exit( -1 ); 69: } 70: FD_SET( com, &readfds_save ); 71: tcp_peeraddr_print( com ); 72: } 73: for( i=0 ; i<FD_SETSIZE ; i++ ) 74: { 75: if( FD_ISSET(i,&readfds) ) 76: { 77: if( echo_reply_once( i )<=0 ) 78: { 79: printf("[%d,%d] connection closed.\n",getpid(),i ); 80: close( i ); 81: FD_CLR( i,&readfds_save ); 82: } 83: } 84: } 85: } 86: } 87: 88: #define BUFFERSIZE 1024 89: 90: int 91: echo_reply_once( int com ) 92: { 93: char line[BUFFERSIZE] ; 94: int rcount ; 95: int wcount ; 96: 97: if( (rcount=readline(com,line,BUFFERSIZE)) > 0 ) 98: { 99: printf("[%d,%d] read() %d bytes, %s",getpid(),com,rcount,line ); 100: fflush( stdout ); 101: if( (wcount=writen(com,line,rcount))!= rcount ) 102: { 103: perror("write"); 104: exit( 1 ); 105: } 106: } 107: return( rcount ); 108: } <以下省略> ----------------------------------------------------------------------実行例。
サーバ側。サーバは、終了しないので、最後に、^C か Del を押して、割り込みを掛けて終了させる。
クライアント側(その1)。---------------------------------------------------------------------- % ./echo-server-selectrun telnet adonis1 1231 [20937,4] connection from 130.158.86.1:23650 [20937,4] read() 5 bytes, 012 [20937,5] connection from 130.158.86.9:20203 [20937,5] read() 5 bytes, abc [20937,5] read() 5 bytes, def [20937,5] connection closed. [20937,4] read() 5 bytes, 345 [20937,4] connection closed. ^C %
----------------------------------------------------------------------
クライアント側(その2)。---------------------------------------------------------------------- Trying 130.158.86.1... Connected to adonis1. Escape character is '^]'. 012012 345
345 ^] telnet> quit
Connection closed. %
----------------------------------------------------------------------
---------------------------------------------------------------------- % telnet adonis1 1231Trying 130.158.86.1... Connected to adonis1. Escape character is '^]'. abc
abc def
def ^] telnet> quit
Connection closed. %
----------------------------------------------------------------------
図4 複数のクライアントが接続した時(select())
クライアントが意図的に '\n' を送らないことがある。これは、一種の DoS 攻撃(Denial of Service攻撃、サービス運用妨害攻撃)である。 その他に、writen() に対して、クライアントが read() しないという攻撃も ある。
echo-server-fork.c では、クライアントご とにプロセス(スレッド)が存在するので、安全に readline() を使うことが できる。ただし、プロセスをコピーする処理が重たいので、過剰な connect() による接続要求による DoS 攻撃には弱くなる。(fork() の処理ばかりして、 通常の業務ができなくなる。)
ヒント:accept() で止まる前に、wait() する方法がある。ただし、単純に wait() すると、一つのクライアントが終了するまで他のクライアントが接続 できなくなり、せっかく fork() した意味がなくなってしまう。よって、子プ ロセスが終了したときだけ、wait() したい。それには、waitpid() や wait3(), wait4() で、WNOHANG オプションを使い、終了したプロセスが存在 する間、それらを全て待ち消去すればよい。
ヒント:子プロセスの終了を、 ソフトウェア割り込み(後述) (SIGCHLD)で知る方法もある。複数の子プロセスが終了しても、割り込みは1 回しか起こらないことがあることに注意しなさい。accept() システムコール がエラーで戻って来ることがあることにも注意しなさい。
このエラーは、実際に同じポート番号を別のプロセスが使っている時にも出る が、既に以前に使っていたプロセスが終了している時にも出ることがある。 後者の場合、次のオプションを付けると、この状態が緩和されることがある。---------------------------------------------------------------------- % ./echo-server-forkbind: Address already in use port number 1231 is already used. wait a moment or kill another program. %
----------------------------------------------------------------------
setsockopt( sock, SOL_SOCKET, SO_REUSEADDR, 0, 0);この効果を確かめなさい。
また、同じポート番号の再利用を制限している理由を考えなさい。
pipe() の代りに、TCP/IP のストリームを使って、異なるホスト上のプロセス を接続して、フィルタの処理を行わせてみなさい。
ヒント:例題 pipe-rw-nodup.cを tcp_connect() やtcp_acc_port() を使って書き直すとよい。 プログラムを1つだけ作るのではなく、tcp_connect() をするものと tcp_acc_port() をするものの2つを作るとよい。tcp_acc_port() をする側で は、fork() をして、複数のクライアントに対応するのではなく、通 信要求受付用の socket() を close() して、1つの通信先に専念するものでよい。
この課題では、fork() は、しなくてもよい。パイプは、fork() でできた親子 関係のあるプロセス間で通信を行うものであるが、TCP/IPでは、親子関係がな いプロセス間で通信ができる。
TCP/IP なら本来双方向で使える。しかし、この練習問題だと単方向だけを使 う。こうすると、パイプと同じような使い方ができる。
練習問題37 で、3つ以上のプロセスを接続できるようにしなさい。
ヒント:練習問題17 3個のプロセスをパイプで結ぶに相当する。
プログラムを1つだけ作るのではなく、先頭用、中間用、末尾用の3つを作る とよい。中間用のものは、うまく作ると、何個でもはさみ込めるようになる。
ヒント:dup(), dup2(), close() などで、標準入出力を切り替えて、 execve() などで、プログラムを実行する。
たとえば、シェルに次のように打ち込むことを考える。
% command1 | command2 | command3この時、シェルは、fork() しながら2つのパイプで3つのプロセスを結び、 execve() など command1, command2, command3 を実行する。この問題では、 パイプではなくTCP/IP 結ぶ。そして、close(), dup(), close() して、 execve() でcommand1, command2, command3 を実行する。
この課題では、tcp_accept() 側で fork() を行う必要はない。1回のコマン ドを実行したら終了するようにする。
上の tcp_peeraddr_print() は、IP アドレスを数字で表示していた。 これを、ホスト名で表示するようにしなさい。 それには、gethostbyaddr()を利用するとよい。
普通は、ホスト名からIPアドレスを調べる手続き gethostbyname() が使われ る。gethostbyaddr() は、その逆を行う手続きである。
finger サーバを作成しなさい。これは、受け取った文字列を引数にして、 finger コマンドを実行するとよい。そして、実行結果を、接続先に返すよう にするとよい。
finger コマンドのプロセスと finger サーバの間は、パイプで接続しなさい。 今までの例題で利用した方法を組み合わせることで実現することができるはず である(pipe(),fork(),dup(),close(),execve()など)。popen() ライブラリ 関数を利用してもよい。
finger コマンドのプロセスと finger サーバ間をパイプで結ぶ代わりに、 finger コマンドを exec する前に、配管工事をして、標準出力(1)をTCP/IPの ストリームに接続する方法もある。
http サーバを作成しなさい。これは、受け取ったファイル名のファイルを開 いて、内容を読み込み、それを接続先に返すようにするとよい。
この課題では、特にセキュリティに気をつけなさい。必ず次の条件を満たすよ うなサーバを作りなさい。
~/public_html
)以下のファイル
しかアクセスできないようにすること。
..
が含まれていたら、アクセスを拒否すること。
---------------------- Content-Type: 拡張子 ---------------------- text/html .html text/plain .txt image/gif .gif image/jpeg .jpeg ----------------------
HTTP proxy 作りなさい。次の機能を、1つ以上付けなさい。