システムプログラム(第8回): ネットワーク・プログラミング/サーバ側(2)

                                       筑波大学 システム情報系 情報工学域
                                       新城 靖
                                       <yas@cs.tsukuba.ac.jp>

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

捕捉

実習室

毎回、数名来ている。来ている人は、楽しそう。

データ構造とアルゴリズム

データ構造とアルゴリズムは相似。 HTTP のメッセージの構造は、トップレベルでは構造体。構造体を処理するよう に手続きを並べたい。 ヘッダの「行」は、「文字の繰り返し+行末\r\n」。 「文字繰り返し」の処理は、ループであるが、 実際には、次のように「行」単位で処理ができる関数を呼べば良い。 「行」単位の処理の関数の内部には、ループが1個含まれる。

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_receive_request( FILE *in, char *filename, size_t size )
{
    int count;
    char **vec;
	....    
        string_split(requestline,' ',&count,&vec); /* 内部で malloc() を呼んでいる */
	...
	if( ... ) {
	    ...
	    /* free_string_vector(count,vec) がない */
	    return( 0 );
	}
	...
	free_string_vector( count,vec ); /* 内部で free() を呼んでいる */
	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;
}
以上のプログラムは、間違い。真似しないように。

auto変数

mallc(), free() をしなくても OK。
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; 
}

C言語のポインタ

マニュアルには、ポインタが良く出る。
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 ); /* 普通は使わない。*/

snprintf()のsize

int snprintf(char * restrict str, size_t size, 
         const char * restrict format, ...);
正解。第2引数に、第1引数のサイズを入れる。
    char buf[BUFSIZE];
	 snprintf( buf, BUFSIZE, "%s", s );
    char *buf;
         buf = malloc( BUFSIZE );
	 snprintf( buf, BUFSIZE, "%s", s );
間違い。第2引数に、入力データの長さを入れる。 入力が大きければ、バッファ・オーバーフローが生じる。
    char buf[BUFSIZE];
	 snprintf( buf, strlen(s), "%s", s );
size に入力から計算した値を入れてはいけ ない。 strlcpy(), strlcat() でも同様。
size_t strlcpy(char *dst, const char *src, size_t size);
size_t strlcat(char *dst, const char *src, size_t size);

sizeof()の使い方、配列とポインタの違い

正解1。
    char buf[BUFSIZE];
	 snprintf( buf, BUFSIZE, "%s", s );
正解2。
    char buf[BUFSIZE];
	 snprintf( buf, sizeof(buf), "%s", s );
正解3。
    char *buf;
         buf = malloc( BUFSIZE );
	 snprintf( buf, BUFSIZE, "%s", s );
以下は間違い。
    char *buf;
         buf = malloc( BUFSIZE );
	 snprintf( buf, sizeof(buf), "%s", s );
sizeof(buf) は、ポインタ変数のサイズ sizeof(char *) を返す。 文字操作,文字列操作ライブラリ参照。 ~yas/syspro/cc/sizeof.c を実行して、予想通りの結果が表示されるか確認しなさい。

今日の重要な話

TCP/IP によるネットワーク・プログラミング

復習

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

複数のクライアントに対してサービスの同時に提供することが望ましい。

echo-server-nofork-fdopenへの複数のクライアントの接続

echo-server-nofork-fdopen.c は、1つのクライアントからの接続されると、そのクライアントに掛り切りに なって、他のクライアントにはサービスを提供できないという問題がある。

サーバ側

$ 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 aloe30.local 1231
[7512] accepting (fd==4) to [::]:1231
[7512] accepting incoming connections (acc==4) ...
[7512] connection (fd==5) from [2001:2f8:3a:1711::230:39]:52941
[7512] received (fd==5) 5 bytes, [012
]
^C
$ []
クライアント側(その1)。
$ telnet aloe30 1231 [←]
Trying 2001:2f8:3a:1711::230:30...
Connected to aloe30.
Escape character is '^]'.
012[←]
012
[]
クライアント側(その2)。
$ telnet aloe30 1231 [←]
Trying 2001:2f8:3a:1711::230:30...
Connected to aloe30.
Escape character is '^]'.
abc[←]
[]
クライアント(その2)は、クライアント(その1)が終了するまでサービス を受けられない。

標準のechoサーバへの複数のクライアントの接続

標準の echo サーバ(ポート番号 7 で動作している) は、複数のクライアント から接続されれた場合、同時にサービスを提供することができる。

クライアント側(その1)。

$ telnet aloe30 7 [←]
Trying 2001:2f8:3a:1711::230:30...
Connected to aloe30.
Escape character is '^]'.
012[←]
012
[]
クライアント側(その2)。
$ telnet aloe30 7 [←]
Trying 2001:2f8:3a:1711::230:30...
Connected to aloe30.
Escape character is '^]'.
abc[←]
abc
[]

複数のクライアントを同時に扱う方法

複数のクライアントに対してサービスの同時に提供するには次のような方法が ある。 スレッドのプログラミングは非常に難しい。 大学院の授業「並行システム」 等で扱う。

fork()

fork() は、親プロセスをコピーすることにより、子プロセスを作成するシステ ム・コールである。 前半では、exec とともに 使う例を示した。 ネットワーク・プログラミングでは、exec を伴わない fork() が使われる ことがある。

3回forkするプログラム

このプログラムは、表面的には 3 回 fork() して画面に hello と表示するプ ログラムである。
   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-fork-forkの親子関係

図? fork()システム・コールによるプロセスのコピー

echo-server-fork-fdopen.c

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

echo-server-fork-fdopen.c のmain()

[echo-server-fork-fdopen.c]

   1:	
   2:	/*
   3:	  echo-server-fork-fdopen.c -- 受け取った文字列をそのまま返すサーバ(fork版)
   4:	  ~yas/syspro/ipc/echo-server-fork-fdopen.c
   5:	*/
   6:	#include <stdio.h>
   7:	#include <stdlib.h>     /* exit() */
   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:	#include <string.h>     /* strlen() */
  15:	#include <unistd.h>     /* getpid(), gethostname() */
  16:	
  17:	extern  void echo_server( int portno, int ip_version );
  18:	extern  void delete_zombie();
  19:	extern  void echo_receive_request_and_send_reply( int com );
  20:	extern  int  echo_receive_request( char *line, size_t size, FILE *in );
  21:	extern  void echo_send_reply( char *line, FILE *out );
  22:	extern  void print_my_host_port( int portno );
  23:	extern  void tcp_sockaddr_print( int com );
  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 = 46; /* Both IPv4 and IPv6 by default */
  43:	        echo_server( portno, ip_version );
  44:	}
  45:	
main() は、 echo-server-nofork-fdopen.c と同じである。

echo-server-fork-fdopen.c のecho_server()

echo_server() は、echoサーバのメインループである。
  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:	        tcp_sockaddr_print( acc );
  57:	        while( 1 )
  58:	        {
  59:	                delete_zombie();
  60:	                printf("[%d] accepting incoming connections (acc==%d) ...\n",
  61:	                       getpid(),acc );
  62:	                if( (com = accept( acc,0,0 )) < 0 )
  63:	                {
  64:	                        perror("accept");
  65:	                        exit( -1 );
  66:	                }
  67:	                tcp_peeraddr_print( com );
  68:	                if( (child_pid=fork()) > 0 ) /* parent */
  69:	                {
  70:	                        close( com );
  71:	                }
  72:	                else if( child_pid == 0 ) /* child */
  73:	                {
  74:	                        close( acc );
  75:	                        echo_receive_request_and_send_reply( com );
  76:	                        exit( 0 );
  77:	                }
  78:	                else
  79:	                {
  80:	                        perror("fork");
  81:	                        exit( -1 );
  82:	                }  
  83:	        }
  84:	}
  85:	

delete_zombie() は、ゾンビ・プロセス(後述)を消去するものである。 (改善の余地がある。) echo サービスとしての処理は、fork() システムコールで分身を作り、子プロ セス側で行う。親プロセスは、すぐにaccept() に戻る。

getpid() は、自分自身の PID (Process ID (identifier)) を返すシステムコー ルである。PID (pid_t) は、32 ビット(システムによっては16ビット)の整数 である。実行結果で printf() の表示に、PID が表示されているが、同じではないことに 注意しなさい。

echo-server-fork-fdopen.c のdelete_zombie()

delete_zombie() は、終了した子プロセスの残骸を回収する関数である。
  86:	void
  87:	delete_zombie()
  88:	{
  89:	        pid_t pid ;
  90:	
  91:	        while( (pid=wait4(-1,0,WNOHANG,0)) >0 )
  92:	        {
  93:	                printf("[%d] zombi %d deleted.\n",getpid(),pid );
  94:	                continue;
  95:	        }
  96:	}
  97:	
wait4() システムコールを使って、終了した子プロセスをwait してあげてい る。ただし、WNOHANG オプションを付けてあるので、終了した子プロセスがい なければ、待たずに返ってくる。

echo-server-fork-fdopen.c のその他の関数

他の関数は、 echo-server-nofork-fdopen.c と同じである。 次のように diff コマンドで調べると、ほとんど違いがないことがわかる。 diff コマンドの結果、「+」は、追加された行、 「-」は、削除された行、 「!」は、修正された行を意味する。

 $ diff -c echo-server-nofork-fdopen.c  echo-server-fork-fdopen.c
 *** echo-server-nofork-fdopen.c	2021-06-04 18:36:03.467817000 +0900
 --- echo-server-fork-fdopen.c	2021-05-26 15:54:19.003067000 +0900
 ***************
 *** 1,7 ****

   /*
 !   echo-server-nofork-fdopen.c -- 受け取った文字列をそのまま返すサーバ(fork無し版)
 !   ~yas/syspro/ipc/echo-server-nofork-fdopen.c
   */
   #include <stdio.h>
   #include <stdlib.h>	/* exit() */
 --- 1,7 ----

   /*
 !   echo-server-fork-fdopen.c -- 受け取った文字列をそのまま返すサーバ(fork版)
 !   ~yas/syspro/ipc/echo-server-fork-fdopen.c
   */
   #include <stdio.h>
   #include <stdlib.h>	/* exit() */
 ***************
 *** 15,20 ****
 --- 15,21 ----
   #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 )
 ***************
 *** 54,59 ****
 --- 56,62 ----
	 tcp_sockaddr_print( acc );
	 while( 1 )
	 {
 + 		delete_zombie();
		 printf("[%d] accepting incoming connections (acc==%d) ...\n",
			getpid(),acc );
		 if( (com = accept( acc,0,0 )) < 0 )
 ***************
 *** 62,68 ****
			 exit( -1 );
		 }
		 tcp_peeraddr_print( com );
 ! 		echo_receive_request_and_send_reply( com );
	 }
   }

 --- 65,97 ----
			 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;
	 }
   }

 $ 

echo-server-fork-fdopenの実行例

サーバ側。 サーバは、終了しないので、最後に、^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 aloe30.local 1231
[11153] accepting (fd==4) to [::]:1231
[11153] accepting incoming connections (acc==4) ...
[11153] connection (fd==5) from [2001:2f8:3a:1711::230:39]:53033
[11153] accepting incoming connections (acc==4) ...
[11154] received (fd==5) 5 bytes, [012
]
[11153] connection (fd==5) from [2001:2f8:3a:1711::230:40]:63307
[11153] accepting incoming connections (acc==4) ...
[11238] received (fd==5) 5 bytes, [abc
]
[11238] received (fd==5) 5 bytes, [def
]
[11154] received (fd==5) 5 bytes, [345
]
[11154] connection (fd==5) closed.
[11238] connection (fd==5) closed.
[]
クライアント側(その1)。
$ telnet aloe30 1231 [←]
Trying 2001:2f8:3a:1711::230:30...
Connected to aloe30.
Escape character is '^]'.
012[←]
012
345[←]
345
^]
telnet> ^D
Connection closed.
$ []
クライアント側(その2)。
$ telnet aloe30 1231 [←]
Trying 2001:2f8:3a:1711::230:30...
Connected to aloe30.
Escape character is '^]'.
abc[←]
abc
def[←]
def
^]
telnet> ^D
Connection closed.
$ []

図解

図1 サーバが accept() で接続要求を待っている

図1 サーバが accept() で接続要求を待っている

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

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

図3 accept() の結果、通信用ポート com が作られる

図3 accept() の結果、通信用ポート com が作られる

図4 fork() して、親子に別れる。

図4 fork() して、親子に別れる。

図5 親は、com を close()、子は acc を close() する

図5 親は、com を close()、子は acc を close() する

図6 親は、再び accept() で待ち、子は、特定のクライアントに対して read()/write() する。

図6 親は、再び accept() で待ち、子は、特定のクライアントに対して read()/write() する。

図7 サーバが別のクライアントから接続要求を受け付ける

図7 サーバが別のクライアントから接続要求を受け付ける

図8 accept() の結果、通信用ポート com が作られる

図8 accept() の結果、通信用ポート com が作られる

図9 fork() して、親子に別れる。

図9 fork() して、親子に別れる。

図10 親は、com を close()、子は acc を close() する

図10 親は、com を close()、子は acc を close() する

図11 親は、再び accept() で待ち、子は、特定のクライアントに対して read()/write() する。

図11 親は、再び accept() で待ち、子は、特定のクライアントに対して read()/write() する。

ゾンビ・プロセス

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

$ ./echo-server-fork-fdopen 1231 [←]
run telnet aloe30.local 1231
[11153] accepting (fd==4) to [::]:1231
[11153] accepting incoming connections (acc==4) ...
[11153] connection (fd==5) from [2001:2f8:3a:1711::230:39]:53033
[11153] accepting incoming connections (acc==4) ...
[11154] received (fd==5) 5 bytes, [012
]
[11153] connection (fd==5) from [2001:2f8:3a:1711::230:40]:63307
[11153] accepting incoming connections (acc==4) ...
[11238] received (fd==5) 5 bytes, [abc
]
[11238] received (fd==5) 5 bytes, [def
]
[11154] received (fd==5) 5 bytes, [345
]
[11154] connection (fd==5) closed.
[11238] connection (fd==5) closed.
[]
ps コマンドで見ると、状態(STAT) がゾンビを意味する Z と表示される。

$ ps l | grep echo [←]
 1013 11153  1109   0  31  0  4279484    904 -      S+   s000    0:00.00 ./echo-server-fork-fdopen 1231
 1013 11154 11153   0   0  0        0      0 -      Z+   s000    0:00.00 (echo-server-fork)
 1013 11238 11153   0   0  0        0      0 -      Z+   s000    0:00.00 (echo-server-fork)
 1013 12042  7217   0  31  0  4400052    812 -      S+   s007    0:00.00 grep echo
$ []
echo-server-fork-fdopen.c では、 delete_zombie() でゾンビを消そうとはしている。しかし、accept() で止まっ ている状態なので、どこかのクライアントから接続要求が来ない限りは、 delete_zombie() が呼ばれないので、ゾンビとして残っている。

この状態で接続要求が来ると、ゾンビが回収される。 クライアント

$ telnet aloe30 1231 [←]
Trying 2001:2f8:3a:1711::230:30...
Connected to aloe30.
Escape character is '^]'.
[]

サーバ側

[11153] connection (fd==5) from [2001:2f8:3a:1711::230:39]:53048
[11153] zombi 11238 deleted.
[11153] zombi 11154 deleted.
[11153] accepting incoming connections (acc==4) ...
[]
ゾンビが回収された後の ps コマンドの結果:
$ ps l | grep echo [←]
 1013 11153  1109   0  31  0  4279484    904 -      S+   s000    0:00.00 ./echo-server-fork-fdopen 1231
 1013 12326 11153   0  31  0  4418468    416 -      S+   s000    0:00.00 ./echo-server-fork-fdopen 1231
 1013 13009  7217   0  31  0  4417460    840 -      S+   s007    0:00.00 grep echo
$ [←]

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

練習問題

練習問題(801) 複数のクライアントから要求を受け付けるHTTPサーバ

練習問題(708) で、複数のクライアントから同時に接続を受けるようにしなさい。 ただしこの課題では、 echo-server-fork-fdopen.c の方法と同様にクライアントごとに fork() システム・コールでプロセスを生 成する方法を使いなさい。スレッドを使ってはならない。

実行結果として、サーバ側で、複数同時に受け付けていることを示すログをつ けなさい。複数接続できることを確認するには、telnet のように遅いクライア ントを用いるとよい。単に Web ブラウザで複数のページを開けても、通信その ものは短時間で終わるので、複数接続できることの確認にはならない。

練習問題(802) 複数のクライアントから要求を受け付けるHTTPサーバ(最大クライアント数付き)

練習問題(801) で、 同時に扱えるクライアントの最大数を設定できるようにしなさい。た とえば最大2と設定した場合、2つの接続まで同時に処理を行うが、3つ目が 来た場合、接続を切るか他の接続が閉じられるまで処理を遅延する。 この課題では、 echo-server-fork-fdopen.c の方法と同様にクライアントごとに fork() システム・コールでプロセスを生 成する方法を使いなさい。スレッドを使ってはならない。

注意: 上限を設定する目的は、攻撃等でサーバの CPU 時間が奪われることやプ ロセスが増えすぎることを防ぐことにある。プロセスの終了を busy wait で待っ てはならない。待つならば、accept() や wait() のように、CPU 時間を消費し ない形で待つようにしなさい。

ヒント: 最大数まで fork() したら、accept() をやめて wait して子プロセスを待つ方法が考えられる。

練習問題(803) Keep Alive 対応の HTTPサーバ

練習問題(801) で HTTP の Keep-Alive に対応しなさい。

練習問題(804) ゾンビの即座な回収

echo-server-fork-fdopen.c では、accept() している途中で、子プロセスが終了するとゾンビになってし まう。子プロセスが終了したら直ちにゾンビを回収するようにしなさい。

ヒント:子プロセスの終了を、 シグナル SIGCHLD で知る方法もある。複数の子プロセスが終了しても、シグナルは1回 しか送られないことがあることに注意しなさい。

シグナルを使うと、accept() システムコールがエラーで戻って来るシステムも ある。その場合、エラー番号が EINTR なら単純に終了しないで、再び accept() に向うべきである。

練習問題(805) ゾンビの即座な回収ができる HTTPサーバ

練習問題(801) で、 練習問題(804) と同様に シグナルを使って即座にゾンビを回収するようにしなさい。

この課題では、複数のクライアントに対するサービスの同時提供を実現するた めに、fork() を使いなさい。select() や Pthread を用いてはならない。

練習問題(806) 安全にIPv4とIPv6の両方に対応可能なechoサーバ

echo-server-fork-fdopen.c は、IPv4 と IPv6 の両方にサービスを提供できるが、 IPV6_V6ONLY オプションを設定していないので、 意図せずIPv4からの接続を許してしまうことがある。 IPV6_V6ONLY を利用しながら、IPv4 と IPv6 の両方に対応するように変更しなさい。

ヒント: IPv4 と IPv6 専用のプロセスやスレッドを作成する。

練習問題(807) IPv4とIPv6の両方に対応可能なechoサーバ(select)

echo-server-select.c は、IPv4 と IPv6 の両方にサービスを提供できるが、 IPV6_V6ONLY オプションを設定していないので、 意図せずIPv4からの接続を許してしまうことがある。 IPV6_V6ONLY を利用しながら、IPv4 と IPv6 の両方に対応するように変更しなさい。

ヒント: IPv4 の acc と IPv6 の acc を2つ作成し、 両方同時に監視する。

練習問題(808) IPv4とIPv6の両方に対応可能な HTTP サーバ

練習問題(801) で、IPv6 について IPV6_V6ONLY オプションを設定し、 意図せずIPv4からの接続 をさせないようにしなさい。 その上で、IPv4 と IPv6 の両方に対応するようにしなさい。

なお同時に扱えるクライアント数は、IPv4 と IPv6 で個別に数えても 統合して数えてもどちらでも良い。たとえば、最大2と設定した場合、 個別に数えて IPv4 で 2, IPv6 で 2、合計 4 としてもよい。


Last updated: 2021/06/09 22:42:49
Yasushi Shinjo / <yas@cs.tsukuba.ac.jp>