ここまでのサーバプログラムでは,一つのクライアントからの接続を受け入れることしかできない.もちろん,
OSの提供するTCP通信プロトコルモジュールは複数の接続をサポートしており,bind()されたファイルディ スクリプタをclose()してしまわなければサーバプログラムの動作に関わらず待ち受けを継続することができる.
ところが,実際にはデータ送受信処理に制御が移っており,別のクライアントからの接続を accept()すること ができなくなってしまっている.
実際に提供されている多くのサービスではこのようなことはなく,例えばHTTPサーバは複数クライアントか らの接続を受けつけ,接続中のクライアントが大きなファイルをダウンロードしている最中でも新規の接続を受 けつけてくれる.
マルチクライアント型のサーバを作成する方法として,ここではforkシステムコールを使う方法と,selectシ ステムコールを使う方法の2 種類について説明する.
5.7.1 fork システムコール
UNIXで頻繁に用いられる方法はプロセスのforkと呼ばれるものである.これは実行中のプロセスの資源,本 テキストのサーバプログラムで言えば各変数値,ポインタとポインタに格納されたデータ,ファイルディスクリプ タをそっくりコピーしてしまい,実行を継続する新しいプロセス(子プロセス)を生成するというものである(図 18).
プロセスのforkはfork システムコールを用いて行われる.fork()は,親プロセスでは子プロセスのプロセ スIDを返し,子プロセスでは0 を返す.また,forkに失敗した場合には負の値を返す.この返り値を使ってプ ロセスを区別し,プログラムのfork() 以降の部分の動作を決めるのが簡単である.複数のアクセスを受け付け るようなサーバでは,たとえば,親プロセスではクライアントからの接続要求だけを処理し、実際のクライアン トの処理は子プロセスに任せてしまうという方法が考えられる.
21複数のIPアドレスを持たない限り,個別のアドレス指定は意味が無い.一方,通常のサービスでは任意のアドレスからの接続要求を受 け付けるのが自然であるが,例えばルータやゲートウェイの役割を果たすサーバの場合には,待ち受け側のアドレスによってクライアントか らの接続要求を区別する必要がある.
親プロセス
↓ fork()
↓ ...
→資源のコピー 子プロセス
↓ ...
図 18: プロセスのfork
while(1) {
connected_fd = accept(listening_fd, (struct sockaddr *)&client_addr, &len);
/** fork **/
if ( ( pid = fork() ) < 0 ) { exit(1);
} else if ( pid == 0 ) {
/* fork で新しく生成されるプロセスの処理 */
close(listening_fd);
printf( "Accepted connection\n" );
while(1) {
/*** データの送受信処理 ***/
}
exit( close(connected_fd) );
}
/* 親プロセスは connected_fd を閉じて接続要求待ちのループに戻る */
close(connected_fd);
}
図 19: fork()による多重接続
図19のように,親プロセスでは待ち受けの無限ループを実行し,子プロセスではaccept()したソケットに対 してデータ送受信処理を行うことで,多重接続を実現している.
5.7.2 select システムコール
fork システムコール以外のマルチクライアントプログラムの実現方法として,selectシステムコールを用い る方法がある.
select()システムコールは,複数のファイルディスクリプタを監視することを可能にする.select()は引数
に3 つのfd_set型ポインタを指定してファイルディスクリプタを監視する.3 つのポインタはそれぞれファイ
ルディスクリプタの入力(readfds)/出力(writefds)/例外(exceptfds)状態を示すビットマスクを指している.本実 験のサーバ用途には入力状態の監視を利用する.
nに監視対象のディスクリプタのうち最大の番号に1を加えた値をtimeoutに監視時間を与えてselect()を 呼び出すことでファイルディスクリプタの監視を行う.呼出しの際のビットマスクが監視対象を示し,戻ってきた 際のビットマスクが使用可能なファイルディスクリプタを示している.
実際のビットマップ操作には専用のマクロFD_CLR/FD_ISSET/FD_SET/FD_ZEROを使えばよい.4つのマクロの うち,FD_ZEROは1つのfd_set型ポインタを引数にとり,ビットマスクを全てクリアすることで初期化する.残り の3つのマクロは第1引数にファイルディスクリプタを,第2引数にfd_set型ポインタをとる.FD_CLR/FD_SET はファイルディスクリプタに対応するビットをクリアするかセットし,FD_ISSETはファイルディスクリプタに対 応するビットがセットされていれば真を,そうでなければ偽を返す.
select()の返り値はセットされたビットの数,ゼロは監視時間中に対象のファイルディスクリプタが使用可能 にならなかったことを示す.また,ヌルポインタを指定すれば対象の条件は監視されず,ヌル監視時間を指定すれ ばいずれかのファイルディスクリプタが使用可能になるまでブロックされる.
6 課題内容とレポート作成について
6.1 実験スケジュール
実験は7週間にわたって行う.レポートを作成する期間を考慮すると,理想的には,次のようなスケジュール で実験を進めるのが望ましい.
第1週 [課題1]サーバの動作確認
[課題2]WWWサーバ
第2週 [課題3(グループ実験)]ファイアウォールの構築
第3週 [課題3(グループ実験)]ファイアウォールの構築(続きと評価)
第4週 [課題4]異なる言語で実装したサーバ・クライアント間通信の動作確認
第5週 [課題5(グループ実験)]ネットワークアプリケーションの作成
第6週 [課題5(グループ実験)]ネットワークアプリケーションの作成(続きと動作確認)
第7週 予備日・他グループのアプリケーションを評価・レポート作成