java-recipes

ホーム ネットワーク › N-04: UDP 通信

N-04: UDP 通信(DatagramSocket)

DatagramSocket DatagramPacket を使った UDP 通信の基本を解説します。UDP(User Datagram Protocol)は TCP と異なり接続確立を行わないコネクションレスなプロトコルです。 オーバーヘッドが小さく、遅延に敏感なリアルタイムアプリケーションに適しています。

TCP と UDP の違い

項目TCPUDP
接続方式コネクション型(3ウェイハンドシェイク)コネクションレス
信頼性高い(順序保証・再送あり)なし(ベストエフォート)
速度・遅延比較的遅い(確認応答あり)高速・低遅延
パケット順序保証される保証されない
ユースケースHTTP、FTP、メールDNS、動画ストリーミング、ゲーム
Java クラスServerSocket / SocketDatagramSocket / DatagramPacket

サンプルコード

受信側(レシーバー)を別スレッドで起動し、送信側がデータグラムパケットを送る実装例です。 ログ収集や監視データの送信など、多少のパケット欠落が許容されるユースケースに適したパターンです。

Sample.java
import java.io.*;
import java.net.*;
import java.util.concurrent.*;

public class UdpSocketSample {
    private static final int PORT = 9001;
    private static final String HOST = "localhost";

    // 受信側: DatagramSocket.receive() でパケット受信
    public static void startReceiver(ExecutorService executor) throws Exception {
        DatagramSocket receiverSocket = new DatagramSocket(PORT);
        executor.submit(new Runnable() {
            @Override
            public void run() {
                try {
                    byte[] buffer = new byte[1024];
                    DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
                    receiverSocket.receive(packet); // パケット受信(ブロッキング)
                    String message = new String(packet.getData(), 0, packet.getLength(), "UTF-8");
                    System.out.println("受信: " + message
                            + " (from " + packet.getAddress() + ":" + packet.getPort() + ")");
                    receiverSocket.close();
                } catch (IOException e) {
                    // 受信終了
                }
            }
        });
    }

    // 送信側: DatagramPacket でデータグラム送信
    public static void sendMessage(String message) throws IOException {
        try (DatagramSocket senderSocket = new DatagramSocket()) {
            byte[] data = message.getBytes("UTF-8");
            InetAddress address = InetAddress.getByName(HOST);
            DatagramPacket packet = new DatagramPacket(data, data.length, address, PORT);
            senderSocket.send(packet);
            System.out.println("送信: " + message);
        }
    }

    public static void main(String[] args) throws Exception {
        ExecutorService executor = Executors.newSingleThreadExecutor();

        startReceiver(executor);
        Thread.sleep(100); // 受信側起動待ち

        sendMessage("ログメッセージ: サービス起動完了");

        executor.awaitTermination(2, TimeUnit.SECONDS);
        executor.shutdown();
    }
}

よくあるミス・注意点

⚠️ UDP はコネクションレスなので相手が受信できなくてもエラーにならない

senderSocket.send(packet) は 受信側が存在しない場合でも例外をスローせずに正常終了します。 「送信が成功した」=「相手が受信した」ではない点に注意してください。 受信確認が必要な場合は TCP を使うか、アプリケーション層で確認応答の仕組みを自前で実装する必要があります。

// UDP: 受信側がいなくても例外が発生しない
senderSocket.send(packet); // 相手が起動していなくても成功する

// 受信確認が必要な場合は TCP(ServerSocket / Socket)を使うこと

📌 1 つの DatagramPacket の最大サイズは 65507 バイト

UDP ペイロードの理論的な最大サイズは 65507 バイト(65535 - IP ヘッダー 20 バイト - UDP ヘッダー 8 バイト)です。 これを超えるデータは送信できません。また、ネットワーク経路の MTU(最大転送単位)によって 実際にはもっと小さいサイズに制限される場合があります。 大きなデータを送る場合は TCP を使うか、データを分割して送ることを検討してください。

📌 パケットの順序は保証されない

UDP ではパケットが送信した順番通りに届く保証はありません。 連続して複数のパケットを送ると、受信側では逆順や途中抜けで届く場合があります。 順序が重要なデータ(例: 動画フレーム番号、トランザクション ID)は アプリケーション側でシーケンス番号を付与して管理する必要があります。

📌 UDP は低遅延が求められるアプリに向く

TCP の3ウェイハンドシェイクや確認応答(ACK)がない分、UDP は遅延が小さく高速です。 オンラインゲーム(位置情報の送信)、動画・音声ストリーミング、DNS クエリなど 「多少のパケット欠落より低遅延を優先したい」場面で活躍します。 信頼性よりもリアルタイム性が重要な場合に UDP を選択しましょう。

テストする観点

  • 受信側を起動した後に送信すると、メッセージが正しく届くか
  • 日本語を含む文字列が UTF-8 で正しくエンコード・デコードされるか(バイト列のエンコーディング確認)
  • 送信したバイト数と受信した packet.getLength() が一致するか
  • 1024 バイトを超えるペイロードを送信した場合に正しく処理されるか(バッファサイズの境界値テスト)
  • packet.getAddress()packet.getPort() に送信元情報が正しく格納されているか
  • 受信タイムアウト(socket.setSoTimeout())を設定した場合にパケットが来なければ SocketTimeoutException が発生するか

GitHub でソースコードを見る →