電子・情報工学系/システム情報工学研究科CS専攻 新城 靖 <yas@is.tsukuba.ac.jp>
このページは、次の URL にあります。
http://www.coins.tsukuba.ac.jp/~syspro/2005/No6.html
あるいは、次のページから手繰っていくこともできます。
http://www.coins.tsukuba.ac.jp/~syspro/2005/
http://www.coins.tsukuba.ac.jp/~yas/
インターネット上のアプリケーションの多くは、TCP/IPという仕組みを用いて 通信を行っている。
TCP/IPは、信頼性のある(reliable)双方向のストリーム転送サービス (stream transport service)を提供する通信プロトコルである(図1)。ス トリームは、次のような性質がある転送サービスである。
図1(a) TCP/IPにより提供される双方向ストリーム
図1(b) Unixのパイプにより提供される単方向ストリーム
なお、C言語のライブラリ関数である fopen(), fgets(), fputs() なども、 ストリームと呼ばれることがある。これは、もともとランダム・アクセス可能 で、メモリ中の配列と同じようにアクセスするすることもできるファイルを、 まるでプロセス間通信のストリームと同じように扱うことができることにも 関係している。
TCP/IPによる通信では、図2に示すように、4つのプロトコル(規約、約束事) の層が使われる。TCP/IP自身は、TCP層と IP層という2つのプロトコルに分解 される。このようにさまざまなプロトコルが決められ、全体として層をなして いる。この様子を、プロトコル・スタックと呼ぶ。
データグラムは、書留めではない郵便に似ている。 データグラムは、 パケット と呼ばれることもある。(パケットという言葉は、データの送り手と受けての 間に結合が作られる作られる時にも使われることがある。データグラムは、結 合が作られない通信で使われるひと固まりのデータで、電報(telegram)との類 推から名前が付けらた。)
12.34.56.78は
(((12 * 256)+34)*256+56)*256+78という数を表する。
TCP/IPを使った通信は、まるでプロセス同士が電話で会話するよう に進められる。普通の電話では、日本語を話す人と英語を話す人は、電話で情 報交換を行うことができない。同様に、同じTCP/IPを使っていても、会話の方 法が違うと、まったく情報交換を行うことができない。ゆえに、TCP/IPの上に さらに、情報交換のためにさまざまなプロトコルが取り決められている。
TCP/IPの上に構築されているプロトコルの例を、表1に示す。
ポート番号 | プロトコルの名前 | 目的 |
---|---|---|
21 | FTP(File Transfer Protocol) | ファイル転送 |
22 | SSH (Secure Shell) | 暗号通信路によるログイン |
23 | Telnet | 遠隔ログイン(telnet) |
25 | SMTP(Simple Mail Transfer Protocol) | 電子メールの転送 |
79 | finger | finger name の取得 |
80 | HTTP(HyperText Transfer Protocol) | WWWのデータ転送 |
110 | POP(Post Office Protocol) | 電子メールのアクセス |
119 | NNTP(Network News Transfer Protocol) | ネットワーク・ニュースの記事の転送 |
143 | IMAP(Internet Message Access Protocol) | 電子メールのアクセス |
513 | login | 遠隔ログイン(rlogin) |
/etc/services に、他のポート番号が掲載されている。
現在LANでは、イーサネットがよく使われいる。イーサネットは、同軸 ケーブル、より対線(Twisted Pair Cable)、または、光ファイバ を使ってデータグラムを転送する。 無線LAN (IEEE 802.11b/g/a) も、データグラムを転送する。
IPのデータグラムを転送する時に、物理的なデータグラム転送サービスではな く、他のプロトコルが使われることもある。 モデムなどを使ったシリアル回線では、PPP(Point to Point Protocol)というプロトコルの上に、IPデータグラムが流される。
IP上に構築された UDP(User Datagram Protocol)も、IPとほとんど同じ機能 を提供する。
ネットワークに接続されている計算機の中で、ネットワークに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アドレスを持つホスト上で動いている プロセスを区別するために使われる。
以下に、通信路が開設される手順を示す。
こうして一度通信路が開設されると、クライアントとサーバは、どちらからで もデータを送り始めることができる。
図3(a) TCP/IP通信路の開設(1) 図3(b) TCP/IP通信路の開設(2) 図3(c) TCP/IP通信路の開設(3)
TCP/IPにおける通信路開設において、クライアントは、サーバ側の接続要求受 付用ポートのポート番号を、事前に知っている必要がある。表1に、いくつか の応用層のプロトコルについて、公に利用目的が決められているポート番号を 示す。
クライアント側の通信用ポートのポート番号は、通常は、オペレーティング・ システムにより自動的に割り当てられる。サーバ側の通信用ポートのポート番 号も、同様である。
図?(a) 構造化されていないもの
図?(b) 構造化されたもの
クライアント・サーバ・モデルは、プロセス間通信を構造化したものであり、 最近の用語でいうと、デザイン・パターンの1つである。プログラミングの歴 史の中で「構造化」という言葉は、まず、「制御構造」に対して使われた。構 造化プログラミングとは、goto文を、よい goto 文と悪い goto文に分け、よ い goto 文だけを使うようにしようとするものである。初期のプログラミング では、アセンブリ言語や貧弱な制御構造しか持たない Fortran が使われてい たが、その時は、jump 命令や goto 文が多用されていた。そのような jump 命令や goto 文にも、分かりやすいものとわかりにくいものがあった。そこで、 よい goto 文のパターンを整理して、それだけを使ってプログラムを書くのが よいとされた。そしてよい goto 文にはプログラミング言語のレベルでif、 while、continue、break、そして、手続き呼出し(call)とreturn という特別 な形式が割り当てられた。C言語や Pascal では、goto 文が残されたが、 Java などの最近の言語ではgoto 文が記述できなくなっている。
プロセス間通信を構造化するという意味でのクライアント・サーバ・モデル
では、まずプロセスをクライアントとサーバの2種類に分ける。
図? 通信のパタンからみたクライアントとサーバの定義
クライアント・サーバ・モデルに基づくプログラムには次のようなことを行う プロセスは存在しない。
図? サービスの授受によるクライアントとサーバの定義
サービスを提供する方は、1つのプログラム(コンピュータ)で複数の利用者 の面倒をみる。その結果、1台のサーバに複数のクライアントがつながる。
図? 複数のクライアントによるサーバの共有
通信を開始するパタンで、コンピュータ、プログラム、人間は、次の2つに分 類される。
図? 能動的なクライアントと受動的なサーバ
例:WWWサーバは、WWWクライアントから何か要求が来ない限り、ずっと 黙っている。
コンピュータを使う時には、人間が能動的になり、コンピュータが受動的にな る。
テレビを見ている時には、人間が受動的になり、テレビが能動的になる。
講義形式の授業では、サービスの授受では、教官がサーバで、学生がクライア ントになる。通信の開始の方法では、教官が能動的になり、学生が受動的にな る。
大学以上では、学生は、能動的になることが求められている。
プログラム | 通信パタン | サービス授受 | 結合作成 |
---|---|---|---|
ファイル・サーバ | サーバ | サーバ | サーバ* |
DNS名前サーバ | サーバ | サーバ | サーバ |
Webサーバ | サーバ | サーバ | サーバ |
Webブラウザ | ライアント | クライアント | クライアント |
メールリーダ | クライアント | クライアント | クライアント |
メール配送発信側 | クライアント | クライアント | クライアント |
メール配送着信側 | サーバ | サーバ | サーバ |
動画像送信 | - | サーバ | サーバ* |
動画像受信 | - | クライアント | クライアント* |
UNIX オペレーティング・システム上で動作するプログラムがTCP/IPの機能を 使う場合、UNIXオペレーティング・システムが提供するソケットというインタ フェースを通じて利用することになる。ソケットは、TCP/IP をはじめとして、 XNS, OSI などさまざまな通信プロトコルを UNIX オペレーティング・システ ム上で使うために設計されたものである。TCP/IP だけを考えると、ソケット のインタフェースは、繁雑であり、使いにくくなっている。
UNIXでは、ソケットをドメイン(Protocol Family)と型で区別する。 下の表は、socket() システム・コールに与えるドメインと型である。
ドメイン | 型 | option | プロトコル |
---|---|---|---|
PF_INET | SOCK_STREAM | 0 | TCP/IP |
PF_INET | SOCK_DGRAM | 0 | UDP/IP |
PF_INET | SOCK_RAW | ? | IP,ICMPなど |
PF_INET6 | SOCK_STREAM | 0 | TCP/IP(IPv6) |
PF_INET6 | SOCK_DGRAM | 0 | UDP/IP(IPv6) |
PF_UNIX | SOCK_STREAM | 0 | 同一ホスト内(Unixドメイン)のストリーム |
PF_UNIX | SOCK_DGRAM | 0 | 同一ホスト内(Unixドメイン)のデータグラム |
PF_NS | SOCK_STREAM | ? | Xerox Network Systems protocol のストリーム(SPP) |
PF_NS | SOCK_SEQPACKET | ? | Xerox Network Systems protocol の順序付きパケット |
PF_NS | SOCK_RDM | ? | Xerox Network Systems protocol の信頼性のあるデータグラム |
これ以外の組み合わせ、使えない。たとえば、PF_INETとSOCK_SEQPACKET を socket システム・コールで指定しても、うまくいかない。
名前 | 説明 |
---|---|
socket() | 通信プロトコルに対応したソケット・オブジェクトを作成する |
connect() | 結合(conection)を確立させる。サーバのアドレスを固定する。 |
listen() | サーバ側で接続要求の待ち受けを開始する。 |
accept() | サーバ側で接続されたソケットを得る。 |
bind() | ソケットにアドレス(名前)を付ける。 |
getpeername() | 通信相手のアドレス(名前)を得る。 |
getsockname() | 自分のアドレス(名前)を得る。 |
send(),sendto(),sendmsg() | メッセージを送信する。 |
recv(),recvfrom(),recvmsg() | メッセージを受信する。 |
shutdown() | 双方向の結合を部分的に切断する。 |
getsockopt() | オプションの現在の値を取得する。 |
setsockopt() | オプションを設定する。 |
select(), poll() | 複数の入出力(通信を含む)を多重化する。 |
write() | メッセージを送信する。ファイルと共通。 |
read() | メッセージを受信する。ファイルと共通。 |
close() | ファイル記述子(ファイルディスクリプタ)を閉じる。他に参照しているファイル記述子が なければ、ソケット・オブジェクトを削除する。ファイルと共通。 |
TCP/IPで通信する時には、通信相手のIPアドレス(32ビットの整数、番号)が 必要になる。IPアドレスは、コンピュータにとって扱いやすいが、人間にとっ て分かりにくい。
人間にとってわかりやすい記号(文字列)を使ったコンピュータの名前から IPアドレスに変換するサービスがあれば便利である。このサービスを、 名前サービス(name service)、 という。 名前サービスを提供するプログラム(プロセス)を、名前サーバという。
名前から名前を指している番号に変換することを 名前解決(name resolution) という。
インターネットで使われている名前サービスは、 DNS(Domain Name System) と呼ばれる。 DNS では、膨大な数のホスト名を含む名前空間を階層的にドメイン(領域)に 分割して管理ている。 この空間の構造は、木構造と同じものでいる。
% 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アドレスとポート番号を 数字で打ち込む こともできる(以下のIPアドレスは、IPv4の場合)。
% telnet 12.34.56.78 23![]()
% telnet localhost echo注意:セキュリティ上の理由から、echo などの、システムプログラムの講義 くらいでしか役に立たないようなサービスを停止することが、最近では一般的 である。Trying 127.0.0.1... Connected to localhost. Escape character is '^]'. hello
hello exit
exit quit
quit aaa
aaa ^] telnet> quit
Connection closed. %
![]()
以下は、telnet を使って、adonis9.coins.tsukuba.ac.jp 上で動作している echo サーバを利用している(adonis9 上のサービスは、情報学類内のコンピュー タからしかつながらないような設定をしている)。
% egrep echo /etc/servicesecho 7/tcp echo 7/udp at-echo 204/tcp # AppleTalk echo at-echo 204/udp echo 4/ddp # AppleTalk Echo Protocol % telnet adonis9.coins.tsukuba.ac.jp 7
Trying 130.158.86.29... Connected to adonis9.coins.tsukuba.ac.jp. Escape character is '^]'. hello
hello aaa
aaa ^] telnet> quit
Connection closed. %
![]()
% cp ~yas/syspro/ipc/echo-client-fdopen.c .このプログラムは、コマンドラインから2つ以上の引数をとる。第1引数で指 定されたホスト上の、第2引数で指定されたポートで動作しているサーバに接 続する。そして、キーボードから1行単位で入力し、そのサーバへ送る。echo サービスのサーバは、同じ文字列を送り返して来る。このプログラムは、サー バから送り返されてきた文字列を受取り、結果を画面に表示する。% make echo-client-fdopen
cc echo-client-fdopen.c -o echo-client-fdopen % ./echo-client-fdopen
Usage: ./echo-client-fdopen host port % ./echo-client-fdopen adonis7.coins.tsukuba.ac.jp 7
==> hello
sent: [hello ] received: [hello ] ==> aaa
sent: [aaa ] received: [aaa ] ==> ^D %
![]()
TCP/IPのクライアント側のプログラムで大事な標準のシステムコールとライブ ラリ関数は、次の通りである。
1: /* 2: echo-client-fdopen.c -- 文字列を送受信するクライアント(TCP/IP版) 3: ~yas/syspro/ipc/echo-client-fdopen.c 4: Created on: 1998/06/01 23:48:29 5: */ 6: #include <stdio.h> 7: #include <sys/types.h> /* socket() */ 8: #include <sys/socket.h> /* socket() */ 9: #include <netinet/in.h> /* struct sockaddr_in */ 10: #include <netdb.h> /* getaddrinfo() */ 11: 12: extern int echo_client( char *server, int portno ); 13: extern int fdopen_sock( int sock, FILE **inp, FILE **outp ); 14: 15: main( int argc, char *argv[] ) 16: { 17: char *server ; 18: int portno ; 19: if( argc != 3 ) 20: { 21: fprintf( stdout,"Usage: %s host port\n",argv[0] ); 22: exit( -1 ); 23: } 24: server = argv[1] ; 25: portno = strtol( argv[2],0,10 ); 26: echo_client( server, portno ); 27: }main() 関数は、コマンドラインの引数を調べて、echo_client() を読んでい る。strtol() で、引数で文字列として与えられた数を、int に変換している。
28: 29: #define BUFFERSIZE 1024 30: 31: int 32: echo_client( char *server, int portno ) 33: { 34: int sock ; 35: char sbuf[BUFFERSIZE]; 36: char rbuf[BUFFERSIZE]; 37: FILE *in, *out ; 38: 39: sock = tcp_connect( server, portno ); 40: if( sock<0 ) 41: exit( -1 ); 42: if( fdopen_sock(sock,&in,&out) < 0 ) 43: { 44: fprintf(stderr,"fdooen()\n"); 45: exit( 1 ); 46: } 47: printf("==> "); fflush(stdout); 48: while( fgets(sbuf,BUFFERSIZE,stdin) ) 49: { 50: fprintf(stdout,"sending: [%s]\n",sbuf ); 51: fprintf(out,"%s",sbuf ); 52: fgets( rbuf,BUFFERSIZE,in ); 53: printf("received: [%s]\n",rbuf ); 54: printf("==> "); fflush(stdout); 55: } 56: printf("\n"); 57: fclose( in ); 58: fclose( out ); 59: }echo_client() では、tcp_connect() という関数を呼び出している。この結果、 サーバとの間に TCP/IP通信路の開設され、通信可能なファイル記述子 (ファイルディスクリプタ) が返さ れる。このファイル記述子は、標準入出力(0,1,2)や open() システム・コー ルの結果と同じもので、 write() システムコールや read() システムコールの第一引数とし て使うことができる。つまり、write() システムコールを使うと、ネットワー クに対してデータを送り出すことができ、read() システムコールを使うとネッ トワークからデータを受け取ることができる。最後に不要になったら close() で解放する。
このプログラムでは、fdopen_sock() (後述)を使って、通信可能なファイル記 述子 com から2つの FILE * を作成している。1つは、入力用、1つは出力 用である。その結果、 高水準入出力ライブラリ を使って通信が行えるようになっている。fprintf() で出力用の FILE * に書 き込むと、ネットワークに対してデータが送り出される。入力用の FILE * に fgets() を行うと、ネットワークからデータを受け取ることができる。
TCP/IP の通信では、行単位(最後に\n)でデータを送受信することが多い。 echo サービスでは、1行送り、1行受け取る。他のサービスでは、1行送っ て複数行受け取ったり、受け取る方では行の概念がなくなるもの(HTTPで画像 データを受け取る場合など)もある。その場合は、fprintf() や fgets() では なくて、fwrite() や fread() を使う必要がある。
60: 61: /* 新城、システムプログラム講義用 */ 62: 63: int tcp_connect( char *server, int portno ) 64: { 65: struct addrinfo hints, *ai; 66: struct sockaddr_in addr ; /* sockaddr_storage */ 67: int s ; 68: int err ; 69: 70: if( (s = socket(PF_INET, SOCK_STREAM, 0)) < 0 ) 71: { 72: perror("socket"); 73: return( -1 ); 74: } 75: 76: memset( &hints, 0, sizeof(hints) ); 77: hints.ai_family = AF_INET ; 78: hints.ai_socktype = SOCK_STREAM ; 79: if( (err = getaddrinfo( server, NULL, &hints, &ai )) ) 80: { 81: fprintf(stderr,"unknown host %s (%s)\n",server,gai_strerror(err) ); 82: close( s ); 83: return( -1 ); 84: } 85: if( ai->ai_addrlen > sizeof(addr) ) 86: { 87: fprintf(stderr,"sockaddr too large (%d) > (%d)\n", 88: ai->ai_addrlen,sizeof(addr) ); 89: freeaddrinfo( ai ); 90: close( s ); 91: return( -1 ); 92: } 93: memcpy( &addr, ai->ai_addr, ai->ai_addrlen ); 94: addr.sin_port = htons( portno ); 95: freeaddrinfo( ai ); 96: 97: if( connect(s, (struct sockaddr *)&addr, sizeof(addr)) < 0 ) 98: { 99: perror( server ); 100: close( s ); 101: return( -1 ); 102: } 103: return( s ); 104: }tcp_connect() は、通信路の開 設の仕事のうち、クライアント側の仕事をする関数である。まず、クライア ント側のソケットを、socket() システムコールで作成している。 PF_INET と SOCK_STREAMの組み合わせなので、 TCP を使うことを意味する。
socket() の引数で、PF_INET の変りに、AF_INET と書いてもよい。ここでは、 Protocol を選んでいるので、PF_ が正しいが、実際には、PF_INET と AF_INET は同じであり、また、多くのテキストで混在されて使われいる。
ソケットが作成できたら、connect() システムコールで接続する。その第2引 数と第3引数で、接続先のアドレスを指定しなければならない。 接続先には、sockaddr を使う。 この構造体には、IP アドレスとポート番号が含まれている。
IP アドレスは、文字列で与えられた接続先のサーバのホスト名から ライブラリ関数 getaddrinfo() を使って調べている。(従来は、getaddrinfo() ではなく gethostbyname() が よく使われていた。getaddrinfo() は、IPv6 にも対応している。)
ポート番号は、引数で与えられたものをそのまま使う。ただし、ネットワーク・ バイトオーダ(ビッグエンディアン)に変換する必要がある。
複数バイトの整数をメモリに置く時に、連続した複数番地のメモリを使う。 どういう順番で置くかで2つの方法がある。 数の大小比較の時に、最も効いてくる(most significant)なビット(を含むバ イト、上位バイト)をどこに置くかで、次の2つの方法がある。
図? バイト・オーダ(その1) 図? バイト・オーダ(その2)
#include <netinet/in.h> unsigned long int htonl(unsigned long int hostlong ); unsigned short int htons(unsigned short int hostshort); unsigned long int ntohl(unsigned long int netlong ); unsigned short int ntohs(unsigned short int netshort );ここで、n は、network、h は、ホスト、s は short、l は、long を意味する。 ネットワークの変りに、ファイルでもよい。ファイルやネットワークに出力す る時には、htonl() や htons() で標準系(ビッグエンディアン)に変換する。 入力の時は、逆に ntohl() や ntohs() で元にもどす。
ポート番号は、16 ビットなので、ライブラリ関数 htons() (host to network, short))を使って変換している。
IP アドレスもネットワーク・バイトオーダになっているが、これは getaddrinfo() 関数で、struct addrinfo の ai_addr に保存それた段階そう なっている。
ポート番号として、整数(short)ではなく、/etc/services ファイルに含まれ た文字列で指定したいこともある。その場合は、getaddrinfo() 関数の第2引 数に文字列を渡す。
getaddrinfo() の中で malloc() されたメモリは、free() を直接呼び出して 解放するのではなく、freeaddrinfo() を呼び出して解放する。 (直接 free() を呼ぶと、ai_next の先が解放されない。)
105: 106: int 107: fdopen_sock( int sock, FILE **inp, FILE **outp ) 108: { 109: int sock2 ; 110: if( (sock2=dup(sock)) < 0 ) 111: { 112: return( -1 ); 113: } 114: if( (*inp = fdopen( sock2, "r" )) == NULL ) 115: { 116: close( sock2 ); 117: return( -1 ); 118: } 119: if( (*outp = fdopen( sock, "w" )) == NULL ) 120: { 121: fclose( *inp ); 122: *inp = 0 ; 123: return( -1 ); 124: } 125: setvbuf(*outp, (char *)NULL, _IOLBF, 0); 126: return( 0 ); 127: }fdopen_sock() は、dup() してファイル記述子を増やし、fdopen() を呼んで いる。fdopen() は、既に open() や pipe() 等で得られたファイル記述子から 高水準入出力ライブラリ が使えるようにするためのものである。 setvbuf() では、バッファリングを行わせないようにしている。これにより、 たとえば fprintf() を行うと、即座にネットワークに送り出される。
1: 2: /* 3: EchoClient.java -- 文字列を送受信するクライアント(TCP/IP版) 4: ~yas/syspro/ipc/EchoClient.java 5: Created on 2004/02/14 21:09:17 6: */ 7: 8: import java.net.*; 9: import java.io.*; 10: 11: class EchoClient 12: { 13: public static void main(String argv[]) throws IOException { 14: if( argv.length != 2 ) 15: { 16: System.err.println("Usage: % java EchoClient host port"); 17: System.exit( -1 ); 18: } 19: String server = argv[0]; 20: int portno = Integer.parseInt( argv[1] ); 21: echo_client( server, portno ); 22: } 23: 24: public static void echo_client( String server, int portno ) 25: throws IOException 26: { 27: Socket sock = new Socket( server, portno ); 28: BufferedReader in = new BufferedReader( 29: new InputStreamReader( sock.getInputStream() )); 30: PrintStream out = new PrintStream( sock.getOutputStream() ); 31: stdout.print("==> "); 32: String sline; 33: while( (sline = stdin.readLine())!= null ) 34: { 35: stdout.println("sending: ["+sline +"]"); 36: out.println( sline ); 37: String rline = in.readLine(); 38: stdout.println("received: ["+rline+"]"); 39: stdout.print("==> "); 40: } 41: stdout.println(""); 42: in.close(); 43: out.close(); 44: sock.close(); 45: } 46: static java.io.BufferedReader stdin = 47: new java.io.BufferedReader( new java.io.InputStreamReader(System.in) ); 48: static java.io.PrintStream stdout = System.out; 49: static java.io.PrintStream stderr = System.err; 50: 51: }
次の通信プロトコルのクライアントを1つ以上作りなさい。(以下の項目には、 各プロトコルを説明したページへのリンクが埋め込まれている。)
まず、telnet で、これらのサーバに接続しなさい。そして、それぞれのプロ トコルに従って、要求を打ち込み、どのような結果が返ってくるかを調べなさ い。次に、telnet で行った要求の送信と結果の受信を行うようなプログラムを 作りなさい。このとき、必要なパラメタは、main() の引数から取りなさい。
注意。それぞれのプロトコルでは、接続先として次のホストを使いなさい。
プログラムをつくる時には、行末の扱い(CR-LF)に注意しないさい。
HTTP, finger, SMTP, NNTP など、インターネットで使われている通信プロト コルの多くでは、行末を表す記号として、キャリッジ・リターン(carrige return,CR)とライン・フィード(Line feed, LF)の両方が必要であると定めら れている。Unix では、通常ライン・フィード(ニュー・ライン、New Line, NL と呼ばれることもある)だけが行末の記号として使われる。よって、画面 に文字列を表示し、改行したい場合は、次のようなプログラムが使われる。
printf("Hello,world\n");しかしながら、インターネットの上のプログラムを作成する時には、次のよう にしなければならないことが多い。
printf("Hello,world\r\n");ここで、'\r' がキャリッジ・リターン、'\n' がライン・フィードである。い ずれも、C言語のソース・プログラム上では2文字に見えるが、Cコンパイラ により、1文字に変換される。それぞれ、アスキーでは、13(0x0d), 10(0x0a) である。
規格上は、CR-LF が必要とされているが、実際にプログラムを作る時には、LF だけでも動くことがある。しかし、それはたまたまその特定のサーバのプログ ラムが良くできていて、LF だけでも動くようになっているからである。
ヒント:最大nまで、fork() して、それぞれ子プロセスで1個のファイルを コピーする。子供が終了したら、次の子供を fork() する。
2005/06/06補足:
% ./time-client host 37Mon May 19 02:49:05 JST 2003 %
![]()
このプログラムでは、TCP/IP でサーバに接続した後、何も送らずにサーバか ら4バイトの数を読み込む。その4バイトの数は、ネットワーク・バイト・オー ダになっているので、ntohl() で、ホストのバイト・オーダに変換する。この 値に、ある値で補正して、Unix で使われているtime_t に変換する。最後に、 strftime() や localtime() でカレンダーの形式に変換する。
RFC868 Time Protocol では、値は、1900年1月1日 0:00 (GMT) を基準にした 秒数を返す。time() システムコールや gettimeofday() システムコールでは、 1970 年を基準にしている。strftime() や localtime() を使う前に、差分を補正する必要がある。
この課題では、ポート番号 37 の time を使いなさい。13 の daytime を使っ てはならない。
接続先のホストとしては、次のどれかを使いなさい。