筑波大学 システム情報系 情報工学域
                                       新城 靖
                                       <yas@cs.tsukuba.ac.jp>
このページは、次の URL にあります。
	http://www.coins.tsukuba.ac.jp/~syspro/2014/2014-08-01
/index.html
あるいは、次のページから手繰っていくこともできます。
	http://www.coins.tsukuba.ac.jp/~syspro/2014/
	http://www.coins.tsukuba.ac.jp/~yas/
HTTP の内容(content)は、バイトの並び。バイト単位のループでもプログラムは書けるが、 遅いので、ある程度の大きさの塊(buffer size単位)で処理するのが普通。 テキストだけ扱うならば、行の並びと仮定しても問題ない。
http_receive_request( FILE *in, char *filename, size_t size )
{
    char *buf;
        buf = malloc( BUFSIZE );
	...
	/* free(buf) がない */
	return( 1 ); 
}
http_receive_request( FILE *in, char *filename, size_t size )
{
    char *buf;
        buf = malloc( BUFSIZE );
	...
	if( ... ) {
	    ...
	    /* free(buf) がない */
	    return( 0 );
	}
	...
	free( buf );
	return( 1 ); 
}
http_send_reply( FILE *out, char *filename )
{
    FILE *f;
        f = fopen(...);
	...
	/* fclose( f ) がない */
	return;
}
http_send_reply( FILE *out, char *filename )
{
    FILE *f;
        f = fopen(...);
	...
	if( ... ) {
	    ...
	    /* fclose( f ) がない */
	    return( 0 );
	}
	...
	fclose( f );
	return;
}
以上のプログラムは、間違い。真似しないように。
http_send_reply( FILE *out, char *filename )
{
    auto char buf[BUFSIZE];
        ...
	return;
}
auto と書かなくても OK 。
http_send_reply( FILE *out, char *filename )
{
    char buf[BUFSIZE];
        ...
	return; 
}
FREAD(3)                 BSD Library Functions Manual                 FREAD(3)
NAME
     fread, fwrite -- binary stream input/output
LIBRARY
     Standard C Library (libc, -lc)
SYNOPSIS
     #include <stdio.h>
     size_t
     fread(void *restrict ptr, size_t size, size_t nitems,
         FILE *restrict stream);
型を合わせるために、ポインタ変数を使わなくてもよい。
    char *buf;
        buf = malloc( BUFSIZE );
	...
	fread( buf, 1, BUFSIZE, in );
以下でも OK。C言語で配列変数の名前を書くと、先頭要素のポインタの意味に
なる。
    char buf[BUFSIZE];
    	...
	fread( buf, 1, BUFSIZE, in );
以下でも同じ。
    char buf[BUFSIZE];
    	...
	fread( &buf[0], 1, BUFSIZE, in );
    char buf[BUFSIZE];
    	...
	fread( &buf, 1, BUFSIZE, in ); /* 普通は使わない。*/
サーバ側
$ cp ~yas/syspro/ipc/echo-server-nofork-fdopen.c . 
$ make echo-server-nofork-fdopen 
cc     echo-server-nofork-fdopen.c   -o echo-server-nofork-fdopen
$ ./echo-server-nofork-fdopen  
Usage: ./echo-server-nofork-fdopen portno {ipversion}
$ ./echo-server-nofork-fdopen 1231 
run telnet crocus39.coins.tsukuba.ac.jp 1231
[457] accepting incoming connections (acc==3) ...
[457] connection (fd==4) from 130.158.86.247:50290
[457] received (fd==4) 5 bytes, [012
]
^C
$ 
クライアント側(その1)。
$ telnet crocus39.coins.tsukuba.ac.jp 1231 
Trying 130.158.86.249...
Connected to crocus39.coins.tsukuba.ac.jp.
Escape character is '^]'.
012
012
クライアント側(その2)。
$ telnet crocus39.coins.tsukuba.ac.jp 1231 
Trying 130.158.86.249...
Connected to crocus39.coins.tsukuba.ac.jp.
Escape character is '^]'.
abc
クライアント(その2)は、クライアント(その1)が終了するまでサービス
を受けられない。
クライアント側(その1)。
$ telnet crocus39.coins.tsukuba.ac.jp 1231 
Trying 130.158.86.249...
Connected to crocus39.coins.tsukuba.ac.jp.
Escape character is '^]'.
012
012
345
345
^]
telnet> ^D
Connection closed.
$ 
クライアント側(その2)。
$ telnet crocus39.coins.tsukuba.ac.jp 1231 
Trying 130.158.86.249...
Connected to crocus39.coins.tsukuba.ac.jp.
Escape character is '^]'.
abc
abc
サーバ側
$ ./echo-server-nofork-fdopen 1231 
run telnet crocus39.coins.tsukuba.ac.jp 1231
[457] accepting incoming connections (acc==3) ...
[457] connection (fd==4) from 130.158.86.247:50290
[457] received (fd==4) 5 bytes, [012
]
[457] received (fd==4) 5 bytes, [345
]
[457] connection (fd==4) closed.
[457] accepting incoming connections (acc==3) ...
[457] connection (fd==4) from 130.158.86.248:50290
[457] received (fd==4) 5 bytes, [abc
]
   1:	/*
   2:	  fork-hello.c -- 画面に文字列を表示するプログラム
   3:	  ~yas/syspro/proc/fork-hello.c
   4:	  Start: 2001/05/13 23:19:01
   5:	*/
   6:	
   7:	#include <stdio.h>
   8:	#include <unistd.h> /* fork() */
   9:	
  10:	int
  11:	main()
  12:	{
  13:	        fork();
  14:	        fork();
  15:	        fork();
  16:	        printf("hello\n");
  17:	}
実行すると、画面には 8 回 hello と表示される。
 $ ls fork-hello.c
 $ ls: fork-hello.c: No such file or directory
 $ cp ~yas/syspro/proc/fork-hello.c .
 $ make fork-hello
 cc     fork-hello.c   -o fork-hello
 $ ./fork-hello
 hello
 hello
 hello
 hello
 $ hello
 hello
 hello
 hello
 
プロセスは、1回のfork() で、2倍に増える。3回のforkで、2 3 個
に増える。親子関係は複雑。
図? fork()システム・コールによるプロセスのコピー
   1:	
   2:	/*
   3:	  echo-server-fork-fdopen.c -- 受け取った文字列をそのまま返すサーバ(fork版)
   4:	  ~yas/syspro/ipc/echo-server-fork-fdopen.c
   5:	  Created on 2004/05/09 19:57:07
   6:	*/
   7:	#include <stdio.h>
   8:	#include <stdlib.h>     /* exit() */
   9:	#include <sys/types.h>  /* socket(), wait4() */
  10:	#include <sys/socket.h> /* socket() */
  11:	#include <netinet/in.h> /* struct sockaddr_in */
  12:	#include <sys/resource.h> /* wait4() */
  13:	#include <sys/wait.h>   /* wait4() */
  14:	#include <netdb.h>      /* getnameinfo() */
  15:	#include <string.h>     /* strlen() */
  16:	#include <unistd.h>     /* getpid(), gethostname() */
  17:	
  18:	extern  void echo_server( int portno, int ip_version );
  19:	extern  void delete_zombie();
  20:	extern  void echo_receive_request_and_send_reply( int com );
  21:	extern  int  echo_receive_request( char *line, size_t size, FILE *in );
  22:	extern  void echo_send_reply( char *line, FILE *out );
  23:	extern  void print_my_host_port( int portno );
  24:	extern  void tcp_peeraddr_print( int com );
  25:	extern  void sockaddr_print( struct sockaddr *addrp, socklen_t addr_len );
  26:	extern  int  tcp_acc_port( int portno, int pf );
  27:	extern  int  fdopen_sock( int sock, FILE **inp, FILE **outp );
  28:	
  29:	int
  30:	main( int argc, char *argv[] )
  31:	{
  32:	        int portno, ip_version;
  33:	
  34:	        if( !(argc == 2 || argc==3) ) {
  35:	                fprintf(stderr,"Usage: %s portno {ipversion}\n",argv[0] );
  36:	                exit( 1 );
  37:	        }
  38:	        portno = strtol( argv[1],0,10 );
  39:	        if( argc == 3 )
  40:	                ip_version = strtol( argv[2],0,10 );
  41:	        else
  42:	                ip_version = 4; /* IPv4 by default */
  43:	        echo_server( portno, ip_version );
  44:	}
  45:	
main() は、
echo-server-nofork-fdopen.c
と同じである。
  46:	void
  47:	echo_server( int portno, int ip_version )
  48:	{
  49:	        int acc,com ;
  50:	        pid_t child_pid ;
  51:	
  52:	        acc = tcp_acc_port( portno, ip_version );
  53:	        if( acc<0 )
  54:	                exit( 1 );
  55:	        print_my_host_port( portno );
  56:	        while( 1 )
  57:	        {
  58:	                delete_zombie();
  59:	                printf("[%d] accepting incoming connections (acc==%d) ...\n",
  60:	                       getpid(),acc );
  61:	                if( (com = accept( acc,0,0 )) < 0 )
  62:	                {
  63:	                        perror("accept");
  64:	                        exit( -1 );
  65:	                }
  66:	                tcp_peeraddr_print( com );
  67:	                if( (child_pid=fork()) > 0 ) /* parent */
  68:	                {
  69:	                        close( com );
  70:	                }
  71:	                else if( child_pid == 0 ) /* child */
  72:	                {
  73:	                        close( acc );
  74:	                        echo_receive_request_and_send_reply( com );
  75:	                        exit( 0 );
  76:	                }
  77:	                else
  78:	                {
  79:	                        perror("fork");
  80:	                        exit( -1 );
  81:	                }  
  82:	        }
  83:	}
  84:	
delete_zombie() は、ゾンビ・プロセス(後述)を消去するものである。 (改善の余地がある。) echo サービスとしての処理は、fork() システムコールで分身を作り、子プロ セス側で行う。親プロセスは、すぐにaccept() に戻る。
getpid() は、自分自身の PID (Process ID (identifier)) を返すシステムコー ルである。PID (pid_t) は、32 ビット(システムによっては16ビット)の整数 である。実行結果で printf() の表示に、PID が表示されているが、同じではないことに 注意しなさい。
  85:	void
  86:	delete_zombie()
  87:	{
  88:	        pid_t pid ;
  89:	
  90:	        while( (pid=wait4(-1,0,WNOHANG,0)) >0 )
  91:	        {
  92:	                printf("[%d] zombi %d deleted.\n",getpid(),pid );
  93:	                continue;
  94:	        }
  95:	}
  96:	
wait4() システムコールを使って、終了した子プロセスをwait してあげてい
る。ただし、WNOHANG オプションを付けてあるので、終了した子プロセスがい
なければ、待たずに返ってくる。
+」は、追加された行、
「-」は、削除された行、
「!」は、修正された行を意味する。
 $ diff -c echo-server-nofork-fdopen.c  echo-server-fork-fdopen.c
 *** echo-server-nofork-fdopen.c 2014-07-28 15:59:10.000000000 +0900
 --- echo-server-fork-fdopen.c   2014-07-31 11:25:38.000000000 +0900
 ***************
 *** 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,8 ----
   /*
 !   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 ****
 --- 16,22 ----
   #include <unistd.h>   /* getpid(), gethostname() */
   extern        void echo_server( int portno, int ip_version );
 + extern        void delete_zombie();
   extern        void echo_receive_request_and_send_reply( int com );
   extern  int  echo_receive_request( char *line, size_t size, FILE *in );
   extern        void echo_send_reply( char *line, FILE *out );
 ***************
 *** 46,51 ****
 --- 47,53 ----
   echo_server( int portno, int ip_version )
   {
	 int acc,com ;
 +       pid_t child_pid ;
	 acc = tcp_acc_port( portno, ip_version );
	 if( acc<0 )
 ***************
 *** 53,58 ****
 --- 55,61 ----
	 print_my_host_port( portno );
	 while( 1 )
	 {
 +               delete_zombie();
		 printf("[%d] accepting incoming connections (acc==%d) ...\n",
			getpid(),acc );
		 if( (com = accept( acc,0,0 )) < 0 )
 ***************
 *** 61,67 ****
			 exit( -1 );
		 }
		 tcp_peeraddr_print( com );
 !               echo_receive_request_and_send_reply( com );
	 }
   }
 --- 64,96 ----
			 exit( -1 );
		 }
		 tcp_peeraddr_print( com );
 !               if( (child_pid=fork()) > 0 ) /* parent */
 !               {
 !                       close( com );
 !               }
 !               else if( child_pid == 0 ) /* child */
 !               {
 !                       close( acc );
 !                       echo_receive_request_and_send_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;
	 }
   }
 $ 
サーバ側。 サーバは、終了しないので、最後に、^C を押して、割り込みを掛け て終了させる。
注意:全員がポート番号 1231 を使うとプログラムが動かないことがある。
$ ls echo-server-fork-fdopen.c 
ls: echo-server-fork-fdopen.c: No such file or directory
$ cp ~yas/syspro/ipc/echo-server-fork-fdopen.c . 
$ ls -l echo-server-fork-fdopen.c 
-rw-r--r--  1 yas  prof  6001  7 31 11:28 echo-server-fork-fdopen.c
$ make echo-server-fork-fdopen 
cc     echo-server-fork-fdopen.c   -o echo-server-fork-fdopen
$ ./echo-server-fork-fdopen 
Usage: ./echo-server-fork-fdopen portno {ipversion}
$ ./echo-server-fork-fdopen 1231 
run telnet crocus39.coins.tsukuba.ac.jp 1231
[496] accepting incoming connections (acc==3) ...
[496] connection (fd==4) from 130.158.86.247:50426
[496] accepting incoming connections (acc==3) ...
[499] received (fd==4) 5 bytes, [012
]
[496] connection (fd==4) from 130.158.86.248:50351
[496] accepting incoming connections (acc==3) ...
[502] received (fd==4) 5 bytes, [abc
]
[502] received (fd==4) 5 bytes, [def
]
[499] received (fd==4) 5 bytes, [345
]
[499] connection (fd==4) closed.
[502] connection (fd==4) closed.
クライアント側(その1)。
$ telnet crocus39.coins.tsukuba.ac.jp 1231 
Trying 130.158.86.249...
Connected to crocus39.coins.tsukuba.ac.jp.
Escape character is '^]'.
012
012
345
345
^]
telnet> ^D
Connection closed.
$ 
クライアント側(その2)。
$ telnet crocus39.coins.tsukuba.ac.jp 1231 
Trying 130.158.86.249...
Connected to crocus39.coins.tsukuba.ac.jp.
Escape character is '^]'.
abc
abc
def
def
^]
telnet> ^D
Connection closed.
$ 
図1 サーバが accept() で接続要求を待っている
図2 サーバが accept()している時にクライアントが connect()した所
図3 accept() の結果、通信用ポート com が作られる
図4 fork() して、親子に別れる。
図5 親は、com を close()、子は acc を close() する
図6 親は、再び accept() で待ち、子は、特定のクライアントに対して read()/write() する。
図7 サーバが別のクライアントから接続要求を受け付ける
図8 accept() の結果、通信用ポート com が作られる
図9 fork() して、親子に別れる。
図10 親は、com を close()、子は acc を close() する
図11 親は、再び accept() で待ち、子は、特定のクライアントに対して read()/write() する。
$ ./echo-server-fork-fdopen 1231 
run telnet crocus39.coins.tsukuba.ac.jp 1231
[496] accepting incoming connections (acc==3) ...
[496] connection (fd==4) from 130.158.86.247:50426
[496] accepting incoming connections (acc==3) ...
[499] received (fd==4) 5 bytes, [012
]
[496] connection (fd==4) from 130.158.86.248:50351
[496] accepting incoming connections (acc==3) ...
[502] received (fd==4) 5 bytes, [abc
]
[502] received (fd==4) 5 bytes, [def
]
[499] received (fd==4) 5 bytes, [345
]
[499] connection (fd==4) closed.
[502] connection (fd==4) closed.
ps コマンドで見ると、状態(STAT) がゾンビを意味する Z と表示される。
$ ps l | egrep echo 
 1013   496   384   0  31  0  2443008    556 -      S+   s000    0:00.00 ./echo-server-fork-fdopen 1231
 1013   499   496   0   0  0        0      0 -      Z+   s000    0:00.00 (echo-server-fork)
 1013   502   496   0   0  0        0      0 -      Z+   s000    0:00.00 (echo-server-fork)
 1013   586   515   0  31  0  2461016    384 -      S+   s001    0:00.00 egrep echo
$ 
echo-server-fork-fdopen.c では、
delete_zombie() でゾンビを消そうとはしている。しかし、accept() で止まっ
ている状態なので、どこかのクライアントから接続要求が来ない限りは、
delete_zombie() が呼ばれないので、ゾンビとして残っている。
この状態で接続要求が来ると、ゾンビが回収される。 クライアント
$ telnet crocus39.coins.tsukuba.ac.jp 1231 
Trying 130.158.86.249...
Connected to crocus39.coins.tsukuba.ac.jp.
Escape character is '^]'.
[496] connection (fd==4) from 130.158.86.247:50455
[496] zombi 502 deleted.
[496] zombi 499 deleted.
[496] accepting incoming connections (acc==3) ...
ゾンビが回収された後の ps コマンドの結果:
$ ps l | egrep echo 
 1013   496   384   0  31  0  2443008    556 -      S+   s000    0:00.00 ./echo-server-fork-fdopen 1231
 1013   587   496   0  31  0  2451200    232 -      S+   s000    0:00.00 ./echo-server-fork-fdopen 1231
 1013   589   515   0  31  0  2461016    380 -      S+   s001    0:00.00 egrep echo
$ 
実行結果として、サーバ側で、複数同時に受け付けていることを示すログをつ けなさい。複数接続できることを確認するには、telnet のように遅いクライア ントを用いるとよい。単に Web ブラウザで複数のページを開けても、通信その ものは短時間で終わるので、複数接続できることの確認にはならない。
注意: 上限を設定する目的は、攻撃等でサーバの CPU 時間が奪われることやプ ロセスが増えすぎることを防ぐことにある。プロセスの終了を busy wait で待っ てはならない。待つならば、accept() や wait() のように、CPU 時間を消費し ない形で待つようにしなさい。
Keep-Alive に対応しなさい。
ヒント:子プロセスの終了を、ソフトウェア割り込み(signal, SIGCHLD)で知 る方法もある。複数の子プロセスが終了しても、割り込みは1回しか起こらな いことがあることに注意しなさい。
ソフトウェア割込みを使うと、accept() システムコールがエラーで戻って来 るシステムもある。その場合、エラー番号が EINTR なら単純に終了しないで、 再び accept() に向うべきである。
この課題では、複数のクライアントに対するサービスの同時提供を実現するた めに、fork() を使いなさい。select() や Pthread を用いてはならない。
ヒント: IPv4 と IPv6 専用のプロセスやスレッドを作成する。
ヒント: IPv4 の acc と IPv6 の acc を2つ作成し、 両方同時に監視する。
なお同時に扱えるクライアント数は、IPv4 と IPv6 で個別に数えても 統合して数えてもどちらでも良い。たとえば、最大2と設定した場合、 個別に数えて IPv4 で 2, IPv6 で 2、合計 4 としてもよい。