[Java How To Programming] [Home on 246net] [Home on Alles net]
Powered by SmartDoc

UDP通信

TAKAHASHI, Toru
torutk@02.246.ne.jp

目次

UDP通信のプログラミング

概要

UDPとは

UDPはUser Datagram Protocolの頭字語です。ソケットという用語を使っていますが、コネクションレスのプロトコルです。TCPに比べてオーバーヘッドが少なく効率のよい通信ですが、送信できるサイズに制約があり、トラフィック状況に合わせた送信調整や順序化、再送といった制御がありません。

JavaでUDPを扱うAPI

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パッケージでは、以下のクラスが提供されています。

UDPの送受信〜java.netパッケージ

UDP送信

基本的な手順は以下です。

  1. DatagramSocketの生成
  2. InetAddressまたはInetSocketAddressの生成
  3. DatagramPacketの生成
  4. DatagramSocketのsendメソッドでDatagramPacketを送信
DatagramSocketの生成
        socket = new DatagramSocket();

UDPの送信では、宛先アドレス・ポート番号はsocketではなくpacketに設定します。

InetSocketAddressの生成
        String address = "Adelphi";
        int port = 1234;
        InetSocketAddress address = new InetSocketAddress(address, port);

InetSocketAddressは、ホスト名とポート番号を保持するクラスです。InetAddressの場合はポート番号を持たないので、後述のDatagramPacket生成時にポート番号を指定する必要があります。

ホスト名には、実行するマシンでIPアドレス解決可能な文字列、あるいはIPアドレスの文字列表現を指定することができます。IPv6アドレスも対応しています。

DatagramPacketの生成
        byte[] sendData = ... 
        DatagramPacket packet = new DatagramPacket(
            sendData, 0, sendData.length, address
        );

sendDataはbyte[]型です。byte[]に直接データを詰めるのは大変なので、ByteArrayOutputStreamやByteBufferを使用するのが便利です。

DatagramSocketの送信
        socket.send(packet);

UDP受信

UDP受信の基本的な手順は以下です。

  1. DatagramSocketの生成
  2. DatagramPacketの生成
  3. DatagramSocketのreceiveメソッドでDatagramPacketに受信データを詰める
DatagramSocketの生成
        int port = 1234;
        socket = new DatagramSocket(port);

受信で使用する場合は、ソケット生成時にポート番号を指定します。

複数のアドレス(ネットワーク・インタフェース)を持つマシンでは、すべてのアドレスで上記ポート番号でリッスンします。特定のアドレスだけで受信させるようにすることも可能です。その場合には、コンストラクタでローカルのアドレスを指定します。

DatagramPacketの生成
        byte[] array = ...
        DatagramPacket packet = new DatagramPacket(
            array, array.length
        );

受信データを詰めるためのbyte配列を指定します。この配列より大きいサイズのデータを受信したときは、越えたデータは捨てられます。そこで、最大サイズ+1の配列を用意します。

DatagramSocketの受信
             socket.receive(packet);

UDPパケットを受信します。受信するまでブロックします。

データの作成

送信するデータは、byte型の配列で用意する必要があります。受信データはbyte型の配列に格納されます。

しかし、通常送受信するデータは文字列や数値型などであり、byte型の配列に格納するのは少々面倒です。

文字列(String)をbyte配列へ詰める
StringクラスのgetBytesメソッドが利用できます。引数に文字セット(例:"UTF-8")を指定する必要があります。送受信する両者で同一の文字セットを使用します。文字セットとして指定できる一覧は、java.nio.charset.CharsetクラスのメソッドavailableCharsetsで取得できます。
整数型(int)をbyte配列へ詰める
ByteArrayOutputStreamとDataOutputStreamを組み合わせて使用する方法、ByteBufferを使用する方法、BigIntegerを使用する方法などがあります。DataOutputStreamはintの格納はBig Endianのみです。ByteBufferはデフォルトはBig EndianですがorderメソッドでLittle Endianに変更することができます。BigIntegerはBig Endianのみです。
文字列をbyte配列に詰める
        String message = "EHLO pisa\r\n";
        byte[] array = message.getBytes("UTF-8");
int型をbyte配列に詰める〜ByteArrayOutputStreamとDataOutputSteam
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        DataOutputStream dos = new DataOutputStream(baos);
        dos.writeInt(value);
        dos.flush();
        byte[] array = baos.toByteArray();
int型をbyte配列に詰める〜ByteBuffer
        ByteBuffer buffer = ByteBuffer.allocate(1024); // 最大サイズ 1024byte
        buffer.putInt(value);
        byte[] array = buffer.array();
int型をbyte配列に詰める〜BigInteger
        BigInteger intValue = BigInteger.valueOf(value);
        byte[] array = intValue.toByteArray();

UDPの送受信〜java.nioパッケージ

(今後記述予定)

サンプル・プログラム

UDP送受信ツール:パケット欠落を調査する

送信プログラム、受信プログラムの2つから構成されます。指定した回数を送信し、受信できた回数を見ることで、UDPデータグラムの到達性を調べることを目的としたツールです。UDPのAPIとしては、java.netパッケージを使っていますが、データの作成にはByteBufferを使用しています。

ソースおよびバイナリの配布

UDPパケット欠落調査用送受信ツール
バージョン 2007/06/17
配布アーカイブ udptool-20070617.zip

画面

送信プログラム画面

送信プログラムは、UDPのデータグラムを画面上から送信先アドレス・ポート番号、データグラムサイズ、送信回数と送信間隔を設定し出力します。

受信プログラム画面

受信プログラムは、UDPのデータグラムを画面上から受信ポート番号、受信サイズ、受信バッファサイズを指定してデータを受信待ちに入ります。受信すると、受信回数と受信IDを更新します。欠落が発生すると、発生した受信IDを表示します。

UDP送信プログラムのソースコード説明

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を実行して画面だけ表示させることができます。

また、独立性が高くなったことで試験容易性を高める結果ももたらします。

UDP送信プログラムを構成するクラス
クラス名 内容
UdpSendModel モデルを実現するクラス。画面表示用データを永続化
UdpSendView ビューのインタフェース
UdpSendFrame ビューの実装クラス
UdpSendPresenter プレゼンターを実現するクラス。送信処理

Viewのソースコード

Model-View-Presenterの中でViewを実現しているクラスが2つあります。1つは、Presenterに対するインタフェースを定義しているUdpSendViewインタフェースで、もう1つがUdpSendViewインタフェースを実装しているUdpSendFrameクラスです。

インタフェースには、画面に表示させるデータをセットするセッターメソッド群、画面に入力されたデータを取得するゲッターメソッド群、画面に対するイベントを通知してもらうためにリスナーを登録するメソッド群が定義されています。

インタフェースを実装するクラスは、Swing APIで構築しています。javax.swing.JFrameを継承してトップレベル・ウィンドウを作成し、画面のレイアウトはjavax.swing.GroupLayoutを使用して構築しています。

画面の入力項目は、javax.swing.JComboBox部品を使用し、毎回ゼロから入力しなくても候補をドロップダウンリストから選択できるようにしています。

Presenterのソースコード

Model-View-Presenterの中でPresenterを実現しているクラスがUdpSendPresenterクラスです。

ViewとModelの双方のオブジェクトの参照を保持し、プログラム起動時の初期化処理、画面上で発生したイベントの通知を受け対応する処理を実行し、終了時には終了処理を行います。また、このツールの主要機能であるネットワークへのUDPパケットの送出もこのクラスで実行しています。

Modelのソースコード

Model-View-Presenterの中でModelを実現しているクラスがUdpSendModelクラスです。

通常、Modelにおいてビジネスロジック(そのプログラムにおける主要機能)を実現することが多いのですが、小規模なツールでネットワーク処理のようなインタフェースに近い処理が主要機能の場合、Presenterで実装してしまった方がシンプルになることがあります。

このツールでは、Modelではユーザが入力した履歴を、次回実行したときにその履歴をViewにおいてComboBoxのリストに表示することで入力を楽にするために永続化する機能を持たせています。

永続化には、Java Preferences APIを使用しました。Preferences APIは、プラットフォーム非依存にデータを永続化する手段を提供します。これは、同一マシンでもログインするユーザによって別なデータ領域を使用することができるので、ユーザ固有のデータを保存するのに適しています。

ただし、リスト構造のような可変個数のデータを保存するには少し工夫が必要です。今回は、一つの文字列にスペースで区切って複数のデータを格納しています。