筑波大学 システム情報系 情報工学域 新城 靖 <yas@cs.tsukuba.ac.jp>
このページは、次の URL にあります。
http://www.coins.tsukuba.ac.jp/~syspro/2023/2023-06-28
あるいは、次のページから手繰っていくこともできます。
http://www.coins.tsukuba.ac.jp/~syspro/2023/
http://www.coins.tsukuba.ac.jp/~yas/
自分にできないことを他の人にやってもらうことは良いこと。 人に聞く練習をしたい。
~yas/syspro/ipc/echo-client-fdopen-one.c
なら次のようにして
コピーできる。
$ cp ~yas/syspro/ipc/echo-client-fdopen-one.c .
「$
」は、プロンプトであり、実際には、「azalea16:~s2012345$
」のようになっている。コピーする時には、打たない。また、末尾
の「.
」を打つこと。これはカレント・ワーキング・ディレクトリの意
味である。
M-x replace-regexp
Replace regexp (default 省略): ^......
Replace regexp ^...... with:
ただし、Web ページにあるプログラム(行番号があるもの)は、一部省略されて
いるので、そのまま行番号を削除しただけでは動作しないことがある。
したがって、cpコマンド でコピーすべきである。
字下げが気に入らない時には、M-x indent-region を使うとよい。region を設 定するには、C-SPC (control + space) でマークを設定して、カーソルを移動 させる。タブ・キーも使えるが、1行ずつしかできない。
カーソル移動には、M-<
やM->
も使
える。
インターネット上のアプリケーションの多くは、TCP/IPという仕組みを用いて 通信を行っている。
TCP/IPは、信頼性のある(reliable)双方向のストリーム転送サービス (stream transport service)を提供する通信プロトコルである(図1)。ス トリームは、次のような性質がある転送サービスである。
図1(a) TCP/IPにより提供される双方向ストリーム
図1(b) Unixのパイプにより提供される単方向ストリーム
なお、C言語のライブラリ関数である fopen(), fgets(), fputs() なども、 ストリームと呼ばれることがある。これは、もともとランダム・アクセス可能 で、メモリ中の配列と同じようにアクセスするすることもできるファイルを、 まるでプロセス間通信のストリームと同じように扱うことができることにも 関係している。
TCP/IP では、プロセスとプロセスが、電話で会話をするように通信が行われ る。普通の電話で人間同士が話をするには、まず電話番号を指定して、話相手 に電話をとってもらわなければならない。TCP/IP においても同様である。 TCP/IPでは、電話を掛ける方をクライアント・プロセス、電話を待つ方をサー バ・プロセスと言いう。
TCP/IP では、回線を接続する段階では、クライアント・ プロセスとサーバ・プロセスは非対称である。一度通信路が確立された後は、 両方のプロセスは、TCP/IPのレベルでは、まったく対称的になる。
TCP/IPにおいてプロセス間に通信路を開設するには、IPアドレスとポート番 号が必要である。ポート番号は、同じIPアドレスを持つホスト(ネットワークに接続されたコンピュータ)上で動いている プロセスを区別するために使われる。
以下に、通信路が開設される手順を示す。
図3(a) TCP/IP通信路の開設(1)
図3(b) TCP/IP通信路の開設(2)
図3(c) TCP/IP通信路の開設(3)
図3(d) TCP/IP通信路の開設(4)
図3(e) TCP/IP通信路の開設(5)
TCP/IPにおける通信路開設において、クライアントは、サーバ側の接続要求受 付用ポートのポート番号を、事前に知っている必要がある。サーバは、普通、 ポート番号を固定する。いくつかの主要なサービスでは、利用すべきポート番 号が決められている。たとえば、HTTP ならば、80 を使う。
クライアント側の通信用ポートのポート番号は、通常は、オペレーティング・ システムにより自動的に割り当てられる。
図?(a) 構造化されていないもの
図?(b) 構造化されたもの
プログラミングの歴 史の中で「構造化」という言葉は、まず、「制御構造」に対して使われた。構 造化プログラミングとは、goto文を、よい goto 文と悪い goto文に分け、よ い goto 文だけを使うようにしようとするものである。初期のプログラミング では、アセンブリ言語や貧弱な制御構造しか持たない Fortran が使われてい たが、その時は、jump 命令や goto 文が多用されていた。そのような jump 命令や goto 文にも、分かりやすいものとわかりにくいものがあった。そこで、 よい goto 文のパターンを整理して、それだけを使ってプログラムを書くのが よいとされた。そしてよい goto 文にはプログラミング言語のレベルでif、 while、continue、break、そして、手続き呼出し(call)とreturn という特別 な形式が割り当てられた。C言語や Pascal では、goto 文が残されたが、 Java などの最近の言語ではgoto 文が記述できなくなっている。
プロセス間通信を構造化するという意味でのクライアント・サーバ・モデル では、まずプロセスをクライアントとサーバの2種類に分ける。
図? 通信のパタンからみたクライアントとサーバの定義
クライアント・サーバ・モデルに基づくプログラムには次のようなことを行う プロセスは存在しない。
com = tcp_connect(); // 接続要求。accept() と対応。 write(com, message); // 要求メッセージの送信 read(com, message); // 応答メッセージの受信 write(com, message); // 要求メッセージの送信 read(com, message); // 応答メッセージの受信 ... // 必要回数繰り返す close(com); // 接続の切断。ここで、write(), read() は、抽象的な意味。 具体的なシステム・コールの使い方を説明したものではない。 write() や read() は、複数の具体的なシステム・コールと対応することが ある。たとえば、1回のシステム・コールでは送信できない場合、(ループして) 複数回のシステム・コールを用いることもある。
acc = tcp_acc_port(); // 受付端の登録。 while( 1 ) { com = accept(acc); // 実際の受付。connect() と対応。 while( !eof(com) ) { read(com, message); // 要求メッセージの受信 write(com, message); // 応答メッセージの送信 } close(com); // 接続の切断。 }
int tcp_connect( char *server, int portno )
[独自, サーバ側]
server
のポート番号 (portno)
へ
TCPで通信路を開設する。
そのTCP/IPのストリームに対応したファイル記述子を返す。
int fdopen_sock( int sock, FILE **inp, FILE **outp )
[独自]
FILE *
に変換する。2つのうち、1つは受信用、もう1つは送信用。
int tcp_acc_port( int portno, int ip_version )
[独自, サーバ側]
int fprintf(FILE *out,char *fmt, ...)
[標準]
char *fgets(char *buf, int n, FILE *in )
[標準]
size_t fwrite(void *buf, size_t size, size_t nitems, FILE *out)
[標準]
size_t fread(void *buf, size_t size, size_t nitems, FILE *in)
) [標準]
FILE *
については、
前半第3回「11. ファイルアクセス(基本)」
も参照。
サーバ側では、次の標準のシステム・コールを用いる。
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)
[標準, サーバ側]
$ telnet hostname
以後、ユーザ名とパスワードを打ち、そのホストへログインできる。そしてシェ
ルにより対話的に利用できる。(coins では、telnet による遠隔ログインのサー
ビスを提供していない。)
$ telnet hostname portno
図? TCPの汎用クライアントとしてのtelnet
以下の例は、telnet コマンドをクライアントとして用いて、echo サーバに接 続して、文字列を送受信している。 coins の azalea PC で Linux が起動している時、 jelly PC (常に Linux が起動している時) では、 TCPのポート番号7で、echo のサーバが動いている。 (violet01, violet03 では動作していない。)
$ grep '^echo.*/tcp' /etc/services
echo 7/tcp
$ telnet azalea15.coins.tsukuba.ac.jp 7
Trying 2001:2f8:3a:1711::231:15...
Connected to azalea15.coins.tsukuba.ac.jp.
Escape character is '^]'.
hello
hello
exit
exit
quit
quit
^]
telnet> quit
Connection closed.
$
telnet コマンドは、^C (control+C) や ^D (control+D)
を打っても終了しない(そのままサーバに送信することがある)。^]
を打つと、ローカルの telnet コマンドを制御することができる。ここで、
quit
などのコマンドが使える。
図? telnet コマンドによる echo サーバへの接続
023年 6月 6日 火曜日 18:19:36 JST
$ grep daytime /etc/services
daytime 13/tcp
daytime 13/udp
$ date
2023年 6月 6日 火曜日 18:20:14 JST
$ telnet azalea15.coins.tsukuba.ac.jp 13
Trying 2001:2f8:3a:1711::231:15...
Connected to azalea15.coins.tsukuba.ac.jp.
Escape character is '^]'.
06 JUN 2023 18:20:15 JST
Connection closed by foreign host.
$
図? telnet コマンドによる daytime サーバへの接続
2023年6月現在、echo サービスや daytime サービスは、azalea01-azalea30 Linux が動作している時、 および、jelly1-jelly10 で利用可能である。 azalea01-azalea30 でWindows が動いている時は使えない。 動作しない時は、 COINS Status で、PC が動いているか、Linux が動いているかを確認しなさい。
coins の echo サービスや daytime サービスを利用する実験を行なう時には、 coins 内のコンピュータに ssh でログインして行なうとよい。自分の PC で telnet コマンドを動かすなら、その PC を VPN でcoins の LAN に接続する こともできるが、少し難しい所がある。
macOS では、Macports の inetutils パッケージに含まれている gtelnet コマンドか、 Homebrew の telnet パッケージの telnet コマンドを使う。
Windows 10 では、dism コマンドで使えるようになる。
しかし使いにくいので、実験ではssh でログインして Linux から使 うことを奨める。Windows の telnet では、デフォルトで localecho が無効になっているので、 キーボードから打った文字が画面に表示されない。実験では、キーボードから 打った文字も画面に表示された方が便利である。そのためには、^]で、 telnet のプロンプトで「set localecho」として、ローカルエコーを有効にす る。
$ cp ~yas/syspro/ipc/echo-client-fdopen-one.c .
$ make echo-client-fdopen-one
cc echo-client-fdopen-one.c -o echo-client-fdopen-one
$ ./echo-client-fdopen-one
Usage: ./echo-client-fdopen-one host port 'message'
$ ./echo-client-fdopen-one azalea15.coins.tsukuba.ac.jp 7 hello
sent: 6 bytes [hello
]
received: 6 bytes [hello
]
$ ./echo-client-fdopen-one azalea15.coins.tsukuba.ac.jp 7 exit
sent: 5 bytes [exit
]
received: 5 bytes [exit
]
$ ./echo-client-fdopen-one azalea15.coins.tsukuba.ac.jp 7 quit
sent: 5 bytes [quit
]
received: 5 bytes [quit
]
$
このプログラムは、コマンドラインから3つの引数をとる。第1引数で指
定されたホスト上の、第2引数で指定されたポートで動作しているサーバに接
続する。そして、第3引数で与えられたメッセージをサーバへ送る。echo
サービスのサーバは、同じ文字列を送り返して来る。このプログラムは、サー
バから送り返されてきた文字列を受取り、結果を画面に表示する。
telnet コマンドとは異なり、文字列を1つしか送受信しない。
echo-client-fdopen-one.c
]
1: /* 2: echo-client-fdopen-one.c -- 文字列を送受信するクライアント(TCP/IP版) 3: ~yas/syspro/ipc/echo-client-fdopen-one.c 4: Created on: 2009/06/01 21:13:38 5: */ 6: #include <stdio.h> 7: #include <stdlib.h> /* exit() */ 8: #include <string.h> /* memset(), memcpy() */ 9: #include <sys/types.h> /* socket() */ 10: #include <sys/socket.h> /* socket() */ 11: #include <netinet/in.h> /* struct sockaddr_in */ 12: #include <netdb.h> /* getaddrinfo() */ 13: #include <string.h> /* strlen() */ 14: #include <unistd.h> /* close() */ 15: 16: extern int echo_client_one( char *server, int portno, char *message ); 17: extern int echo_send_request( FILE *out, char *message ); 18: extern int echo_receive_reply( FILE *in, char buf[], int size ); 19: extern int tcp_connect( char *server, int portno ); 20: extern int fdopen_sock( int sock, FILE **inp, FILE **outp ); 21: 22: int 23: main( int argc, char *argv[] ) 24: { 25: char *server ; 26: int portno ; 27: char *message ; 28: int err; 29: if( argc != 4 ) 30: { 31: fprintf( stderr,"Usage: %s host port 'message'\n",argv[0] ); 32: exit( -1 ); 33: } 34: server = argv[1] ; 35: portno = strtol( argv[2],0,10 ); 36: message = argv[3]; 37: err = echo_client_one( server, portno, message ); 38: return( err ); 39: } 40:main() 関数は、コマンドラインの引数を調べて、echo_client_one() を呼んで いる。第2引数のポート番号については、strtol() で、文字列として与えられ た数を、int に変換している。
41: #define BUFFERSIZE 1024 42: 43: int 44: echo_client_one( char *server, int portno, char *message ) 45: { 46: int sock ; 47: FILE *in, *out ; 48: char rbuf[BUFFERSIZE]; 49: int res; 50: 51: sock = tcp_connect( server, portno ); 52: if( sock<0 ) 53: return( 1 ); 54: if( fdopen_sock(sock,&in,&out) < 0 ) 55: { 56: fprintf(stderr,"fdooen()\n"); 57: close( sock ); 58: return( 1 ); 59: } 60: res = echo_send_request( out, message ); 61: if( res < 0 ) 62: { 63: fprintf(stderr,"fprintf()\n"); 64: fclose( in ); 65: fclose( out ); 66: return( 1 ); 67: } 68: printf("sent: %d bytes [%s\n]\n",res,message ); 69: res = echo_receive_reply( in, rbuf, BUFFERSIZE ); 70: if( res < 0 ) 71: { 72: fprintf(stderr,"fprintf()\n"); 73: fclose( in ); 74: fclose( out ); 75: return( 1 ); 76: } 77: printf("received: %d bytes [%s]\n", res, rbuf ); 78: fclose( in ); 79: fclose( out ); 80: return( 0 ); 81: }echo_client_one() では、tcp_connect() という関数を呼び出している。この結果、 サーバとの間に TCP/IP通信路の開設され、通信可能なファイル記述子 (ファイルディスクリプタ) が返さ れる。このファイル記述子は、標準入出力(0,1,2)や open() システム・コー ルの結果と同じもので、 write() システムコールや read() システムコールの第一引数とし て使うことができる。つまり、write() システムコールを使うと、ネットワー クに対してデータを送り出すことができ、read() システムコールを使うとネッ トワークからデータを受け取ることができる。最後に不要になったら close() で解放する。
このプログラムでは、fdopen_sock() を使って、通信可能なファイル記 述子 com から2つの FILE * を作成している。1つは、入力用、1つは出力 用である。その結果、 高水準入出力ライブラリ を使って通信が行えるようになっている。fprintf() で出力用の FILE * に書 き込むと、ネットワークに対してデータが送り出される。入力用の FILE * に fgets() を行うと、ネットワークからデータを受け取ることができる。
echo_send_request() を呼び出して、要求メッセージを送信している。 echo_receive_reply() を呼び出して、応答メッセージを受信している。
78: int 79: echo_send_request( FILE *out, char *message ) 80: { 81: int res; 82: res = fprintf( out, "%s\n", message ); /* send a request with '\n' */ 83: return( res ); 84: } 85:
echo_send_request() は、エコー・サービスで、要求メッセージを 送信する関数である。 TCP/IP の通信では、行単位(最後に\n)でデータを送受信することが多い。 このプログラムでは、fprintf() で行末に改行(\n)を付加している。
86: int 87: echo_receive_reply( FILE *in, char buf[], int size ) 88: { 89: char *res; 90: res = fgets( buf, size, in ); /* receive a reply message */ 91: if( res ) 92: return( strlen(buf) ); 93: else 94: return( -1 ); 95: } 96:
echo_receive_reply() は、エコー・サービスで、応答メッセージを
受信する関数である。
fgets() を使って、文字列のデータを行末「\n
」まで受信している。
echo サービスでは、1行送り、1行受け取る。他のサービスでは、1行送っ て複数行受け取ったり、受け取る方では行の概念がなくなるもの(HTTPで画像 データを受け取る場合など)もある。その場合は、fprintf() や fgets() では なくて、fwrite() や fread() を使う必要がある。
http://www.coins.tsukuba.ac.jp:80/~syspro/
Firefox などのクライアントは、まずホスト名 www.coins.tsukuba.ac.jp を IP アドレスに変換する。そして、その IP アドレスとポート 番号 80 を使ってサーバとの間に TCP/IP の通信路を開設する。そして、クラ イアントは、開設した通信路を使って、サーバに次のような文字列を送る。
GET /~syspro/ HTTP/1.0←↓
Host: www.coins.tsukuba.ac.jp←↓
←↓
ここで、"GET" が命令の種類、"/~syspro/" は、GETの引数の、要求してい るデータを表わす URL (ファイル名)、"HTTP/1.0" は、使っているプロトコル のバージョンである。
"Host:" は、名前ベースの仮想ホストを使っている場合に必要となる。
空行は、命令のヘッダ部分の終りを意味するもので あり、必要である。
行末の 「←」は、キャリッジ・リターンのコード(0x0d, C言語で
'\r'
)、「↓」は、ラインフィード(ニューライン)のコード(0x0a, C言語で'\n'
)である。HTTP の
ヘッダでは、行末に「←↓」を付けるように規定されている。(サーバを構築
する場合には、「←」か「↓」のどちらか1つしかこない場合でもきちんと動
作することが望ましい。)
仮想ホストの実現方法には、2種類ある。
"HTTP/1.0 302 Found"
が返されることがある。
HTTP/1.1 200 OK←↓ Date: Tue, 06 Jun 2023 09:42:21 GMT←↓ Server: Apache←↓ Last-Modified: Wed, 05 Apr 2023 06:58:57 GMT←↓ ETag: "6aa-5f89153723d54"←↓ Accept-Ranges: bytes←↓ Content-Length: 1706←↓ Connection: close←↓ Content-Type: text/html←↓ ←↓ (空行) 本文(HTML)応答のうち、最初の行が、状態行(status line)と呼ばれる、要求が成功した か失敗したかわ表わしている行である。"200" とは、成功したという意味であ る(表3参照)。2行目から最初の空行(「←↓」だけの行)までは、応答メッ セージのヘッダである。応答メッセージのヘッダには、データの型や、サーバ のバージョン、データが更新された日付と時刻、バイト数などが記録されてい る。
応答で、最初の空行(「←↓」だけの行)の次が、データの本体である。この 例では、HTMLで記述されたデータが返されている。サーバは、データ転送が完 了すると、TCP/IP の通信路を切断する。
クライアントは、受け取ったデータを整形して利用者に対して表示する。たと えば、インライン・イメージとして指定されたデータを続けてサーバに要求し て展開したり、フォントを変えたりして表示する。
$ host www.coins.tsukuba.ac.jp↓ www.coins.tsukuba.ac.jp is an alias for violet03.coins.tsukuba.ac.jp. violet03.coins.tsukuba.ac.jp has address 130.158.231.86 violet03.coins.tsukuba.ac.jp has IPv6 address 2001:2f8:3a:1711::231:86 $ telnet www.coins.tsukuba.ac.jp 80↓ Trying 2001:2f8:3a:1711::231:86... Connected to violet03.coins.tsukuba.ac.jp. Escape character is '^]'. GET /~syspro/ HTTP/1.0↓ Host: www.coins.tsukuba.ac.jp↓ ↓ HTTP/1.1 200 OK←↓ Date: Tue, 06 Jun 2023 09:42:21 GMT←↓ Server: Apache←↓ Last-Modified: Wed, 05 Apr 2023 06:58:57 GMT←↓ ETag: "6aa-5f89153723d54"←↓ Accept-Ranges: bytes←↓ Content-Length: 1706←↓ Connection: close←↓ Content-Type: text/html←↓ ←↓ <HTML>↓ <HEAD>↓ <META HTTP-EQUIV="content-type" CONTENT="text/html;charset=utf-8">↓ <TITLE> システムプログラム↓ </TITLE>↓ <STYLE TYPE="text/css"><!--↓ @import url(coins-syspro.css);↓ --></STYLE>↓ </HEAD>↓ ↓ <BODY>↓ ↓ <H1><A ID="title">システムプログラム</A></H1>↓ ↓ </P><P>↓ ↓ <H2><A ID="2023/" HREF="2023/">■2023年</A></H2>↓ ↓ <H2 ID="past">■以前の内容</H2>↓ ↓ <UL>↓ <LI><A HREF="2022/">2022年</A>↓ <LI><A HREF="2021/">2021年</A>↓ <LI><A HREF="2020/">2020年</A>↓ ... <HR>↓ Last updated: 2023/04/05 15:58:28↓ <BR>↓ <ADDRESS> <A HREF="http://www.cs.tsukuba.ac.jp/~yas/">Yasushi Shinjo</A> / <yas@is.tsukuba.ac.jp> </ADDRESS>↓ </BODY>↓ </HTML>↓ Connection closed by foreign host. $![]()
telnet で接続した後に、HTTP の要求メッセージを3行(空行含む)送っている。
それに対して、HTTP/1.1 200 OK
以下が、HTTPの応答である。
表2 HTTPで定義されている命令(methods)の例
命令 説明 -------------------------------------------------------------------- GET 情報を得る(ヘッダと本体の両方) HEAD 情報のヘッダのみを得る POST 新しく情報を作る
表3 HTTPで定義されている状態コードの例
状態コード 説明 -------------------------------------------------------------------- 200 OK(エラーなし) 301 要求されたデータが移動した 302 見つからない 303 別のページを見よ 304 ページは変更されていない 400 要求の形式にエラーがある 401 ページの閲覧が承認されななかった 403 アクセスが許されていない 404 要求されたデータが見つからない 500 サーバで内部エラーが起きた 501 メソッドが実装されていない
システムプログラムの授業では、主に次のデータを扱うことにし、 marshaling/unmarshalingの問題を深くは取り扱わない。
\n
や \r\n
等の行末を意味
する制御文字を置く。C言語の
文字列操作ライブラリ(前半第2回)
で操作できる。
telnet コマンドで手で HTTP をしゃべり対話できるのは、http:// だけ。 https:// の場合、telnet コマンドは使えない。 練習問題 openssl s_clientコマンド を使えば、手で HTTP をしゃべり対話するこができる。
HTTP/2 は、HTTP/1.1 の改良で、1つのストリームを多重化して、複数の要求と 応答を混在させて扱えるようにした。クライアントが要求していない 資源をサーバ側が先に push する機能がある(不用なデータが送られることがある)。
HTTP/3 は、HTTP/2 の改良で、HTTP のメッセージを運ぶ通信路として TCP/IP (上の TLS) を, QUIC と呼ばれる仕組み UDP/IP (上 の TLS) に置換ること で高速化している。
$ telnet サーバ名 80
接続後、キーボードからHTTPに従い要求メッセージを打ち込む。
たとえば、以下の例は、coins のユーザ syspro の
ホーム・ページ
http://www.coins.tsukuba.ac.jp/~syspro/index.html
を得るための要求を示す。
$ telnet www.coins.tsukuba.ac.jp 80
GET /~syspro/index.html HTTP/1.0
Host: www.coins.tsukuba.ac.jp
最初の行は、空白で区切られた3つの部分がなること、
最後に空行を打つことに
注意しなさい。要求を打つと、問題がなければ画面には目的の HTML ファイル
が表示される。
~syspro/ 以下の Web ページは、場所により様々な文字コードが 使われている。 ホーム・ページ( http://www.coins.tsukuba.ac.jp/~syspro/index.html ) は、文字コードとして UTF-8 を用いている(2023年6月)。 このページをtelnet コマンドで表示する時には、端末の文字コードとしては UTF-8 に設定すると良い。
実験には、/~syspro/index.html
以外で、英語や端末の文字コードと一
致しているページを選ぶ方法もある。たとえば、自分のホームディレクトリ以
下のファイル(~/public_html/file1.html) を作成したとする。
これを Web ブラウザでアクセスするには、次のような URL を打ち込む。
$ telnet www.coins.tsukuba.ac.jp 80
GET /~自分のログイン名/file1.html HTTP/1.0
Host: www.coins.tsukuba.ac.jp
余裕があれば、次のことも行いなさい。
/index.html
以外のものを指定してみなさい。
Host:
行を付けた時と付けない時で動作が異なるような Web サイトを
探しなさい。
練習問題(601) で、キーボードからどのような文字列を打っ たのかを思い出しなさい。そして、それを fprintf() で stdout に表示するプログ ラムを作成しなさい。
$ ./print-http-request-fixed
GET /index.html HTTP/1.0←↓
Host: www.coins.tsukuba.ac.jp←↓
←↓
$
注意: 画面には、「←↓」は表示されない。
C言語では、「\r\n
」である。
HTTPの要求参照。
次のファイルをコピーして修正しても良い。
print-http-request-fixed.c
]
注意2: この課題では、printf() ではなく、fprintf() を使いなさい。そうす ると、ネットワーク通信を行うプログラムに改変する時に簡単になる。
$ ./print-http-request www.example.com /index.html
GET /index.html HTTP/1.0←↓
Host: www.example.com←↓
←↓
$ ./print-http-request www.coins.tsukuba.ac.jp /~syspro/index.html
GET /~syspro/index.html HTTP/1.0←↓
Host: www.coins.tsukuba.ac.jp←↓
←↓
$
注意: 画面には、←↓ は表示されない。
次のファイルをコピーして修正しても良い。
print-http-request.c
]
注意2: この課題では、printf() ではなく、fprintf() を使いなさい。そうす ると、ネットワーク通信を行うプログラムに改変する時に簡単になる。
HTTPサーバに HTML 等のテキスト・ファイルを要求し、その内容を画面に表示 するプログラムを作りなさい。このプログラムの名前を、wcat とする。
wcat コマンドは、次のように3つの引数を与えて利用するものとする。
$ ./wcat host port file
(ここに、サーバから取得したテキストが表示される)
$
ここで、host は、ホスト名、port は、TCP/IP のポート番号、file は、得る
べきファイル名である。これは、URL の文法で記述すると、次のようになる。
http://host:port/file
なお、wcat では、ポート番号の引数を省略しないものとする(省略可能なよ うに工夫してもよい)。HTTP プロトコルで用いられる標準のポート番号は、 80である。
プログラム全体の構造は、次のようになる。
プログラムをつくる時には、
行末の扱い(CR-LF)に注意しないさい。
HTTP では、行末は、\n
ではなく \r\n
となっていることが部分ある。
空行も、\n
ではなく \r\n
となっている部分がある。
$ wget http://www.coins.tsukuba.ac.jp/~syspro/index.html
詳しくは、man wget か、wget -h を実行しなさい。
次のオプションを使ってみなさい。
$ curl http://www.coins.tsukuba.ac.jp/~syspro/index.html > index.html
$ curl http://www.coins.tsukuba.ac.jp/~syspro/index.html -o index.html
詳しくは、man curl か、curl -h を実行しなさい。
次のオプションを使ってみなさい。
$ ./http-response-header file.txt
(ここにヘッダ部分が表示される)
$
HTTPの応答のヘッダ部分は、複数行から構成される。ヘッダの終わりには
空行がある。従って、空行が来るまで、ループして行単位でヘッダを読み込む。
このプログラムを作成する時に用いるデータは、 wget コマンドやcurlコマンドを用いて作成す ることができる。
$ wget --save-headers URL -O file.txt
注意:wget の -O
は、大文字である。
小文字-o
は、別の意味がある。
wget のバージョンによっては、--save-headers
の代わりに-S
が使えることもある。
curl コマンドの場合は、ヘッダと本文を別々に取得し、cat コマンドで接合し て生成できる。
$ curl URL -D headers.txt -o content.txt
$ cat headers.txt content.txt > file.txt
$ ./http-response-content file.txt
(ここに本体部分が表示される)
$
$ ./wsave host port file localfile
(画面には何も出力されない)
$
最初の3つの引数は、練習問題(604) のwcatと同じである。
最後の引数は、保存するローカル・ファイルである。
このプログラムでは、本体部分ではバイナリデータを扱う必要がある。 fdopen_sock() を使う場合、ヘッダについては、fgets() を使っ てデータを送受信してもよい。しかし、ヘッダが終わった後、本体部分では、 fread() を使う必要がある。
本体をファイルに保存する部分は、 前半第3回/ファイルアクセス(応用) のプログラムと似たものになると思われる。 ただし、コピー元は、ネットワークで、コピー先はファイルになる。 fread() を使う場合には、入力したバイト数(読み込んだ要素の数)を調べ、 そのバイト数の分を fwrite() 等でファイルに出力する。
HTTPの応答は、バッファ・サイズよりも大きくなる可能性がある。1回の fread() では受信できないことがある。そのため、すべてのデータを受信する まで、バッファ単位でループする必要がある。
また、fread() で文字列を読み込んだとしても、最後に 0 (NULL) で終端 されないので、注意しなさい。
レポートには、 練習問題(604) と同様の結果も含めなさい。
2023/06/28 捕捉。 練習問題(604) と同様に、テキストファイルが取得できることを、ls コマンドや cat コマン ドを使って示しなさい。さらに、バイナリ・ファイルが使えることを、ls コ マンド等で示しなさい。バイナリ・ファイルとしては、画像ファイル等を用い て、ファイルが壊れずに保存できたことを確認しなさい。あるいは、普通の Web ブラウザや wget コマンドやcurlコマンド を用いて取得した結果と、作成した wsave の結果をsha1sum コマンドや cmp コマンドで比較しなさい。
ヒント:最大nまで、fork() して、それぞれ子プロセスで1個のファイルを コピーする。子供が終了したら、次の子供を fork() する。
実行速度に差があるので、複数のプロセスに均等にURL をばらまく方法では最 速にはならない。
プログラムを終了した後でもう一度実行したときに続きを行う行うことができ るものだけをこの課題を満たしたと認める。プログラムを終了しないものは、 この課題では中断とは認めない。 この課題ではシグナルでプロセスを中断することは行わない。
wget コマンドは、-c オプション、 curl コマンドは、-C オプションを指定すると、中断した続きから再開する。
まず、telnet で、サーバに接続しなさい。そして、それぞれのプロトコルに従っ て、要求を打ち込み、どのような結果が返ってくるかを調べなさい。レポート には、この結果を含めなさい。
次に、telnet で行った要求の送信と結果の受信を行うようなプログラム smtpput を作成しなさい。このページに記述されているように、このプログラムは、メー ルサーバ、送信元アドレス、送信先アドレスは、main() の引数から受け取りな さい。また、メール本文は、標準入力から受け取りなさい。
SMTP は、テキスト・ベースのプロトコルなので、全ての通信にfprintf() や fgets() を使ってもよい。
この課題では、必ずサーバからの応答を解析すること。エラーから生じた時に は、以後の処理を中止する機能を持つこと。
この課題では、fgets() でキーボードからデータを読み込むことはしてはなら ない。main() の引数で得られるパラメタ以外で、サーバに送るべきデータは、 プログラムの内部で fprintf() や snprintf() 等を用いて作成すること。 たとえば、"MAIL FROM:" や "RCPT TO:" などは、fprintf() のフォーマット文 字列で指定する方法がある。キーボードや main() の引数として、本来プログ ラムで生成すべき文字列を与えてはならない。
接続先として次のホストを使いなさい。
プログラムをつくる時には、 行末の扱い(CR-LF)にも注意しないさい。
NNTPで、ネットワーク・ニュースの記事を取得し、 画面に表示するプログラムを作成しなさい。
まず、telnet で、サーバに接続しなさい。そして、それぞれのプロトコルに従っ て、要求を打ち込み、どのような結果が返ってくるかを調べなさい。レポート には、この結果を含めなさい。 次に、telnet で行った要求の送信と結果の受信を行うようなプログラム nncat を作成しなさい。このページに記述されているように、このプログラムは、 必要なパラメタを main() の引数や標準入力から受け取りなさい。
NNTP は、テキスト・ベースのプロトコルなので、全ての通信にfprintf() や fgets() を使ってもよい。
注意1: この課題では、必ずサーバからの応答を解析すること。エラーから生じ た時には、以後の処理を中止する機能を持つこと。
注意2: この課題では、fgets() でキーボードからデータを読み込むことはして はならない。main() の引数で得られるパラメタ以外で、サーバに送るべきデー タは、プログラムの内部で fprintf() や snprintf() 等を用いて作成すること。 たとえば、"GROUP " や "ARTICLE" などは、fprintf() のフォーマット文字列 で指定する方法がある。キーボードや main() の引数として、本来プログラム で生成すべき文字列を与えてはならない。
接続先として次のホストを使いなさい。
プログラムをつくる時には、 行末の扱い(CR-LF)にも注意しないさい。
$ ./time-client host 37
Sun May 26 20:54:23 JST 2023
$
このプログラムでは、TCP/IP でサーバに接続した後、何も送らずにサーバか ら4バイトの数を読み込む。その4バイトの数は、ネットワーク・バイト・オー ダになっているので、ntohl() で、ホストのバイト・オーダに変換する。この 値に、ある値で補正して、Unix で使われているtime_t に変換する。最後に、 strftime() や localtime() でカレンダーの形式に変換する。
RFC868 Time Protocol では、値は、1900年1月1日 0:00 (GMT) を基準にした 秒数を返す。time() システムコールや gettimeofday() システムコールでは、 1970 年を基準にしている。strftime() や localtime() を使う前に、差分を補正する必要がある。
この課題では、ポート番号 37 の time を使いなさい。ポート番号 13 の daytime を使ってはならない。
接続先のホストとしては、次のどれかを使いなさい。
次のようなオプションがよく使わせる。
-a
(all)
-n
(number)
多くのブラウザで、次のような方法で開発ツールを実行し、HTTP の要求と応答を表示できる。
(クリックで拡大)
図? Firefoxの開発ツール
$ openssl s_client -connect サーバ名:ポート番号 -quiet
接続後、キーボードからHTTPに従い要求メッセージを打ち込む。
たとえば、以下の例は、coins のトップページの
https://www.coins.tsukuba.ac.jp/
を得るための要求を示す。
なお以下の例で、 ^M は、Control+M、すなわち、リターン・キー(Enterキー)である。
C言語の '\r'
を意味する。
端末でキーボードから単純に Control+M と打っても、^J 、すなわち、ラインフィード(ニューライン)(C言語の '\n'
)と同じ扱いになる。
こういう場合には、^V (Control+V) に続けて ^M と打つと良い。
^V は、端末で特殊なキーを打つためのキーで、^M だけでなく ^C や ^D を打ち込むためにも使える。
$ openssl s_client -connect www.coins.tsukuba.ac.jp:443 -quietdepth=2 C = JP, O = "SECOM Trust Systems CO.,LTD.", OU = Security Communication RootCA2 verify return:1 depth=1 C = JP, O = "SECOM Trust Systems CO.,LTD.", CN = NII Open Domain CA - G7 RSA verify return:1 depth=0 C = JP, ST = Ibaraki, L = Tsukuba, O = University of Tsukuba, CN = www.coins.tsukuba.ac.jp verify return:1 GET / HTTP/1.0^M
Host: www.coins.tsukuba.ac.jp^M
^M
HTTP/1.1 200 OK Date: Wed, 07 Jun 2023 09:07:23 GMT Server: Apache X-Powered-By: PHP/8.2.7 Link: <https://www.coins.tsukuba.ac.jp/wp-json/>; rel="https://api.w.org/", <https://www.coins.tsukuba.ac.jp/wp-json/wp/v2/pages/378>; rel="alternate"; type="application/json", <https://www.coins.tsukuba.ac.jp/>; rel=shortlink Connection: close Content-Type: text/html; charset=UTF-8 <!DOCTYPE html> <html dir="ltr" lang="ja" prefix="og: https://ogp.me/ns#" > <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=Edge, chrome=1"> <meta name="apple-mobile-web-app-capable" content="yes"> <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="google-site-verification" content="EnRHzHoRO1VhJKNxavGiBMawFQBjuwYmNgnMFZ4Zj8A" /> <link rel="shortcut icon" href="https://www.coins.tsukuba.ac.jp/wp/wp-content/themes/coins/images/share/favicon.ico"> <link rel="icon" href="https://www.coins.tsukuba.ac.jp/wp/wp-content/themes/coins/images/share/favicon.gif" type="image/gif"> <link rel="stylesheet" href="https://www.coins.tsukuba.ac.jp/wp/wp-content/themes/coins/style.css"> <link rel="stylesheet" href="https://www.coins.tsukuba.ac.jp/wp/wp-content/themes/coins/print.css"> <link rel="alternate" type="application/rss+xml" title="筑波大学 情報学群 情報科学類" href="https://www.coins.tsukuba.ac.jp/feed/"> <!-- All in One SEO 4.3.2 - aioseo.com --> <title>筑波大学 情報学群 情報科学類 | 大切な仲間と最先端のカリキュラムを</title> ... <!-- / #page --></div> </body $
![]()