-------------------------------------------------------------------- ポート番号 プロトコルの名前 目的 -------------------------------------------------------------------- 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において、プロセス間に形成されたストリーム通信路のことを、計算機 間に張られた物理的な回線に似ていることから、仮想的回線(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に、いくつか の応用層のプロトコルについて、公に利用目的が決められているポート番号を 示す。1024以上のポート番号は、特に利用目的が決められていない。よって、 利用されていなければ、利用者が自由に使ってもよい。
クライアント側の通信用ポートのポート番号は、通常は、オペレーティング・ システムにより自動的に割り当てられる。サーバ側の通信用ポートのポート番 号も、同様である。
プロセス間通信におけるクライアント・サーバ・モデルにおける意味の他に、 クライアントとサーバという言葉は、サービスを受けるプロセスとサービスを 提供するプロセスの意味で使われることがある。インターネットにおけるプロ セス間通信では、多くの場合、サービスの授受の関係におけるクライアントと サーバと、プロセス間通信におけるクライアントとサーバが一致している(稀 に一致していないこともあるので、注意しなさい)。
DNSでは、主に名前をIPアドレスへ変換するサービスが使われている。その他 に、名前から電子メールの配送先、名前から名前サーバが動いているホストの 名前、名前から名前サーバ自身の管理情報、逆にIPアドレスから名前を引くた めにも使われる。
-------------------------------------------------------------------- % telnet nirai ↓ --------------------------------------------------------------------これは、次の省略形である。
-------------------------------------------------------------------- % telnet nirai telnet ↓ --------------------------------------------------------------------ここで、第2引数の "telnet" という文字列は、TCP/IP のポート番号を示す 記号である。この記号は、/etc/services というファイルに次のように格納さ れている。
-------------------------------------------------------------------- telnet 23/tcp --------------------------------------------------------------------
telnet コマンドは、/etc/services ファイルを検索し、与えられた記号から ポート番号(この例では23)を得る。また、nirai の IP アドレスを、 /etc/hosts や DNS から得る。telnet コマンドは、この IP アドレスとポー ト番号の2つを使って、TCP/IP の通信路を開設する。(注意:NISが動いてい る場合には、/etc/services や /etc/hosts の代わりに、NISのデータベース (マップ)が検索される。)
telnet コマンドでは、次のように、ホストのIPアドレスとポート番号を数字 で打ち込むこともできる。
-------------------------------------------------------------------- % telnet 133.13.48.2 23 ↓ --------------------------------------------------------------------ここで、133.13.48.2 とは、32ビットのIPアドレスを8ビットずつに区切り、 それぞれの8ビットの整数を10進数で表記したものである。よって、実際のIP アドレスは、次のようしてに計算できる。
((((133*256)+13)*256)+48)*256+2 == 2232233986よって、telnet コマンドに次のようIPアドレスを与えてもよい。
-------------------------------------------------------------------- % telnet 2232233986 23 ↓ --------------------------------------------------------------------
-------------------------------------------------------------------- % finger shinjo@nirai↓ ^^^^^^^^^^^^^^^^^^^ [nirai] Login name: yas In real life: Yasushi SHINJO Directory: /home/teacher/yas Shell: /usr/local/bin/tcsh On since Sep 08 19:48:13 on pts/0 from top.ie.u-ryukyu. 9 minutes 29 seconds Idle Time No Plan. Login name: j94070 In real life: Yasushi [j94] Shinjo Directory: /home/pw/pw010/j94070 Shell: /usr/local/bin/tcsh No Plan. Login name: j94077 In real life: Yasushi SHINJO Directory: /home/pw/pw007/j94077 Shell: /usr/local/bin/tcsh No Plan. % --------------------------------------------------------------------ここで、下線(^^^^)を付けた部分が、利用者のタイプである。この例で、 finger コマンドは、クライアントとして、ホスト nirai のポート番号 79(finger)のポートに対して、TCP/IP による通信路を開設する。そして、次 のような文字列を送る。
-------------------------------------------------------------------- shinjo↓ --------------------------------------------------------------------すると、nirai 上のサーバ(fingerd, finger daemon)は、上の例で示した情 報をクライアントに返す。サーバは、データ転送が完了すると、TCP/IP の通 信路を切断する。finger コマンド(クライアント)は、受け取ったデータを 整形して利用者に対して表示する。
■課題1: telnet コマンドによる fingerd へのアクセス
telnet コマンドを使って、finger サーバにアクセスして、自分の情報を検索 してみなさい。アクセス先のホストとしては、nirai, kanai, pw001-pw060 の中から選びなさい。その様子を、レポートに添付しなさ い。アクセスの様子の記録には、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)と呼ばれるプロトコルを構築し、データの転送を行ってい る。Netscape や 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.ie.u-ryukyu.ac.jp:80/Welcome.html
Netscape などのクライアントは、まずホスト名 www.ie.u-ryukyu.ac.jp とポー ト番号 80 を使ってサーバとの間に TCP/IP の通信路を開設する。そして、ク ライアントは、開設した通信路を使って、サーバに次のような文字列を送る。
-------------------------------------------------------------------- GET /Welcome.html HTTP/1.0↓ ↓ --------------------------------------------------------------------
ここで、"GET" が命令の種類、"/Welcome.html" は、GETの引数の、要求して いるデータを表わす URL (ファイル名)、"HTTP/1.0" は、使っているプロトコ ルのバージョンである。次の空行は、命令のヘッダ部分の終りを意味するもの であり、必要である。
すると、サーバは、クライアントに対して次のようなデータを送り返す。(注 意:データは、常に更新されるので、必ずしもこの通りのデータが返されると は限らない。)
--------------------------------------------------------------------
HTTP/1.0 200 Document follows
MIME-Version: 1.0
Server: CERN/3.0pre6
Date: Thursday, 07-Sep-95 13:14:34 GMT
Content-Type: text/html
Content-Length: 782
Last-Modified: Monday, 07-Aug-95 03:55:13 GMT
<HEAD>
<TITLE>University of Ryukyus WWW Home Page.</TITLE>
</HEAD>
<BODY>
<IMG SRC="title.gif">
<H3>
Welcome to the University of the Ryukyus.
</H3>
<HR>
<UL>
<LI> <A HREF="http://www.ie.u-ryukyu.ac.jp/Welcome.html">
English</A>
<LI> <A HREF="http://www.ie.u-ryukyu.ac.jp/Welcome-j.html">
Japanese (any,mixed)</A>
<LI> <A HREF="http://www.ie.u-ryukyu.ac.jp/Welcome-j.html">
Japanese (EUC only)</A>
<LI> <A HREF="http://www.ie.u-ryukyu.ac.jp/Welcome-j.html">
Japanese (JIS only)</A>
<LI> <A HREF="http://www.ie.u-ryukyu.ac.jp/Welcome-j.html">
Japanese (Shift-JIS only)</A>
<LI> <A HREF="kanji-menu.html"> Kanji code menu </A>
</UL>
<HR>
<ADDRESS>
<a href="web-master.html">Web Master </a>of University of the Ryukyus.
</ADDRESS>
</BODY>
--------------------------------------------------------------------
最初の行が、状態行(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 <message-id> メッセージID<message-id>の記事の内容を得る。 HELP ヘルプ・メッセージの表示 QUIT 終了 POST 記事を投稿する。 --------------------------------------------------------------------
表5 NNTPの応答
--------------------------------------------------------------------応答コード 説明
-------------------------------------------------------------------- 100 ヘルプのテキストが続く。 200 要求受け付け可能である(投稿可)。 201 要求受け付け可能である(投稿不可)。 205 通信路を切断する。 211 ニュース・グループが選ばれた。 記事の数、記事番号の上限、下限、ニュース・グループ名。 400 サービスを中断する。 411 そのようなニュース・グループがない。 421 もうそのニュース・グループには次の記事がない。 500 コマンドが認識できなった。 501 コマンドの文法に誤りがあった。 502 アクセスが制限されている。 --------------------------------------------------------------------以下に、telnet コマンドを利用して、NNTP サーバに接続した様子を示す。
--------------------------------------------------------------------
1: % telnet spn.ie.u-ryukyu.ac.jp nntp↓
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
2: Trying 133.13.48.3 ...
3: Connected to spn.ie.u-ryukyu.ac.jp.
4: Escape character is '^]'.
5: 200 spn NNTP[auth] server version 1.5.12.1 (1 Jan 1995) ready at
Thu Sep 7 22:56:28 1995 (posting ok).
6: help↓
^^^^^^
7: 100 This server accepts the following commands:
8: ARTICLE BODY GROUP
9: HEAD LAST LIST
10: NEXT POST QUIT
11: STAT NEWGROUPS HELP
12: IHAVE NEWNEWS SLAVE
13: DATE
14:
15: Additionally, the following extensions are supported:
16:
17: XHDR Retrieve a single header line from a range of articles.
18: LISTGROUP Retrieve a list of valid article-numbers.
19: XOVER Return news overview data.
20: XGTITLE Same as LIST NEWSGROUPS (for backward compatibility).
21:
22: Bugs to Stan Barber (Internet: nntp@academ.com)
23: .
24: quit↓
^^^^
25: 205 spn closing connection. Goodbye.
26: Connection closed by foreign host.
27: %
--------------------------------------------------------------------
ここで、下線(^^^^)で示した部分が、キーボードからのタイプである。この 例では、ホスト spn.ie.u-ryukyu.ac.jp のポート番号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 サーバ spn.ie.u-ryukyu.ac.jp にアクセス し、ネットワーク・ニュースの記事を画面に表示させなさい。その記事のニュー ス・グループ、ニュース・グループ内の番号、記事の先頭の20行程度を報告書 に添付しなさい。このとき、表4に示したコマンド、GROUP と ARTICLE を使 うとよい。
注意すべきこととして、GROUP コマンドでは、ニュース・グループ名を一度に 与えることがあげられる。たとえば、ura.comp.sys.mac というニュース・グ ループならば、次のように、一度に全部のニュース・グループ名を与える。
GROUP ura.comp.sys.mac次のように、部分的に与えることはできない。
GROUP ura 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:<reverse-path> 新しいメールの転送を開始する。 <reverse-path>は、エラーが起きた時の送り返し先のアドレス。 RCPT TO:<forward-path> メールの送り先として <forward-path> を指定する。 DATA メールのデータを送り始める。 VRFY <login-name> メールの受け手を確認する。 EXPN <ml-name> メーリング・リストの受取人を表示する。 SEND FROM:<reverse-path> メールを転送する。 HELO <domain> 最初に接続した時に自分自身を相手に知らせる。 QUIT 接続を切る。 --------------------------------------------------------------------表7 SMTPの応答コード
--------------------------------------------------------------------コード 説明
-------------------------------------------------------------------- 220 <domain> というドメインで要求受け付け可能である。 250 そのアドレスへのメールは、受け付け可能である。 251 そのアドレスは、ローカルには受取人がいない。 --------------------------------------------------------------------以下に、telnet コマンドを利用して、SMTP サーバ(sendmail)に接続した様 子を示す。
--------------------------------------------------------------------
1: % telnet nirai smtp↓
^^^^^^^^^^^^^^^^^^
2: Trying 133.13.48.2 ...
3: Connected to nirai.ie.u-ryukyu.ac.jp.
4: Escape character is '^]'.
5: 220 ie.u-ryukyu.ac.jp ESMTP Sendmail 8.8.8/3.6W-98061617; Fri, 16 Oct 1998 19:30:46 +0900 (JST)
6: vrfy root↓
^^^^^^^^^^^
7: 250 Super-User <root@nirai.ie.u-ryukyu.ac.jp>
8: quit↓
^^^^^^
9: 221 nirai.ie.u-ryukyu.ac.jp closing connection
10: Connection closed by foreign host.
11: %
--------------------------------------------------------------------
ここで、下線(^^^^)で示した部分が、キーボードからのタイプである。この 例では、ホスト nirai のポート番号 25 (smtp) のポートに、TCP/IPにより接 続を試みている。2行目から4行目は、telnet コマンドによる定型的な表示で ある。ここで、通信路が開設されている。すると、サーバは、220 という応答 を返している。これは、SMTP で定義されている応答であり、サーバが、コマ ンド要求を受け付け可能であることを意味している。
第6行では、"vrfy" (verify) というコマンドをサーバに送っている。これに 対して、サーバは、250 という応答に続けて、電子メールのアドレスを返して いる。
次に、8行において、"quit" というコマンドをサーバに送っている。これに対 してサーバは、221 という応答を返し、続いて TCP/IP の通信路を切断してい る。10行目は、telnet コマンドが生成したメッセージである。
■課題5: telnet コマンドによる SMTP サーバへのアクセス
telnet コマンドを使って、nirai にアクセスし、自分のログイン名と 存在しないログイン名について、VRFY で調べてみなさい。そして、ホストに よる応答の違いを比較しなさい。
■課題6: SMTP によるメール発信の観察
nirai 以外のホストで、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() のソース・プログラムは、 /usr/open/classes/slab/info2/lib/fdopen2.c にある。
■コンパイルとリンク
initport() を使うプログラムは、コンパイル時に "inet.h" というヘッダ・ ファイルを読み込まなければならない。これは、 /usr/open/classes/slab/info2/include/ に置かれている。よって、コンパイ ル時に、cc コマンドに次のようなオプションを与える必要がある。
% cc -I/usr/open/classes/slab/info2/include/ -c file.cまた、initport() などの関数は、次のいずれかのファイルに格納されている。
/usr/open/classes/slab/info2/lib/libinfo2-sun4.a Sun用 /usr/open/classes/slab/info2/lib/libinfo2-rs6000.a IBM用これを利用するためには、リンク時に次のように指定する(Sunの場合)。
% cc -o commmand file.o /usr/open/classes/slab/info2/lib/libinfo2-sun4.aまたは、次のように -L, -l オプションを用いて指定する。
% cc -o commmand file.o -L/usr/open/classes/slab/info2/lib/ - linfo2-sun4以下の実験で使うコマンドについては、次の Makefile にコンパイルとリンク のための設定が格納されている。
/usr/open/classes/slab/info2/6-tcp/Makefile
4.2.2 finger クライアントのコンパイルと実行
遠隔のfingerサーバに対して問い合わせを行うコマンド、rfinger (remote finter)のソース・プログラムを、以下のファイルに示す。
--------------------------------------------------------------------
/usr/open/classes/slab/info2/6-tcp/rfinger.c:
--------------------------------------------------------------------
1:
2: /*
3: rfinger.c -- Remote Finger / 遠隔専用 finger コマンド。
4: $Header: /home/h1/yas/slab-info2-inet/6-tcp/RCS/rfinger.c,v 1.3
1995/09/11 13:42:45 yas Exp $
5: Start: 1995/09/08 21:04:33
6: */
7: #include <stdio.h>
8: #include <sys/types.h>
9: #include <sys/socket.h>
10: #include <netinet/in.h>
11: #include "inet.h"
12:
13: main( argc,argv )
14: int argc ;
15: char *argv[] ;
16: {
17: /* 引数は、コマンド名を入れて3個。 */
18: if( argc != 3 )
19: {
20: fprintf( stdout,"Usage: %s host \"who\"\n",argv[0] );
21: exit( -1 );
22: }
23: rfinter( argv[1],argv[2] );
24: }
25:
26: #define BUFFSIZE 1024
27:
28: rfinter( host,who )
29: char *host ; /* ホスト名 */
30: char *who ; /* 調べたい人のキー */
31: {
32: int fd ;
33: char buff[BUFFSIZE] ;
34: int count ;
35:
36: /* buff で確保されているメモリよりも長い who については、エラーに
する。*/
37: if( strlen(who) >= BUFFSIZE-2 )
38: {
39: fprintf(stderr,"search key too long: \n%s\n",who );
40: exit( -1 );
41: }
42: /*
43: initport() 関数により、TCP/IPのクライアント側における通信用ポート
を設
44: 定する。このとき、ポート名と接続先のホストを指定する。この関数に
より、
45: TCI/IPの通信路が開設され、read(), write() により通信を行うことが
可能に
46: なる。エラーが起きたときには、-1 が返される。
47: */
48: fd = initport(PORT_NAME("finger"),CLIENT,SOCK_STREAM,host);
49: if( fd < 0 )
50: {
51: fprintf(stderr,"initport(,,host:%s) = %d\n",
52: host, fd);
53: exit( -1 );
54: }
55:
56: /*
57: 通信内容を、バッファ buff に作る。このとき、sprintf() を使うと便
利であ
58: る。これは、printf() と同じ形式の引数をとることができる。結果は、
画面
59: に表示されるのではなく、buff (string)に保存される。最後の '\n' は、
必
60: 要である。
61: */
62: sprintf( buff,"%s\n", who );
63: count = strlen( buff );
64:
65: /*
66: 通信用ポートに対応しているファイル記述子を引数としてwrite() シス
テム・
67: コールを発行することで、TCP/IPによるプロセス間通信が行われる。こ
れによ
68: り、buff 番地から count バイトのデータが、通信相手に送られる。
69: */
70: if( write(fd,buff,count) != count )
71: {
72: perror("write() to socket");
73: exit( -1 );
74: }
75: /*
76: 通信用ポートに対応しているファイル記述子を引数として read() シス
テム・
77: コールを発行することにより、TCP/IPによるプロセス間通信が行われる。
これ
78: により、通信相手から送られてきたデータが、buff 番地から始まる
BUFFSIZE
79: バイトの領域へ書き込まれる。read() システム・コールのリターン・バ
リュー
80: は、実際に送られてきたデータのバイト数である。
81: */
82: while( (count=read(fd,buff,BUFFSIZE)) > 0 )
83: {
84: if( write(1,buff,count) == -1 )
85: {
86: perror("stdout");
87: exit( 1 );
88: }
89: }
90: close( fd ); /* TCP/IP connection を閉じる。*/
91: close( 1 ); /* 標準出力のファイルを閉じる。*/
92: }
93: /* --------------------- rfinger.c --------------------- */
94: static char rcsid[] =
95: "$Header: /home/h1/yas/slab-info2-inet/6-tcp/RCS/rfinger.c,v 1.3
1995/09/11 13:42:45 yas Exp $" ;
--------------------------------------------------------------------
■課題8: rfinger.c のコンパイルと実行
以下のように、rfinger.c をコンパイルして、実行してみなさい。
--------------------------------------------------------------------
% cp /usr/open/classes/slab/info2/6-tcp/{rfinger.c,Makefile} .
% make rfinger
% ./rfinger pw001 $USER
--------------------------------------------------------------------
報告書には、自分の情報を rfinger で調べた結果を示しなさい。ログイン名 で引いた時と、finger name として設定している文字列で引いたときの結果を 示しなさい。もし、finger name をまだ設定していないならば、chfn コマン ドで設定しなさい。このとき、設定の前後で、表示や検索の様子がどう変わる かを調べて報告しなさい。
4.2.3 WWW クライアントの作成
■課題9: WWW クライアントの作成
WWWサーバからデータを得るプログラムを作りなさい。そのプログラムの名前 を、wcat とする。wcat のプログラムの骨組みを /usr/open/classes/slab/info2/6-tcp/wcat.c に示す。これをコピーして、使 いなさい。
-------------------------------------------------------------------- % cp /usr/open/classes/slab/info2/6-tcp/wcat.c . % --------------------------------------------------------------------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) である。
4.2.4 NNTP クライアントの作成
■課題10: NNTP クライアントの作成
NNTPサーバから記事を1つ得るプログラムを作りなさい。そのプログラムの名 前を、nncat とする。nncat のプログラムの骨組みを /usr/open/classes/slab/info2/6-tcp/nncat.c に示す。これをコピーして、 使いなさい。
-------------------------------------------------------------------- % cp /usr/open/classes/slab/info2/6-tcp/nncat.c . --------------------------------------------------------------------nncat コマンドは、次のように3つの引数を与えて利用するものとする。
-------------------------------------------------------------------- % ./nncat host newsgroup number --------------------------------------------------------------------
ここで、host は、NNTPサーバが動いているホストの名前、newsgroup は、ニュー ス・グループ、number は、記事番号である。NNTP プロトコルで用いられる標 準のポート番号は、119(nntp) である。initport() では、PORT_NAME("nntp") の形式を利用するとよい。
報告書には、作成した nncat コマンドの動作例を示しなさい。NNTPサーバが 動いているホストとしては、spn (spn.ie.u-ryukyu.ac.jp) を指定しなさい。
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 の プログラムの骨組みを/usr/open/classes/slab/info2/6-tcp/mverify.c に示 す。これをコピーして、使いなさい。
-------------------------------------------------------------------- % ./mverify host who --------------------------------------------------------------------mverify コマンドは、次のように2つの引数を与えて利用するものとする。
-------------------------------------------------------------------- % ./mverify host who --------------------------------------------------------------------
ここで、host は、SMTPサーバ(sendmailデーモン)が動いているホストの名前、 who は確認するアドレスである。SMTPプロトコルで用いられる標準のポート番 号は、25(smtp) である。initport() では、PORT_NAME("smtp") の形式を利用 するとよい。
報告書には、作成した mverify コマンドの動作例を示しなさい。NNTPサーバ が動いているホストとしては、nirai を指定しなさい。自分のアドレスを確認 してみなさい。
-------------------------------------------------------------------- % ./mverify nirai $USER --------------------------------------------------------------------
4.3 WWWサーバの機能拡張
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のサーバのプ ログラムの概要を示す。サーバのソース・プログラムを、次のファイルに示す。
--------------------------------------------------------------------
/usr/open/classes/slab/info2/6-tcp/http-simple.c:
--------------------------------------------------------------------
1:
2: /*
3: http-simple.c -- 簡単な WWW サーバ (HTTP サーバ)
4: $Header: /home/h1/yas/slab-info2-inet/6-tcp/RCS/httpd-simple.c,v
1.7 1995/09/18 16:53:04 yas Exp $
5: Start: 1995/09/03 00:27:22
6: */
7:
8: #include <stdio.h>
9: #include <sys/types.h>
10: #include <sys/time.h>
11: #include <sys/socket.h>
12: #include <netinet/in.h>
13: #include "inet.h"
14:
15: extern int fdopen2(/* int fd, FILE **finp, FILE **foutp */);
16:
関数usage()は、このプログラムの利用方法を表示する関数である。
17: void
18: usage()
19: {
20: fprintf( stdout,"Usage: httpd-simple [-p port] directory\n" );
21: exit( -1 );
22: }
23:
関数main()は、引数とオプションの解析を行う。
24: main( argc,argv )
25: int argc ;
26: char *argv[] ;
27: {
28: extern char *optarg; /* getopt() */
29: extern int optind; /* getopt() */
30: int c ;
31: char *dir ;
32: int port ;
33:
34: port = getuid() + 10000 ;
実験では、大勢の人が同時にプログラムを作る。よって、各自重ならないよう
なポート番号を使わなければならない。この実験では、getuid() + 10000 を
使うことにする。もしそのポート番号が、使われていたら、initport() の中
の bind() システム・コールで次のエラーが出る。
-------------------------------------------------------------------- #define EADDRINUSE 48 // Address already in use --------------------------------------------------------------------その時は、前に走らせた自分のプロセスがいないか、調べなさい。もし自分の プロセスが、そのポートを使っていれば、^Cやkill コマンドなどで殺しなさ い。もし、他の人がそのポート番号を使っていたら、-p オプションで空いて いるポート番号を指定し別のポート番号を使うか、他のホストにログインして そこで指定されたポート番号を使うようにしなさい。
35:
36: while( (c = getopt(argc,argv,"p:")) != EOF )
37: {
38: switch( c )
39: {
40: case 'p': port = atoi( optarg ); break;
41: default:
42: usage();
43: }
44: }
getopt() は、オプションを解析するためのC言語のライブラリ関数である。 getopt() の引数 "p" は、-p オプションを受け付けるという意味である。pに 続くコロン(":")は、-p string の形で、オプションを受け付けるという意 味である。getopt() は、オプションを見つけると、リターン・バリューとし てそれを返す。その時、受け付ける文字列は、大域変数 optarg にセットされ る。
atoi() は、文字列(ascii)を、整数に変えるC言語のライブラリ関数である。
45: if( argc - optind != 1 ) 46: usage(); 47: dir = argv[optind+0] ; 48: httpd_simple( port, dir ); 49: } 50:
getopt() によるオプションの解析が終了した時、optind に、最初の引数(オ プションではなく必須の文字列)の添え字がセットされている。これを、argc と比べて、引数の数が得られる。httpd-simple は、引数としてディレクトリ 名を1つ必要とする。
以下の関数 httpd_simple()は、このサーバの主要部分である。
51: httpd_simple( port,dir )
52: int port ;
53: char *dir ;
54: {
55: int client ; /* 通信用ポート */
56: int fd; /* 要求受付用ポート */
57: fd_set fds; /* select_server_stream() のための変数 */
58:
59: if( chdir(dir) < 0 )
60: {
61: perror( dir );
62: exit( -1 );
63: }
カレント・ワーキング・ディレクトリを、指定されたディレクトリに移す。移
動できない時には、perror() ライブラリ関数により、エラーメッセージを表
示して、プログラムを終了する。
64:
65: fd = initport( PORT_NUMBER(port),SERVER,SOCK_STREAM );
66: if( fd < 0 )
67: {
68: fprintf( stderr,"initport() returns %d\n",fd );
69: exit( -1 );
70: }
initport() により、サーバ側の要求受付用ポートを作る。そのポートには、
portで指定されたポート番号が付けられる。
71: print_my_url( port );受付可能な URL を表示する。print_my_url()関数の本体は、135行目にある。
72: FD_ZERO(&fds);FD_ZERO()は、select_server_stream() のための変数 fds を初期化する。
73: while( 1 )
74: {
75: client = select_server_stream( fd,&fds );
76: perform( client );
77: }
78: }
この 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() システム・コールを発行することにより、クライ アントと通信を行うことができる。
79:
80: #define BUFFSIZE 1024
81:
82: perform( client )
83: int client ;
84: {
85: FILE *fin, *fout ;
86:
87: char buff[BUFFSIZE+1] ;
88: char filename[BUFFSIZE] ;
89:
90: print_client( client );
関数 print_client() を呼び出し、クライアントの情報を表示する。関数
print_client() のプログラムは、144行目にある。
91: if( fdopen2(client,&fin,&fout) == 0 )
92: {
93: fprintf( stderr,"fdopen2() faild.\n" );
94: fprintf( fout,"HTTP/1.0 500 Internal Server Error\r\n" );
95: return( -1 );
96: }
関数fdopen2() により、ファイル記述子から、FILE *を得る。これにより、
fgets(), fprintf() といった標準入出力ライブラリ関数を利用して、プロセ
ス間通信を行うことが可能になる。fdopen2() については、4.2.1節を参照し
なさい。
97: if( fgets( buff,BUFFSIZE,fin ) == 0 )
98: {
99: fprintf( stderr,"request line read faild.\n" );
100: fprintf( fout,"HTTP/1.0 500 Internal Server Error\r\n" );
101: goto error ;
102: }
fgets()により、クライアントからの要求の最初の行を読む。この行には、最
も大事な情報であるファイル名が含まれている。
103:
104: if( sscanf( buff,"GET %s HTTP/1.0", filename ) != 1 )
105: {
106: fprintf( stderr,"no filename in the request line\n" );
107: fprintf( fout, "HTTP/1.0 400 Bad Request\r\n" );
108: goto error;
109: }
sscanf() により、ファイル名を抜き出す。sscanf() は、scanf() と同じよう
な動きをする。scanf() の場合、標準入力から形式(format)に従って入力を行
う。sscanf() は、標準入力ではなく、文字列(string)から形式に従って入力
を行う。sscanf() の引数は、文字列の先頭番地である。
110: if( strstr(filename,"/../" ) || filename[0] != '/' )
111: {
112: fprintf( stderr,"Dangerous file name [%s] \n",filename );
113: fprintf( fout, "HTTP/1.0 404 Not Found\r\n" );
114: goto error;
115: }
得られたファイル名に、".." が含まれていたり、"/" で始まっていない場合
は、エラーにする。
116: 117: fprintf( fout,"HTTP/1.0 200 Document follows\r\n" ); 118: fprintf( fout,"Content-Type: text/plain\r\n" ); 119: fprintf( fout,"\r\n" ); 120: fprintf( fout,"request[%s]\n",filename );
成功した場合、ここでクライアントに結果を返している。最初の fprintf() にある 200 は、表3に示されている応答のコードである。"\r\n"より上が HTTP のヘッダ、"\r\n"より下が、内容である。ヘッダでは、内容の型 (Content-Type)が、普通のテキスト(text/plain)であることを返している。 このプログラムでは、内容として得られたファイル名を返している。
もし実際にファイルの内容を返す時には、"\r\n"の次に、ファイルの内容を続 ければよい。すなわち、ここでそのファイルを、fopen() して、fread() する とよい。ただし、アクセスするファイルの先頭には、"./" を付けて、カレン ト・ワーキング・ディレクトリ以下のファイルしかアクセスを許さないように しなさい。たとえば、"/Welcome.html" というファイルが要求された場合、 実際には、次のようなファイルを開くようにしなさい。
fopen("./Welcome.html","r");
さらに、ファイルの内容によって、Content-Type: を変える必要がある。たと
えば、HTML ならば、Content-Type: text/htmlとする必要がある。
121: 122: fclose( fin ); 123: fclose( fout ); 124: return( 0 );2つの fclose() により通信路が切断される。成功した場合、0を返す。(た だし、呼び出し側は、この値を参照していない。)
125: 126: error: 127: fprintf( fout,"Content-Type: text/html\r\n" ); 128: fprintf( fout, "\r\n" ); 129: fprintf( fout, "<H1>Error.</H1><ADDRESS>httpd-simple</ADDRESS>\n"); 130: fclose( fin ); 131: fclose( fout ); 132: return( -1 ); 133: }エラーの時は、内容として、HTML でエラーメッセージを返している。
134:
135: print_my_url( port )
136: int port ;
137: {
138: char hostname[BUFFSIZE+1] ;
139: gethostname( hostname,BUFFSIZE );
140: hostname[BUFFSIZE] = 0 ;
141: printf("http://%s:%d/\n",hostname, port );
142: }
print_my_url()は、自分自身が受け付けることができる URL の形式を表示す
る関数である。 gethostname() は、ホスト名を得るシステム・コールである。
143:
144: print_client( client )
145: int client ;
146: {
147: struct sockaddr_in addr;
148: int addrlen;
149: addrlen = sizeof(addr);
150: if( getpeername(client, &addr, &addrlen) < 0 )
151: {
152: perror("getpeername");
153: return( -1 );
154: }
155: printf("host: ");
156: print_ip_address( addr.sin_addr.s_addr );
157: printf(", port: %d\n",addr.sin_port );
158: }
print_client() は、クライアントの情報を返す関数である。引数は、通信用
ポートに対応したファイル記述子である。getpeername()は、通信相手の名前
を返すシステム・コールである。TCP/IP における名前とは、ホストのIPアド
レスとポート番号である。それは、sockaddr_in 構造体の sin_addr.s_addr
と sin_port に設定される。
159:
160: print_ip_address( ipaddr )
161: unsigned long ipaddr ;
162: {
163: union
164: {
165: unsigned long l ;
166: struct { unsigned char c1, c2, c3, c4 } b;
167: } x ;
168: x.l = ipaddr ;
169: printf("%d.%d.%d.%d", x.b.c1, x.b.c2, x.b.c3, x.b.c4 );
170: }
171:
172: /* --------------------- http-simple.c --------------------- */
173: static char rcsid[] =
174: "$Header: /home/h1/yas/slab-info2-inet/6-tcp/RCS/httpd-simple.c,v
1.7 1995/09/18 16:53:04 yas Exp $" ;
--------------------------------------------------------------------
print_ip_address()は、IPアドレスを、点つき10進数で表示する関数である。 ここでは、共用体を利用して32ビットの整数を8ビットずつ区切ってアクセス している。
■課題12: httpd-simple のコンパイルと実行
以下のように、http-simple.c をコンパイルして、実行してみなさい。
--------------------------------------------------------------------
% cp /usr/open/classes/slab/info2/6-tcp/{http-simple.c,Makefile} .
% make http-simple
%
--------------------------------------------------------------------
実行する時には、ウィンドウを2つ用意しなさい。1つのウィンドウで、次の
ように http-simple を実行しなさい。
-------------------------------------------------------------------- % ./http-simple ~/public_html/ http://nirai:13520/ --------------------------------------------------------------------
ここで、"~/public_html/" は、このサーバが提供するデータを含んでいるディ レクトリを指定する。あらかじめ作成しておくこと。ここで、 http-simple が表示した URL を記憶しなさい。実際には、ホスト名やポート番号が異なる はずである。
★注意: http-simple は、サーバ・プロセスなので、内部に無限ループを含 んでいる。よって、http-simple は、自動的に終了しない。実験が終了したら、 必ず^Cやkillコマンドでこのプロセスを終了さなさい。もし、実験に使用した プロセスが残されていた場合、減点の対象とする。
もう1つのウィンドウで、次のように Netscape を起動しなさい。(lynx でも よい。4.2.3節で作成した wcat コマンドを使ってもよい。)
-------------------------------------------------------------------- % Netscape http://nirai:13520/Welcome.html --------------------------------------------------------------------ここで Netscape の引数は、先ほど ./http-simple が起動時に表示した URL に 引き続き、何か適当な文字列(これ例では、"Welcome.html")を続けたもので ある。あらかじめ Netscape を起動していた場合、Open URL を使って、URL http://nirai:13520/Welcome.html の資源を得なさい。すると、次のような画 面が現われるはずである。
-------------------------------------------------------------------- request[/Welcome.html] --------------------------------------------------------------------これは、http-simple.c:perform() の中で、次の fprintf() の呼び出しに対 応している。
-------------------------------------------------------------------- fprintf( fout,"request[%s]\n",filename ); --------------------------------------------------------------------
報告書には、Netscape に与えた URL と、結果として表示された画面を示しなさ い。
■課題13: ファイルの内容を返す httpd
http-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> や &ly;PRE>,</PRE> を付けて返すようにし なさい。
■課題15: アクセス制限付httpd
http-simple.c は、どのホストからの要求も受け付けている。特定のホストか らの要求しか受け付けないように、変更しなさい。現在のプログラムは、クラ イアントのIPアドレスをprint_client() により表示している。この課題では、 IPアドレスを表示するだけでなく、その内容を調べて、アクセスを許すか許さ ないかを決めなさい。たとえば、次のような方法が考えられる。
(1) 特定のホストだけからのアクセスを許す。
(2) pw0?? というホスト名を持つからのアクセスを許す。
(3) 133.13.52.* というパタンのIPアドレスを持つホストからのアクセスのみ を許す。ここで、「*」は、任意のアドレスを意味する。
(4) 133.13.*.* というパタンのIPアドレスを持つホストからのアクセスのみ を許す。ここで、「*」は、任意のアドレスを意味する。
(5) *.u-ryukyu.ac.jp というホスト名を持つホストからのアクセスのみを許 す。ここで、「*」は、任意の名前を意味する。
IP アドレスからホスト名を得るには、ライブラリ関数 gethostbyaddr() を使 うとよい。
5 報告書
この実験テーマで行った全ての課題について、1通の報告書を提出しなさい。 それぞれの課題について、作成したプログラムとその説明(4.1を除く)、お よび、その実行結果を付けなさい。この実験では、プログラムの動きを説明す るフローチャートを付加する必要はない。開発環境と実行環境(計算機、オペ レーティング・システムのバージョン、コンパイラ)を載せなさい。
その他、この実験を通じて得たことを報告しなさい。
この実験に要した時間(単位は時間)を書きなさい。
この実験指導書の改善点を書きなさい。
一般的な注意:
報告書を、日本語で記述しなさい。プログラム、表、図、数式の羅列は、日本 語ではない。それらは、必ず本文中から参照しなさい。たとえば、「○○を図 1に示す」、あるいは、「○○を以下に示す。<以下、参照しているものを続 ける>」といった形式を使うとよい。数学における証明のように論理的に記述 する。主語と述語を明確に書くとよい。けっして、script コマンドの結果の みを並べただけの報告書を提出しないように。
参考資料
/usr/open/doc/inet/protocols/http-1.0.txt
HTTP 1.0 の仕様。テキスト形式。
http://www.w3.org/hypertext/WWW/Protocols/Overview.html
HTTPプロトコルについて。
/usr/open/doc/rfc/rfc821.txt
SMTP (Simple Mail Transfer Protocol)
/usr/open/doc/rfc/rfc977.txt
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 を検索するとよい。
1995年9月における archie の結果:
-------------------------------------------------------------------- ftp.ics.es.osaka-u.ac.jp /UUNET91/vol8/pub -rw-r--r-- 34704 Jun 30 1990 sized_io.shar.Z /UUNET/tape7/pub -rw-rw-r-- 34704 Jun 30 1990 sized_io.shar.Z ftp.lab.kdd.co.jp /pub5/unix -rw-r--r-- 22851 Dec 17 1990 sized_io.shar.gz --------------------------------------------------------------------