UDPはUser Datagram Protocolの頭字語です。ソケットという用語を使っていますが、コネクションレスのプロトコルです。TCPに比べてオーバーヘッドが少なく効率のよい通信ですが、送信できるサイズに制約があり、トラフィック状況に合わせた送信調整や順序化、再送といった制御がありません。
Javaでは、UDP特有のAPIとしてjava.netパッケージで提供されるもの、java.nioパッケージで提供されるものと大きく2種類があります。
基本は、Javaの当初のバージョンから提供されているjava.netパッケージです。世の中の大半のソケット・プログラミングの解説もこちらのパッケージについてがほとんどです。
java.netパッケージでは、以下のクラスが提供されています。
一方、JDK1.4から新たにjava.nio(Java New I/O)パッケージが搭載されました。これにより、従来のjava.netパッケージの機能に加え、バッファ管理、ノンブロッキング・モードと多重化などを提供するスケーラブルなI/O、文字セットなどの高度な機能が利用できるようになりました。
java.nioパッケージでは、以下のクラスが提供されています。
基本的な手順は以下です。
socket = new DatagramSocket();
UDPの送信では、宛先アドレス・ポート番号はsocketではなくpacketに設定します。
String address = "Adelphi"; int port = 1234; InetSocketAddress address = new InetSocketAddress(address, port);
InetSocketAddressは、ホスト名とポート番号を保持するクラスです。InetAddressの場合はポート番号を持たないので、後述のDatagramPacket生成時にポート番号を指定する必要があります。
ホスト名には、実行するマシンでIPアドレス解決可能な文字列、あるいはIPアドレスの文字列表現を指定することができます。IPv6アドレスも対応しています。
byte[] sendData = ... DatagramPacket packet = new DatagramPacket( sendData, 0, sendData.length, address );
sendDataはbyte[]型です。byte[]に直接データを詰めるのは大変なので、ByteArrayOutputStreamやByteBufferを使用するのが便利です。
socket.send(packet);
UDP受信の基本的な手順は以下です。
int port = 1234; socket = new DatagramSocket(port);
受信で使用する場合は、ソケット生成時にポート番号を指定します。
複数のアドレス(ネットワーク・インタフェース)を持つマシンでは、すべてのアドレスで上記ポート番号でリッスンします。特定のアドレスだけで受信させるようにすることも可能です。その場合には、コンストラクタでローカルのアドレスを指定します。
byte[] array = ... DatagramPacket packet = new DatagramPacket( array, array.length );
受信データを詰めるためのbyte配列を指定します。この配列より大きいサイズのデータを受信したときは、越えたデータは捨てられます。そこで、最大サイズ+1の配列を用意します。
socket.receive(packet);
UDPパケットを受信します。受信するまでブロックします。
送信するデータは、byte型の配列で用意する必要があります。受信データはbyte型の配列に格納されます。
しかし、通常送受信するデータは文字列や数値型などであり、byte型の配列に格納するのは少々面倒です。
String message = "EHLO pisa\r\n"; byte[] array = message.getBytes("UTF-8");
ByteArrayOutputStream baos = new ByteArrayOutputStream(); DataOutputStream dos = new DataOutputStream(baos); dos.writeInt(value); dos.flush(); byte[] array = baos.toByteArray();
ByteBuffer buffer = ByteBuffer.allocate(1024); // 最大サイズ 1024byte buffer.putInt(value); byte[] array = buffer.array();
BigInteger intValue = BigInteger.valueOf(value);
byte[] array = intValue.toByteArray();
(今後記述予定)
送信プログラム、受信プログラムの2つから構成されます。指定した回数を送信し、受信できた回数を見ることで、UDPデータグラムの到達性を調べることを目的としたツールです。UDPのAPIとしては、java.netパッケージを使っていますが、データの作成にはByteBufferを使用しています。
バージョン | 2007/06/17 |
配布アーカイブ | udptool-20070617.zip |
送信プログラムは、UDPのデータグラムを画面上から送信先アドレス・ポート番号、データグラムサイズ、送信回数と送信間隔を設定し出力します。
受信プログラムは、UDPのデータグラムを画面上から受信ポート番号、受信サイズ、受信バッファサイズを指定してデータを受信待ちに入ります。受信すると、受信回数と受信IDを更新します。欠落が発生すると、発生した受信IDを表示します。
Model-View-Presenter(MVP)パターンを適用して設計しています。MVPパターンの明確な説明はなかなか見かけませんので僕なりの理解で記述します。
Model-View-Presenterパターンは、もともとModel-View-Controllerパターンから分派して登場してきたもので、非常にMVCに近いパターンです。ただ、MVCと違うのは、ModelとViewの依存関係を断ち切ることにより、Modelが依存性を通じてViewに働きかけること(変更通知)がなくなった点です。この結果、Viewは状態変化を感知して表示を更新する自律性が無くなり、ダム的な機能に限られます。代わりにPresenterがかなり頑張ってGUIの調停を行ってGUIの表示更新を実現しています。
一見退化したMVC(「コントローラが頑張るMVC」)にも見えます。しかし、Viewがダム的な機能になったことで、Viewの部分を独立して作成しやすくなり、Viewのダミー(モック)を用意すればViewなしでModel-Presenterの機能を動かすことができるようになりました。
今回の例では、UdpSendFrame(MVPの中でViewを実装しているクラス)は独立して作成しています。このクラスにはmainメソッドが設けてあり、他のクラスがなくてもこのmainを実行して画面だけ表示させることができます。
また、独立性が高くなったことで試験容易性を高める結果ももたらします。
クラス名 | 内容 |
---|---|
UdpSendModel | モデルを実現するクラス。画面表示用データを永続化 |
UdpSendView | ビューのインタフェース |
UdpSendFrame | ビューの実装クラス |
UdpSendPresenter | プレゼンターを実現するクラス。送信処理 |
Model-View-Presenterの中でViewを実現しているクラスが2つあります。1つは、Presenterに対するインタフェースを定義しているUdpSendViewインタフェースで、もう1つがUdpSendViewインタフェースを実装しているUdpSendFrameクラスです。
インタフェースには、画面に表示させるデータをセットするセッターメソッド群、画面に入力されたデータを取得するゲッターメソッド群、画面に対するイベントを通知してもらうためにリスナーを登録するメソッド群が定義されています。
インタフェースを実装するクラスは、Swing APIで構築しています。javax.swing.JFrameを継承してトップレベル・ウィンドウを作成し、画面のレイアウトはjavax.swing.GroupLayoutを使用して構築しています。
画面の入力項目は、javax.swing.JComboBox部品を使用し、毎回ゼロから入力しなくても候補をドロップダウンリストから選択できるようにしています。
Model-View-Presenterの中でPresenterを実現しているクラスがUdpSendPresenterクラスです。
ViewとModelの双方のオブジェクトの参照を保持し、プログラム起動時の初期化処理、画面上で発生したイベントの通知を受け対応する処理を実行し、終了時には終了処理を行います。また、このツールの主要機能であるネットワークへのUDPパケットの送出もこのクラスで実行しています。
Model-View-Presenterの中でModelを実現しているクラスがUdpSendModelクラスです。
通常、Modelにおいてビジネスロジック(そのプログラムにおける主要機能)を実現することが多いのですが、小規模なツールでネットワーク処理のようなインタフェースに近い処理が主要機能の場合、Presenterで実装してしまった方がシンプルになることがあります。
このツールでは、Modelではユーザが入力した履歴を、次回実行したときにその履歴をViewにおいてComboBoxのリストに表示することで入力を楽にするために永続化する機能を持たせています。
永続化には、Java Preferences APIを使用しました。Preferences APIは、プラットフォーム非依存にデータを永続化する手段を提供します。これは、同一マシンでもログインするユーザによって別なデータ領域を使用することができるので、ユーザ固有のデータを保存するのに適しています。
ただし、リスト構造のような可変個数のデータを保存するには少し工夫が必要です。今回は、一つの文字列にスペースで区切って複数のデータを格納しています。