筑波大学 システム情報工学研究科 コンピュータサイエンス専攻, 電子・情報工学系 新城 靖 <yas@is.tsukuba.ac.jp>
このページは、次の URL にあります。
http://www.coins.tsukuba.ac.jp/~syspro/2009/No6.html
あるいは、次のページから手繰っていくこともできます。
http://www.coins.tsukuba.ac.jp/~syspro/2009/
http://www.coins.tsukuba.ac.jp/~yas/
~yas/syspro/ipc/echo-client-fdopen.c
なら次のようにして
コピーできる。
% cp ~yas/syspro/ipc/echo-client-fdopen.c .
字下げが気に入らない時には、M-x indent-region を使うとよい。region を設 定するには、C-SPC (control + space) でマークを設定して、カーソルを移動 させる。タブ・キーも使えるが、1行ずつしかできない。
カーソル移動には、M-<
やM->
も使
える。
本日の課題では、Java言語でプログラムを作成してもよい。ただし、利用して もよいのは、Socket, ServerSocket, DatagramSocket 等の Socket API に近い レベルのAPI を提供するクラスのみである。高レベルのクラス、たとえば、 HttpURLConnection を用いてはならない。
インターネット上のアプリケーションの多くは、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)との類 推から名前が付けらた。)
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種類に分ける。
図? 通信のパタンからみたクライアントとサーバの定義
クライアント・サーバ・モデルに基づくプログラムには次のようなことを行う プロセスは存在しない。
connect(s); // 接続要求。accept() と対応。 send(s,message); // 要求 receive(s,message); // 応答 send(s,message); // 要求 receive(s,message); // 応答 ... // 必要回数繰り返す close(s); // 接続の切断。注意1:ここで、connect(), send(), receive() は、抽象的な意味。 具体的なシステム・コールの使い方を説明したものではない。
send() や receive() は、複数の具体的なシステム・コールと対応することが ある。たとえば、1回のシステム・コールでは送信できない場合、(ループして) 複数回のシステム・コールを用いることもある。
make_port(a); // 受付端の登録。 while( 1 ) { s=accept(a); // 実際の受付。connect() と対応。 while( !eof(s) ) { receive(s,message); // 要求の受信 send(s,message); // 応答の送信 } close(s); // 接続の切断。 }
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アドレス(IPv4で32ビット/IPv6で128ビットの整数、番号)が 必要になる。IPアドレスは、コンピュータにとって扱いやすいが、人間にとっ て分かりにくい。
人間にとってわかりやすい記号(文字列)を使ったコンピュータの名前から IPアドレスに変換するサービスがあれば便利である。このサービスを、 名前サービス(name service)、 という。 名前サービスを提供するプログラム(プロセス)を、名前サーバという。
名前から名前を指している番号に変換することを 名前解決(name resolution) という。
インターネットで使われている名前サービスは、 DNS(Domain Name System) と呼ばれる。 DNS では、膨大な数のホスト名を含む名前空間を階層的にドメイン(領域)に 分割して管理ている。 この空間の構造は、木構造と同じものでいる。
int tcp_connect( char *server, int portno )
[独自]
server
のポート番号 (portno)
へ
TCPで通信路を開設する。
そのTCPのソケットに対応したファイル記述子を返す。
ホスト名は、DNS を用いて IP アドレスに変換される。
int fdopen_sock( int sock, FILE **inp, FILE **outp )
[独自]
FILE *
を取るようなライブラリ関数(例えば、
fprintf() や fgets() )で行えるようにするためもの。入力は、第1引数のソケッ
ト sock
で、結果の FILE *
を、第2引数と第3引数の
場所へ返す。標準のライブラリ関数 fdopen()
を、入力と出力の両方が
扱えるように拡張したもの。
int snprintf(バッファ,バイト数,書式,...)
[標準]
FILE *
については、
前半第3週「11. ファイルアクセス」
参照。
snprintf()
については、
前半第2週「9. 文字,文字列操作ライブラリ」
参照。
図? marshalingとunmarshaling
4個の要素からなる構造体を整列化して送信している。ネットワーク上を流れている時には、整列化された データの先頭にはネットワークのヘッダが付加されている。
C言語で扱える整数
2バイト、または、4バイトの整数をメモリに保存する方法 : メモリの下位番地に上位バイトを置くか下位バイトを置くか
図? バイト・オーダ
名前 | 方向 | ビット数 |
uint32_t htonl(uint32_t hostlong) | ホストからネットワークへ変換 | 32ビット |
uint16_t htons(uint16_t hostshort) | ホストからネットワークへ変換 | 16ビット |
uint32_t ntohl(uint32_t netlong) | ネットワークからホストへ変換 | 32ビット |
uint16_t ntohs(uint16_t netshort) | ネットワークからホストへ変換 | 16ビット |
unsigned long int hostlong, netlong; hostlong = 0x12345678 ; netlong = htonl( hostlong ); send(conn, &netlong, sizeof(netlong), 0);
送信側:
char buf[BUFSIZE]; hostlong = 0x12345678 ; snprintf(buf,BUFSIZE,"%d\n",hostlong ); send(conn, buf, strlen(buf), 0);思ったほど遅くはない。
注意:sscanf() は、整数をデコードするために使う分には問題ないが、 文字列を受け取るために使うとバッファ・オーバーフローが生じる可能性があるので、 使わない方がよい。
% telnet hostname
以後、ユーザ名とパスワードを打ち、そのホストへログインできる。そしてシェ
ルにより対話的に利用できる。(coins では、telnet による遠隔ログインのサー
ビスを提供していない。)
% telnet hostname portno
以下の例は、echo サービス(ポート番号7番)を提供しているサーバに telnet コマンドをクライアントとして接続している。echo サービスは、送られて来た 文字列(最後に改行)をそのまま送り返すものである。
% egrep '^echo[ ].*/tcp' /etc/services
echo 7/tcp # Echo
% telnet azalea20.coins.tsukuba.ac.jp 7
Trying 130.158.86.40...
Connected to azalea20.coins.tsukuba.ac.jp.
Escape character is '^]'.
hello
hello
exit
exit
quit
quit
aaa
aaa
^]
telnet> quit
Connection closed.
%
注意:coins では、echo サービスを iMac で動作させている。
セキュリティ上の理由から、echo などの、システムプログラムの講義くらいで
しか役に立たないようなサービスを停止することが、最近では一般的である。
% cp ~yas/syspro/ipc/echo-client-fdopen-one.c .
% make echo-client-fdopen-one
cc echo-client-fdopen-one.c -o echo-client-fdopen-one
% ./echo-client-fdopen-one
Usage: ./echo-client-fdopen-one host port 'message'
% ./echo-client-fdopen-one azalea20.coins.tsukuba.ac.jp 7 hello
sending: [hello
]
received: [hello
]
% ./echo-client-fdopen-one azalea20.coins.tsukuba.ac.jp 7 exit
sending: [exit
]
received: [exit
]
% ./echo-client-fdopen-one azalea20.coins.tsukuba.ac.jp 7 quit
sending: [quit
]
received: [quit
]
%
このプログラムは、コマンドラインから3つの引数をとる。第1引数で指
定されたホスト上の、第2引数で指定されたポートで動作しているサーバに接
続する。そして、第3引数で与えられたメッセージをサーバへ送る。echo
サービスのサーバは、同じ文字列を送り返して来る。このプログラムは、サー
バから送り返されてきた文字列を受取り、結果を画面に表示する。
telnet コマンドとは異なり、文字列を1つしか送受信しない。
1: /* 2: echo-client-fdopen-one.c -- 文字列を送受信するクライアント(TCP/IP版) 3: ~yas/syspro/ipc/echo-client-fdopen-one.c 4: Created on: 2009/06/01 21:13:38 5: */ 6: #include <stdio.h> 7: #include <stdlib.h> /* exit() */ 8: #include <string.h> /* memset(), memcpy() */ 9: #include <sys/types.h> /* socket() */ 10: #include <sys/socket.h> /* socket() */ 11: #include <netinet/in.h> /* struct sockaddr_in */ 12: #include <netdb.h> /* getaddrinfo() */ 13: #include <string.h> /* strlen() */ 14: 15: extern int echo_client_one( char *server, int portno, char *message ); 16: extern int tcp_connect( char *server, int portno ); 17: extern int sockaddr_in_init( struct sockaddr_in *addr, int addrlen, 18: char *hostname, int portno ); 19: 20: main( int argc, char *argv[] ) 21: { 22: char *server ; 23: int portno ; 24: char *message ; 25: if( argc != 4 ) 26: { 27: fprintf( stdout,"Usage: %s host port 'message'\n",argv[0] ); 28: exit( -1 ); 29: } 30: server = argv[1] ; 31: portno = strtol( argv[2],0,10 ); 32: message = argv[3]; 33: echo_client_one( server, portno, message ); 34: } 35:main() 関数は、コマンドラインの引数を調べて、echo_client_one() を呼んで いる。第2引数のポート番号については、strtol() で、文字列として与えられ た数を、int に変換している。
36: #define BUFFERSIZE 1024 37: 38: int 39: echo_client_one( char *server, int portno, char *message ) 40: { 41: int sock ; 42: FILE *in, *out ; 43: char sbuf[BUFFERSIZE]; 44: char rbuf[BUFFERSIZE]; 45: 46: sock = tcp_connect( server, portno ); 47: if( sock<0 ) 48: exit( -1 ); 49: if( fdopen_sock(sock,&in,&out) < 0 ) 50: { 51: fprintf(stderr,"fdooen()\n"); 52: exit( 1 ); 53: } 54: 55: snprintf( sbuf, BUFFERSIZE, "%s\n", message ); /* add '\n' */ 56: fprintf( stdout, "sending: [%s]\n",sbuf ); 57: fprintf( out, "%s", sbuf ); 58: fgets( rbuf, BUFFERSIZE, in ); 59: printf("received: [%s]\n", rbuf ); 60: 61: fclose( in ); 62: fclose( out ); 63: }echo_client_one() では、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() を使う必要がある。
このプログラムでは、snprintf() で行末に改行(\n)を付加している。 snprintf() を使わないで、fprintf() で付加する方法もある(この方がコピー が減るので速い)。
fprintf( out, "%s\n", message );
http://www.coins.tsukuba.ac.jp:80/index.html
Firefox などのクライアントは、まずホスト名 www.coins.tsukuba.ac.jp とポート 番号 80 を使ってサーバとの間に TCP/IP の通信路を開設する。そして、クラ イアントは、開設した通信路を使って、サーバに次のような文字列を送る。
GET /index.html HTTP/1.0←↓
←↓
ここで、"GET" が命令の種類、"/index.html" は、GETの引数の、要求してい るデータを表わす URL (ファイル名)、"HTTP/1.0" は、使っているプロトコル のバージョンである。次の空行は、命令のヘッダ部分の終りを意味するもので あり、必要である。「←」は、キャリッジ・リターンのコード(0x0d,C言語で' \r')、「↓」は、ニューラインのコード(0x0a,C言語で'\n')である。HTTP の ヘッダでは、行末に「←↓」を付けるように規定されている。(サーバを構築 する場合には、「←」か「↓」のどちらか1つしかこない場合でもきちんと動 作することが求められている。)
HTTP/1.1 200 OK←↓ Date: Tue, 02 Jun 2009 12:25:47 GMT←↓ Content-Type: text/html←↓ ←↓ (空行) 本文(HTML)応答のうち、最初の行が、状態行(status line)と呼ばれる、要求が成功した か失敗したかわ表わしている行である。"200" とは、成功したという意味であ る(表3参照)。2行目から最初の空行(「←↓」だけの行)までは、応答メッ セージのヘッダである。応答メッセージのヘッダには、データの型や、サーバ のバージョン、データが更新された日付と時刻、バイト数などが記録されてい る。
応答で、最初の空行(「←↓」だけの行)の次が、データの本体である。この 例では、HTMLで記述されたデータが返されている。サーバは、データ転送が完 了すると、TCP/IP の通信路を切断する。
クライアントは、受け取ったデータを整形して利用者に対して表示する。たと えば、インライン・イメージとして指定されたデータを続けてサーバに要求し て展開したり、フォントを変えたりして表示する。
% telnet www.coins.tsukuba.ac.jp 80 Trying 130.158.86.207... Connected to orchid-nwd.coins.tsukuba.ac.jp. Escape character is '^]'. GET /index.html HTTP/1.0↓ ↓ HTTP/1.1 200 OK←↓ Date: Tue, 02 Jun 2009 12:25:47 GMT←↓ Server: Apache/2.0.63 (Unix) PHP/5.2.6←↓ Accept-Ranges: bytes←↓ Connection: close←↓ Content-Type: text/html←↓ ←↓ <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">←↓ <html lang="ja">←↓ <head>←↓ <meta http-equiv="Content-Type" content="text/html; charset=Shift_JIS">←↓ <title>筑波大学 情報学群 情報科学類・情報学類</title>←↓ <META http-equiv="Content-Style-Type" content="text/css">←↓ <META name="情報学群,情報学類,情報科学類,情報学,情報工学,情報科学">←↓ <link href="./99_main.css" rel="stylesheet" type="text/css">←↓ <link href="./99_top.css" rel="stylesheet" type="text/css">←↓ </head>←↓ <body>←↓ <div id="wrapper-top">←↓ ←↓ ... </div>←↓ </body>←↓ </html>←↓
telnet で接続した後に、HTTP の要求メッセージを2行(空行含む)送っている。
それに対して、HTTP/1.1 200 OK
以下が、HTTPの応答である。
表3 HTTPで定義されている状態コードの例
状態コード 説明 -------------------------------------------------------------------- 200 OK(エラーなし) 301 要求されたデータが移動した 302 見つからない 303 別のページを見よ 304 ページは変更されていない 400 要求の形式にエラーがある 401 ページの閲覧が承認されななかった 403 アクセスが許されていない 404 要求されたデータが見つからない 501 メソッドが実装されていない
% telnet サーバ名 80
接続後、キーボードからHTTPに従い要求メッセージを打ち込む。
たとえば、以下の例は、coins のトップページを得るための要求を示す。
% telnet www.coins.tsukuba.ac.jp 80
GET /index.html HTTP/1.0
...
最初の行は、空白で区切られた3つの部分がなること、改行が2個あることに
注意しなさい。要求を打つと、問題がなければ画面には目的の HTML ファイル
が表示される。
coins のトップページは、文字コードとして Shift_JIS を用いている(2009年 6月)。自分で試す時には、端末の文字コードを Shift_JIS にするか、 /index.html 以外で、英語や端末の文字コードと一致しているページを選ぶと よい。たとえば、自分のホーム・ページ(~/public_html/index.html) を アクセスするには、GET に次のようなファイル名を与える。
GET /~ログイン名/index.html HTTP/1.0←↓
←↓
Web サーバとしては、coins 以外のものにも接続してみなさい。また、要求す
るファイルとして、/index.html
以外のものを指定してみなさい。
練習問題(602) wgetコマンド
wget は、URLを引数として取り、その資源をサーバから取得してファイルに保
存するコマンドである。wget コマンドを使ってみなさい。
% wget http://www.coins.tsukuba.ac.jp/index.html
詳しくは、man wget か、wget -h を実行しなさい。
次のオプションを使ってみなさい。
練習問題(601) で、キーボードからどのような文字列を打っ たのかを思い出しなさい。そして、それを printf() で画面に表示するプログ ラムを作成しなさい。
% ./http-print-request-get-index-html
GET /index.html HTTP/1.0
%
% ./http-print-request /index.html
GET /index.html HTTP/1.0
% ./http-print-request /01_compliment.html
GET /01_compliment.html HTTP/1.0
%
% ./http-response-header file.txt
(ここにヘッダ部分が表示される)
%
このプログラムを作成する時に用いるデータは、
wget コマンドを用いて作成す
ることができる。
% wget -s URL -O file.txt
注意:wget の -O
は、大文字である。
小文字-o
は、別の意味がある。
% ./http-response-content file.txt
(ここに本体部分が表示される)
%
wcat コマンドは、次のように3つの引数を与えて利用するものとする。
% ./wcat host port file
(ここに、サーバから取得したテキストが表示される)
%
ここで、host は、ホスト名、port は、TCP/IP のポート番号、file は、得る
べきファイル名である。これは、URL の文法で記述すると、次のようになる。
http://host:port/file
なお、wcat では、ポート番号の引数を省略しないものとする(省略可能なよ うに工夫してもよい)。HTTP プロトコルで用いられる標準のポート番号は、 80である。
プログラム全体の構造は、次のようになる。
空行に続いて、本体を受信する。テキストのみを扱う場合、ヘッダと同じ方法 で受信してもよい。受信したデータは、必ず画面(標準出力)に表示する。
プログラムをつくる時には、できれは 行末の扱い(CR-LF)に注意しないさい。 余裕があれば、受け取ったデータを画面に表示する前に、Unix に合わせて行 末のキャリッジ・リターンのコードを削除するようにしなさい。
% ./wsave host port file localfile
(画面には何も出力されない)
%
最初の3つの引数は、練習問題(607) のwcatと同じである。
最後の引数は、保存するローカル・ファイルである。
このプログラムでは、本体部分ではバイナリデータを扱う必要がある。 fdopen_sock() を使う場合、ヘッダについては、fgets() を使っ てデータを送受信してもよい。しかし、ヘッダが終わった後、本体部分では、 fread() を使う必要がある。
本体をファイルに保存する部分は、 前半第3週/ファイルアクセス(応用) のプログラムと似たものになると思われる。 ただし、コピー元は、ネットワークで、コピー先はファイルになる。 fread() を使う場合には、入力したバイト数(読み込んだ要素の数)を調べ、 そのバイト数の分を fwrite() 等でファイルに出力する。
HTTPの応答は、バッファ・サイズよりも大きくなる可能性がある。1回の fread() では受信できないことがある。そのため、すべてのデータを受信する まで、バッファ単位でループする必要がある。
また、fread() で文字列を読み込んだとしても、最後に 0 (NULL) で終端 されないので、注意しなさい。
NNTPクライアントを作成しなさい。
まず、telnet で、これらのサーバに接続しなさい。そして、それぞれのプロ トコルに従って、要求を打ち込み、どのような結果が返ってくるかを調べなさ い。
次に、telnet で行った要求の送信と結果の受信を行うようなプログラムを 作りなさい。このとき、必要なパラメタは、main() の引数から取りなさい。 NNTP は、テキスト・ベースのプロトコルなので、全ての通信にfprintf() や fgets() を使ってもよい。
この課題では、fgets() でキーボードからデータを読み込むことはしてはなら ない。main() の引数で得られるパラメタ以外で、サーバに送るべきデータは、 プログラムの内部で fprintf() や snprintf() 等を用いて作成すること。たと えば、"GET " や "HTTP/1.0" などは、fprintf() のフォーマット文字列で指定 する方法がある。キーボードや main() の引数として、本来プログラムで生成 すべき文字列を与えてはならない。
接続先として次のホストを使いなさい。
プログラムをつくる時には、 行末の扱い(CR-LF)にも注意しないさい。
ヒント:最大nまで、fork() して、それぞれ子プロセスで1個のファイルを コピーする。子供が終了したら、次の子供を fork() する。
実行速度に差があるので、複数のプロセスに均等にURL をばらまく方法では最 速にはならない。
プログラムを終了した後でもう一度実行したときに続きを行う行うことができ るものだけをこの課題を満たしたと認める。プログラムを終了しないものは、 この課題では中断とは認めない。 この課題ではシグナルでプロセスを中断することは行わない。
wget コマンドは、-c オプションを指定すると、中断した続きから再開する。
% ./time-client host 37
Mon 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 を使ってはならない。
接続先のホストとしては、次のどれかを使いなさい。
次のようなオプションがよく使わせる。
-a
(all)
-n
(number)