* 2 TCP/IPプログラミング *1 目的 この実験では、インターネットで使われている通信規約であるTCP/IPの 利用法について学ぶ。具体的は、NNTPサーバやWWWのサーバと telnet コマンドにより通信を行い、通信方法を調べる。そして、C言語によりクライ アントのプログラムを作成、および、サーバのプログラムを作成する。 *2 関連科目 情101 ソフトウエア基礎 I 2 情102 ソフトウエア基礎 II 2 情103 ソフトウエア基礎演習 I 1 情104 ソフトウエア基礎演習 II 1 情211 情報通信 2 *3 準備 *3.1 TCP/IP ***ストリーム TCP/IPは、信頼性のある(reliable)双方向のストリーム転送サービス (stream transport service)を提供する通信プロトコルである(図1)。ス トリームとは、通信する2つのプロセス間に結合(connection)が形成され、複 数回に分けて送り出したデータでも、順番が入れ替わらないが、データの区切 りがわからなくなるような転送サービスである。UNIXのパイプは、双方向 ではなく、単方向であるが、同じストリームに分類される転送サービスである。 図1 TCP/IPによりより提供されるストリーム なお、C言語のライブラリ関数である fopen(), fgets(), fputs() なども、 ストリームと呼ばれることがある。これは、もともとランダム・アクセス可能 で、メモリ中の配列と同じようにアクセスするすることもできるファイルを、 まるでプロセス間通信のストリームと同じように扱うことができることに由来 する。ストリームの元の意味は、プロセス間通信である。 ***層 TCP/IPによる通信では、図2に示すように、4つのプロトコル(規約、約束事) の層を構成される。TCP/IP自身は、TCP層と IP層という2つの層に分解される。 このようにさまざまなプロトコルが決められ、全体として層をなしている。こ の様子を、プロトコル・スタックと呼ぶ。 図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)を使ってデータを転送する。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において、プロセス間に形成されたストリーム通信路のことを、計算機 間に張られた物理的な回線に似ていることから、仮想的回線(virtual circuit)とも言う。TCP/IP では、回線を接続する段階では、クライアント・ プロセスとサーバ・プロセスは非対称である。一度仮想回線が接続された後は、 両方のプロセスは、TCP/IPのレベルでは、まったく対称的になる。 TCP/IPでプロセス間に仮想回線を開設するには、IPアドレスとポート番号が使 われる。ポート番号は、同じIPアドレスを持つホスト上で動いているプロセス を区別するために使われる。 図3に、通信路が開設される手順を示す。 (1)サーバ・プロセスがポート番号を指定して、要求受付用ポートを作る。 サーバ・プロセスは、クライアント・プロセスからの接続要求を待つ。 要求受付用ポートでは、データの送受信はできない。 (2)クライアント・プロセスが通信用ポートを作る。このポートを、サーバ・ プロセスが動いているIPアドレスと、サーバ・プロセスが作った通信ポート のポート番号を使って、接続要求を行う(図3(a))。 (3)接続要求が受け付けられると、サーバ・プロセスには、新たに通信用ポー トが作られる(図3(b))。これは、特定のクライアントとの通信のために 使われる。 こうして一度通信路が開設されると、クライアントとサーバは、どちらからで もデータを送り始めることができる。 表1に示されているポート番号の例は、公に利用目的が決められているもので ある。それ以外に、1024以上のポート番号は、特に利用目的が決められていな いので、空いていれば利用者が自由に使ってもよい。 ***プロセス間通信におけるクライアント・サーバ・モデル プロセス間通信は、本来自由に行うことができる。どのプロセスも自由にメッ セージを送信する権利がある。 クライアント・サーバ・モデルは、本来対称的なプロセスを最初にメッセージ を送る方(クライアント・プロセス)と受ける方(サーバ・プロセス)に分類 することで、プロセス間通信を構造化し、わかりやすくするものである。 TCP/IPの接続におけるクライアントとサーバの役割は、このプロセス間通信に おけるクライアント・サーバ・モデルの1つの例になっている。 その他に、クライアントとサーバという言葉は、サービスを受ける方と提供す る方の意味で使われることがある。インターネットにおけるプロセス間通信で は、多くの場合、サービスの授受の関係におけるクライアントとサーバと、プ ロセス間通信におけるクライアントとサーバが一致している。 ***ソケット UNIX 上で動作するプログラムがTCP/IPの機能を使う場合、UNIXが提供するソ ケットというインタフェースを通じて利用することになる。ソケットは、 TCP/IP を始めとして、XNS, OSI などさまざまな通信プロトコルを UNIX 上で 使うために設計されたものである。TCP/IP だけを考えると、ソケットのイン タフェースは、繁雑であり、使いにくくなっている。 ***DNS(Domain Name Service) TCP/IPによる通信は、通信相手のIPアドレス(32ビットの整数)とポート番号 さえわかれば、可能である。しかしながら、人間にとってIPアドレスは扱いや すいものではない。人間にとってわかりやすい記号の名前から、IPアドレスに 変換するサービスがあれば便利である。このサービスを、名前サービス、それ を行うプログラムを名前サーバという。 インターネットにおける名前サービスは、名前空間をドメイン(領域)に分割 して、階層的管理することで実現されている。これを、ドメイン・ネーム・サー ビス(Domain Name Service, DNS)という。DNSという言葉は、名前サービス を提供するプログラム(名前サーバ)を差すこともある。 DNSでは、主に名前をIPアドレスへ変換するサービスが使われている。その他 に、名前から電子メールの配送先、名前から名前サーバが動いているホストの 名前、名前から、名前サーバ自身の管理情報、逆にIPアドレスから名前を引く ためにも使われる。 **3.2 telnet コマンド ソフトウェア基礎で勉強した telnet コマンドの使い方を復習しなさい。 *4 実験 *** 以下に、課題の必修・選択の別を示す。 (1) 課題1必修。 (2) 課題2必修。 (3) 課題3,5から1つ以上選択。課題4,6,7は、オプション。 (4) 課題8必修。 (5) 課題9-11から1つ以上選択。 (6) 課題12必修。 (7) 課題13-15から1つ以上選択。 ** 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 133.13.30.131 23 ---------------------------------------------------------------------- ここで、133.13.30.131 とは、32ビットのIPアドレスを8ビットづつに区切り、 それぞれの8ビットの整数を10進数で表記したものである。よって、実際のIP アドレスは、次のようしてに計算できる。 ((((133*256)+13)*256)+30)*256+131 == 2232229507 よって、telnet コマンドに次のようIPアドレスを与えてもよい。 ---------------------------------------------------------------------- % telnet 2232229507 ---------------------------------------------------------------------- ***4.1.2 telnet コマンドによる fingerd へのアクセス finger コマンドは、利用者の情報を表示するコマンドである。finger コマン ドは、ネットワーク経由で遠隔の利用者の情報を調べることもできる。 そのような finger コマンドの利用例を以下に示す。 ---------------------------------------------------------------------- % finger shinjo@bw01↓ ^^^^^^^^^^^^^^^^^^^ [bw01] 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/b5/y94/j94070 Shell: /usr/local/bin/tcsh No Plan. Login name: j94077 In real life: Yasushi SHINJO Directory: /home/b2/y94/j94077 Shell: /usr/local/bin/tcsh No Plan. % ---------------------------------------------------------------------- ここで、下線(^^^^)を付けた部分が、利用者のタイプである。 この例で、finger コマンドは、クライアントとして、ホスト bw01 のポート 番号79(finger)のポートに対して、TCP/IP による仮想回線を開設する。そし て、次のような文字列を送る。 ---------------------------------------------------------------------- shinjo↓ ---------------------------------------------------------------------- すると、bw01 上のサーバ(fingerd, finger daemon)は、上の例で示した情 報をクライアントに返す。サーバは、データ転送が完了すると、TCP/IP の結 合を切断する。finger コマンド(クライアント)は、受け取ったデータをを 整形して利用者に対して表示する。 ****■課題1: telnet コマンドを使って、finger サーバにアクセスして、自分の情報を検索 してみなさい。アクセス先のホストとしては、bw01-bw05, design1, design2 の中から選びなさい。その様子を、レポートに添付しなさい。script コマン ドや emacs 中のshell を使うとよい。また、cw01-cw20 に対して行ってみな さい。その結果と、前の結果を比較しなさい。 ***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.ie.u-ryukyu.ac.jp:80/Welcome.html Mosaic などのクライアントは、まずホスト名 www.ie.u-ryukyu.ac.jp とポー ト番号80 を使ってサーバとの間に TCP/IP の通信路を開設する。そして、次 のような文字列を送る。 ---------------------------------------------------------------------- GET /Welcome.html HTTP/1.0↓ ↓ ---------------------------------------------------------------------- ここで、"GET" が命令の種類、"/Welcome.html" は、要求しているデータを表 わす 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 University of Ryukyus WWW Home Page.

Welcome to the University of the Ryukyus.



Web Master of University of the Ryukyus.
---------------------------------------------------------------------- 最初の行が、状態行(status line)と呼ばれる、要求が成功したか失敗した かわ表わしている。"200" とは、成功したという意味である。2行目から最初 の空行までは、これから送るデータのメタ情報である。具体的には、データの 型や、サーバのバージョン、データが更新された日付と時刻、バイト数などが 記録されている。 最初の空行の次が、データの本体である。この例では、HTMLで記述されたデー タが返されている。サーバは、データ転送が完了すると、TCP/IP の結合を切 断する。 クライアントは、受け取ったデータをを整形して利用者に対して表示する。た とえば、インライン・イメージとして指定されたデータを続けてサーバに要求 して展開したり、フォントを変えたりして表示する。 ****■課題2: 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 終了 ---------------------------------------------------------------------- ****表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.31.5 ... 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 で定義されている応答であり、 サーバが、要求を受け付け可能であり、かつ、要求としては投稿要求も受け付 けることを意味している。200 以降の文字列は、コメントである。 第6行では、"help" というコマンドをサーバに送っている。これに対して、 サーバは、100 という応答に続けて、受け付け可能なコマンドなど、簡単な使 い方を返している。23行目に「.」からなる行がある。これが、1つのコマン ドに対する応答の終りを示している。 次に、24行において、"quit" というコマンドをサーバに送っている。これに たいして、サーバは、205 という応答を返し、続いて TCP/IP の結合を切断し ている。26行目は、telnet コマンドが生成したメッセージである。 ****■課題3: telnet コマンドを使って、NNTP サーバ spn.ie.u-ryukyu.ac.jp にアクセス し、ネットワーク・ニュースの記事を画面に表示させなさい。その記事のニュー ス・グループ、ニュース・グループ内の番号、記事の先頭の20行程度を報告書 に添付しなさい。このとき、表NNTP のコマンドを使うとよい。 ****■課題4: その他の 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 の例とし ては、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 サーバに接続した様子を示す。 ---------------------------------------------------------------------- 1: % telnet bw01 smtp↓ ^^^^^^^^^^^^^^^^^^ 2: Trying 133.13.30.131 ... 3: Connected to bw01. 4: Escape character is '^]'. 5: 220 bw.ie.u-ryukyu.ac.jp Sendmail ALPHA-6.58/6.4J.6-UReecs-UG-21.0 ready at Fri, 8 Sep 1995 00:25:05 +0900 6: vrfy root↓ ^^^^^^^^^^^ 7: 250 8: quit↓ ^^^^^^ 9: 221 bw.ie.u-ryukyu.ac.jp closing connection 10: Connection closed by foreign host. 11: % ---------------------------------------------------------------------- ここで、下線(^^^^)で示した部分が、キーボードからのタイプである。この 例では、ホスト bw01 のポート番号25(smtp)のポートに、TCP/IPにより接続を 試みている。2行目から4行目は、telnet コマンドによる定型的な表示である。 ここで、接続が確立されている。すると、サーバは、220 という応答を返して いる。これは、SMTP で定義されている応答であり、サーバが、コマンド要求 を受け付け可能であり、 第6行では、"vrfy" (verify) というコマンドをサーバに送っている。これに対して、 サーバは、250 という応答に続けて、電子メールのアドレスを返している。 次に、8行において、"quit" というコマンドをサーバに送っている。これにた いして、サーバは、221 という応答を返し、続いて TCP/IP の結合を切断して いる。10行目は、telnet コマンドが生成したメッセージである。 ****■課題5: telnet コマンドを使って、bw01 と bw02 にアクセスし、自分のログイン名と 存在しないログイン名について、VRFY で調べてみなさい。そして、ホストに よる応答の違いを比較しなさい。 ****■課題6: bw01 以外のホストで、mail コマンドに -v オプションを付けて、メールを送っ て、その様子を観察しなさい。すると、SMTP による電子メールの配送の様子 が観察される。ただし、mail -v では、電子メールのデータ(DATAの部分)は、 表示されない。ここでデータとは、To: 行や From: 行などのヘッダも含む。 SMTP のレベルにおいては、データの部分の To: 行は、配送には使われない。 その代わりに、SMTP で定義されている RCPT が使われる。 ****■課題7: SMTP コマンドを使って電子メールを送ってみなさい。SMTP の定義は、RFC821 というドキュメントにある。RFC821は、/usr/open/doc/rfc/rfc821.txt にあ る。mail -v の結果を参考にするとよい。 ** 4.2 クライアントの作成 4.1節で telnet でやったことを、C言語のプログラムで実現する。 stream ライブラリを使う。 ***4.2.1 initport() と fdopen2() ****streamライブラリ この実験では、Don Libes 氏により開発された stream ライブラリを用いる。 これは、UNIX 上で TCP/IP を使うプログラムを書きやすくするためのライブ ラリである。このライブラリを使うことで、UNIX固有の繁雑なソケットを利用 することなく、もともとの TCP/IP のモデル(3.1節の図3参照)に近い形で TCP/IPを利用するプログラムを書くことが可能になる。 ****initport() steam ライブラリでは、initport() という関数が、中心的な役割を果たす。 initport() は、TCP/IP のクライアント側(接続を行う方)でも、サーバ側 (接続されるのを待つ方)でも使われる。この項では、クライアント側の使い 方を示す。 initport() は、クライアント側では、次の2種類の形式で使われる。 ---------------------------------------------------------------------- #include #include #include #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 の接続を確立するものである(3.1節 の図3参照)。重要な引数は、TCP/IP のポート番号 port と、ホスト名 host である。その他の引数 CLIENT と SOCK_STREAM は、定数であ。これらは、ク ライアント側で使う限り、必ず指定されなければならない。 結果として、1つのファイル記述子 s が返される。これは、TCP/IP の双方向 ストリームに対応している。よって返されたファイル記述子に対して次のよう に write() システム・コールでデータを書き込むと、そのデータは、接続先 に送り届けられる。 write( s, buf, size ); 逆に、接続先から送られてきたデータは、read() システム・コールにより受 け取ることができる。 read( s, buf, size ); 1つのファイル記述子にたいして、read(),write() もと利用することができ る。これは、ファイルを読み書き(O_RDWR)モードで開いた時と似ている。 もう1つの形式は、ポート番号の代わりにポート名(サービス名)を指定する 方法である。たとえば、"telnet" というサービスの場合、次のようにして使 うことができる。 s = initport(PORT_NAME("telnet"),CLIENT,SOCK_STREAM,host); エラーが生じると、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用 これを利用するためには、リンク時に次のように指定する。 % 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 以下の実験で使うコマンドについては、 /usr/open/classes/slab/info2/2-tcp/Makefile に、コンパイルとリンクのた めの設定が格納されている。これを利用するとよい。 ***4.2.2 finger クライアントのコンパイルと実行 遠隔のfingerサーバに対して問い合わせを行うコマンド、rfinger (remote finter)のソース・プログラムを、以下のファイルに示す。 ---------------------------------------------------------------------- /usr/open/classes/slab/info2/2-tcp/rfinger.c: ---------------------------------------------------------------------- 1: 2: /* 3: rfinger.c -- Remote Finger / 遠隔専用 finger コマンド。 4: $Header: /net/home/cvs/teacher/kono/os/ex/tcp/slab.txt,v 1.1.1.1 2007/12/18 03:39:44 kono Exp $ 5: Start: 1995/09/08 21:04:33 6: */ 7: #include 8: #include 9: #include 10: #include 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: /net/home/cvs/teacher/kono/os/ex/tcp/slab.txt,v 1.1.1.1 2007/12/18 03:39:44 kono Exp $" ; ---------------------------------------------------------------------- ****■課題8: rfinger.c をコンパイルして、実行してみなさい。 ---------------------------------------------------------------------- % cp /usr/open/classes/slab/info2/2-tcp/{rfinger.c,Makefile} . % make rfinger % ./rfinger bw01 $USER ---------------------------------------------------------------------- 報告書には、自分の情報を rfinger で調べた結果を示しなさい。ログイン名 で引いた時と、finger name として設定している文字列で引いたときの結果を 示しなさい。もし、finger name をまだ設定していないならば、chfn コマン ドで設定しなさい。このとき、設定の前後で、表示や検索の様子がどう変わる かを調べて報告しなさい。 ***4.2.3 WWW クライアントの作成 ****■課題9: WWWサーバからデータを得るプログラムを作りなさい。そのプログラムの名前 を、wcat とする。wcat のプログラムの骨組みを /usr/open/classes/slab/info2/2-tcp/wcat.c に示す。これをコピーして、使 いなさい。 ---------------------------------------------------------------------- % cp /usr/open/classes/slab/info2/2-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' がライン・フィードである。い ずれも、ソース・プログラム上では2文字に見えるが、Cコンパイラにより、 1文字に変換される。それぞれ、アスキーでは、13(0x0d), 10(0x0a) である。 ***4.2.4 NNTP クライアントの作成 ****■課題10: NNTPサーバから記事を1つ得るプログラムを作りなさい。そのプログラムの名 前を、nncat とする。nncat のプログラムの骨組みを /usr/open/classes/slab/info2/2-tcp/nncat.c に示す。これをコピーして、 使いなさい。 ---------------------------------------------------------------------- % cp /usr/open/classes/slab/info2/2-tcp/nncat.c . ---------------------------------------------------------------------- nncat コマンドは、次のように3つの引数を与えて利用するものとする。 ---------------------------------------------------------------------- % ./nncat host newsgroup number ---------------------------------------------------------------------- ここで、host は、NNTPサーバが動いているホストの名前newsgroup は、ニュー ス・グループ、number は記事番号である。HTTP プロトコルで用いられる標準 のポート番号は、119(nntp) である。initport() では、PORT_NAME("nntp") の形式を利用するとよい。 報告書には、作成した nncat コマンドの動作例を示しなさい。NNTPサーバが 動いているホストとしては、spn (spn.ie.u-ryukyu.ac.jp) を指定しなさい。 NNTP でやり取りされるデータの行末には、キャリッジ・リターン(carrige return, CR)とライン・フィード(Line feed, LF)の両方が必要であると定め られている。UNIX では、通常ライン・フィード(ニュー・ライン、New Line, NL と呼ばれることもある)だけが行末の記号として使われる。よって、画面 に文字列を表示し、改行したい場合は、次のようなプログラムが使われる。 printf("Hello,world\n"); これは、NNTP では、次のようにしなければならない。 printf("Hello,world\r\n"); ここで、'\r' がキャリッジ・リターン、'\n' がライン・フィードである。い ずれも、ソース・プログラム上では2文字に見えるが、Cコンパイラにより、 1文字に変換される。それぞれ、アスキーでは、13(0x0d), 10(0x0a) である。 余裕があれば、画面に表示する前に、UNIX に合わせて行末のキャリッジ・リ ターンのコードを削除するようにしなさい。 ***4.2.5 SMTP クライアントの作成 ****■課題11: SMTPサーバに対して、VRFY 手続きを送り、受取人のアドレスを確認するプロ グラムを作りなさい。そのプログラムの名前を、mverify とする。mverify の プログラムの骨組みを/usr/open/classes/slab/info2/2-tcp/mverify.c に示 す。これをコピーして、使いなさい。 ---------------------------------------------------------------------- % cp /usr/open/classes/slab/info2/2-tcp/mverify.c . ---------------------------------------------------------------------- mverify コマンドは、次のように2つの引数を与えて利用するものとする。 ---------------------------------------------------------------------- % ./mverify host who ---------------------------------------------------------------------- ここで、host は、SMTPサーバ(sendmailデーモン)が動いているホストの名前、 who は確認するアドレスである。SMTPプロトコルで用いられる標準のポート番 号は、25(smtp) である。initport() では、PORT_NAME("smtp") の形式を利用 するとよい。 報告書には、作成した mverify コマンドの動作例を示しなさい。NNTPサーバ が動いているホストとしては、bw01 を指定しなさい。自分のアドレスを確認 してみなさい。 ---------------------------------------------------------------------- % ./mverify bw01 $USER ---------------------------------------------------------------------- ** 4.3 WWWサーバの改良 4.2節では、HTTP, NNTP, または、SMTP のクライアントのプログラムを作成し た。この節では、簡単な HTTP サーバのプログラムを解読し、機能拡張と改良 を行う。 ***4.3.1 initport()(サーバ側) 4.2.1項では、streamライブラリのinitport()関数のうち、クライアント側の 使い方を示した。ここでは、サーバ側の使い方を示す。サーバ側では、 initport() は、次のような形式で使われる。 ---------------------------------------------------------------------- #include #include #include #include #include "inet.h" s = initport( PORT_NUMBER(port),SERVER,SOCK_STREAM ); int port ; ---------------------------------------------------------------------- これにより、サーバ側に受け付け用のTCP/IPのポートが作られる(3.1節の 図3参照)。そのポート番号は、引数portにより指定される。initport()に対 するその他の引数 (SERVER, SOCK_STREAM) は、定数である。これらは、サー バ側で使う限り、必ず指定されなければならない。 結果として、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() は、1つのサーバ・プロセスで複数のクライアント を同時に扱う事を可能にする。サーバ・プロセスは、複数のクライアントの間 に、複数のTCP/IPの接続を同時に保持することができる。そして、それらの接 続の中で、データが届いたものを選び、返す機能がある。 select_server_stream() により返されたファイル記述子は、クライアントか らのデータが到着している、入出力可能なTCP/IPの通信ポートに対応している。 サーバの処理は、この通信ポートから要求を読みだし、それに応じた処理わ行 い、結果をこの通信ポートに書き出すことである。 なお、ネットワーク・プログラミングの教科書の多くは、ソケットを直接利用 するように書かれている。この実験においても、stream ライブラリを使うこ となく、直接ソケットを利用してプログラムを作成してもよい。 ***4.3.2 httpd-simple ここでは、簡単な HTTP サーバ(WWWのサーバ)を例に、TCP/IPのサーバのプ ログラムの概要を示す。サーバのソース・プログラムを、次のファイルに示す。 ---------------------------------------------------------------------- /usr/open/classes/slab/info2/2-tcp/http-simple.c: ---------------------------------------------------------------------- 1: 2: /* 3: http-simple.c -- 簡単な WWW サーバ (HTTP サーバ) 4: $Header: /net/home/cvs/teacher/kono/os/ex/tcp/slab.txt,v 1.1.1.1 2007/12/18 03:39:44 kono Exp $ 5: Start: 1995/09/03 00:27:22 6: */ 7: 8: #include 9: #include 10: #include 11: #include 12: #include 13: #include "inet.h" 14: 15: /* 16: fdopen2() は、標準のライブラリ関数 fdopen() を拡張したものである。これ 17: は、TCP/IPの通信用ポートに対応しているのように、入出力可能なファイル記 18: 述子について、2つの入出用と出力用の FILE * (fdopen() のマニュアルでは、 19: stream)を返す。引数の fd には、initport() や connect(), accept() で作 20: られた入出力可能(read()システム・コールもwrite() システム・コールも使 21: える)ファイル記述子を指定する。結果として作られた FILE * は、引数とし 22: て与えられた番地 finp, foutp にセットされる。エラーが起きた時には、0 23: が返される。成功した時には、1 が返される。 24: 25: fdopen2() のソース・プログラムは、 26: /usr/open/classes/slab/info2/lib/fdopen2.c にある。 27: */ 28: extern int fdopen2(/* int fd, FILE **finp, FILE **foutp */); 29: 30: void 31: usage() 32: { 33: fprintf( stdout,"Usage: httpd-simple [-p port] directory\n" ); 34: exit( -1 ); 35: } 36: 37: main( argc,argv ) 38: int argc ; 39: char *argv[] ; 40: { 41: extern char *optarg; /* getopt() */ 42: extern int optind; /* getopt() */ 43: int c ; 44: char *dir ; 45: int port ; 46: 47: /* 48: 実験では、大勢の人が同時にプログラムを作る。よってね各自重ならないよう 49: なポート番号を使わなければならない。この実験では、getuid() + 10000 を 50: 使うことにする。もしそのポート番号が、使われていたら、initport() の中 51: の bind() システム・コールで次のエラーが出る。 52: ---------------------------------------------------------------------- 53: #define EADDRINUSE 48 // Address already in use 54: ---------------------------------------------------------------------- 55: その時は、前に走らせた自分のプロセスがいないか、調べなさい。もし自分の 56: プロセスが、そのポートを使っていれば、kill コマンドなどで殺しなさい。 57: もし、他の人がそのポート番号を使っていたら、-p オプションで空いている 58: ポート番号を指定してつかうか、他のホストにログインしてこのプログラムを 59: 走らせるようにしなさい。 60: */ 61: 62: port = getuid() + 10000 ; 63: 64: /* 65: 66: getopt() は、オプションを解析するためのライブラリ関数。引数 "p" は、 67: -p オプションを受け付けるという意味である。続くコロンは、-p string の 68: 形で、文字列を受け付けるという意味である。オプションを見つけると、リター 69: ン・バリューとして返す。その時、受け付ける文字列は、大域変数 optarg に 70: セットされる。 71: */ 72: while( (c = getopt(argc,argv,"p:")) != EOF ) 73: { 74: switch( c ) 75: { 76: /* atoi() は、文字列(ascii)を、整数に変えるライブラリ関数。*/ 77: case 'p': port = atoi( optarg ); break; 78: default: 79: usage(); 80: } 81: } 82: /* 83: getopt() によるオプションの解析が終了した時、optind に、最初の引数(オ 84: プションではなく必須)の添え字がセットされている。これを、argc と比べ 85: て、引数の数が得られる。ここでは、ディレクトリ名が1つ必要である。 86: */ 87: if( argc - optind != 1 ) 88: usage(); 89: dir = argv[optind+0] ; 90: httpd_simple( port, dir ); 91: } 92: 93: httpd_simple( port,dir ) 94: int port ; 95: char *dir ; 96: { 97: int client ; /* 通信用ポート */ 98: int fd; /* 要求受付用ポート */ 99: fd_set fds; /* select_server_stream() のための変数 */ 100: 101: /* カレント・ワーキング・ディレクトリを、指定されたディレクトリに移す。*/ 102: if( chdir(dir) < 0 ) 103: { 104: perror( dir ); 105: exit( -1 ); 106: } 107: 108: /* initport() で、サーバ側の要求受付用ポートを作る。そのポートには、 109: portで指定されたポート番号が付けられる。*/ 110: fd = initport( PORT_NUMBER(port),SERVER,SOCK_STREAM ); 111: if( fd < 0 ) 112: { 113: fprintf( stderr,"initport() returns %d\n",fd ); 114: exit( -1 ); 115: } 116: /* 受付可能な URL を表示する。*/ 117: print_my_url( port ); 118: 119: /* select_server_stream() のための変数の初期化 */ 120: FD_ZERO(&fds); 121: 122: /* 123: サーバのメイン・ループ。サーバ・プロセスは、普通、このように決して終了 124: することがないプログラムである。サーバ・プロセスには、このように無限ルー 125: プが1つある。これを、メイン・ループという。 126: */ 127: while( 1 ) 128: { 129: /* 130: select_server_stream() は、内部で自動的にクライアントからの接続要求を 131: 受け付ける(accept())。このとき、通信用ポートが1つ作られる。 132: select_server_stream() は、クライアントから要求データが送られてきた通 133: 信ポート(のファイル記述子)を返す。 134: */ 135: client = select_server_stream( fd,&fds ); 136: 137: /*1つのクライアントについて要求を処理する関数を呼ぶ。*/ 138: perform( client ); 139: 140: /*TCP/IPの通信路は、perform() により切断される。HTTPでは、1つの要求の 141: 完了ごとに通信路が切断される。*/ 142: } 143: } 144: 145: #define BUFFSIZE 1024 146: 147: /* 148: 1つのクライアントについて要求を処理する関数。引数は、通信用ポートのファ 149: イル記述子。read(), write() により、クライアントと通信を行うことができ 150: る。 151: */ 152: perform( client ) 153: int client ; 154: { 155: FILE *fin, *fout ; 156: 157: char buff[BUFFSIZE+1] ; 158: char filename[BUFFSIZE] ; 159: 160: /* クライアントの情報を表示。*/ 161: print_client( client ); 162: 163: /* 164: fdopen2() により、ファイル記述子から、FILE *を得る。これにより、 165: fgets(), fprintf() といった標準入出力ライブラリ関数を利用して、プロセ 166: ス間通信を行うことが可能になる。 167: */ 168: if( fdopen2(client,&fin,&fout) == 0 ) 169: { 170: fprintf( stderr,"fdopen2() faild.\n" ); 171: fprintf( fout,"HTTP/1.0 500 Internal Server Error\r\n" ); 172: return( -1 ); 173: } 174: /* 175: クライアントからの要求の最初の行を読む。これに一番大事な情報であるファ 176: イル名が含まれている。 177: */ 178: if( fgets( buff,BUFFSIZE,fin ) == 0 ) 179: { 180: fprintf( stderr,"request line read faild.\n" ); 181: fprintf( fout,"HTTP/1.0 500 Internal Server Error\r\n" ); 182: goto error ; 183: } 184: 185: /* sscanf() により、ファイル名を抜き出す。*/ 186: if( sscanf( buff,"GET %s HTTP/1.0", filename ) != 1 ) 187: { 188: fprintf( stderr,"no filename in the request line\n" ); 189: fprintf( fout, "HTTP/1.0 400 Bad Request\r\n" ); 190: goto error; 191: } 192: /* 193: ファイル名に、".." が含まれていたり、"/" で始まっていない場合は、エラー 194: にする。 195: */ 196: if( strstr(filename,"/../" ) || filename[0] != '/' ) 197: { 198: fprintf( stderr,"Dangerous file name [%s] \n",filename ); 199: fprintf( fout, "HTTP/1.0 404 Not Found\r\n" ); 200: goto error; 201: } 202: 203: fprintf( fout,"HTTP/1.0 200 Document follows\r\n" ); 204: fprintf( fout,"Content-Type: text/plain\r\n" ); 205: fprintf( fout,"\r\n" ); 206: fprintf( fout,"request[%s]\n",filename ); 207: 208: /* 209: もし実際にファイルの内容を返す時には、ここでそのファイルを、fopen() し 210: て、fread() するとよい。ただし、アクセスするファイルの先頭には、"./" 211: を付けて、カレント・ワーキング・ディレクトリ以下のファイルしかアクセス 212: を許さないようにしなさい。また、ファイルのによって、Content-Type: を変 213: えること。 214: */ 215: 216: /* 2つの fclose() により通信路が切断される。*/ 217: fclose( fin ); 218: fclose( fout ); 219: return( 0 ); 220: 221: error: 222: /* エラーの時は、内容として、HTML でエラーメッセージを返す。*/ 223: fprintf( fout,"Content-Type: text/html\r\n" ); 224: fprintf( fout, "\r\n" ); 225: fprintf( fout, "

Error.

httpd-simple
\n"); 226: fclose( fin ); 227: fclose( fout ); 228: return( -1 ); 229: } 230: 231: /* 自分自身がサービスできる URL を表示する関数。*/ 232: print_my_url( port ) 233: int port ; 234: { 235: char hostname[BUFFSIZE+1] ; 236: /* gethostname() は、ホスト名を得るシステム・コール。*/ 237: gethostname( hostname,BUFFSIZE ); 238: hostname[BUFFSIZE] = 0 ; 239: printf("http://%s:%d/\n",hostname, port ); 240: } 241: 242: /* 243: クライアントの情報を返す関数。引数は、通信用ポートに対応したファイル記 244: 述子。 245: */ 246: print_client( client ) 247: int client ; 248: { 249: struct sockaddr_in addr; 250: int addrlen; 251: /* 252: getpeername() システム・コールは、通信相手の名前を返す。名前とは、 253: TCP/IP では、ホストのIPアドレスとポート番号である。それは、sockaddr_in 254: 構造体の sin_addr.s_addr と sin_port に設定される。 255: */ 256: addrlen = sizeof(addr); 257: if( getpeername(client, &addr, &addrlen) < 0 ) 258: { 259: perror("getpeername"); 260: return( -1 ); 261: } 262: printf("host: "); 263: print_ip_address( addr.sin_addr.s_addr ); 264: printf(", port: %d\n",addr.sin_port ); 265: } 266: 267: /*IPアドレスを、点つき10進数で表示する関数。*/ 268: print_ip_address( ipaddr ) 269: unsigned long ipaddr ; 270: { 271: /* 272: この共用体を使えば、32ビットの整数を、8ビットずつ区切ってアクセスする 273: ことができる。 274: */ 275: union 276: { 277: unsigned long l ; 278: struct { unsigned char c1, c2, c3, c4 } b; 279: } x ; 280: x.l = ipaddr ; 281: printf("%d.%d.%d.%d", x.b.c1, x.b.c2, x.b.c3, x.b.c4 ); 282: } 283: 284: /* --------------------- http-simple.c --------------------- */ 285: static char rcsid[] = 286: "$Header: /net/home/cvs/teacher/kono/os/ex/tcp/slab.txt,v 1.1.1.1 2007/12/18 03:39:44 kono Exp $" ; ---------------------------------------------------------------------- ****■課題12: http-simple.c をコンパイルして、実行してみなさい。 ---------------------------------------------------------------------- % cp /usr/open/classes/slab/info2/2-tcp/{http-simple.c,Makefile} . % make http-simple % ---------------------------------------------------------------------- ウィンドウを2つ用意しなさい。1つのウィンドウで、次のように http-simple を実行しなさい。 ---------------------------------------------------------------------- % ./http-simple ~/public_html/ http://bw01:13520/ ---------------------------------------------------------------------- ~/public_html/ は、このサーバが提供するデータを含んでいるディレクトリ を指定する。あらかじめ作成しておくこと。ここで、 http-simple が表示し た URL を記憶しなさい。実際には、ホスト名やポート番号が異なるはずであ る。 ★注意: http-simple は、サーバ・プロセスなので、自動的に終了しない。 実験が終了したら、必ず^Cやkillコマンドでこのプロセスを終了さなさい。も し、実験に使用したプロセスが残されていた場合、この減点の対象とする。 もう1つのウィンドウで、次のように Mosaic を起動しなさい。 (lynx でもよい。) ---------------------------------------------------------------------- % Mosaic http://bw01:13520/Welcome.html ---------------------------------------------------------------------- ここで Mosaic の引数は、先ほど ./http-simple が起動時に表示した URL に 引き続き、何か適当な文字列(これ例では、"Welcome.html")を続けたもので ある。 あらかじめ Mosaic を起動していた場合、Open URL を使って、URL http://bw01:13520/Welcome.html の資源を得なさい。すると、次のような画 面が現われるはずである。 ---------------------------------------------------------------------- request[/Welcome.html] ---------------------------------------------------------------------- これは、http-simple.c:perform() の中で、次の fprintf() の呼び出しに対 応している。 ---------------------------------------------------------------------- fprintf( fout,"request[%s]\n",filename ); ---------------------------------------------------------------------- ****■課題13: 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: 課題13では、ファイルを開き、その内容を返していた。ファイルの内容ではな く、プログラムを実行してその結果を返すようなプログラムを作りなさい。実 行するプログラムとしては、次のようなものが考えられる。 (1) who (2) finger (3) ps プログラムを実行し、その結果を取り込むためには、popen() ライブラリ関数 を使うとよい。 内容の型としては、"Content-Type: text/plain\r\n" を付けるとよい。余裕 があれば、"Content-Type: text/html\r\n" にして、その他の文字列とともに、 プログラムの出力に ,
,
を付けて返すようにし なさい。 ****■課題15: http-simple.c は、どのホストからの要求も受け付けている。特定のホストか らの要求しか受け付けないように、改良しなさい。現在のプログラムは、クラ イアントのIPアドレスをprint_client() により表示している。この課題では、 IPアドレスを表示するだけでなく、その内容を調べて、アクセスを許すか許さ ないかを決めなさい。たとえば、次のような方法が考えられる。 (1) 特定のホストだけからのアクセスを許す。 (2) bw0? というホストからのアクセスを許す。 (3) 133.13.30.* というパタンのIPアドレスを持つホストからのアクセスのみ を許す。 (4) 133.13.*.* というパタンのIPアドレスを持つホストからのアクセスのみ を許す。 (5) *.u-ryukyu.ac.jp というホスト名を持つホストからのアクセスのみ を許す。 IP アドレスからホスト名を得るには、ライブラリ関数gethostbyaddr()を使う とよい。 *5 報告書 この章の全ての実験課題について、1通の報告書を提出しなさい。それぞれの 実験について、作成したプログラムとその説明(4.1を除く)、および、その 実行結果を付けなさい。この実験では、プログラムの動きを説明するフローチャー トを付加する必要はない。開発環境と実行環境(計算機、オペレーティング・ システムのバージョン、コンパイラ)を載せなさい。 その他、この実験を通じて得たことを報告しなさい。 この実験に要した時間(単位は時間)を書きなさい。 この実験指導書の改善点を書きなさい。 *一般的な注意: 報告書を、日本語で記述しなさい。プログラム、表、図、数式の羅列は、日本 語ではない。それらは、必ず本文中から参照しなさい。たとえば、「○○を図 1に示す」、あるいは、「○○を以下に示す。<以下、参照しているものを続 ける>」といった形式を使うとよい。数学における証明のように論理的に記述 する。主語と述語を明確に書くとよい。 *参考資料 /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) *付録 **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 #include #include 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. ---------------------------------------------------------------------- **streadm ライブラリ(sized_io ライブラリ)の入手先。 archieで sized_io を検索するとよい。 ------------------------------------------------------------ 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 ------------------------------------------------------------ *end