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

                                       筑波大学 システム情報工学研究科 
                                       コンピュータサイエンス専攻, 電子・情報工学系
                                       新城 靖
                                       <yas@is.tsukuba.ac.jp>

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

今日の重要な話

復習

echoサーバ

TCP/IP のポート番号 7 (echo) では、受け取ったデータをそのまま返すサー ビスを提供している。以下は、これと同じような機能を提供するサーバである。

echo-server-nofork-fdopen.c

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

   1:	
   2:	/*
   3:	        echo-server-nofork-fdopen.c -- 受け取った文字列をそのまま返すサーバ(fork無し版)
   4:	        ~yas/syspro/ipc/echo-server-nofork-fdopen.c
   5:	        Created on 2004/05/09 19:08:47
   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 );
  18:	extern  void echo_reply( int com );
  19:	extern  void print_my_host_port( int portno );
  20:	extern  void tcp_peeraddr_print( int com );
  21:	extern  void sockaddr_print( struct sockaddr *addrp, socklen_t addr_len );
  22:	extern  tcp_acc_port( int portno );
  23:	extern  int fdopen_sock( int sock, FILE **inp, FILE **outp );
  24:	
  25:	main( int argc, char *argv[] )
  26:	{
  27:	    int portno ;
  28:	        if( argc != 2 )
  29:	        {
  30:	            fprintf( stdout,"Usage: %s portno\n",argv[0] );
  31:	            exit( -1 );
  32:	        }
  33:	        portno = strtol( argv[1],0,10 );
  34:	        echo_server( portno );
  35:	}
  36:	
main() は、引数として、ポート番号を取る。 文字列で受け取ったポート番号を strtol() で int へ変換している。 最後に、echo_server() を呼ぶ。

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

echo_server() は、echoサーバのメインループである。

  37:	void
  38:	echo_server( int portno )
  39:	{
  40:	    int acc,com ;
  41:	        acc = tcp_acc_port( portno );
  42:	        if( acc<0 )
  43:	            exit( -1 );
  44:	        print_my_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:	            echo_reply( com );
  54:	        }
  55:	}
  56:	

tcp_acc_port() は、引数で与えられたポート番号を使って接続要求受付用ポー トを作成し、そのファイル記述子(ソケット)を返す。 このファイル記述子は、クライアント側とは異なり、そまままでは通信に 用いることはできない。 print_my_host_port() は、telnet で接続する時のヒントを表示する。

サーバのプログラムの特徴は、内部に無限ループを持っていることである。 サーバは、普通の状態では、終了しない。

accept() は、接続要求を待つシステムコールである。クライアントから接続 が来るまで、システムコールを実行したまま止まっているように見える。接続 要求が届くと、TCP/IP通信路の開設され、通信用ポートのファイル記述子が返 される。このファイル記述子は、クライアント側と同様に 標準入出力(0,1,2)や open() システム・コールの結果と同じもので、 ファイルに対する write() システムコールや read() システムコールの第一引数とし て使うことができる。つまり、write() システムコールを使うと、ネットワー クに対してデータを送り出すことができ、read() システムコールを使うとネッ トワークからデータを受け取ることができる。最後に不要になったら close() で解放する。

tcp_peeraddr_print() を呼び出し、通信相手(peer)のIPアドレスとポート番号 を表示する。

最後に、echo_reply() を呼び出し、その通信相手についての処理を行う。

echo-server-nofork-fdopen.c のecho_reply()

echo_reply() は、1つの通信相手のクライアントに対してechoサービスを提供 する。

  57:	#define BUFFERSIZE      1024
  58:	
  59:	void
  60:	echo_reply( int com )
  61:	{
  62:	    char line[BUFFERSIZE] ;
  63:	    int rcount ;
  64:	    int wcount ;
  65:	    FILE *in, *out ;
  66:	
  67:	        if( fdopen_sock(com,&in,&out) < 0 )
  68:	        {
  69:	            fprintf(stderr,"fdooen()\n");
  70:	            exit( 1 );
  71:	        }
  72:	        while( fgets(line,BUFFERSIZE,in) )
  73:	        {
  74:	            rcount = strlen( line );
  75:	            printf("[%d] received (fd==%d) %d bytes, [%s]\n",getpid(),com,rcount,line );
  76:	            fflush( stdout );
  77:	            fprintf(out,"%s",line );
  78:	        }
  79:	        printf("[%d] connection (fd==%d) closed.\n",getpid(),com );
  80:	        fclose( in );
  81:	        fclose( out );
  82:	}
  83:	

この echo_reply() は、特定のクライアント専用のecho サービスを提供する。 クライアントからの要求が続く限り、動作する。

このプログラムでは、 クライアント側 と同様に fdopen_sock() を使って、通信可能なファイル記 述子 com から2つの FILE * を作成している。1つは、入力用、1つは出力 用である。その結果、 高水準入出力ライブラリ を使って通信が行えるようになっている。fprintf() で出力用の FILE * に書 き込むと、ネットワークに対してデータが送り出される。入力用の FILE * に fgets() を行うと、ネットワークからデータを受け取ることができる。

クライアントからの要求は、fgets() で読込んでいる。それを、サーバ側の端 末に printf() で表示している。fflush() は、printf() の内部のバッファ (stdoutのバッファ)に溜っているデータを書き出すものである。

クライアントには、fprintf() で結果を送り返している。こちらは、 fdopen_sock() でバッファリングを抑止しているのでfflush() は不要になって いる。

print_my_host_port()

print_my_host_port() は、telnet で接続する時のヒントを表示する。

tcp_peeraddr_print()

tcp_peeraddr_print() は、通信相手(peer)のアドレス(TCP/IPの場合、IPアド レスとポート番号)を表示する。

sockaddr_print()

sockaddr_print() は、引数で与えられた struct sockaddr に含まれる IP アドレスとポート番号を画面に表示する関数である。

tcp_acc_port()

tcp_acc_port() は、 通信路の開設 の仕事のうち、サーバ側で接続要求受付用ポートを作る関数である。

fdopen_sock()

fdopen_sock() は、TCP/IP による通信を、fprintf(), fgets(), fread() 等で 行えるようにする関数である。 この関数は、 クライアント側 とまったく同じである。

echo-server-nofork-fdopenの実行例

サーバ側。 サーバは、終了しないので、最後に、^C を押して、割り込みを掛け て終了させる。

注意:全員がポート番号 1231 を使うとプログラムが動かないことがある。

% ls echo-server-nofork-fdopen.c [←]
ls: echo-server-nofork-fdopen.c: No such file or directory
% 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
% ./echo-server-nofork-fdopen 1231 [←]
run telnet azalea20 1231 
[2192] connection (fd==4) from 130.158.86.40:52013
[2192] received (fd==4) 5 bytes, [123
]
[2192] received (fd==4) 5 bytes, [456
]
[2192] received (fd==4) 5 bytes, [789
]
[2192] connection (fd==4) closed.
^C
% []
クライアント側
% telnet azalea20 1231 [←]
Trying 130.158.86.40...
Connected to azalea20.coins.tsukuba.ac.jp.
Escape character is '^]'.
123[←]
123
456[←]
456
789[←]
789
^]
telnet> quit
Connection closed.
% []

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

複数のクライアントに対してサービスの同時に提供することが望ましい。 echo-server-nofork-fdopen.c は、1つのクライアントからの接続されると、そのクライアントに掛り切りに なって、他のクライアントにはサービスを提供できないという問題がある。 標準の echo サーバ(ポート番号 7 で動作している) は、複数のクライアント から接続されれた場合、同時にサービスを提供することができる。

複数のクライアントに対してサービスの同時に提供するには次のような方法が ある。

Javaによるechoサーバ

HTTP

先週 説明した HTTP を復習しなさい。

http-server

以下のプログラムは、HTTP で要求を受け取り、 HTTで常に一定の内容を返すサーバである。 echoサーバ と似せて作ってあるので、まずは、echoサーバを読んで理解することを奨める。

http-server.c

http-server.c のmain()

   1:	
   2:	/*
   3:	        http-server-.c -- 常に同じ内容を返す HTTP サーバ(forkなし版)
   4:	        ~yas/syspro/ipc/http-server.c
   5:	        Created on: 2002/06/03 00:48:43
   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 http_server( int portno );
  18:	extern  void http_receive_request_and_send_reply( int com );
  19:	extern  int  http_receive_request( FILE *in );
  20:	extern  int  http_send_reply( FILE *out );
  21:	extern  int  http_send_reply_bad_request( FILE *out );
  22:	extern  void print_my_host_port_http( int portno );
  23:	extern  void tcp_peeraddr_print( int com );
  24:	extern  void sockaddr_print( struct sockaddr *addrp, socklen_t addr_len );
  25:	extern  tcp_acc_port( int portno );
  26:	extern  int fdopen_sock( int sock, FILE **inp, FILE **outp );
  27:	extern  char *fgets_chop(char *str, int size, FILE *stream);
  28:	
  29:	main( int argc, char *argv[] )
  30:	{
  31:	    int portno ;
  32:	        if( argc != 2 )
  33:	        {
  34:	            fprintf( stdout,"Usage: %s portno\n",argv[0] );
  35:	            exit( -1 );
  36:	        }
  37:	        portno = strtol( argv[1],0,10 );
  38:	        http_server( portno );
  39:	}
  40:	
main() は、引数として、ポート番号を取る。 文字列で受け取ったポート番号を strtol() で int へ変換している。 最後に、http_server() を呼ぶ。 echo-server-nofork-fdopen.cのmain()とほとんど同じ。

http-server.c のhttp_server()

http_server() は、HTTPサーバのメインループである。

  41:	void
  42:	http_server( int portno )
  43:	{
  44:	    int acc,com ;
  45:	    pid_t child_pid ;
  46:	        acc = tcp_acc_port( portno );
  47:	        if( acc<0 )
  48:	            exit( -1 );
  49:	        print_my_host_port_http( portno );
  50:	        while( 1 )
  51:	        {
  52:	            if( (com = accept( acc,0,0 )) < 0 )
  53:	            {
  54:	                perror("accept");
  55:	                exit( -1 );
  56:	            }
  57:	            tcp_peeraddr_print( com );
  58:	            http_receive_request_and_send_reply( com );
  59:	        }
  60:	}
  61:	

echo-server-nofork-fdopen.cのecho_server()とほとんど同じ。違いは、次の所だけ。

http-server.c の http_receive_request_and_send_reply()

http_receive_request_and_send_reply() は、 1つの通信相手のクライアントに対して HTTP により要求を受付け、 HTTPにより応答を返す。

  62:	#define BUFFERSIZE      1024
  63:	
  64:	void
  65:	http_receive_request_and_send_reply( int com )
  66:	{
  67:	    FILE *in, *out ;
  68:	    int err ;
  69:	        if( fdopen_sock(com,&in,&out) < 0 )
  70:	        {
  71:	            fprintf(stderr,"fdooen()\n");
  72:	            exit( 1 );
  73:	        }
  74:	        if( http_receive_request( in ) )
  75:	        {
  76:	            http_send_reply( out );
  77:	        }
  78:	        else
  79:	        {
  80:	            http_send_reply_bad_request( out );
  81:	        }
  82:	        fclose( in );
  83:	        fclose( out );
  84:	}
  85:	
この関数では、 echo-server-nofork-fdopen.cのecho_reply() と同様に、 fdopen_sock() を使って、通信可能なファイル記 述子 com から2つの FILE * を作成している。

http_receive_request() を呼び出し、HTTP で要求を受け取っている。 もしエラーがなければ、http_send_reply() を呼び出し、HTTP で OK の応答を送信する。 エラーが有れば、http_send_reply_bad_request() を呼び出し、エラーの 応答を送信する。

http-server.c の http_receive_request()

http_receive_request()は、HTTP の要求を解析し、画面に表示している。
  86:	int
  87:	http_receive_request( FILE *in )
  88:	{
  89:	    char requestline[BUFFERSIZE] ;
  90:	    char rheader[BUFFERSIZE] ;
  91:	        
  92:	        if( fgets_chop(requestline,BUFFERSIZE,in) <= 0 )
  93:	        {
  94:	            printf("No request line.\n");
  95:	            return( 0 );
  96:	        }
  97:	        printf("requestline is [%s]\n",requestline );
  98:	        if( strchr(requestline,'<') ||
  99:	            strstr(requestline,"..") )
 100:	        {
 101:	            printf("Dangerous request line found. exit.\n");
 102:	            return( 0 );
 103:	        }
 104:	
 105:	        while( fgets_chop(rheader,BUFFERSIZE,in) )
 106:	        {
 107:	            if( strcmp(rheader,"") == 0 )
 108:	                break;
 109:	            printf("Ignored: %s\n",rheader );
 110:	        }
 111:	        return( 1 );
 112:	}
 113:	

http_receive_request() は、まず、HTTPの最初の行(request line)を fgets_chop() で読み込む。fgets_chop() は、fgets() と同じだが、 行末の改行 \n\r を取り除く。

要求行の内容に対して簡単なエラーチェックをしている。次のような パタンが含まれるとエラーにしている。

最初の要求行に続き、オプションの行を読み出している。 オプションの最後は、空行で終わる。 空行が来るまで、オプションの行を読み出す。

このプログラムでは、オプションは全て無視している。 しかし、HTTP の要求で送られてきたものは、必ず全部読み出す必要がある。

http-server.c の http_send_reply()

http_send_reply()は、HTTP の応答メッセージを File *out に出力している。
 114:	int
 115:	http_send_reply( FILE *out )
 116:	{
 117:	        fprintf(out,"HTTP/1.0 200 OK\r\nContent-Type: text/html\r\n\r\n");
 118:	        fprintf(out,"<html><head></head><body>hello.</body></html>\n");
 119:	}
 120:	

http_send_reply() は、HTTPのヘッダと本体を出力している。 ヘッダは、status line に Content-Typeが続く。 空行以降が本体である。

本体では、HTMLの文書を出力している。

http-server.c の http_send_reply_bad_request()

http_send_reply_bad_request()は、 HTTP の応答メッセージ(エラー)を File *out に出力している。
 121:	int
 122:	http_send_reply_bad_request( FILE *out )
 123:	{
 124:	        fprintf(out,"HTTP/1.0 400 Bad Request\r\nContent-Type: text/html\r\n\r\n");
 125:	        fprintf(out,"<html><head></head><body>400 Bad Request</body></html>\n");
 126:	}
 127:	

http_send_reply_bad_request() は、HTTPのヘッダと本体を出力している。 ヘッダは、status line (エラー)に Content-Typeが続く。 空行以降が本体である。

本体では、HTMLの文書を出力している。

http-server.c の print_my_host_port_http()

echo-server-nofork-fdopen.cのprint_my_host_port() と同様に、画面にサーバのホスト名 とポート番号を表示している。ただし、Web ブラウザでアクセスする時に便利 なように、URL の形をしている。

http-server.c のtcp_peeraddr_print()

echo-server-nofork-fdopen.cのtcp_peeraddr_print()と同じ。

http-server.c のsockaddr_print()

echo-server-nofork-fdopen.cのsockaddr_print()と同じ。

http-server.c のtcp_acc_port()

echo-server-nofork-fdopen.cのtcp_acc_port()と同じ。

http-server.c のfdopen_sock()

echo-server-nofork-fdopen.cのfdopen_sock()と同じ。

fgets_chop()

fgets_chop() は、fgets() と似ているが、行末の \n\r を削除する。

http-serverの実行例

サーバ側。 サーバは、終了しないので、最後に、^C を押して、割り込みを掛け て終了させる。

注意:全員がポート番号 1231 を使うとプログラムが動かないことがある。

% ls http-server.c [←]
ls: http-server.c: No such file or directory
% cp ~yas/syspro/ipc/http-server.c . [←]
% make http-server  [←]
cc     http-server.c   -o http-server
% ./http-server  [←]
Usage: ./http-server portno
% ./http-server 1231 [←]
open http://azalea20:1231/index.html
[2595] connection (fd==4) from 130.158.83.140:51617
requestline is [GET /index.html HTTP/1.1]
Ignored: Host: azalea20.coins.tsukuba.ac.jp:1231
Ignored: User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; ja-JP-mac; rv:1.9.0.10) Gecko/2009042315 Firefox/3.0.10
Ignored: Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Ignored: Accept-Language: ja,en;q=0.7,en-us;q=0.3
Ignored: Accept-Encoding: gzip,deflate
Ignored: Accept-Charset: ISO-2022-JP,utf-8;q=0.7,*;q=0.7
Ignored: Keep-Alive: 300
Ignored: Connection: keep-alive
[2595] connection (fd==4) from 130.158.83.140:51618
requestline is [GET /favicon.ico HTTP/1.1]
Ignored: Host: azalea20.coins.tsukuba.ac.jp:1231
Ignored: User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; ja-JP-mac; rv:1.9.0.10) Gecko/2009042315 Firefox/3.0.10
Ignored: Accept: image/png,image/*;q=0.8,*/*;q=0.5
Ignored: Accept-Language: ja,en;q=0.7,en-us;q=0.3
Ignored: Accept-Encoding: gzip,deflate
Ignored: Accept-Charset: ISO-2022-JP,utf-8;q=0.7,*;q=0.7
Ignored: Keep-Alive: 300
Ignored: Connection: keep-alive
[2595] connection (fd==4) from 130.158.83.140:51619
requestline is [GET /favicon.ico HTTP/1.1]
Ignored: Host: azalea20.coins.tsukuba.ac.jp:1231
Ignored: User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; ja-JP-mac; rv:1.9.0.10) Gecko/2009042315 Firefox/3.0.10
Ignored: Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Ignored: Accept-Language: ja,en;q=0.7,en-us;q=0.3
Ignored: Accept-Encoding: gzip,deflate
Ignored: Accept-Charset: ISO-2022-JP,utf-8;q=0.7,*;q=0.7
Ignored: Keep-Alive: 300
Ignored: Connection: keep-alive
^C
% []
クライアント側
% open http://azalea20:1231/index.html [←]
Firefox で "http://azalea20:1231/index.html" を開く。

練習問題

練習問題(701) echo-serverのコンパイルと実行

echo-server.c を、コンパイルして実行しなさい。 次のクライアントでアクセスしなさい。

練習問題(702) http-serverのコンパイルと実行

http-server.c を、コンパイルして実行しなさい。 次のクライアントでアクセスしなさい。 画面に現れる次の部分に着目しなさい。

練習問題(703) HTTPサーバの応答(テキスト版)

コマンドラインからファイル名を受け取り、画面に対して HTTPの応答を返すプ ログラムを作成しなさい。
% ./http-response /index.html [←]
HTTP/1.0 200 OK
Content-Type: text/html

<HTML>
中略
</HTML>
% []
この課題では、セキュリティに気をつけなさい。必ず次の条件を満たすように しなさい。 次のようなプログラムを作成すればよい。
  1. main() の引数 argv[1] をファイル名とする。
  2. 1. のファイル名と ~/public_html からファイル名を作成する。 snprinf() を用いる。(strcat() は用いないこと。)
  3. ファイルを fopen() で開く。成功すれば、次の処理を行う。
    1. "HTTP/1.0 200 OK\r\n" の行を fprintf() で stdout に出力する。
    2. Content-Type: の行を fprintf() で stdout に出力する。
    3. 空行を fprintf() で stdout に出力する。
    4. ファイルの内容をを fread() し、stdout に対して fwrite() で出力する。 ファイルの末尾まで繰り返す。
    5. fclose() でファイルを閉じる。
  4. ファイルがなければ、"HTTP/1.0 200 OK<以下省略>" のエラー メッセージを出力する。
エラーが生じた時、Apache は、Content-Type: text/html で HTML を出力して いる。これを Web ブラウザや telnet コマンドで確認しなさい。そして、それ と類似の結果を stdout に出力しなさい。

なお、この課題は、HTTP の応答を画面(stdout)に出力するが、ネットワーク通 信を一切行わない。

練習問題(704) HTTPサーバの応答(バイナリ版)

練習問題(703) を拡張して、 バイナリ・ファイルを扱えるようにしなさい。 次の型(Content-Type:)を扱えるようにしなさい。
Content-Type: 拡張子
text/html .html
text/plain .txt
image/gif .gif
image/jpeg .jpeg
image/png .png

練習問題(705) HTTPサーバの要求解析

標準入力から HTTP の GET 要求を受け取り、要求行からファイル名を取り出す プログラムを作成しなさい。
% cat > reqest.data [←]
GET /index.html HTTP/1.0[←]
Accept: image/png,*/*;q=0.5[←]
Accept-Language: ja,en;q=0.7,en-us;q=0.3[←]
[←]
^D
% ./http-request-analyze < reqest.data [←]
/index.html
% []
このプログラムは、次のような動作を行う。
  1. stdin から fgets() で 1 行読み込む。行末の \r や \n や \r\n を削除する。 fgets_chop()を使うと便利である。
  2. 最初の行を保存する。
  3. 2行目以降、空行が出てくるまで読み捨てる。
  4. 空行が出てくれば、ループを抜け、保存してある最初の行を解析する。
クライアントは、不正な要求行を送ってくる可能性がある。 そのような場合には、エラーにしなさい。 たとえば、次のようなデータを送ってくる可能性がある。 このような場合も、エラーとして扱い、プログラムがクラッシュしたり、バッ ファ・オーバーフローを起こしたりすることがないようにしなさい。

なお、この課題は、HTTP の要求を標準入力(stdin)から読むが、ネットワーク 通信を一切行わない。

この課題を行う場合、次のファイルに含まれる関数を利用してもよい。

このファイルには、次のような関数が含まれている。
int string_split( char *str, char del, int *countp, char ***vecp )
文字列 str を、区切り文字 del で分解する。 結果を、countp と vecp に返す。 ここで、count と vec は、 main() の引数の argc, argv と同じ形式になっている。
void free_string_vector( int count, char **vec )
string_split() で得た vec のメモリを解放する。
これらの関数は、次のようにして使う。
    int count;
    char **vec;
    int i ;
        str = "GET /index.html HTTP/1.0";
	if( string_split( str, ' ', &count, &vec ) < 0 )
	{
	    perror("string_split-malloc");
	    exit( 1 );
	}
	for( i=0 ; i< count; i++ )
	    printf("%d: %s\n", i, vec[i] );
	free_string_vector( count, vec );

練習問題(706) HTTPサーバ

まず、 練習問題(704) を実施しなさい。 次に、 練習問題(705) を実施しなさい。 そして、その時作成した関数を用いて、HTTP サーバを作成しなさい。 この課題を行う場合、http-server.c に含まれる関数を利用してもよい。

実際の WWW ブラウザ (Firefoxなど)で、動作を確認しなさい。telnet だけで は、きちんと HTTP のプロトコルに従っているか確認できないので、不十分で ある。

練習問題(707) IPアドレスによるアクセス制御が可能なHTTPサーバ

練習問題(706) で、学類内の計算機からの接続要求だけを受け付けるようにしなさい。 たとえば、クライアントのIPアドレスを getpeername() で調べ、 130.158.86.0-130.158.87.255 の範囲についてのみアクセスを許可するように することが考えられる。

練習問題(708) ドメイン名によるアクセス制御が可能なHTTPサーバ

練習問題(707) で、ドメイン名 coins.tsukuba.ac.jp を持つホストからのアクセスだけを許す ようにしなさい。

練習問題(709) カレンダを表示するHTTPサーバ

要求された月のカレンダーを表示するような HTTP サーバを作成しなさい。 要求の与え方とては、次のような方法が考えられる。
GET /?month=6&year=2009 HTTP/1.0[←]
[←]
カレンダの出力については、cal コマンドを実行した結果を用いても良い。こ れに対して、HTTP で結果を返す。Content-Type: としては、text/plain に するか、text/html で <PRE></PRE>を使う方法が 考えられる。その他の手法でもよい。

このサーバを実行するための HTML の form を用意しなさい。

実際の WWW ブラウザ (Firefoxなど)で、動作を確認しなさい。telnet だけで は、きちんと HTTP のプロトコルに従っているか確認できないので、不十分で ある。

練習問題(710) CGIが実行可能なHTTPサーバ

練習問題(706) で、CGI により外部のプログラムを実行することができるようにしなさい。拡 張子が .cgi のプログラムを CGI と見なし、プログラムを実行し、そ の結果をクライアントに返しなさい。

実際の WWW ブラウザ (Firefoxなど)で、動作を確認しなさい。telnet だけで は、きちんと HTTP のプロトコルに従っているか確認できないので、不十分で ある。

練習問題(711) spipe-last

クライアントから送られてくるデータを、コマンドの標準入力に接続して実行 するプログラムを作りなさい。たとえば、次のようなコマンドを実行すること を考える。

% ./spipe-last 1231 head -5 [←]
<クライアントから送られてきた文字列のうち、先頭の5行だけがサーバの画面に表示される>
% []
spipe-last は、指定されたポート番号で tcp_acc_port() を行う。 クライアントからの接続要求を受け付けた後、不要なポートを閉じ、標準出力を切 り替えて、コマンドラインから指定されたコマンドを実行する。 すなわち、dup(), dup2(), close() などで、標準出力を切り替えて、 execve() などで、プログラムを実行する。 コマンドを実行したらそのまま終了する。

クライアントとしては、telnet を用いてもよい。 または、以下の spipe-first を用いることもできる。

この課題では、fork() を行わないことを推奨する。 TCP/IP なら本来双方向で使えるが、この練習問題では単方向だけを 使うことで、パイプの機能を代替する。

また、fork() とパイプを使う実現方法もある。コマンドを実行するする時に fork() して実行し、その標準入力をパイプにして、TCP/IP で受け取ったデー タをパイプに書き込んでもよい。

練習問題(712) spipe-first

練習問題(711) spipe-last のクライアントを作成しなさい。 このクライアントは、引数で指定されたホスト名とポート番号のサーバに接続 する。そして、引数で指定されたコマンドを実行する。 たとえば、次のように実行された場合、spipe-first は、ls -l の結果を画面 に表示される代わりにサーバに送られるようにする。
% ./spipe-first hostname port ls -l [←]
spipe-first は、指定されたホスト名とポート番号でtcp_connect() を行う。 不要なポートを閉じ、標準出力を切り替えた後、指定されたコマンドを実行す る。すなわち、dup(), dup2(), close() などで、標準入出力を切り替えて、 execve() などで、プログラムを実行する。 コマンドを実行したらそのまま終了する。

この課題では、spipe-last と同様に、fork() を行わないことを推奨する。

練習問題(713) spipe-middle

練習問題(711) spipe-lastサーバと 練習問題(712) spipe-firstクライアントの 間に入るプロキシ spipe-middle を作成しなさい。 このプロキシは、次のような引数をとる。
% ./spipe-middle myport serverhost serverport sort +4 [←]
spipe-middle は、第2引数と第3引数で指定されたサーバにtcp_connect() で接 続要求を行う。 tcp_acc_port() により、第1引数で指定された ポート番号で接続受付用ポートを作成する。 spipe-first から接続要求を受け取ると、 不要なポートを閉じ、標準入出力を切り替える。そして、第4引数以降で指定さ れたコマンドを実行する。

練習問題(714) spipeでのクライアントとサーバの入れ替え

練習問題(711) spipe-last練習問題(712) spipe-firstで、 TCP/IPのクライアントとサーバを入れ替えてみなさい。 練習問題(713) spipe-middleも クライアントとサーバを入れ替えなさい。

練習問題(715) fingerサーバ

finger サーバを作成しなさい。これは、受け取った文字列を引数にして、 finger コマンドを実行するとよい。そして、実行結果を、接続先に返すよう にするとよい。

finger コマンドのプロセスと finger サーバの間は、パイプで接続しなさい。 今までの例題で利用した方法を組み合わせることで実現することができるはず である(pipe(),fork(),dup(),close(),execve()など)。popen() ライブラリ 関数を利用してもよい。

finger コマンドのプロセスと finger サーバ間をパイプで結ぶ代わりに、 finger コマンドを exec する前に、配管工事をして、標準出力(1)をTCP/IPの ストリームに接続する方法もある。

受け取った文字列をそのまま popen() や system() などに渡すのは危険である。 というのも、これらの引数は、シェル(/bin/sh)が解釈するからである。 popen() や system() を使う場合には、 finger コ マンドが受付けるのに相応しいもの( isalpha() や isdigit() の並び)かどう かを検査しなさい。もし、シェルが解釈する特殊な文字列(|;<> など)が含まれていると、意図しないプログラムが実行させられることがある。

fork() や execve() を用いて直接 /usr/bin/finger を実行する方法はシェル を経由しないので、比較的安全である。

getpwnam() ライブラリ関数や utmp ファイルを用いて、finger コマンドと似たような 動きを実現してもよい。

練習問題(716) HTTP Proxy

HTTP proxy 作りなさい。次の機能を、1つ以上付けなさい。

練習問題(717) サーバ自由課題

その他、HTTPサーバ、または、 HTTP Proxy と同程度以上の複 雑さを持つサーバを作成しなさい。

練習問題(718) IPアドレスをホスト名に逆変換する

上の tcp_peeraddr_print() は、IP アドレスを数字で表示していた。 これを、ホスト名で表示するようにしなさい。

練習問題(719) SO_REUSEADDR

サーバを実行すると、次のようなエラーが出ることがある。
% ./echo-server-fork 1231 [←]
bind: 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);
この効果を確かめなさい。

また、同じポート番号の再利用を制限している理由を考えなさい。

練習問題(720) utmpの内容を表示するHTTPサーバ

utmpの内容を表示するような HTTP サーバを作成しなさい。 HTTPにより要求を受け取り、結果をHTMLの<TABLE>で表現して HTTPで返しなさい。

実際の WWW ブラウザ (Firefoxなど)で、動作を確認しなさい。telnet だけで は、きちんと HTTP のプロトコルに従っているか確認できないので、不十分で ある。

練習問題(721) ディレクトリの内容を表示するHTTPサーバ

opendir() ライブラリ関数を用いて、ディレクトリの内容を表示するような HTTP サーバを作成しなさい。HTTPにより要求を受け取り、結果をHTMLで表現し てHTTPの応答として返しなさい。ディレクトリの内部に子供のディレクトリが 含まれていた場合、それをハイパーリンクとして表現して返し、その子供のディ レクトリについても表示できるようにしなさい。

実際の WWW ブラウザ (Firefoxなど)で、動作を確認しなさい。telnet だけで は、きちんと HTTP のプロトコルに従っているか確認できないので、不十分で ある。


Last updated: 2009/06/16 07:41:44
Yasushi Shinjo / <yas@is.tsukuba.ac.jp>