筑波大学 システム情報系 情報工学域
新城 靖
<yas@cs.tsukuba.ac.jp>
このページは、次の URL にあります。
http://www.coins.tsukuba.ac.jp/~syspro/2012/2012-06-06
/index.html
あるいは、次のページから手繰っていくこともできます。
http://www.coins.tsukuba.ac.jp/~syspro/2012/
http://www.coins.tsukuba.ac.jp/~yas/
例題をコピーする時の注意点
仮想ホストの実現方法には、2種類ある。
"HTTP/1.0 302 Found" が返されることがある。
例:
http://www.example.com/index.html
GET /index.html HTTP/1.0←↓
Host: www.example.com←↓
←↓
extern void echo_server( int portno, int ip_version );
void echo_server( int portno, int ip_version );変数については、次のような使い方をするのがよい。
char *optarg;
extern char *optarg;
HTTP の内容(content)は、バイトの並び。バイト単位のループでもプログラムは書けるが、 遅いので、ある程度の大きさの塊(buffer size単位)で処理するのが普通。 テキストだけ扱うならば、行の並びと仮定しても問題ない。
char *name;
name = "Name"
printf("Hello, %s\n",name );
char buf[BUFFERSIZE];
snprintf(buf,BUFFERSIZE,"Hello, %s\n",name );
strcpy(), strcat() は論外。
$ telnet www 80
Trying 130.158.86.1...
Connected to www.coins.tsukuba.ac.jp.
Escape character is '^]'.
GET /.. HTTP/1.0
HTTP/1.1 400 Bad Request
Date: Mon, 07 Jun 2010 11:26:36 GMT
Server: Apache
Content-Length: 226
Connection: close
Content-Type: text/html; charset=iso-8859-1
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>400 Bad Request</title>
</head><body>
<h1>Bad Request</h1>
<p>Your browser sent a request that this server could not understand.<br />
</p>
</body></html>
Connection closed by foreign host.
$
このうち、1行目、Content-Type:、空行、本文はしっかり返したい。
サーバ側
$ 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 cosmos10(v6) 1231
[15980] accepting incoming connections (fd==3) ...
[15980] connection (fd==4) from 130.158.86.150:57562
[15980] received (fd==4) 5 bytes, [012
]
^C
$
クライアント側(その1)。
$ telnet cosmos10 1231
Trying 130.158.86.150...
Connected to cosmos10.coins.tsukuba.ac.jp.
Escape character is '^]'.
012
012
クライアント側(その2)。
$ telnet cosmos10 1231
Trying 130.158.86.150...
Connected to cosmos10.coins.tsukuba.ac.jp.
Escape character is '^]'.
abc
クライアント(その2)は、クライアント(その1)が終了するまでサービス
を受けられない。
クライアント側(その1)。
$ telnet cosmos10 7
Trying 130.158.86.150...
Connected to cosmos10.coins.tsukuba.ac.jp.
Escape character is '^]'.
012
012
345
345
^]
telnet> ^D
Connection closed.
$
クライアント側(その2)。
$ telnet cosmos10 7
Trying 130.158.86.150...
Connected to cosmos10.coins.tsukuba.ac.jp.
Escape character is '^]'.
abc
abc
123
123
^]
telnet> ^D
Connection closed.
$
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:
9: main()
10: {
11: fork();
12: fork();
13: fork();
14: printf("hello\n");
15: }
実行すると、画面には 8 回 hello と表示される。
$ 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:
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 void print_my_host_port( int portno );
21: extern void tcp_peeraddr_print( int com );
22: extern void sockaddr_print( struct sockaddr *addrp, socklen_t addr_len );
23: extern int tcp_acc_port( int portno, int pf );
24: extern int fdopen_sock( int sock, FILE **inp, FILE **outp );
25:
26: main( int argc, char *argv[] )
27: {
28: int portno, ip_version;
29: if( !(argc == 2 || argc==3) ) {
30: fprintf(stderr,"Usage: %s portno {ipversion}\n",argv[0] );
31: exit( 1 );
32: }
33: portno = strtol( argv[1],0,10 );
34: if( argc == 3 )
35: ip_version = strtol( argv[2],0,10 );
36: else
37: ip_version = 4; /* IPv4 by default */
38: echo_server( portno, ip_version );
39: }
40:
main() は、
echo-server-nofork-fdopen.c
と同じである。
41: void
42: echo_server( int portno, int ip_version )
43: {
44: int acc,com ;
45: pid_t child_pid ;
46: acc = tcp_acc_port( portno, ip_version );
47: if( acc<0 )
48: exit( -1 );
49: print_my_host_port( portno );
50: while( 1 )
51: {
52: delete_zombie();
53: printf("[%d] accepting incoming connections (fd==%d) ...\n",getpid(),acc );
54: if( (com = accept( acc,0,0 )) < 0 )
55: {
56: perror("accept");
57: exit( -1 );
58: }
59: tcp_peeraddr_print( com );
60: if( (child_pid=fork()) > 0 ) /* parent */
61: {
62: close( com );
63: }
64: else if( child_pid == 0 ) /* child */
65: {
66: close( acc );
67: echo_receive_request_and_send_reply( com );
68: exit( 0 );
69: }
70: else
71: {
72: perror("fork");
73: exit( -1 );
74: }
75: }
76: }
77:
delete_zombie() は、ゾンビ・プロセス(後述)を消去するものである。 (改善の余地がある。) echo サービスとしての処理は、fork() システムコールで分身を作り、子プロ セス側で行う。親プロセスは、すぐにaccept() に戻る。
getpid() は、自分自身の PID (Process ID (identifier)) を返すシステムコー ルである。PID (pid_t) は、32 ビット(システムによっては16ビット)の整数 である。実行結果で printf() の表示に、PID が表示されているが、同じではないことに 注意しなさい。
78: void
79: delete_zombie()
80: {
81: pid_t pid ;
82: while( (pid=wait4(-1,0,WNOHANG,0)) >0 )
83: {
84: printf("[%d] zombi %d deleted.\n",getpid(),pid );
85: continue;
86: }
87: }
88:
wait4() システムコールを使って、終了した子プロセスをwait してあげてい
る。ただし、WNOHANG オプションを付けてあるので、終了した子プロセスがい
なければ、待たずに返ってくる。
+」は、追加された行、
「-」は、削除された行、
「!」は、修正された行を意味する。
$ diff -c echo-server-nofork-fdopen.c echo-server-fork-fdopen.c
*** echo-server-nofork-fdopen.c 2012-05-28 15:12:48.000000000 +0900
--- echo-server-fork-fdopen.c 2012-06-04 15:05:44.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() */
***************
*** 15,20 ****
--- 15,21 ----
#include <string.h> /* strlen() */
extern void echo_server( int portno, int ip_version );
+ extern void delete_zombie();
extern void echo_receive_request_and_send_reply( int com );
extern void print_my_host_port( int portno );
extern void tcp_peeraddr_print( int com );
***************
*** 41,52 ****
--- 42,55 ----
echo_server( int portno, int ip_version )
{
int acc,com ;
+ pid_t child_pid ;
acc = tcp_acc_port( portno, ip_version );
if( acc<0 )
exit( -1 );
print_my_host_port( portno );
while( 1 )
{
+ delete_zombie();
printf("[%d] accepting incoming connections (fd==%d) ...\n",getpid(),acc );
if( (com = accept( acc,0,0 )) < 0 )
{
***************
*** 54,63 ****
exit( -1 );
}
tcp_peeraddr_print( com );
! echo_receive_request_and_send_reply( com );
}
}
#define BUFFERSIZE 1024
void
--- 57,91 ----
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;
+ }
+ }
+
#define BUFFERSIZE 1024
void
サーバ側。 サーバは、終了しないので、最後に、^C を押して、割り込みを掛け て終了させる。
注意:全員がポート番号 1231 を使うとプログラムが動かないことがある。
$ cp ~yas/syspro/ipc/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 cosmos10(v6) 1231
[17071] accepting incoming connections (fd==3) ...
[17071] connection (fd==4) from 130.158.86.150:57741
[17071] accepting incoming connections (fd==3) ...
[17073] received (fd==4) 5 bytes, [012
]
[17071] connection (fd==4) from 130.158.86.1:54330
[17071] accepting incoming connections (fd==3) ...
[17074] received (fd==4) 5 bytes, [abc
]
[17074] received (fd==4) 5 bytes, [def
]
[17073] received (fd==4) 5 bytes, [345
]
[17073] connection (fd==4) closed.
[17074] connection (fd==4) closed.
^C
$
クライアント側(その1)。
$ telnet cosmos10 1231
Trying 130.158.86.150...
Connected to cosmos10.coins.tsukuba.ac.jp.
Escape character is '^]'.
012
012
345
345
^]
telnet> quit
Connection closed.
$
クライアント側(その2)。
$ telnet cosmos10 1231
Trying 130.158.86.150...
Connected to cosmos10.coins.tsukuba.ac.jp.
Escape character is '^]'.
abc
abc
def
def
^]
telnet> quit
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 cosmos10(v6) 1231
[17103] accepting incoming connections (fd==3) ...
[17103] connection (fd==4) from 130.158.86.150:57745
[17103] accepting incoming connections (fd==3) ...
[17111] received (fd==4) 5 bytes, [012
]
[17103] connection (fd==4) from 130.158.86.1:54390
[17103] accepting incoming connections (fd==3) ...
[17112] received (fd==4) 5 bytes, [abc
]
[17112] received (fd==4) 5 bytes, [def
]
[17112] connection (fd==4) closed.
ps コマンドで見ると、ゾンビの状態(STAT) は、Z と表示される。
$ ps uxw|egrep echo
yas 17112 0.0 0.0 0 0 s003 Z+ 3:21PM 0:00.00 (echo-server-fork)
yas 17111 0.0 0.0 2434840 192 s003 S+ 3:21PM 0:00.00 ./echo-server-fork-fdopen 1231
yas 17103 0.0 0.0 2434840 364 s003 S+ 3:20PM 0:00.00 ./echo-server-fork-fdopen 1231
yas 17145 0.0 0.0 2426928 328 p1 R+ 3:22PM 0:00.00 egrep echo
$
echo-server-fork-fdopen.c では、
delete_zombie() でゾンビを消そうとはしている。しかし、accept() で止まっ
ている状態なので、どこかのクライアントから接続要求が来ない限りは、
delete_zombie() が呼ばれないので、ゾンビとして残っている。
この状態で接続要求が来ると、ゾンビが回収される。
[17103] connection (fd==4) from 130.158.86.1:54408
[17103] zombi 17112 deleted.
[17103] accepting incoming connections (fd==3) ...
ゾンビが回収された後の ps コマンドの結果:
$ ps uxw|egrep echo
yas 17152 0.0 0.0 2434840 184 s003 S+ 3:23PM 0:00.00 ./echo-server-fork-fdopen 1231
yas 17111 0.0 0.0 2434840 192 s003 S+ 3:21PM 0:00.00 ./echo-server-fork-fdopen 1231
yas 17103 0.0 0.0 2434840 364 s003 S+ 3:20PM 0:00.00 ./echo-server-fork-fdopen 1231
yas 17157 0.0 0.0 2435120 452 p1 S+ 3:25PM 0:00.00 egrep echo
$
実行結果として、サーバ側で、複数同時に受け付けていることを示すログをつ けなさい。複数接続できることを確認するには、telnet のように遅いクライア ントを用いるとよい。単に Web ブラウザで複数のページを開けても、通信その ものは短時間で終わるので、複数接続できることの確認にはならない。
注意: 上限を設定する目的は、攻撃等でサーバの CPU 時間が奪われることやプ ロセスが増えすぎることを防ぐことにある。プロセスの終了を busy wait で待っ てはならない。待つならば、accept() や wait() のように、CPU 時間を消費し ない形で待つようにしなさい。
Keep-Alive に対応しなさい。
ヒント:子プロセスの終了を、ソフトウェア割り込み(signal, SIGCHLD)で知 る方法もある。複数の子プロセスが終了しても、割り込みは1回しか起こらな いことがあることに注意しなさい。
ソフトウェア割込みを使うと、accept() システムコールがエラーで戻って来 るシステムもある。その場合、エラー番号が EINTR なら単純に終了しないで、 再び accept() に向うべきである。
ヒント: IPv4 と IPv6 専用のプロセスやスレッドを作成する。
ヒント: IPv4 の acc と IPv6 の acc を2つ作成し、 両方同時に監視する。
なお同時に扱えるクライアント数は、IPv4 と IPv6 で個別に数えても 統合して数えてもどちらでも良い。たとえば、最大2と設定した場合、 個別に数えて IPv4 で 2, IPv6 で 2、合計 4 としてもよい。