実験 6 通信基礎実験 1 目的 ネットワークを通じてデータ転送を行うことを体験的に学ぶために 本実験ではT CP/IPプロトコルを使い ワークステーション間で通信を行うクライアントサーバモデルのプログラムを作成する 2 解説 1 ネットワークとプロトコルネットワーク ( コンピュータネットワーク ) とは2 台以上のコンピュータが何らかの線でつながったものである しかし 線で接続されているだけではコンピュータ間で通信を行うことが出来ず コンピュータ間の通信を行う物理的な手順 ( ネットワークカードなどのハードウェアに関する通信方法 ) 論理的な手順 ( アプリケーションにおけるソフトウェアに関する通信方法 ) を与える必要がある このようなコンピュータ間の通信手段の取り決めをプロトコルという インターネットとは 大学や企業などの組織内で構築されたネットワークを相互に接続する広域ネットワークで インターネットプロトコル (IP) と呼ばれるプロトコルによって通信が行われる 通信線には電話線 イーサネットケーブル 同軸ケーブル 光ケーブル 鳩などがある 2 TCP/IP TCP/IPはアメリカ国防省の高等研究計画局 (Advanced Research Projects Agency, ARPA) のネットワークであるARPANET 上で開発された 当初 ARPANETはパケット交換を使用して広域ネットワークを構築するために設計されたが TCP/IPプロトコルはLANにも適用されるようになった 1980 年代始めに発表されたUNIX4.2BSDにおいてTCP/IPが実装されていたことから ワークステーションの通信プロトコルとして実質的な標準となり 1983 年にはARPANETの標準プロトコルとして使用されるようになった 3 UDP UDPはトランスポート層のプロトコルで アプリケーションからアプリケーションへの通信を提供する IPがインターネットアドレスをともにパケットの交換を行うのに対し UDPはポートという概念でパケットの交換を行う IP で確立された通信路上にポートという出入り口を持った通信路が仮想的に複数確立される -1-
UDPはTCPのコネクション型通信と比べ 信頼性の低いコネクションレス型の通信を行い 誤り制御 順序付け フロー制御を行わない そのため UD Pモジュールの処理が単純になり TCPに比べ 高速なデータ転送が可能になる このようにIPがもつ最低限の機能のみをユーザーアプリケーションに提供しているため 誤り制御などの信頼性については上位のプロトコルで対処する必要がある 4 IPアドレスネットワークに接続されたパソコンやワークステーションで通信を行うためには それぞれのコンピュータを区別するためのアドレスが必要である インターネット上の ( 正確にはインターネットプロトコルで通信を行う ) 装置に割り当てられるアドレスをインターネットアドレス (IPアドレス) と言い 172. 24.4.1 のようにドットで区切られた4つの10 進数で表される インターネットに接続される全ての装置にはユニークなIPアドレスが割り当てられるようになっており それらはすべてNIC( Network Information Center) で管理されている そのなかでも 日本で使用されるIPアドレスはJPNIC で管理されている しかし インターネットが急速に普及するにつれてIPアドレスの不足が心配されるようになったきた そのため 組織内部だけのクローズなLAN 環境では インターネットにアクセスする場合だけ本来のユニークなアドレス ( グローバルアドレス ) を割り当てる方法が利用されるようになった プライベートアドレス空間からグローバルアドレス空間 ( インターネット ) を アクセスできるようにする仕組みとしては プロキシや NAT(Network Address Translator) が利用される 5 クライアントサーバモデルのネットワークプログラミングクライアントサーバモデルはネットワークアプリケーションの形態の1つで 1つのアプリケーションがクライアント( サービスを要求するもの) とサーバ( サービスを提供するもの ) で構成されている クライアントとサーバはネットワーク上でお互いに通信を行いながら具体的な処理を行う 一般的にサーバはクライアントからの要求を待ち 要求に対応した処理を行い その結果をクライアントへ送信する たとえば WWWはクライアントサーバモデルのシステムであり Netscape Navigator Internet Explorer などのクライアントと Apache Netscape Enterprise Server などのサーバがHTTPによる通信を行っている 本実験では クライアントとサーバの2つのプログラムがネットワーク上でお互いに通信を行いながら具体的な処理を行うプログラムを作成する -2-
6 ソケットインターフェースソケットインターフェースは, トランスポート層とアプリケーション層の間の インターフェースで, ユーザが作成するアプリケーション層のプログラムから, トランスポート層のプロトコルモジュール ( TCP,UDP) を呼び出すために使用される TCP か UDP どちらかを選択できるが, ここでは UDP によるコネクションレス型通信の基本的手順を説明する ソケットインターフェースのシステムコールを使用する例を下図に示す プログラム内で呼ばれるシステムコールを説明する client process server process socket() socket() bind() bind() request sendto() recvfrom() recvfrom() sendto() reply close() close() 1 socket() クライアント, サーバともに通信の最初に呼び出すシステムコール 通信のための通信ポートの作成, 使用するプロトコルを設定 2 bind() 作成したソケットを識別するためのアドレスを与える 3 sendto (), recvfrom() 通信相手のソケットアドレスを指定して, メッセージの送受信を行う クライアントは sendto() で要求して, recvfrom() で受け取る 4 close() データ転送終了時にソケットをクローズする処理を行う -3-
7 ポート番号 ポート番号にはユーザが自由に使える物と, すでにきめられたサービスのために割り当てられているものがある 以下に一般に割り当てられているポートの一覧を示す ポート番号 用途 0-255 ウェルノウンポート ( telnet, ftp など良く使われるサービス用 ) 256-1023 予約済ポート ( スーパーユーザプロセスのみ利用可能 ) 1024-5000 自動割り当てポート ( ポートを指定しなかった場合に自動的にシステムが割り当てる ) 5001-65535 ユーザが自由に使えるポート 3 実験 1 週目は文字通信プログラムを作成する 通信で送られてきた ASCII 文字 ( 半角英数字 ) を大文字にしてクライアントへ返信するサーバープログラムと, 通信で文字を送り, また送られてきたデータを受信 表示するクライアントプログラムを使用して, ソケット通信を学ぶ -4-
4 実験結果 #include < stdio.h> #include < sys/types.h> #include < sys/socket.h> #include < netinet/in.h> #include < netdb.h> #define MAXLINE 512 #define SERV_UDP_PORT 35567 / * 穴埋め : 使用するポート番号を定義する*/ #define Error fprintf main( argc, argv) int argc; char *argv[]; { int n, sockfd, servlen, dummylen; struct sockaddr_in serv_addr, cli_addr; struct hostent *host; char *hname; char *program; char sendline[ MAXLINE ], recvline[ MAXLINE + 1 ]; int errno; program = argv[ 0 ]; if ( argc == 1) { fprintf( stderr, " 引数 ( サーバーホスト名 ) を指定してください. n" ); /* 穴埋め : 引数で指定したサーバ情報を host に代入する*/ if (( host = gethostbyname( argv[ 1 ])) == NULL) { 5 fprintf( stderr, "%s n", h_errno ); /* serv_addr にサーバアドレスを設定する*/ bzero(( char *) &serv_addr, sizeof( serv_addr )); serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = inet_addr( inet_ntoa( host-> h_addr )); serv_addr.sin_port = htons( SERV_UDP_PORT ); /* 穴埋め :socket() */ if (( sockfd = socket( serv_addr.sin_family, SOCK_DGRAM, 0 )) == -1) { fprintf( stderr, "Socket Error: %d n", errno ); -5-
/* cli_addr にローカルアドレスを設定する*/ bzero(( char *) &cli_addr, sizeof( cli_addr )); cli_addr.sin_family = AF_INET; cli_addr.sin_addr.s_addr = htonl( INADDR_ANY ); cli_addr.sin_port = htons( 0 ); /* 穴埋め :bind() */ if ( bind( sockfd, ( struct sockaddr *)&cli_addr, sizeof( cli_addr )) == -1) { fprintf( stderr, "Bind Error: %d! n", errno ); /* main loop */ printf (" 終了する場合は Ctrl+D を入力してください. n" ); while( printf ("%%"),fgets( sendline, MAXLINE, stdin )!= NULL) { n = strlen( sendline ); /* 穴埋め: ループ内の処理 */ if (( servlen = sendto( sockfd, &sendline, n, 0, ( struct sockaddr *)&serv_addr, sizeof ( serv_addr ))) == -1) fprintf( stderr, "Send Message is failed!: %d n", errno ); continue; if ( ( recvfrom( sockfd, &recvline, servlen, 0, ( struct sockaddr *) &cli_addr, &dummylen)) == -1) { fprintf( stderr, "Receive Message is failed!: %d n", errno ); continue; recvline[ n ] = 0; fputs( recvline, stdout ); printf (" 終了します n" ); close( sockfd ); 入力した文字列がサーバに送られ 大文字になって帰ってくる様子を確認 -6-
5 考察 1 本実験では UDP というコネクションレス型の通信プログラムを作成したが TCP を利用してプログラムを作成する場合 どのような点が異なるか 2.2.2 の手順と比較して具体的に説明せよ クライアント側クライアント側では, socket (),connect(),write(),read(),close() の 5 つのシステムコールを使用する. UDP とは違い, 一度繋ぐと通信路を確保して接 続をし続けるため, クライアント側に返すために宛先が必要ないため,bind () は存在しない. write() read() はそれぞれ sendto() recvfrom() に対応しており, データの送信受信を行う. サーバ側サーバ側では, socket (),bind(),listen(),accept(),write(),read(),close() の 7 つのシステムコールを使用する. こちら側は相手から繋いでもらうためにソケットにアドレスを設定しないといけないため, bind() が存在する. listen() は接続待ち状態であり, クライアント側の connect() を待つ ( また, 接続されるクライアント数もここで設定する 3). accept() はクライアントから connect() されたときにその接続を受け, 接続された場合にクライアント側に通知する. write() read() はクライアント側とデータの送受をするための関数である. このように, TCP プロトコルを使った通信では, 確実に相手に情報を届けることができ, 送ることができなかった場合もちゃんとエラーが返すことができるようになっている. MSN メッセンジャーなどが TCP で動いている例である. 2 本実験で作成したプログラムを, 効率的なプログラム, 他人から見て読みやすいプログラムに改良するとしたら, どのような点が考えられるか検討せよ. プログラムの穴を埋めているときに気になったのが変数に注釈文がついていないこと 変数を使おうと思ったときにどの変数を使って良いのか分からず 新しい変数を作ってしまった これは明らかな無駄である 宣言型変数名 ; // 変数の使用目的などの形にすれば改善は可能 3 本実験を行って, 難しかった点, 興味を持った点などについて感想を述べよ 穴埋めプログラムのはずが 変数宣言や エラー処理などで穴埋めで無い部分を加えすぎてしまったので組み直すことになってしまった 明らかな無駄である かといって1から組めるわけでもないので全部組めと言われても困るところがある プログラム全体を見れば何を行っているのかだいたい想像はつくが 1 行 1 行に注釈文を付けることは無理である この状態で次の実験がうまくいくのか心配である -7-
-8-