TCP/IPプログラミング

■1 目的

この演習では、インターネットで使われている通信規約であるTCP/IPの 利用法について学ぶ。具体的は、NNTPサーバやWWWのサーバと telnet コマンドにより通信を行い、通信形式を調べる。そして、C言語によりクライ アントのプログラムとサーバのプログラムを作成する。

■2 関連科目

この実験項目に関連している科目を、以下に示す。

■3 準備

■3.1 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 だけを考えると、ソケット のインタフェースは、繁雑であり、使いにくくなっている。

■DNS(Domain Name Service)

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

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

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

■3.2 telnet コマンド

telnet コマンドの使い方を調べなさい。

■4 演習(実験)

■4.1 telnet コマンドによるサーバへのアクセス

■4.1.1 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  [←]
--------------------------------------------------------------------

■4.1.2 telnet コマンドによる fingerd へのアクセス

finger コマンドは、利用者の情報を表示するコマンドである。finger コマン ドは、ネットワーク経由で遠隔の利用者の情報を調べることもできる。そのよ うな finger コマンドの利用例を以下に示す。

--------------------------------------------------------------------
% finger shinjo@host1 [←]
  ^^^^^^^^^^^^^^^^^^^
[host1]
Login name: yas                         In real life: Yasushi Shinjo
Directory: /home/admin/yas              Shell: /bin/tcsh
On since Jul  5 12:39:24 on ttyp6 from host2:0.0
42 minutes Idle Time
New mail received Fri Jul  5 13:23:16 1996;
  unread since Fri Jul  5 13:12:49 1996
No Plan.

Login name: yas                         In real life: Yasushi Shinjo
Directory: /home/admin/yas              Shell: /bin/tcsh
On since Jun 28 16:05:46 on ttyp8 from host3:0.0
1 day 0 hours Idle Time
% []
--------------------------------------------------------------------

ここで、下線(^^^^)を付けた部分が、利用者のタイプである。この例で、 finger コマンドは、クライアントとして、ホスト host1 のポート番号 79(finger)のポートに対して、TCP/IP による通信路を開設する。そして、次 のような文字列を送る。

--------------------------------------------------------------------
shinjo↓
--------------------------------------------------------------------

すると、host1 上のサーバ(fingerd, finger daemon)は、上の例で示した情 報をクライアントに返す。サーバは、データ転送が完了すると、TCP/IP の通 信路を切断する。finger コマンド(クライアント)は、受け取ったデータを 整形して利用者に対して表示する。

■課題1:  telnet コマンドによる fingerd へのアクセス

telnet コマンドを使って、finger サーバにアクセスして、自分の情報を検索 してみなさい。アクセス先のホストとしては、host1-host5, cw01-cw20, design1, design2 の中から選びなさい。その様子を、レポートに添付しなさ い。アクセスの様子の記録には、script コマンドや emacs 中の M-x shell を使うとよい。script コマンドの結果は、cleanscript などを使って整える こと。

注意:この課題は、ログインすることなく遠隔ホスト上の fingerd と直接対 話するものである。telnet コマンドにり、ログインしてfinger コマンドを実 行するのではない。

■4.1.3 telnet コマンドによる WWW サーバへのアクセス

WWW (the World-Wide Web)では、TCP/IP の上にさらに HTTP (HyperText Transfer Protocol)と呼ばれるプロトコルを構築し、データの転送を行ってい る。Mosaic や Lynx などのブラウザは、WWW サーバとの間に TCP/IP による 通信路を開設する。そして、クライアントは、必要なデータを得るための命令 を送る。これに対してサーバは、命令に応じた処理を行い結果を返す。この命 令の形式や結果の形式を定めたものが、HTTP である。HTTP 通信プロトコルを 受け付けるサーバを、HTTP サーバと呼ぶ。

表2に、HTTP で定義されている命令(メソッド)の例を示す。これらの命令 に対して、サーバは、表3に定義されたような応答を行う。

表2 HTTPで定義されている命令(methods)の例

--------------------------------------------------------------------
命令		説明
--------------------------------------------------------------------
GET		情報を得る(ヘッダと本体の両方)
HEAD		情報のヘッダのみを得る
POST		新しく情報を作る
--------------------------------------------------------------------

表3 HTTPで定義されている状態コードの例

--------------------------------------------------------------------
状態コード	説明
--------------------------------------------------------------------
200		OK(エラーなし)
301		要求されたデータが移動した
400		要求の形式にエラーがある。
404		要求されたデータが見つからない。
--------------------------------------------------------------------

たとえば、次のような URL を持つデータをアクセスすることを考える。

http://www.aaa.bbb.ac.jp:80/index.html

Mosaic などのクライアントは、まずホスト名 www.aaa.bbb.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.0 200 OK←↓
Date: Friday, 05-Jul-96 04:45:58 GMT←↓
Server: NCSA/1.3←↓
MIME-version: 1.0←↓
Content-type: text/html←↓
Last-modified: Wednesday, 14-Feb-96 04:08:21 GMT←↓
Content-length: 699←↓
←↓dnl

<title>Welcome to IISE WWW server</title>

<IMG ALIGN=top SRC="/images/ISE.gif">
<P>

<h1>
筑波大学電子・情報工学系へようこそ!
</H1>

<P>
<A HREF="English/index.html">English Version here.</A>

<hr>

ここは筑波大学  電子・情報工学系のホームページです。

<H1>
<UL>
<LI><A HREF="http:IISE-general.html">電子・情報工学系の紹介</A>
<P>
<LI><A HREF="http:IISE-profs.html">学系教官</A>
<P>
<LI><A HREF="http:IISE-labs.html">学系内研究室</A>
<P>
<LI>大学院案内
<UL>
<LI><A HREF="http:KOUGAKU/index-j.html">工学研究科</A>
<LI><A HREF="http:RIKOUGAKU/index-j.html">理工学研究科</A>
</UL>
</UL>
</H1>

<hr>
本ページに関するお問い合わせは以下のアドレスにお願いします。
<address>www@is.tsukuba.ac.jp</address>
--------------------------------------------------------------------

最初の行が、状態行(status line)と呼ばれる、要求が成功したか失敗した かわ表わしている行である。"200" とは、成功したという意味である(表3参 照)。2行目から最初の空行(「←↓」だけの行)までは、これから送るデー タのメタ情報である。具体的には、データの型や、サーバのバージョン、デー タが更新された日付と時刻、バイト数などが記録されている。

最初の空行の次が、データの本体である。この例では、HTMLで記述されたデー タが返されている。サーバは、データ転送が完了すると、TCP/IP の通信路を 切断する。

クライアントは、受け取ったデータを整形して利用者に対して表示する。たと えば、インライン・イメージとして指定されたデータを続けてサーバに要求し て展開したり、フォントを変えたりして表示する。

■課題2: telnet コマンドによる WWW サーバへのアクセス

telnet コマンドを使って、WWW サーバにアクセスし、データを画面に表示さ せなさい。そのデータの URL とデータの先頭の20行程度を報告書に添付しな さい。

■4.1.4 telnet コマンドによる NNTP サーバへのアクセス

NNTP(Network News Transfer Protocol) とは、ネットワーク・ニュースの記 事の転送や、記事の読み書きを行うためのプロトコルである。mnews や GNUS などのネットワーク・ニュースを読み書きするソフトウェアは、クライアント として NNTP サーバとの間に TCP/IP による通信路を開設する。そして、クラ イアントは、記事を要求する文字列や、ニュース・グループの一覧を要求する コマンドをサーバに送る。これに対してサーバは、要求された記事やニュース・ グループの一覧をクライアントに返す。表4に、クライアントからサーバへ送 られるNNTPのコマンド、表5に、サーバからクライアントへ返される応答を示 す。

表4 NNTPのコマンド

--------------------------------------------------------------------
GROUP	ニュース・グループ名
	ニュース・グループを選択する。結果として、記事の数、記事の番号
	の上限と下限が返される。

ARTICLE 記事番号
	その記事の内容を得る。ニュース・グループが選択されている状態の
	時に使える。

ARTICLE 
	メッセージIDの記事の内容を得る。
HELP
	ヘルプ・メッセージの表示
QUIT
	終了
POST
	記事を投稿する。
--------------------------------------------------------------------

表5 NNTPの応答

--------------------------------------------------------------------
応答コード	説明
--------------------------------------------------------------------
100		ヘルプのテキストが続く。
200		要求受け付け可能である(投稿可)。
201		要求受け付け可能である(投稿不可)。 
205		通信路を切断する。
211		ニュース・グループが選ばれた。
		記事の数、記事番号の上限、下限、ニュース・グループ名。
400		サービスを中断する。
411		そのようなニュース・グループがない。
421		もうそのニュース・グループには次の記事がない。
500		コマンドが認識できなった。
501		コマンドの文法に誤りがあった。
502		アクセスが制限されている。
--------------------------------------------------------------------

以下に、telnet コマンドを利用して、NNTP サーバに接続した様子を示す。

----------------------------------------------------------------------
% telnet newshost nntp [←]
Trying XXX.YYY.ZZZ.UUU ...
Connected to newshost.
Escape character is '^]'.
200 newshost NNTP[auth] server version 1.5.11 (31 January 1991) ready at Fri Jul  5 15:58:07 1996 (posting ok).
help[←]
100 This server accepts the following commands:
ARTICLE     BODY         GROUP
HEAD        LAST         LIST
NEXT        POST         QUIT
STAT        NEWGROUPS    HELP
IHAVE       NEWNEWS      SLAVE

Additionally, the following extention is supported:

XHDR        Retrieve a single header line from a range of articles.
XHIST       Retrieve history file.
XMIME       Control MIME  article handling.

Bugs to Stan Barber (Internet: nntp@tmc.edu; UUCP: ...!bcm!nntp)
.
quit[←]
205 newshost closing connection.  Goodbye.
Connection closed by foreign host.
% []
----------------------------------------------------------------------
ここで、強調で示した部分が、キーボードからのタイプである。 この例では、ホスト newshost のポート番号119(nntp)のポートに、 TCP/IPにより接続を試みている。2行目から4行目は、telnet コマンドによる 定型的な表示である。通信路が開設されると、サーバは、 "200" という応答を返している。これは、NNTP で定義されて いる応答であり、サーバが、要求を受け付け可能であり、かつ、要求としては 投稿要求(POST)も受け付けることを意味している。"200" 以 降の文字列は、コメントである。

第6行では、"help" というコマンドをサーバに送っ ている。これに対して、サーバは、"100" という応答に続けて、 受け付け可能なコマンドなど、簡単な使い方を返している。23行目に "." からなる行がある。これが、1つのコマンドに対する応答 の終りを示している。

次に、24行において、"quit" というコマンドをサーバに送っている。これに たいして、サーバは、205 という応答を返し、続いて TCP/IP の通信路を切断 している。26行目は、telnet コマンドが生成したメッセージである。

■課題3: telnet コマンドによる NNTP サーバへのアクセス

telnet コマンドを使って、NNTP サーバ $NNTPSERVER にアクセス し、ネットワーク・ニュースの記事を画面に表示させなさい。その記事のニュー ス・グループ、ニュース・グループ内の番号、記事の先頭の20行程度を報告書 に添付しなさい。このとき、表4に示したコマンド、GROUP と ARTICLE を使 うとよい。

注意すべきこととして、GROUP コマンドでは、ニュース・グループ名を一度に 与えることがあげられる。たとえば、ura.comp.sys.mac というニュース・グ ループならば、次のように、一度に全部のニュース・グループ名を与える。

GROUP comp.sys.mac
次のように、部分的に与えることはできない。
GROUP comp
GROUP sys
GROUP mac
vin や mnews では、ニュース・グループを階層構造を持つものとして利用者 に提示している。しかしながら、NNTP のレベルにおいては、そのような階層 構造は存在しない。

■課題4: 他のNNTPコマンドの利用

その他の NNTP のコマンドを使ってみなさい。NNTP の定義は、RFC977 という ドキュメントにある。RFC977は、/usr/open/doc/rfc/rfc977.txt にある。

■4.1.5 telnet コマンドによる SMTP サーバへのアクセス

SMTP (Simple Mail Transfer Protocol) とは、電子メールの転送を行うため のプロトコルである。このプロトコルは、MTA (Mail Transfer Agent) と呼ば れるプログラムの間で電子メールを配送したり、MTA と MUA (Mail User Agent) と呼ばれるプログラムの間で電子メールを差し出す時に使われる。MTA の例としては、UNIX 上で動作するsendmailと呼ばれるプログラムがあげられ る。MTA は、一方が SMTP サーバ、もう一方が SMTP クライアントとなり、電 子メールを転送する。表6に、SMTPで定義されている要求、表7に応答を示す。

表6 SMTPで定義されている手続き

--------------------------------------------------------------------
MAIL FROM:
	新しいメールの転送を開始する。
	は、エラーが起きた時の送り返し先のアドレス。


RCPT TO:
	メールの送り先として  を指定する。

DATA
	メールのデータを送り始める。

VRFY	
	メールの受け手を確認する。

EXPN	
	メーリング・リストの受取人を表示する。

SEND FROM:
	メールを転送する。

HELO 
	最初に接続した時に自分自身を相手に知らせる。

QUIT
	接続を切る。
--------------------------------------------------------------------

表7 SMTPの応答コード

--------------------------------------------------------------------
コード	説明
--------------------------------------------------------------------
220	 というドメインで要求受け付け可能である。
250	そのアドレスへのメールは、受け付け可能である。
251	そのアドレスは、ローカルには受取人がいない。
--------------------------------------------------------------------

以下に、telnet コマンドを利用して、SMTP サーバ(sendmail)に接続した様 子を示す。

--------------------------------------------------------------------
  1: % telnet host1 smtpYHM_CR()
       ^^^^^^^^^^^^^^^^^^
  2: Trying XXX.YYY.ZZZ.UUU ...
  3: Connected to host1.
  4: Escape character is '^]'.
  5: 220 aaa.bbb.ccc.ac.jp Sendmail XXXXX ready at Fri, 8 Sep 1995 00:25:05 +0900
  6: vrfy root[←]
     ^^^^^^^^^^^
  7: 250 <root@aaa.bbb.ccc.ac.jp>
  8: quit[←]
     ^^^^^^
  9: 221 aaa.bbb.ccc.ac.jp closing connection
 10: Connection closed by foreign host.
 11: % []
--------------------------------------------------------------------

ここで、下線(^^^^)で示した部分が、キーボードからのタイプである。この 例では、ホスト host1 のポート番号 25 (smtp) のポートに、TCP/IPにより接 続を試みている。2行目から4行目は、telnet コマンドによる定型的な表示で ある。ここで、通信路が開設されている。すると、サーバは、220 という応答 を返している。これは、SMTP で定義されている応答であり、サーバが、コマ ンド要求を受け付け可能であることを意味している。

第6行では、"vrfy" (verify) というコマンドをサーバに送っている。これに 対して、サーバは、250 という応答に続けて、電子メールのアドレスを返して いる。

次に、8行において、"quit" というコマンドをサーバに送っている。これに対 してサーバは、221 という応答を返し、続いて TCP/IP の通信路を切断してい る。10行目は、telnet コマンドが生成したメッセージである。

■課題5:  telnet コマンドによる SMTP サーバへのアクセス

telnet コマンドを使って、自分の電子メールを蓄えているホストにアクセス し、自分のログイン名と存在しないログイン名について、VRFY で調べてみな さい。

■課題6: SMTP によるメール発信の観察

自分の電子メールを蓄えているホストと、そうではないが電子メールを発信す ることができるホストで、mail コマンドに -v オプションを付けてメールを 送り、その様子を観察しなさい。すると、SMTP による電子メールの発信の様 子が観察される。ただし、mail -v では、電子メールのデータ(DATAの部分) は、表示されない。ここでデータとは、To: 行や From: 行などのヘッダも含 む。SMTP のレベルにおいては、データの部分の To: 行は、配送には使われな い。その代わりに、SMTP で定義されている RCPT が使われる。

■課題7: SMTPのよるメール発信

SMTPを使って電子メールを送ってみなさい。SMTP の定義は、RFC821 というド キュメントにある。RFC821は、/usr/open/doc/rfc/rfc821.txt にある。mail -v の結果を参考にするとよい。DATA は、To: 行を含めることを忘れないよう にしなさい。

■4.2 クライアントの作成

この節では、4.1節において telnet コマンドで行ったことを、C言語のプロ グラムで実現する。TCP/IP のプログラミングを簡単にするために、stream ラ イブラリを用いる。

■4.2.1 initport() と fdopen2()

■streamライブラリ

この実験では、Don Libes 氏により開発された stream ライブラリを用いる。 これは、UNIX 上で TCP/IP を使うプログラムを書きやすくするためのライブ ラリである。このライブラリを使うことで、UNIX固有の繁雑なソケットを利用 することなく、もともとの TCP/IP のモデルに近い形でTCP/IPを利用するプロ グラムを書くことが可能になる(3.1節の図3参照)。

■initport()

stream ライブラリでは、initport() という関数が、中心的な役割を果たす。 initport() は、TCP/IP のクライアント側(接続を行う方)でも、サーバ側 (接続されるのを待つ方)でも使われる。この項では、クライアント側の使い 方を示す。

initport() は、クライアント側では、次の2種類の形式で使われる(3.1節の 図3(b)参照)。


--------------------------------------------------------------------
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include "inet.h"

int s = initport(PORT_NUMBER(port), CLIENT, SOCK_STREAM, host);
int s = initport(PORT_NAME(name), CLIENT, SOCK_STREAM, host);
int port ;
char *name ;
char *host;
--------------------------------------------------------------------

前者は、ポート番号を指定して、TCP/IP の通信路を開設するものである。重 要な引数は、サーバ側の要求受付用ポートのポート番号 port と、ホスト名 host である。その他の引数 CLIENT と SOCK_STREAM は、定数である。これら は、クライアント側で使う限り、必ず指定されなければならない。

initport() は、結果として、1つのファイル記述子 s を返す。これは、 TCP/IP の双方向ストリームに対応している。よって返されたファイル記述子 に対して次のように write() システム・コールでデータを書き込むと、その データは、接続先のプロセスに送り届けられる。

write( s, buf, size );
逆に、接続先のプロセスから送られてきたデータは、read() システム・コー ルにより受け取ることができる。
read( s, buf, size );
このように、同じファイル記述子に対して、read() と write() の両方を利用 することができる。

もう1つの形式は、ポート番号の代わりにポート名(サービス名)を指定する 方法である。たとえば、"telnet" というサービスの場合、次のようにして使 うことができる。

s = initport(PORT_NAME("telnet"), CLIENT, SOCK_STREAM, host);
この形式では、initport() は、/etc/services を参照して、与えられたポー ト名をポート番号に変換して利用する。

いずれの形式においても、エラーが生じると、initport() は、-1 を返す。

なお、ネットワーク・プログラミングの教科書の多くは、ソケットを直接利用 するように書かれている。この実験においても、stream ライブラリを使うこ となく、直接ソケットを利用してプログラムを作成してもよい。

■fdopen2() fdopen2() は、この実験のために標準のライブラリ関数 fdopen() を拡張して 作っ関数である。標準の fdopen() は、open() システム・コールなどで開い たファイルについても、fprintf(), fgets(), fputs() などのバッファリング 機能付の入出力関数を使いたい時に使われる。以下に利用例を示す。

--------------------------------------------------------------------
FILE *f ;
	fd = open("file1",O_RDONLY);
	....
	f = fdopen(fd, "r")
	fgets( buff,sizeof(buff),f );
--------------------------------------------------------------------

これにより、open() で開いたファイルについても、バッファリング機能付の 入出力関数が使えるようになる。fdopen() は、open() 以外にも、pipe() や dup() の結果にも使われる。

fdopen2() は、TCP/IPの通信用ポートに対応しているのように、入出力可能な ファイル記述子について、2つの入出用と出力用の FILE * を返す。

--------------------------------------------------------------------
FILE *fin, *fout ;
	fd = initport(PORT_NAME("finger"),CLIENT,SOCK_STREAM,host);
	...
	fdopen2(fd,&fin,&fout)
	fprintf( fout, "%s\n", who );
	fflush( fout );
	fgets( buff, sizeof(buff), fin );
--------------------------------------------------------------------
fdopen2() の第1引数 fd には、initport() や connect(), accept() で作ら れた入出力可能(read()システム・コールもwrite() システム・コールも使え る)ファイル記述子を指定する。結果として作られた FILE * は、第2、第3 引数として与えられた FILE * へのポインタで示された番地に格納される。 fdopen2() は、エラーが起きた時には、0, 成功した時には、0 以外が返され る。

fdopen2() のソース・プログラムは、 ここ にある。

■コンパイルとリンク

initport() を使うプログラムは、コンパイル時に "inet.h" というヘッダ・ ファイルを読み込まなければならない。これは、/home/hlla/yas/export/inet/include に置かれ ている。よって、コンパイル時に、cc コマンドに次のようなオプションを与 える必要がある。

% cc -I/home/hlla/yas/export/inet/include -c file.c

また、initport() などのライブラリ関数は、コンパイルされ次の ファイルに格納されている。

/home/hlla/yas/export/inet/lib/sun4/libinet.a

これを利用するためには、リンク時に次のように指定する(Sun4の場合)。

% cc -o commmand file.o /home/hlla/yas/export/inet/lib/sun4/libinet.a [←]
または、次のように -L, -l オプションを用いて指定する。
% cc -o commmand file.o -L/home/hlla/yas/export/inet/lib/sun4 -linet [←]

以下の演習で使うコマンドについては、次の Makefile にコンパイルとリンク のための設定が格納されている。

/home/hlla/yas/export/inet/examples/Makefile

■4.2.2 finger クライアントのコンパイルと実行

遠隔のfingerサーバに対して問い合わせを行うコマンド、rfinger (remote finter)のソース・プログラムを、 ここ に示す。

■課題8: rfinger.c のコンパイルと実行

以下のように、rfinger.c をコンパイルして、実行してみなさい。

--------------------------------------------------------------------
% cp /home/hlla/yas/export/inet/examples/{rfinger.c,Makefile} . [←]
% make rfinger [←]
% ./rfinger host1 $USER [←]
--------------------------------------------------------------------

報告書には、自分の情報を rfinger で調べた結果を示しなさい。ログイン名 で引いた時と、finger name として設定している文字列で引いたときの結果を 示しなさい。もし、finger name をまだ設定していないならば、chfn コマン ドで設定しなさい。このとき、設定の前後で、表示や検索の様子がどう変わる かを調べて報告しなさい。

■4.2.3 HTTP クライアントの作成

■課題9: HTTPクライアントの作成

HTTPサーバ(httpd, WWWサーバ)からデータを得るプログラムを作りなさい。そ のプログラムの名前を、wcat とする。wcat のプログラムの骨組みを /home/hlla/yas/export/inet/examples/wcat.c に示す。これを以下のようにコピーして、使い なさい。

--------------------------------------------------------------------
% cp /home/hlla/yas/export/inet/examples/{wcat.c,Makefile} . [←]
% make wcat [←]
% []
--------------------------------------------------------------------
wcat コマンドは、次のように3つの引数を与えて利用するものとする。
--------------------------------------------------------------------
% ./wcat host port file [←]
--------------------------------------------------------------------
ここで、host は、ホスト名、port は、TCP/IP のポート番号、file は、得る べきファイル名である。これは、URL の文法で記述すると、次のようになる。

http://host:port/file

なお、wcat では、ポート番号の引数を省略しないものとする(省略可能なよ うに工夫してもよい)。HTTP プロトコルで用いられる標準のポート番号は、 80である。

wcat のプログラムを作る時に、4.2.2 で示した rfinger.c を参考にするとよ い。initport() では、ポート名(サービス名)ではなく、次のようにポート 番号を用いる形式を利用するとよい。

--------------------------------------------------------------------
	fd = initport(PORT_NUMBER( port), CLIENT, SOCK_STREAM, host);
--------------------------------------------------------------------

HTTP では、ヘッダ部分の行末に、キャリッジ・リターン(carrige return,CR) とライン・フィード(Line feed, LF)の両方が必要であると定められている。 UNIX では、通常ライン・フィード(ニュー・ライン、New Line, NL と呼ばれ ることもある)だけが行末の記号として使われる。よって、画面に文字列を表 示し、改行したい場合は、次のようなプログラムが使われる。

printf("Hello,world\n");
これは、HTTP では、次のようにしなければならない。
printf("Hello,world\r\n");
ここで、'\r' がキャリッジ・リターン、'\n' がライン・フィードである。い ずれも、C言語のソース・プログラム上では2文字に見えるが、Cコンパイラ により、1文字に変換される。それぞれ、アスキーでは、13(0x0d), 10(0x0a) である。 余裕があれば、画面に表示する前に、UNIX に合わせて行末のキャリッジ・リ ターンのコードを削除するようにしなさい。

■4.2.4 NNTP クライアントの作成

■課題10: NNTP クライアントの作成

NNTPサーバから記事を1つ得るプログラムを作りなさい。そのプログラムの名 前を、nncat とする。nncat のプログラムの骨組みを /home/hlla/yas/export/inet/examples/nncat.c に示す。これを以下のようにコピーして、使い なさい。

--------------------------------------------------------------------
% cp /home/hlla/yas/export/inet/examples/{nncat.c,Makefile} . [←]
% make nncat [←]
% []
--------------------------------------------------------------------
nncat コマンドは、次のように3つの引数を与えて利用するものとする。
--------------------------------------------------------------------
% ./nncat host newsgroup number [←]
--------------------------------------------------------------------
ここで、host は、NNTPサーバが動いているホストの名前、newsgroup は、ニュー ス・グループ、number は、記事番号である。NNTP プロトコルで用いられる標 準のポート番号は、119(nntp) である。initport() では、PORT_NAME("nntp") の形式を利用するとよい。

報告書には、作成した nncat コマンドの動作例を示しなさい。

NNTP でやり取りされるデータの行末には、キャリッジ・リターン (carrigereturn, CR)とライン・フィード(Line feed, LF)の両方が必要であ ると定められている。UNIX では、通常ライン・フィード(ニュー・ライン、 New Line,NL と呼ばれることもある)だけが行末の記号として使われる。よっ て、画面に文字列を表示し、改行したい場合は、次のようなプログラムが使わ れる。

printf("Hello,world\n");
これを、NNTP では、次のようにしなければならない。
printf("Hello,world\r\n");
ここで、'\r' がキャリッジ・リターン、'\n' がライン・フィードである。い ずれも、C言語のソース・プログラム上では2文字に見えるが、Cコンパイラ により、1文字に変換される。それぞれ、アスキーでは、13(0x0d), 10(0x0a) である。 余裕があれば、画面に表示する前に、UNIX に合わせて行末のキャリッジ・リ ターンのコードを削除するようにしなさい。

■4.2.5 SMTP クライアントの作成

■課題11: SMTP クライアントの作成

SMTPサーバに対して、VRFY 手続きを送り、受取人のアドレスを確認するプロ グラムを作りなさい。そのプログラムの名前を、mverify とする。mverify の プログラムの骨組みを/home/hlla/yas/export/inet/examples/mverify.c に示 す。これをコピーして、使いなさい。

--------------------------------------------------------------------
% cp /home/hlla/yas/export/inet/examples/{mverify.c,Makefile} . [←]
% make mverify [←]
% []
--------------------------------------------------------------------

mverify コマンドは、次のように2つの引数を与えて利用するものとする。

--------------------------------------------------------------------
% ./mverify host who [←]
--------------------------------------------------------------------
ここで、host は、SMTPサーバ(sendmailデーモン)が動いているホストの名前、 who は確認するアドレスである。SMTPプロトコルで用いられる標準のポート番 号は、25(smtp) である。initport() では、PORT_NAME("smtp") の形式を利用 するとよい。

報告書には、作成した mverify コマンドの動作例を示しなさい。SMTPサーバ (sendmail daemon)が動いているホストとしては、自分が利用しているメール・ サーバを指定しなさい。そして、次のように自分のアドレスを確認してみなさい。

--------------------------------------------------------------------
% ./mverify host1 $USER [←]
--------------------------------------------------------------------

■4.3 HTTPサーバの機能拡張

4.2節では、HTTP, NNTP, または、SMTP のクライアントのプログラムを作成し た。この節では、簡単な HTTP サーバのプログラムを解読し、機能拡張を行う。

■4.3.1 initport()(サーバ側)

4.2.1項では、streamライブラリのinitport()関数のうち、クライアント側の 使い方を示した。ここでは、サーバ側の使い方を示す(3.1節図3(a)参照)。 サーバ側では、initport() は、次のような形式で使われる。

--------------------------------------------------------------------
#include <sys/types.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include "inet.h"

int s = initport( PORT_NUMBER( port ), SERVER, SOCK_STREAM );

int port ;
--------------------------------------------------------------------

これにより、サーバ側に要求受付用ポートが作られる(3.1節の図3(a)参 照)。そのポート番号は、引数portにより指定される。initport()に対するそ の他の引数 (SERVER, SOCK_STREAM) は、定数である。これらは、サーバ側で 使う限り、必ず指定されなければならない。

initport() は、結果として、1つのファイル記述子 s を返す。これは、 TCP/IP のサーバ側における要求受付用ポートと対応している。要求受付用ポー トとは、ポート番号を保持し、クライアントからの接続要求を受け付けるため のものである。このポートを通じて、データを送受することはできない。すな わち、このファイル・記述子に対して、read() システム・コールや write() システム・コールにより、入出力を行うことができない。実際に入出力を行う ためのポート(通信用ポート)は、次の select_server_stream() によって得 られる。


--------------------------------------------------------------------
int client = select_server_stream( s, &fds );
int s ;
fd_set fds ;
--------------------------------------------------------------------

select_server_stream() の引数 s は、initport() により返されたファイル 記述子である。fdsは、select_server_stream() 内部で複数の接続扱うための 作業用変数である。fd_set とは、ファイル記述子の集合(配列)を保持する ためのビット配列である。これは、最初に FD_ZERO() により初期化されなけ ればならない。それ以後は、select_server_stream() により管理される。

select_server_stream() は、クライアントからの接続要求が来るか、クライ アントからのデータが到着するまで待つ。クライアントからの接続要求が来る と、それを受け付ける。その結果として、通信用ポートが作られる。そして、 最初のデータがクライアントから送られて来た時に、通信用ポートのファイル 記述子を返す。

select_server_stream() により返されるファイル記述子は、クライアントか らのデータが到着しており、入出力可能なTCP/IPの通信ポートに対応している。 サーバの仕事は、この通信ポートから要求を読みだし、それに応じた処理を行 い、結果をこの通信ポートに書き出すことである。

select_server_stream() は、1つのサーバ・プロセスで複数のクライアント を同時に扱う事を可能にする。サーバ・プロセスは、複数のクライアントの間 に、複数のTCP/IPの接続を同時に保持することができる。そして、それらの接 続の中で、データが届いたものを選び、返す機能がある。

なお、ネットワーク・プログラミングの教科書の多くは、ソケットを直接利用 するように書かれている。この実験においても、stream ライブラリを使うこ となく、直接ソケットを利用してプログラムを作成してもよい。

■4.3.2 httpd-simple

ここでは、簡単な HTTP サーバ(WWWのサーバ)を例に、TCP/IPのサーバのプ ログラムの概要を示す。サーバのソース・プログラムを、次のファイルに示す。

/home/hlla/yas/export/inet/examples/httpd-simple.c

関数usage()は、このプログラムの利用方法を表示する関数である。

関数main()は、引数とオプションの解析を行う。

演習では、大勢の人が同時にプログラムを作る。よって、各自重ならないよう なポート番号を使わなければならない。この実験では、getuid() + 10000 を 使うことにする。もしそのポート番号が、使われていたら、initport() の中 の bind() システム・コールで次のエラーが出る。

--------------------------------------------------------------------
#define	EADDRINUSE	48		// Address already in use
--------------------------------------------------------------------
その時は、前に走らせた自分のプロセスがいないか、調べなさい。もし自分の プロセスが、そのポートを使っていれば、^Cやkill コマンドなどで殺しなさ い。もし、他の人がそのポート番号を使っていたら、-p オプションで空いて いるポート番号を指定し別のポート番号を使うか、他のホストにログインして そこで指定されたポート番号を使うようにしなさい。

getopt() は、オプションを解析するためのC言語のライブラリ関数である。 getopt() の引数 "p" は、-p オプションを受け付けるという意味である。pに 続くコロン(":")は、-p string の形で、オプションを受け付けるという意 味である。getopt() は、オプションを見つけると、リターン・バリューとし てそれを返す。その時、受け付ける文字列は、大域変数 optarg にセットされ る。

atoi() は、文字列(ascii)を、整数に変えるC言語のライブラリ関数である。

getopt() によるオプションの解析が終了した時、optind に、最初の引数(オ プションではなく必須の文字列)の添え字がセットされている。これを、argc と比べて、引数の数が得られる。httpd-simple は、引数としてディレクトリ 名を1つ必要とする。

関数 httpd_simple()は、このサーバの主要部分である。

chdir() システム・コールにより、カレント・ワーキング・ディレクトリを、 指定されたディレクトリに移す。移動できない時には、perror() ライブラリ 関数により、エラーメッセージを表示して、プログラムを終了する。

print_my_url() は、受付可能な URL を表示する。print_my_url()関数の本体 は、135行目にある。

FD_ZERO()は、select_server_stream() のための変数 fds を初期化する。

while ループは、サーバのメイン・ループである。サーバ・プロセスは、 一般に、このように決して終了することがないプログラムである。サーバ・プ ロセスのプログラムには、このように無限ループが1つある。これを、メイン・ ループという。

select_server_stream() は、内部で自動的にクライアントからの接続要求を 受け付ける(accept())。すなわち、クライアントとの間に通信路が開設され る。このとき、通信用ポートが1つ作られる。select_server_stream() は、 クライアントからデータが送られてくると、その通信ポートに対応したファイ ル記述子を返す。

select_server_stream() のリターン・バリューは、変数 client に格納され る。これは、通信用ポートに対応している。これを引数として、1つのクライ アントについて要求を処理する関数 perform() を呼ぶ。

TCP/IPの通信路は、perform() により切断される。すなわち、通信用ポートは、 関数 perform()の内部で破棄される。このように、HTTPでは、1つの要求の完 了ごとに通信路が切断される。

perform() は、1つのクライアントについて要求を処理する関数である。引数 は、通信用ポートに対応しているファイル記述子である。このファイル記述子 に対して、read(), write() システム・コールを発行することにより、クライ アントと通信を行うことができる。

関数 print_client() を呼び出し、クライアントの情報を表示する。関数 print_client() のプログラムは、144行目にある。

関数fdopen2() により、ファイル記述子から、FILE *を得る。これにより、 fgets(), fprintf() といった標準入出力ライブラリ関数を利用して、プロセ ス間通信を行うことが可能になる。fdopen2() については、4.2.1節を参照し なさい。

fgets()により、クライアントからの要求の最初の行を読む。この行には、最 も大事な情報であるファイル名が含まれている。

sscanf() により、ファイル名を抜き出す。sscanf() は、scanf() と同じよう な動きをする。scanf() の場合、標準入力から形式()に従って入力を行 う。sscanf() は、標準入力ではなく、文字列(string)から形式に従って入力 を行う。sscanf() の引数は、文字列の先頭番地である。

strstr() により、受けとったファイル名に "/../" が含まれていないか調べ ている。含まれていた場合は、エラーにする。また、先頭が "/" で始まって いない場合にも、エラーにする。

成功した場合、ここでクライアントに結果を返している。最初の fprintf() にある 200 は、表3に示されている応答のコードである。"\r\n"より上が HTTP のヘッダ、"\r\n"より下が、内容である。ヘッダでは、内容の型 (Content-Type)が、普通のテキスト(text/plain)であることを返している。 このプログラムでは、内容として得られたファイル名を返している。

もし実際にファイルの内容を返す時には、"\r\n"の次に、ファイルの内容を続 ければよい。すなわち、ここでそのファイルを、fopen() して、fread() する とよい。ただし、アクセスするファイルの先頭には、"./" を付けて、カレン ト・ワーキング・ディレクトリ以下のファイルしかアクセスを許さないように しなさい。たとえば、"/index.html" というファイルが要求された場合、 実際には、次のようなファイルを開くようにしなさい。

	fopen("./index.html","r");

さらに、ファイルの内容によって、Content-Type: を変える必要がある。たと えば、HTML ならば、Content-Type: text/htmlとする必要がある。

2つの fclose() により通信路が切断される。成功した場合、0を返す。(た だし、呼び出し側は、この値を参照していない。)

error: ラベル以下は、エラーが発生した時に、内容として、HTML でエラーメッ セージを返している。

print_my_url()は、自分自身が受け付けることができる URL の形式を表示す る関数である。 gethostname() は、ホスト名を得るシステム・コールである。

print_client() は、クライアントの情報を返す関数である。引数は、通信用 ポートに対応したファイル記述子である。getpeername()は、通信相手の名前 を返すシステム・コールである。TCP/IP における名前とは、ホストのIPアド レスとポート番号である。それは、sockaddr_in 構造体の sin_addr.s_addr と sin_port に設定される。

print_ip_address()は、IPアドレスを、点つき10進数で表示する関数である。 ここでは、共用体を利用して32ビットの整数を8ビットずつ区切ってアクセス している。

■課題12: httpd-simple のコンパイルと実行

以下のように、httpd-simple.c をコンパイルして、実行してみなさい。

--------------------------------------------------------------------
% cp /home/hlla/yas/export/inet/examples/{httpd-simple.c,Makefile} . [←]
% make httpd-simple [←]
% []
--------------------------------------------------------------------

実行する時には、ウィンドウを2つ用意しなさい。1つのウィンドウで、次の ように http-simple を実行しなさい。

--------------------------------------------------------------------
% ./http-simple ~/public_html/ [←]
http://host1:13520/
--------------------------------------------------------------------

ここで、"~/public_html/" は、このサーバが提供するデータを含んでいるディ レクトリを指定する。あらかじめ作成しておくこと。ここで、 http-simple が表示した URL を記憶しなさい。実際には、ホスト名やポート番号が異なる はずである。

★注意: http-simple は、サーバ・プロセスなので、内部に無限ループを含 んでいる。よって、http-simple は、自動的に終了しない。演習が終了したら、 必ず^Cやkillコマンドでこのプロセスを終了さなさい。

もう1つのウィンドウで、次のように Mosaic を起動しなさい。(lynx でも よい。4.2.3節で作成した wcat コマンドを使ってもよい。)

--------------------------------------------------------------------
% Mosaic http://host1:13520/index.html [←]
--------------------------------------------------------------------

ここで Mosaic の引数は、先ほど ./http-simple が起動時に表示した URL に 引き続き、何か適当な文字列(これ例では、"index.html")を続けたもので ある。あらかじめ Mosaic を起動していた場合、Open URL を使って、URL  http://host1:13520/index.html の資源を得なさい。すると、次のような画 面が現われるはずである。

--------------------------------------------------------------------
request[/index.html]
--------------------------------------------------------------------
これは、httpd-simple.c:perform() の中で、次の fprintf() の呼び出しに対 応している。
--------------------------------------------------------------------
	 fprintf( fout,"request[%s]\n",filename );
--------------------------------------------------------------------
報告書には、Mosaic に与えた URL と、結果として表示された画面を示しなさ い。

■課題13: ファイルの内容を返す httpd

httpd-simple.c は、要求されたURLを、そのままクライアントに返す機能しか ない。これを変更して、ファイルを開き、その内容を返すようにしなさい。与 えられたファイル名を使ってファイルを、fopen() で開き、fread() で読み込 み、fwrite() でクライアントに返すとよい。このとき、ファイル名の拡張子 が".html" の場合には、"Content-Type: text/html\r\n" を返しなさい。また、 ".gif" の場合には、"Content-Type: image/gif\r\n" を返しなさい。実際の HTTP サーバに習い、ファイルの大きさや日付を返すようにしてもよい。

ファイルが見つからない時には、HTTP のエラーである 404 を返しなさい。

HTTP で、ヘッダと本体を分ける空行を忘れないようにしなさい。

■課題14: プログラムを実行する httpd

課題13では、ファイルを開き、その内容を返していた。ファイルの内容ではな く、プログラムを実行してその結果を返すようなプログラムを作りなさい。実 行するプログラムとしては、次のようなものが考えられる。

  1. who
  2. finger
  3. ps
プログラムを実行し、その結果を取り込むためには、popen() ライブラリ関数 を使うとよい。

内容の型としては、"Content-Type: text/plain\r\n" を付けるとよい。余裕 があれば、"Content-Type: text/html\r\n" にして、その他の文字列とともに、 プログラムの出力に <CODE>, </CODE> や <PRE>, </PRE> を付けて返すようにし なさい。

■課題15: アクセス制限付httpd

httpd-simple.c は、どのホストからの要求も受け付けている。特定のホストか らの要求しか受け付けないように、変更しなさい。現在のプログラムは、クラ イアントのIPアドレスをprint_client() により表示している。この課題では、 IPアドレスを表示するだけでなく、その内容を調べて、アクセスを許すか許さ ないかを決めなさい。たとえば、次のような方法が考えられる。

IP アドレスからホスト名を得るには、ライブラリ関数 gethostbyaddr() を使 うとよい。

■5 報告書

■参考資料

http://www.w3.org/pub/WWW/Protocols/
HTTP の仕様。現在議論中の 1.1 から広く使われている 1.0 まで。
http://www.epistat.m.u-tokyo.ac.jp/internet/draft-fielding-http-spec-01-jp.txt
ハイパーテキストトランスファープロトコール第1.0版仕様書(日本語翻訳版)
RFC 821
SMTP (Simple Mail Transfer Protocol)
RFC 977
NNTP (Network News Transfer Protocol)

■付録A streamライブラリ(sized_ioライブラリ)の README ファイル

--------------------------------------------------------------------

This package provides a quick-and-easy means of providing reliable
and large-packet communication between processes.

It is described further in the man page stream(3) and at length in the
document by Don Libes entitled "Packet-Oriented Communications Using a
Stream Protocol --or-- Making TCP/IP on Berkeley UNIX a Little More
Pleasant to Use", NISTIR 90-4232, January 1990.

It is especially nice because initport() does all the hard work of
initializing TCP connections, and select_server_stream() does the
hard work of connecting processes to each other.

If you are running on 4.3BSD, you should add -DBSD4_3 in the Makefile.
Otherwise, this will compile for a 4.2BSD system.

To install, type

	make install

To test, type

	make test
	reader
	writer	(in different window)
	writer  (in yet another window)
	writer  (in yet ...)
	and so on.


reader and writer are two programs that should communicate with
each other.  Type things into any of the writers and reader will
print it out prefaced by the file descriptor the data came in on.

Bugs and problems to Don Libes
National Bureau of Standards
Bldg 220, Rm A-127
Gaithersburg, MD  20899
(301) 975-3535


SYNOPSIS


#include <sys/socket.h>
#include <netinet/in.h>
#include <inet.h>

	cc [options] [files] sized_io.o stream.o

DESCRIPTION

This package implements packet or stream IO between a server process and
a number of client processes, using the TCP/IP (stream) facilities.

A client uses the call:

	s = initport(PORT_NUMBER(XXX),CLIENT,SOCK_STREAM);

s is the server's data socket and is used as a file descriptor in further
communication.  The port may be specified by name (PORT_NAME("foo")), if it
is registered.

Similarly, the server uses the following call:

	s = initport(PORT_NUMBER(XXX),SERVER,SOCK_STREAM);

s is the server's connection socket.  To receive data or connections, the
server calls select_server_stream().


	client = select_server_stream(s,&fds);


This returns a file descriptor corresponding to a client, when a client has
sent a message to the server.  It handles initial connections as well as
client deaths.  s is the server's connection socket that was returned by
initport().  fds is an int used by select...() for storing a bit string
corresponding to client sockets.  Initialize it to 0, and don't mess with it
after that.

To use the file descriptors in a stream-oriented manner, use read() and
write().  To use the file descriptors in a packet-oriented manner, use
sized_read() and sized_write().  The sized...() calls read and write one
packet at a time, while packet boundaries are ignored in read() and write().

	cc = sized_read(fd,buffer,maxsize)
	cc = sized_write(fd,buffer,size)

The arguments for sized_read() and sized_write() are very similar to read()
and write().  The only difference is that in sized_read(), maxsize is the
maximum size of an acceptable packet.
--------------------------------------------------------------------

■付録B streadm ライブラリ(sized_io ライブラリ)の入手先

streamライブラリのアーカイブ名は、sized_ioになっている。よって、archieで 文字列 sized_io を検索するとよい。

1996年7月における archie の結果:
--------------------------------------------------------------------
ftp.lab.kdd.co.jp
	/Unix
		-rw-r--r--      22851   Dec 17 1990  sized_io.shar.gz
ftp.mei.co.jp
	/free/others/Libraries
		-rw-r--r--      22851   Dec 17 1990  sized_io.shar.gz
ftp.tut.ac.jp
	/.h3/UNIX/lib
		-rw-rw-r--      22173   Jun 25 1995  sized_io.tar.gz
nadia.ics.es.osaka-u.ac.jp
	/d0/UUNET/tape7/pub
		-rw-rw-r--      34704   Jun 30 1990  sized_io.shar.Z
--------------------------------------------------------------------

Last updated: 1996/08/16 23:35:11
Yasushi Shinjo / <yas@is.tsukuba.ac.jp>