プロセス間通信、TCP/IP(3)

システム・プログラムI

                                       電子・情報工学系
                                       新城 靖
                                       <yas@is.tsukuba.ac.jp>

このページは、次の URL にあります。
http://www.hlla.is.tsukuba.ac.jp/~yas/coins/syspro1-1998/1998-06-16
あるいは、次のページから手繰っていくこともできます。
http://www.hlla.is.tsukuba.ac.jp/~yas/coins/
http://www.hlla.is.tsukuba.ac.jp/~yas/index-j.html

■復習

ファイル記述子には、read(), write()。 getchar(), putchar() は、0, 1 と関係。 stderr (2) は、使える。

printf(), fprintf() では、出力先が画面なら、"\n" が現れるまでバッファ リングする(line buffered, _IOLBF)。バッファリングを抑止したい時には、 その都度必要な時に fflush() を呼ぶか、setvbuf(), setbuf() などで、バッ ファリングをしない(unbuffered)ように設定する。

■HTTP Proxy

HTTP のレベルで、クライアントとサーバの間を中継するプログラムを、HTTP Proxy と呼ぶ。HTTP Proxy は、クライアントから見ると、サーバに見え、サー バから見ると、クライアントに見える。

HTTP Proxy は、次のような目的に使われる。

■echo-server-select

HM_SubRef(../1998-06-02/index.html,echo-server-fork,echo-server-fork.c) では、クライアントから接続要求を受け付けるたびに、新しいプロセスを作っ ていた。以下の echo-server-select.cでは、1つのプロセスで実行し ている。

----------------------------------------------------------------------
:	
   2:	/*
   3:	        echo-server-select.c -- 受け取った文字列をそのまま返すサーバ(select版)
   4:	        ~yas/syspro1/ipc/echo-server-select.c
   5:	        $Header: /home/lab2/OS/yas/syspro1/ipc/RCS/echo-server-select.c,v 1.2 1998/06/15 11:37:19 yas Exp $
   6:	        Start: 1997/06/09 19:53:33
   7:	*/
   8:	#include <stdio.h>
   9:	#include <sys/types.h>  /* socket(), time(), select() */
  10:	#include <sys/socket.h> /* socket() */
  11:	#include <netinet/in.h> /* struct sockaddr_in */
  12:	#include <unistd.h>     /* select() */
  13:	#include <bstring.h>    /* select() */
  14:	#include <sys/time.h>   /* select() */
  15:	
  16:	extern  void echo_server( int portno );
  17:	extern  void echo_reply_select( int com );
  18:	extern  void print_host_port( int portno );
  19:	extern  void tcp_peeraddr_print( int com );
  20:	extern  tcp_acc_port( int portno );
  21:	extern  int writen( int fd, char *buf, int nbytes );
  22:	
  23:	main( int argc, char *argv[] )
  24:	{
  25:	    int portno ;
  26:	        if( argc >= 3 )
  27:	        {
  28:	            fprintf( stdout,"Usage: %s host port\n",argv[0] );
  29:	            exit( -1 );
  30:	        }
  31:	        if( argc == 2 )
  32:	            portno = atoi( argv[1] );
  33:	        else
  34:	            portno = getuid();
  35:	        echo_reply_select( portno );
  36:	}
  37:	
  38:	void echo_reply_select( int portno )
  39:	{
  40:	    int acc,com ;
  41:	    fd_set readfds,readfds_save ;
  42:	    int i,n ;
  43:	
  44:	        acc = tcp_acc_port( portno );
  45:	        if( acc<0 )
  46:	            exit( -1 );
  47:	        print_host_port( portno );
  48:	
  49:	        FD_ZERO( &readfds_save );
  50:	        FD_SET( acc,&readfds_save );
  51:	        while( 1 )
  52:	        {
  53:	            readfds = readfds_save ;
  54:	            n = select( FD_SETSIZE,&readfds,0,0,0 );
  55:	            if( n <= 0 )
  56:	            {
  57:	                perror("select");
  58:	                exit( 1 );
  59:	            }
  60:	            if( FD_ISSET(acc,&readfds) )
  61:	            {
  62:	                FD_CLR( acc,&readfds );
  63:	                if( (com = accept( acc,0,0 )) < 0 )
  64:	                {
  65:	                    perror("accept");
  66:	                    exit( -1 );
  67:	                }
  68:	                FD_SET( com, &readfds_save );
  69:	                tcp_peeraddr_print( com );
  70:	            }
  71:	            for( i=0 ; i<FD_SETSIZE ; i++ )
  72:	            {
  73:	                if( FD_ISSET(i,&readfds) )
  74:	                {
  75:	                    if( echo_reply_once( i )<=0 )
  76:	                    {
  77:	                        printf("[%d,%d] connection closed.\n",getpid(),i );
  78:	                        close( i );
  79:	                        FD_CLR( i,&readfds_save );
  80:	                    }
  81:	                }
  82:	            }
  83:	        }
  84:	}
  85:	
  86:	int echo_reply_once( int com )
  87:	{
  88:	    char buff[BUFSIZ] ;
  89:	    int rcount ;
  90:	    int wcount ;
  91:	
  92:	        if( (rcount=read(com,buff,BUFSIZ)) > 0 )
  93:	        {
  94:	            if( (wcount=writen(com,buff,rcount))!= rcount )
  95:	            {
  96:	                 perror("write");
  97:	                 exit( 1 );
  98:	            }
  99:	            printf("[%d,%d] ",getpid(),com );
 100:	            fflush( stdout );
 101:	            write( 1, buff, rcount );
 102:	        }
 103:	        return( rcount );
 104:	}
<以下省略>
----------------------------------------------------------------------

実行例。

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


----------------------------------------------------------------------
% ./echo-server-select [←]
run telnet adonis1 1231 
[1701,4] connection from 130.158.86.1:20822
[1701,4] 012
[1701,5] connection from 130.158.86.11:1479
[1701,5] abc
[1701,5] def
[1701,5] connection closed.
[1701,4] 345
[1701,4] connection closed.
^C
% []
----------------------------------------------------------------------
クライアント側(その1)。
----------------------------------------------------------------------
Trying 130.158.86.1...
Connected to adonis1.
Escape character is '^]'.
012[←]
012
345[←]
345
^]
telnet> quit[←]
Connection closed.
% []
----------------------------------------------------------------------
クライアント側(その2)。
----------------------------------------------------------------------
% telnet adonis1 1231 [←]
Trying 130.158.86.1...
Connected to adonis1.
Escape character is '^]'.
abc[←]
abc
def[←]
def
^]
telnet> quit[←]
Connection closed.
% []
----------------------------------------------------------------------

■tcp_wrapper

インターネットのセキュリティを高めるための道具として、TCP Wrapper があ る。TCP Wrapper は、クライアントの IP アドレスを調べて、接続を許可した り拒否したりする機能がある。そのような機能を持っていないプログラムに、 そのような機能を外付けで付け加えることができる。

次の場所にソースを置いておく。

~yas/syspro1/tcp_wrappers_7.6/ (~yas/syspro1/archives/tcp_wrappers_7.6.tar.gz)

■sized_io(initport())

TCP/IP のプログラムを書く時には、 sized_io ライブラリを利用すると便利である。 stream.c は、短いので目を通しておくとよい。

次の場所にソースを置いておく。

~yas/syspro1/sized_io/ (~yas/syspro1/archives/sized_io.tar.gz)

■ソフトウェア割込みとシグナル

普通の割込み(ハードウェアによる割込み)は、オペレーティング・システム のカーネルにおいて利用されている。

ソフトウェア割込みとは、本来はオペレーティング・システムのカーネルしか 使えない割込みの機能を、ソフトウェアにより実現して、一般の利用者プログ ラム(プロセス)でも使えるようにしたものである。

UNIXでは、ソフトウェア割込みの機能は、シグナル(signal)という名前で実現 されている。シグナルとは、本来はプロセス間通信の一種で、ある事象が起き たことを他のプロセスに知らせることである。ここで伝わるのは、ある事象が 起きたかどうかだけで、引数などを付けることはできない。UNIXでは、プロセ ス間でシグナルにより通信をする他に、キーボードからシグナルを送ることも できる。これは、「ソフトウェア割込み」として、プロセス1つひとつに割込 みボタンが付いているようなものである。また、プログラムの中で例外 (exception)が起きた時にも、ハードウェアの割込みと同様に、ソフトウェ ア割込みが生じる。これも、他のシグナルと同じように受け取ることができる。

UNIXのソフトウェア割込み(シグナル)を使うには、次のようなことが必要で ある。

割り込みハンドラが設定されていない時には、プロセスは、デフォルトの動き をする。デフォルトには、次の3つがある。 signal の種類は、man 5 signal (SGI IRIX) 。

ソフトウェア割り込みは、1つのプログラムの中に制御の流れが1つしかない ようなプログラムの時に有効な方法である。最近のマルチスレッドのプログラ ムでは、シグナルの意味が不明確である。


----------------------------------------------------------------------
   1:	/*
   2:	        signal-int.c -- SIGINT を3回受け付けて終了するプログラム。
   3:	        ~yas/syspro1/proc/proc/signal-int.c
   4:	        $Header: /home/lab2/OS/yas/syspro1/proc/RCS/signal-int.c,v 1.3 1998/06/15 12:24:36 yas Exp $
   5:	        Start: 1997/05/26 18:38:38
   6:	*/
   7:	
   8:	#include <stdio.h>
   9:	#include <signal.h>
  10:	
  11:	int sigint_count = 3 ;
  12:	void sigint_handler();
  13:	
  14:	main()
  15:	{
  16:	        signal( SIGINT, &sigint_handler );
  17:	        printf("main(): going into infinite loop, sigint_count == %d\n", 
  18:	                sigint_count);
  19:	        while( 1 )
  20:	        {
  21:	            printf("main(): sigint_count == %d, pause() ....\n",
  22:	                    sigint_count );
  23:	            pause();
  24:	            printf("main(): return from pause().  sigint_count == %d\n",
  25:	                    sigint_count );
  26:	        }
  27:	}
  28:	
  29:	void sigint_handler()
  30:	{
  31:	        printf("sigint_handler():\n");
  32:	        if( -- sigint_count <= 0 )
  33:	        {
  34:	            printf("sigint_handler(): exit() ... \n");
  35:	            exit( 1 );
  36:	        }
  37:	        signal( SIGINT, &sigint_handler ); /* System V */
  38:	        printf("sigint_handler(): sigint_count == %d\n",sigint_count);
  39:	}
----------------------------------------------------------------------

実行例。

----------------------------------------------------------------------
% stty -a [←]
speed 9600 baud; line = 1; 48 rows; 80 columns
intr = ^C; quit = ^\; erase = ^H; kill = ^U; eof = ^D; eol = ^@; old-swtch = ^Z; susp = ^Z
lnext = ^V; werase = ^W; rprnt = ^R; flush = ^O; stop = ^S; start = ^Q; dsusp = ^Y
parenb -parodd cs8 -cstopb hupcl cread -clocal -cnew_rtscts -loblk 
-ignbrk -brkint -ignpar -parmrk -inpck -istrip -inlcr -igncr icrnl -iuclc 
ixon -ixany -ixoff -imaxbel 
isig icanon iexten -xcase echo echoe echok echoke echoctl -echoprt -echonl -noflsh -flusho -pendin -tostop 
opost -olcuc onlcr -ocrnl -onocr -onlret -ofill -ofdel 
% ./signal-int [←]
main(): going into infinite loop, sigint_count == 3
main(): sigint_count == 3, pause() ....
^C
sigint_handler():
sigint_handler(): sigint_count == 2
main(): return from pause().  sigint_count == 2
main(): sigint_count == 2, pause() ....
^C
sigint_handler():
sigint_handler(): sigint_count == 1
main(): return from pause().  sigint_count == 1
main(): sigint_count == 1, pause() ....
^C
sigint_handler():
sigint_handler(): exit() ... 
% []
----------------------------------------------------------------------
stty で intr に相当するキーを3回押す。

■setjmp() と longjmp()

関数間でも有効な goto がある。

割り込み処理やエラー回復に便利である。しかし、マルチスレッドの時代には、 少し合わない。回復できない例外処理に相当する。C++やJavaの例外の 扱いに大きな影響を与えている。


----------------------------------------------------------------------
   1:	/*
   2:	        setjmp-longjmp.c -- setjmp() と longjmp() のテスト
   3:	        ~yas/syspro1/proc/setjmp-longjmp.c
   4:	        $Header: /home/lab2/OS/yas/syspro1/proc/RCS/setjmp-longjmp.c,v 1.2 1998/06/15 12:33:57 yas Exp $
   5:	        Start: 1997/05/26 20:26:27
   6:	*/
   7:	
   8:	#include <setjmp.h>
   9:	
  10:	extern  void f(void);
  11:	extern  void g(void);
  12:	extern  void h(void);
  13:	
  14:	jmp_buf main_env ;
  15:	
  16:	main()
  17:	{
  18:	    int x ;
  19:	        if( (x=setjmp(main_env)) == 0 )
  20:	        {
  21:	            printf("setjmp(), first time. x == %d\n",x );
  22:	            f();
  23:	        }
  24:	        else
  25:	        {
  26:	            printf("return from longjmp(), x == %d \n",x );
  27:	        }
  28:	}
  29:	
  30:	void f()
  31:	{
  32:	        printf("f() called.\n");
  33:	        g();
  34:	        printf("f() return.\n");
  35:	}
  36:	
  37:	void g()
  38:	{
  39:	        printf("g() called.\n");
  40:	        h();
  41:	        printf("h() return.\n");
  42:	}
  43:	
  44:	void h()
  45:	{
  46:	        printf("h() called.\n");
  47:	        printf("longjmp( main_env,10 )\n");
  48:	        longjmp( main_env,10 );
  49:	        printf("h() return.\n");
  50:	}
----------------------------------------------------------------------

実行例。

----------------------------------------------------------------------
% ./setjmp-longjmp [←]
setjmp(), first time. x == 0
f() called.
g() called.
h() called.
longjmp( main_env,10 )
return from longjmp(), x == 10 
% []
----------------------------------------------------------------------

■練習問題と課題

★練習問題56 fingerサーバ

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

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

★練習問題57 httpサーバ

http サーバを作成しなさい。これは、受け取ったファイル名のファイルを開 いて、内容を読み込み、それを接続先に返すようにするとよい。

この課題では、特にセキュリティに気をつけなさい。必ず次の条件を満たすよ うなサーバを作りなさい。

★練習問題58 HTTP Proxy

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

★練習問題59 kill コマンドによるシグナルの発生

signal-int.c のプロ グラムを動かし、kill コマンドを使って SIGINT (kill -INT)でシグナルを送 りなさい。そして、キーボードから intr のキー^Cを打った時と動 作を比較しなさい。

ヒント:kterm を2つ開いて、片方でこのプログラムを動かし、片方で kill コマンドを動かす。kill 引数として必要な PID は、ps コマンドで調べる。

★練習問題60 killコマンド

kill コマンドと似た動きをするプログラムを作りなさい。

★練習問題61 ^Cで死なないプロセス

SIGINT シグナルを発生させても終了しないプロセスを 作りなさい。

ヒント:そのシグナルを無視するように設定する。

★練習問題62 割込み処理中の割込み

割込み処理の最中に、同じ割込みが発生した時にはどうなるか。

★練習問題63 SIGCHLD

★練習問題50 ゾンビの消去 」 では、ゾンビを消すために、「子プロセスが終了したときだけ、 waitして、まだ実行中の時には、終了を待たない」という方法を使った。 この練習問題では、これをシグナル SIGCHLD を使って 子プロセスの終了を検知するように変更しなさい。

★練習問題64 BSD系のUNIXのシグナルのあつかい

SGI IRIX では、次のようにコンパイル時に _BSD_SIGNALS というマクロを定 義すると BSD 系の UNIX のシグナル機能が使える。
	cc -D_BSD_SIGNALS -o prog prog.c
BSD 系では、一度登録したシグナルは、ハンドラの中で再登録する必要はない。 このことを調べなさい。

また、シグナル・ハンドラの中でシグナルを受けた時の動きを調べなさい。

くわしくは、man 3b signal 。

★練習問題65 Javaの例外

setjmp-longjmp.c と同じような 動きをするプログラムを Java の例外機能を使って書きなさい。

★[syspro1/report6] (1998/06/16提出、1998/07/07)

次の練習問題を1つ以上やりなさい。問題を難しい方に変更してもよい。 締切は、1998年7月7日火曜日とする。当日の日付有効。提出方法は、今までと 同じ電子メール。 (加藤先生のレポートの締切と重なっていたので、こちらを遅くしました。)

■課題提出方法


↑[もどる] [課題提出方法] ←[6月9日] ・[6月16日] →[6月23日]
[sized_oi/stream/initport]
Last updated: 1998/06/30 10:23:07
Yasushi Shinjo / <yas@is.tsukuba.ac.jp>