プロセス間通信、TCP/IP

システム・プログラムI

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

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

■復習

■socketpair() による双方向通信

■TCP/IP

◆ストリーム

TCP/IPは、信頼性のある(reliable)双方向のストリーム転送サービス (stream transport service)を提供する通信プロトコルである(図1)。ス トリームとは、通信する2つのプロセス間に結合(connection,通信路)が形成 され、複数回に分けて送り出したデータでも順番が入れ替わらないがデータの 区切りがわからなくなるような転送サービスである。UNIXのパイプは、双方向 ではなく単方向であるが、同じストリームに分類される転送サービスを提供す るものである。

図1 TCP/IPによりより提供されるストリーム
図1 TCP/IPによりより提供されるストリーム

なお、C言語のライブラリ関数である fopen(), fgets(), fputs() なども、 ストリームと呼ばれることがある。これは、もともとランダム・アクセス可能 で、メモリ中の配列と同じようにアクセスするすることもできるファイルを、 まるでプロセス間通信のストリームと同じように扱うことができることに由来 する。ストリームの元の意味は、プロセス間通信である。

◆層(プロトコル・スタック)

TCP/IPによる通信では、図2に示すように、4つのプロトコル(規約、約束事) の層が使われる。TCP/IP自身は、TCP層と IP層という2つのプロトコルに分解 される。このようにさまざまなプロトコルが決められ、全体として層をなして いる。この様子を、プロトコル・スタックと呼ぶ。

図2 TCP/IPにおけるプロトコル・スタック

図2 TCP/IPにおけるプロトコル・スタック

TCP は、IP という通信プロトコルを利用して実現されている。IPは、(信頼 性がない)データグラム(datagram)転送サービスを提供する通信プロトコル である。データグラムでは、データの送り手と受けての間に結合(通信路)が 形成されず、送出したデータの順番が途中で変ることや送出したデータが失わ れることがある。データグラムは、書留めではない郵便に似ている。IPのデー タグラムが配達されるときに使われる番号が、IPアドレスである。IPアドレス としては、現在32ビットの整数が使われている。

TCP層の上には、応用層が定義されている。この層では、ftp, rlogin, WWW, mnews,sendmail などの、TCP/IP を利用するプログラムの間の会話の方法が定 義される。TCP/IPを使った通信は、まるでプロセス同士が電話で会話するよう に進められる。普通の電話では、日本語を話す人と英語を話す人は、電話で情 報交換を行うことができない。同様に、同じTCP/IPを使っていても、会話の方 法が違うと、まったく情報交換を行うことができない。ゆえに、TCP/IPの上に さらに、情報交換のためにさまざまなプロトコルが取り決められている。

TCP/IPの上に構築されているプロトコルの例を、表1に示す。ポート番号につ いては、後述する。

表1 TCP/IPの上に構築されているプロトコルの例

--------------------------------------------------------------------
ポート番号	プロトコルの名前	目的
--------------------------------------------------------------------
21	FTP(File Transfer Protocol)		ファイル転送
23	Telnet					遠隔ログイン(telnet)
25	SMTP(Simple Mail Transfer Protocol)	電子メールの転送
79	finger					fingerコマンド
80	HTTP(HyperText Transfer Protocol)	WWWのデータ転送
119	NNTP(Network News Transfer Protocol)	ネットワーク・ニュース
						の記事の転送
513	login	遠隔ログイン(rlogin)
--------------------------------------------------------------------

IPのデータグラムを転送するためには、さまざまな物理的な媒体が使われる。 現在LANでは、イーサネットやFDDIがよく使われいる。イーサネットは、同軸 ケーブルやより対線(Twisted Pair Cable)を使ってデータを転送する。FDDIは、 光ケーブルを使っている。モデムなどを使ったシリアル回線では、PPP(Point to Point Protocol)というプロトコルの上に、IPデータグラムが流される。

データグラムは、ネットワーク通信では、最も基本的な転送サービスである。 IP上に構築された UDP(User Datagram Protocol)も、IPとほとんど同じ機能 を提供する。また、イーサネットやFDDIが提供する転送サービスも、データグ ラムである。

◆ホストとルータ

ネットワークに接続されている計算機の中で、ネットワークに1ヵ所の出入り 口(インタフェース)を持っているものは、ホストと呼ばれる。2ヵ所以上の 出入り口を持っている計算機は、ルータと呼ばれる。ルータは、ネットワーク とネットワークを接続するための計算機である。ルータは、入ってきたIPのパ ケットのIPアドレスを見て、どのネットワークに送ればよいかを判断する。

図1で、左端と右端にあり、4層全てそろっている部分がホストである。 TCP/IPの通信は、ホストとホストの間で行われる。中央の、2層しかない部分 は、ルータである。ルータの仕事は、IP層において行われる。

◆仮想回線

TCP/IP では、プロセスとプロセスが、電話で会話をするように通信が行われ る。普通の電話で人間同士が話をするには、まず電話番号を指定して、話相手 に電話をとってもらわなければならない。TCP/IP においても同様である。 TCP/IPでは、電話を掛ける方をクライアント・プロセス、電話を待つ方をサー バ・プロセスと言いう。

TCP/IPにおいて、プロセス間に形成されたストリーム通信路のことを、計算機 間に張られた物理的な回線に似ていることから、仮想的回線(virtual circuit)とも言う。TCP/IP では、回線を接続する段階では、クライアント・ プロセスとサーバ・プロセスは非対称である。一度仮想回線が接続された後は、 両方のプロセスは、TCP/IPのレベルでは、まったく対称的になる。

TCP/IPにおいてプロセス間に仮想回線を開設するには、IPアドレスとポート番 号が必要である。ポート番号は、同じIPアドレスを持つホスト上で動いている プロセスを区別するために使われる。

以下に、通信路が開設される手順を示す。

  1. サーバ・プロセスがポート番号を指定して、接続要求受付用ポートを作る。 サーバ・プロセスは、クライアント・プロセスからの接続要求を待つ(図3 (a))。(注意:要求受付用ポートでは、データの送受信はできない。)
  2. クライアント・プロセスが通信用ポートを作る。このポートを、サーバ・ プロセスが動いているホストのIPアドレスと、サーバ・プロセスが作った接 続要求受付用ポートのポート番号を使って、接続要求を行う(図3(b))。
  3. 接続要求が受け付けられると、サーバ・プロセスには、新たに通信用ポー トが作られる(図3(c))。これは、特定のクライアントとの通信のために 使われる。

こうして一度通信路が開設されると、クライアントとサーバは、どちらからで もデータを送り始めることができる。

図3(a) TCP/IP通信路の開設(1)

図3(a) TCP/IP通信路の開設(1)

図3(b) TCP/IP通信路の開設(2)

図3(b) TCP/IP通信路の開設(2)

図3(c) TCP/IP通信路の開設(3)

図3(c) TCP/IP通信路の開設(3)

TCP/IPにおける通信路開設において、クライアントは、サーバ側の接続要求受 付用ポートのポート番号を、事前に知っている必要がある。表1に、いくつか の応用層のプロトコルについて、公に利用目的が決められているポート番号を 示す。1024以上のポート番号は、特に利用目的が決められていない。よって、 利用されていなければ、利用者が自由に使ってもよい。

クライアント側の通信用ポートのポート番号は、通常は、オペレーティング・ システムにより自動的に割り当てられる。サーバ側の通信用ポートのポート番 号も、同様である。

◆プロセス間通信におけるクライアント・サーバ・モデル

プロセス間通信は、本来自由に行うことができる。どのプロセスも自由にメッ セージを送信する権利がある。プロセス間通信におけるクライアント・サーバ・ モデルは、本来対称的なプロセスを最初にメッセージを送る方(クライアント・ プロセス)と受ける方(サーバ・プロセス)に分類することで、プロセス間通 信を構造化し、わかりやすくするものである。TCP/IPの通信路開設時における クライアントとサーバの役割は、このプロセス間通信におけるクライアント・ サーバ・モデルの1つの例になっている。

プロセス間通信におけるクライアント・サーバ・モデルにおける意味の他に、 クライアントとサーバという言葉は、サービスを受けるプロセスとサービスを 提供するプロセスの意味で使われることがある。インターネットにおけるプロ セス間通信では、多くの場合、サービスの授受の関係におけるクライアントと サーバと、プロセス間通信におけるクライアントとサーバが一致している(稀 に一致していないこともあるので、注意しなさい)。

◆ソケット

UNIX オペレーティング・システム上で動作するプログラムがTCP/IPの機能を 使う場合、UNIXオペレーティング・システムが提供するソケットというインタ フェースを通じて利用することになる。ソケットは、TCP/IP をはじめとして、 XNS, OSI などさまざまな通信プロトコルを UNIX オペレーティング・システ ム上で使うために設計されたものである。TCP/IP だけを考えると、ソケット のインタフェースは、繁雑であり、使いにくくなっている。

UNIXでは、ソケットをドメイン(Address Family)と型で区別する。 下の表は、socket() システム・コールに与えるドメインと型である。

----------------------------------------------------------------------
ドメイン 型		option	プロトコル
----------------------------------------------------------------------
AF_INET	SOCK_STREAM	0	TCP
AF_INET	SOCK_DGRAM	0	UDP
AF_INET	SOCK_RAW	?	IP
AF_INET	SOCK_RAW	?	ICMP
AF_UNIX	SOCK_STREAM	0	(UNIXドメインのストリーム)
AF_UNIX	SOCK_DGRAM	0	(UNIXドメインのデータグラム)
AF_NS	SOCK_SEQPACKET	?	XEROX NS protocol の順序付きパケット
AF_NS	SOCK_RDM	?	XEROX NS protocol の信頼性のあるデータグラム
----------------------------------------------------------------------
これ以外の組み合わせ、使えない。たとえば、AF_INETとSOCK_SEQPACKET を socket システム・コールで指定しても、うまくいかない。

◆DNS(Domain Name Service)

TCP/IPによる通信は、通信相手のIPアドレス(32ビットの整数)とポート番号 (16ビットの整数)さえわかれば、可能である。IPアドレスやポート番号は、 計算機にとって扱いやすいものであるが、人間にとって扱いやすいものではな い。人間にとってわかりやすい記号の名前から、IPアドレスに変換するサービ スがあれば便利である。このサービスを、名前サービス、それを行うプログラ ムを名前サーバという。

インターネットにおける名前サービスは、名前空間をドメイン(領域)に分割 して、階層的に管理することで実現されている。これを、ドメイン・ネーム・ サービス(Domain Name Service, DNS)という。DNSという言葉は、名前サー ビスを提供するプログラム(名前サーバ, Domain Name Server)を意味するこ ともある。

DNSでは、主に名前をIPアドレスへ変換するサービスが使われている。その他 に、名前から電子メールの配送先、名前から名前サーバが動いているホストの 名前、名前から名前サーバ自身の管理情報、逆にIPアドレスから名前を引くた めにも使われる。

■汎用 TCP/IP クライアントとしての telnet

◆ telnet コマンドの使い方

telnet コマンドは、通常、次のように使われる。
--------------------------------------------------------------------
% telnet host1 [←]
--------------------------------------------------------------------
これは、次の省略形である。
--------------------------------------------------------------------
% telnet host1 telnet [←]
--------------------------------------------------------------------
ここで、第2引数の "telnet" という文字列は、TCP/IP のポート番号を示す 記号である。この記号は、/etc/services というファイルに次のように格納さ れている。
--------------------------------------------------------------------
telnet		23/tcp
--------------------------------------------------------------------

telnet コマンドは、/etc/services ファイルを検索し、与えられた記号から ポート番号(この例では23)を得る。また、host1 の IP アドレスを、 /etc/hosts や DNS から得る。telnet コマンドは、この IP アドレスとポー ト番号の2つを使って、TCP/IP の通信路を開設する。(注意:NISが動いてい る場合には、/etc/services や /etc/hosts の代わりに、NISのデータベース (マップ)が検索される。)

telnet コマンドでは、次のように、ホストのIPアドレスとポート番号を数字 で打ち込むこともできる。

--------------------------------------------------------------------
% telnet a.b.c.d 23 [←]
--------------------------------------------------------------------
ここで、a.b.c.d とは、32ビットのIPアドレスを8ビットずつに区切り、 それぞれの8ビットの整数を10進数で表記したものである。よって、実際のIP アドレスは、次のようしてに計算できる。
((((a*256)+b)*256)+c)*256+d == XXXXXXXXXX
よって、telnet コマンドに次のようIPアドレスを与えてもよい。
--------------------------------------------------------------------
% telnet XXXXXXXXXX 23  [←]
--------------------------------------------------------------------

■rdaytime

TCP/IP のポート番号 13 では、時刻を返すサービスを提供している。

----------------------------------------------------------------------
% egrep daytime /etc/services  [←]
daytime       13/tcp                 # Daytime
daytime       13/udp                 #
% []
% telnet adonis1 13 [←]
Trying...
Connected to adonis1.
Escape character is ^].
Mon Jun  2 20:33:07 1997
Connection closed by foreign host.
% []
----------------------------------------------------------------------
''

----------------------------------------------------------------------
   1:	
   2:	/*
   3:	        rdaytime.c -- 遠隔のホストの日付を調べる
   4:	        /usr/local/LECTURES/syspro-1997-shinjo/ipc/rdaytime.c
   5:	        $Header: rdaytime.c,v 1.4 97/06/02 22:27:08 yas Exp $
   6:	        Start: 1995/09/08 21:04:33
   7:	*/
   8:	#include <stdio.h>
   9:	#include <sys/socket.h> /* socket() */
  10:	#include <netinet/in.h> /* struct sockaddr_in */
  11:	#include <netdb.h>      /* gethostbyname() */
  12:	
  13:	main( argc,argv )
  14:	    int argc ;
  15:	    char *argv[] ;
  16:	{
  17:	        if( argc != 3 )
  18:	        {
  19:	            fprintf( stdout,"Usage: %s host port\n",argv[0] );
  20:	            exit( -1 );
  21:	        }
  22:	        rdaytime( argv[1],atoi(argv[2]) );
  23:	}
  24:	
  25:	#define BUFFSIZE        1024
  26:	
  27:	rdaytime( hostname,portno )
  28:	    char *hostname ;
  29:	    int portno ;
  30:	{
  31:	    int s ;
  32:	    int rcount ;
  33:	    char buff[BUFFSIZE];
  34:	
  35:	        s = tcp_connect( hostname, portno );
  36:	        if( s<0 )
  37:	            exit( -1 );
  38:	        while( (rcount=read(s,buff,BUFFSIZE)) > 0 )
  39:	        {
  40:	            if( write(1,buff,rcount) != rcount )
  41:	            {
  42:	                 perror("write");
  43:	                 exit( 1 );
  44:	            }
  45:	        }
  46:	        close( s );
  47:	}
  48:	
  49:	tcp_connect( hostname, portno )
  50:	    char *hostname ;
  51:	    int portno ;
  52:	{
  53:	    struct hostent *hostent ;
  54:	    struct sockaddr_in addr ;
  55:	    int addr_len ;
  56:	    int s ;
  57:	
  58:	        addr.sin_family = AF_INET ;
  59:	        if( (hostent = gethostbyname( hostname )) == NULL )
  60:	        {
  61:	            fprintf(stderr,"unknown host %s\n",hostname );
  62:	            return( -1 );
  63:	        }
  64:	        bcopy( hostent->h_addr, &addr.sin_addr, hostent->h_length );
  65:	        addr.sin_port = htons( portno );
  66:	        if( (s = socket(AF_INET, SOCK_STREAM, 0)) < 0 )
  67:	        {
  68:	            perror("socket");
  69:	            return( -1 );
  70:	        }
  71:	        if( connect(s, &addr, sizeof(addr)) < 0 )
  72:	        {
  73:	            perror( hostname );
  74:	            close( s );
  75:	            return( -1 );
  76:	        }
  77:	        return( s );
  78:	}
----------------------------------------------------------------------

実行例。

----------------------------------------------------------------------
% ./rdaytime adonis1 13 [←]
Mon Jun  2 20:54:44 1997
% ./rdaytime www.tsukuba.ac.jp 13 [←]
Mon Jun  2 20:48:24 1997
% ./rdaytime zzz.tsukuba.ac.jp 13 [←]
unknown host zzz.tsukuba.ac.jp
% []
----------------------------------------------------------------------
その他の hostent 構造体(/etc/hosts, NIS の hosts マップ, DNS)をアクセ スする関数。
----------------------------------------------------------------------
     struct hostent *gethostbyname(const char *name);

     struct hostent *gethostbyaddr(const char *addr, int len,
          int type);
----------------------------------------------------------------------
/etc/services をアクセスするには、次のような関数を使う。
----------------------------------------------------------------------
     struct servent *getservbyname(const char *name,  const  char
     *proto);

     struct servent *getservbyport(int port, const char *proto);
----------------------------------------------------------------------

★練習問題(32) getservbyname()の利用

上の rdaytime.c を、getservbyname() を使うように書き換えなさい。

★練習問題(33) TCP/IPクライアントの作成

次の通信プロトコルのクライアントを作りなさい。

まず、telnet で、これらのサーバに接続しなさい。そして、それぞれのプロ トコルに従って、要求を打ち込み、どのような結果が返ってくるかを調べなさ い。

次に、telnet で行った要求の送信を結果の受信を行うようなプログラムをを 作りなさい。このとき、必要なパラメタは、main() の引数から取りなさい。

注意。それぞれのプロトコルでは、接続先として次のホストを使いなさい。

HTTP
www
finger
coins
SMTP
orchid, localhost
NNTP
jks-news

■rdaytime-server

TCP/IP のポート番号 13 で提供されている、時刻を返すサービスと同等の機 能をもつサービスを他のポート番号で提供することができる。

----------------------------------------------------------------------
   1:	
   2:	/*
   3:	        rdaytime-server.c -- 日付を返すサーバ
   4:	        /usr/local/LECTURES/syspro-1997-shinjo/ipc/rdaytime-server.c
   5:	        $Head1er: rdaytime-server.c,v 1.3 97/06/02 23:53:06 yas Exp $
   6:	        Start: 1995/09/08 21:04:33
   7:	*/
   8:	#include <stdio.h>
   9:	#include <sys/socket.h> /* socket() */
  10:	#include <netinet/in.h> /* struct sockaddr_in */
  11:	#include <netdb.h>      /* gethostbyname() */
  12:	#include <time.h>       /* gettimeofday(), struct timeval */
  13:	
  14:	main( argc,argv )
  15:	    int argc ;
  16:	    char *argv[] ;
  17:	{
  18:	    int portno ;
  19:	        if( argc >= 3 )
  20:	        {
  21:	            fprintf( stdout,"Usage: %s host port\n",argv[0] );
  22:	            exit( -1 );
  23:	        }
  24:	        if( argc == 2 )
  25:	            portno = atoi( argv[1] );
  26:	        else
  27:	            portno = getuid();
  28:	        rdaytime_server( portno );
  29:	}
  30:	
  31:	rdaytime_server( portno )
  32:	    int portno ;
  33:	{
  34:	    int acc,com ;
  35:	    int rcount ;
  36:	        acc = tcp_acc_port( portno );
  37:	        if( acc<0 )
  38:	            exit( -1 );
  39:	        print_host_port( portno );
  40:	        while( 1 )
  41:	        {
  42:	            if( (com = accept( acc,0,0 )) < 0 )
  43:	            {
  44:	                perror("accept");
  45:	                exit( -1 );
  46:	            }
  47:	            rdaytime_reply( com );
  48:	        }
  49:	}
  50:	
  51:	rdaytime_reply( com )
  52:	    int com ;
  53:	{
  54:	    struct timeval timeval ;
  55:	    char *p ;
  56:	    int len ;
  57:	
  58:	        gettimeofday( &timeval, 0 );
  59:	        p = ctime( &timeval.tv_sec );
  60:	        len = strlen( p );
  61:	        if( writen( com, p, len ) != len )
  62:	        {
  63:	            perror("write");
  64:	            exit( -1 );
  65:	        }
  66:	        close( com );
  67:	}
  68:	
  69:	print_host_port( portno )
  70:	    int portno ;
  71:	{
  72:	    char hostname[100] ;
  73:	        gethostname( hostname,sizeof(hostname) );
  74:	        hostname[99] = 0 ;
  75:	        printf("rdaytime %s %d \n",hostname, portno );
  76:	}
  77:	
  78:	tcp_acc_port( portno )
  79:	    int portno ;
  80:	{
  81:	    struct hostent *hostent ;
  82:	    struct sockaddr_in addr ;
  83:	    int addr_len ;
  84:	    int s ;
  85:	
  86:	        if( (s = socket(AF_INET, SOCK_STREAM, 0)) < 0 )
  87:	        {
  88:	            perror("socket");
  89:	            return( -1 );
  90:	        }
  91:	
  92:	        addr.sin_family = AF_INET ;
  93:	        addr.sin_addr.s_addr = INADDR_ANY ;
  94:	        addr.sin_port = htons( portno );
  95:	
  96:	        if( bind(s,&addr,sizeof(addr)) < 0 )
  97:	        {
  98:	            perror( "bind: " );
  99:	            fprintf(stderr,"port number %d is already used. kill another program.", portno );
 100:	            return( -1 );
 101:	        }
 102:	        listen( s, 5 );
 103:	        return( s );
 104:	}
 105:	
 106:	/* See: UNIX Network Programming, SS.6.6, Utility routines */
 107:	
 108:	int
 109:	writen( fd, buff, nbytes )
 110:	    register int         fd ;
 111:	    register char       *buff ;
 112:	    register int         nbytes ;
 113:	{
 114:	    register int         nleft, nwritten ;
 115:	
 116:	        nleft = nbytes ;
 117:	        while( nleft > 0 )
 118:	        {
 119:	            nwritten = write( fd, buff, nleft );
 120:	            if( nwritten <= 0 )
 121:	                return( nwritten );
 122:	            nleft -= nwritten ;
 123:	            buff  += nwritten ;
 124:	        }
 125:	        return( nbytes - nleft );
 126:	}
----------------------------------------------------------------------

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

----------------------------------------------------------------------
% ./rdaytime-server  [←]
rdaytime orchid 1231 
^C
% []
----------------------------------------------------------------------
実行例。クライアント側。rdaytime を使う。

----------------------------------------------------------------------
% ./rdaytime orchid 1231 [←]
Mon Jun  2 23:54:43 1997
% ./rdaytime orchid 1231 [←]
Mon Jun  2 23:54:46 1997
% ./rdaytime orchid 13 [←]
Mon Jun  2 23:54:51 1997
% []
----------------------------------------------------------------------

★[report] report-8,TCP/IPのクライアント

練習問題(33)の中から1つ以上選んでやりなさい。問題を難しい方に変更し てもよい。
[HTTP] [NNTP] [SMTP] [FINGER]
↑[もどる] ←[5月27日] ・[6月3日] →[6月10日]
Last updated: 1997/06/03 00:18:08
Yasushi Shinjo / <yas@is.tsukuba.ac.jp>