通信ネットワーク補足 TCP サーバプログラムその 13 プリント 7 の UDP Multicast の両方を実験できる作品 を検討する問題で作ったクラスの 作成例となる UDPrec と UDPsnd そして前のプリントで示した TcpClient TcpServer のクラスが P:\ 鈴木 \javanet の中にあり 実験で利用できます トランスポート層 : TCP や UDP 送受信は ポート番号で相手側が実行しているソケットプログラムを識 別します つまりサーバに限らず 情報の送信にポート番号を利用しているのです そのため 通信中 の一つのソケットは送信元と宛先の 2 つポート番号があります 送信元のポートをローカルポート 宛 先のポートをリモートポートと呼びます Windows には 使われているソケットの状態を表示するツール netstat コマンド があるので 以下 に実行例を示します (TCP 状態の参考 ) これは 以前のページで紹介した TcpServer と TcpCVlient を動かして それぞれを接続中の状態にして アクセサリのコマンドプロンプトで netstat -no と実行させた例です PID が 1196 番の TcpServer と 3184 番 TcpClient の通信 中で TcpClient のポートが 49370 になってと分かります (PID はプロセス ID で Windows であれ ばタスクマネージャで確認できます ) 例えば java TcpServer 3184 のように実行した時の例です C: work>netstat -no Active Connections Proto Local Address Foreign Address State PID TCP 192.168.0.3:49370 192.168.0.3:49155 ESTABLISHED 3184 TCP 192.168.0.3:49155 192.168.0.3:49370 ESTABLISHED 1196 上記で指定しているオプションで n が IP アドレスとポートをコロンで区切って表示させ る指定で o がプログラムの識別番号表示指定です State キーワードの意味を次の表で示します クライアント状態 State 補足説明サーバー状態 State 表示なし CLOSEED 接続していない待機状態 LISTENING データ送受信可能 ESTABLISHED SYN_SENT 接続要求中 SYN_REC FIN セグメント送出から ACK を受け取 FINE_WAIT1 切断中 1 るまで コネクション確立中データ送受信可能 ESTABLISHED FIN セグメント受け取りから CLOSE_WAIT ACK を受け取って から FIN セグメント FINE_WAIT2 切断中 2 CLOSE_WAIT が受信までの状態 FIN セグメントが受信から ACK 送出ま TIME_WAIT 切断中 3 で FIN セグメント送出から ACK を受け取 LAST_ACK るまで netstat a のコマンド入力で 接続待機状態も表示することもできます これでと UDPsnd の実行状態も確認できます 色々試してみましょう TCP 通信の仕組みその 2( 前回プリントの続き ) TCP プロトコルで送信したデータは 送り先に確実に届いたかを判断する仕組みを含んでい ます もちろん受信側で受信 送信のプログラムが実行していなければなりませんが 受信側が 送信データ を正しく受け取ったかどうかのやり取りが行われます この正しく受け取ったかのやり取りで エラーがあった場合は 内部で再度送り直す規則になっ ています 送り直す場合も 内部的にその回数や経過時間などを記憶し 何回送っても応答が戻 らないようなら通信不能と判断し 実行エラーとします これは シーケンス番号と ACK 番号で同期 (Synchronize) を取りながら確実に届ける仕組み を利用して実現しています これ以外で次のフロー制御と輻輳制御の仕組みが備わっています
通信ネットワーク補足 TCP サーバプログラムその 14 フロー制御基本は確実に伝達することでした 次に受け取るべき情報の確認応答 (ACK 番号 = 受け取った シーケンス番号 + 受信データ量 ) のメッセージ受信で相手が受け取ったと判断する訳ですがその応答セグメントを待っていると遅くなります それで 相手の応答セグメントを待たずに送信できる規則があります それは自身がどれだけのセグメントをまとめて受けられるかを TCP ヘッダ内のウィンドウサイズに指定して ACK のセグメントで応答することから始まります このウィンドウは 広告ウィンドウ (advertised window) と呼ばれます ( これは受信用のバッファに相当します ) また ウインドウプローブ というセグメントで 相手の広告ウィンドウサイズの要求ができます これは定期的に送信され 受信側は ウィンドウ更新通知セグメント で応答する仕組みになっています これを受信した送信側では 以降でその数のセグメントを連続して送ることができます このような制御はフロー制御と呼ばれます ここの連続したセグメント受信において 途中のセグメントを受け取れなかった場合はどうするのでしょうか? 失ったと判断されたセグメントを要求することに相当するセグメント で重複して応答する規則になっています これは重複確認応答や重複 ACK と呼ばれます (ACK シーケンス番号が 次に送って欲しいシーケンス番号 のなので その設定のセグメントで応答する動作です ) データの送り手でこの重複 ACK を受け取った時 タイムアウトを待たずに失われたセグメントが分かるので そのセグメントを再送します この転送は 高速再転送 と呼ばれます 輻輳制御通信路の状態によって セグメントの送る量を制御します これは受信用の広告ウィンドウとは別に 送信用に持つパラメタで輻輳ウィンドウと呼ばれます 接続直後は混雑状態が分からないので 初期の輻輳ウィンドウは 1 のセグメントで連続送信はしません ACK を受け取ったら1 増やした 2 にします 2 セグメント連続送信で応答した ACK が 2 つ戻った場合は それぞれで 1 ずつ増やすので 4 の輻輳ウィンドウとなります 指数関数的に増えていきますが 初期は1なので スロースタート 呼ばれる制御です そして 予め決められた スロースタートのしきい値 に輻輳ウィンドウサイズが達すると 輻輳回避 と呼ばれるアルゴリズムに変ります これは 1 回の ACK 応答に対して 1 輻輳ウィンドウサイズ を割った値だけ増やします そして 重複 ACK のエラーで 輻輳ウィンドウサイズを スロースタートしきい値 +3 に戻し タイムアウトエラーで 1 に戻して スロースタート に戻します なお 実際の送信で使われるウィンドウは 輻輳ウィンドウと広告ウィンドウのどちらか小さい方が使われます 最大セグメント長 MSS(Maximum Segment Size) について TCP セグメントに格納されるデータの最大バイト長のことです TCP は上位アプリケーションからのデータを必要に応じて複数に分割しますが その分割は MSS のサイズより大きなデータの時に行われます (TCP ヘッダの大きさは含まれません ) MSS の決定は TCP のコネクションの確立時です 確立時の送信側で 送信に使うデータリンク層の最大転送単位 (MTU:Maximum Tranmission Unit) の値から 可能な MSS の値を計算して SYN セグメントで相手先に通知する形態です 例えば イーサネットに接続している場合の MTU は 1500 バイトなので IP ヘッダの 20 バイトと TCP ヘッダの 20 バイトを除いた 1460 バイトを MSS とします なお カプセル化などの環境によってもっと少ない値に設定されることがあります
通信ネットワーク補足 TCP サーバプログラムその 15 プリント 7 で UDP Multicast の両方を実験できる作品 を検討する問題がありました その 解答例を示します ( 課題ではありません 参考資料です ) 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 import java.io.*;//bufferedreadered 用 import java.net.*;//datagramsocket InetAddress 用 public class UDPsnd { public static void main(string[] args) throws Exception { if(args.length!= 2){ System.out.println(" 使い方の例 java UdpSndTest IP アドレスポート番号 "); return; String oct1 = args[0].substring(0, args[0].indexof('.'));// 第 1 オクテット InetAddress inet = InetAddress.getByName(args[0]);// 送信先 int portnumber = Integer.parseInt(args[1]);// ポート番号 DatagramSocket sendsocket = new DatagramSocket();//UDP 送信用ソケット if((integer.parseint(oct1) & 0x0f0) == 0xe0){// クラス D sendsocket = new MulticastSocket();// 送信用ソケット System.out.println(" マルチキャスト第 1 オクテット :" + oct1); //((MulticastSocket)sendSocket ).joingroup(inet); // マルチキャストアドレスのグループに参加 ((MulticastSocket)sendSocket ).settimetolive(1);// 寿命設定 :( 外部に流さない ) BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); for(;;){ System.out.print(" 送信文字列 >"); String msg = br.readline();// 送信メッセージキー入力 msg = "s140000" + msg; byte[] buf = msg.getbytes("ms932");// バイト列に変換 DatagramPacket packet; packet = new DatagramPacket(buf, buf.length, inet, portnumber); //IP アドレス ポート番号も指定 sendsocket.send(packet);// 送信 if(msg.equals("s140000")) break; sendsocket.close();// ソケットを閉じる import java.net.*;//datagramsocket 用 public class UDPrec { public static void main(string[] args) throws Exception { if(args.length!= 1 && args.length!= 2){ System.out.println(" 使い方の例 java UDPrec マルチゃストアドレスポート番号 "); System.out.println(" java UDPrec ポート番号 "); return; int portnumber = Integer.parseInt(args[args.length==2? 1 : 0]);// ポート番号 InetAddress inet = InetAddress.getByName(args[0]); DatagramSocket recsocket = new DatagramSocket(portNumber);// 受信用ソケット if(args.length == 2){ String oct1 = args[0].substring(0, args[0].indexof('.'));// 第 1 オクテット if((integer.parseint(oct1) & 0x0f0) == 0xe0){// クラス D recsocket = new MulticastSocket(portNumber);// 送信用ソケット System.out.println(" マルチキャスト第 1 オクテット :" + oct1); ((MulticastSocket)recSocket).joinGroup(inet); // マルチキャストアドレスのグループに参加 else IP.printLocal(); byte[] buf = new byte[256]; DatagramPacket packet = new DatagramPacket(buf, buf.length); for(;;){ System.out.println(" 受信待機状態 "); recsocket.receive(packet);// 受信 & wait int len = packet.getlength();// 受信バイト数取得 String msg = new String(buf, 0, len,"ms932");// 受信バイト列のデコード System.out.print(packet.getAddress() + " より受信 \n "); System.out.println(msg + " : 以上 " + len + "byte を受信しました "); if(msg.equals("s140000")) break; recsocket.close();
通信ネットワーク補足 TCP サーバプログラムその 16 余裕がある人の検討問題 11 ページで示した TcpServer.java のプログラムでは 32 行目の s = tcp_in.readline(); //1 行受信で 1 行の受信文字列を受け取るまで停止して進まない状態になります この状態をブロック状態とよびます 各種サーバのアプリケーションでは ブロック状態になりプログラム全体が進まないと 他の接続などができなくなるので プログラムの進行の流れを複数に分ける方法が使われます これを Java のプログラミングで行う場合は スレッド (Thread) を使って実現します 一つの実行の流れをスレッドと呼びます main から実行したスレッドより 別のスレッドを作る方法の一つを以下で紹介します 次は 接続相手に対して一つのスレッドを使います 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 import java.io.*; import java.net.*; public class SocketThread implements Runnable { Socket socket; // 通信用ソケット Thread thread; // スレッド管理クラス InputStream sok_in; //byte 入力用 InputStreamReader sok_is; // 文字入力用 BufferedReader sok_br; // 文字列入力用 OutputStream os; //byte 出力用 String senddata = " ここはクライアント側へ送信するメッセージとして変更 r n"; String receivedata = null; public void init() throws Exception {// 送受信ストリームを取得 this.sok_in = socket.getinputstream(); this.sok_is = new InputStreamReader(sok_in); this.sok_br = new BufferedReader(sok_is);// 入力用 this.os = socket.getoutputstream();// 出力用 public void send() throws Exception{// 送信メソッド this.os.write(senddata.getbytes()); this.os.flush();// 送信してない情報があれば 全て送信する public void receive() throws Exception {// 受信メソッド System.out.println(receiveData = sok_br.readline()); public void doloop()throws Exception {// 送受信ループ while(thread!= null){ send();// 送信 receive();// 受信 public void run() {// スレッドの実行範囲 try{ init();// 送受信ストリームを取得 doloop();// 送受信ループ catch(exception e) { e.printstacktrace(); thread = null; public SocketThread(Socket socket) throws Exception { this.socket = socket; // コンストラクタ this.thread = new Thread(this);// スレッドインスタンス生成 this.thread.start();// 上記のrunメソッドを別スレッドで実行する
通信ネットワーク補足 TCP サーバプログラムその 17 前述の SocketThread クラスを使うプログラムです プリント 11 の TcpServer.java を TcpServer2.java の名前でコピーします そして プリント 11 の TcpServer.java の 19 行から 22 行を次のように変更すれば何回でも接続可能なサーバになります System.out.println(" クライアントからの接続受け入れ待機 "); while (true) { // クライアントからの接続を待ち 接続してきたら // そのクライアントと通信するソケットを取得する Socket socket = serversock.accept();// クライアント通信用ソケット取得 new SocketThread(socket);// そのソケットと別スレッド通信開始 プリント 11 の TcpServer.java のクラス変数は利用せずに クライアントとの通信は SocketThread クラスに任せる使い方です どのような利用法ができるか? 考えてみましょう
通信ネットワーク補足 TCP サーバプログラムその 18 中間テスト出題範囲 これまでのネットワークの内容全部です 学外の資格試験を目指しているためです ですがそのための基本となる重要部分のみです 基本的な用語の文章問題の空欄を埋める問題が多くを占めます 前期期末テストの出題と同じような問題が 70% ぐらい出題の予定です ( グループログインする Web ページの目標の最後近辺が練習ページになっています ) OSI 参照モデルの階層名 それぞれの階層で使われるプロトコルや機器の名前 などです 一部がトランスポート層の問題に置き換わっています 期末テストと類似する問題で 各コンピュータに設定すべきサブネットアドレスを問う ネットワークアドレスを問う ホストに設定する IP アドレスを問う クラス名を問う 分割数を問う 問題があります 各説明に最も合う コマンドプロンプトで使うコマンド を 解答群から選択する問題が 新しく出題の予定