Tidak semua percakapan jaringan membutuhkan jabat tangan yang formal. Monitoring heartbeat, siaran posisi pemain, dan query DNS hanya butuh satu paket kecil yang dikirim secepat mungkin — mengulanginya lebih murah daripada menjamin paket pertama tiba. Untuk kebutuhan seperti itulah UDP (User Datagram Protocol) ada: tanpa koneksi, tanpa nomor urut, tanpa retransmisi. Java memodelkannya dengan class DatagramSocket sebagai pintu kirim-terima dan DatagramPacket sebagai amplop berisi data beserta alamat tujuan. Chapter ini mengupas cara kerja komunikasi tanpa koneksi tersebut, lalu membangun pola kirim-balas yang deterministik di atas loopback.

Paket sebagai Unit Komunikasi #

Perbedaan paling mendasar dari TCP terletak pada unit datanya. TCP adalah aliran byte tanpa batas pesan, sedangkan UDP mengirim datagram: satu paket dengan batas tegas, maksimal sekitar 65.507 byte payload pada jaringan IPv4. DatagramPacket menampung data itu beserta alamat IP dan port tujuan — data dan alamat menyatu dalam satu objek, karena UDP tidak menyimpan konteks koneksi di sisi penerima. Konsekuensinya terasa pada cara paksaan kode kita membaca:

  • Pengirim membuat DatagramPacket berisi data + alamat tujuan, lalu memanggil socket.send(paket).
  • Penerima menyiapkan penyangga kosong, memanggil socket.receive(paket), lalu membaca isi serta alamat pengirim dari paket yang sama.

Mari kita lihat bentuk paling sederhananya — satu socket mengirim paket ke dirinya sendiri melalui loopback:

java PingUdp.java
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.nio.charset.StandardCharsets;

public class PingUdp {
    public static void main(String[] args) throws Exception {
        // Satu socket mengirim ke dirinya sendiri lewat loopback
        try (DatagramSocket socket = new DatagramSocket(0, InetAddress.getLoopbackAddress())) {
            System.out.println("socket terikat : " + socket.isBound());
            System.out.println("terhubung      : " + socket.isConnected());

            String pesan = "detak Budi";
            byte[] data = pesan.getBytes(StandardCharsets.UTF_8);
            DatagramPacket paket = new DatagramPacket(
                    data, data.length, InetAddress.getLoopbackAddress(), socket.getLocalPort());
            socket.send(paket);

            byte[] penyangga = new byte[1024];
            DatagramPacket terima = new DatagramPacket(penyangga, penyangga.length);
            socket.receive(terima);

            String hasil = new String(terima.getData(), 0, terima.getLength(), StandardCharsets.UTF_8);
            System.out.println("pesan diterima : " + hasil);
            System.out.println("panjang data   : " + terima.getLength());
        }
    }
}

Output yang Dihasilkan:

text
socket terikat : true
terhubung      : false
pesan diterima : detak Budi
panjang data   : 10

Mari kita telaah baris-baris kode di atas:

  1. Baris 11 (new DatagramSocket(0, InetAddress.getLoopbackAddress())): membuat dan mengikat socket ke loopback pada port ephemeral pilihan sistem. Berbeda dengan TCP, langkah ini tidak mendengarkan koneksi — socket UDP siap menerima paket sejak terikat.
  2. Baris 13 (socket.isConnected()): selalu false karena memang tidak ada koneksi. UDP tidak melakukan handshake; connect() pada DatagramSocket hanya menyaring alamat lawan bicara, tidak membuka koneksi nyata.
  3. Baris 16-19 (new DatagramPacket(data, data.length, alamat, port)): amplop lengkap — isi data, panjang data, alamat tujuan, dan port tujuan. Data disalin ke dalam paket pada saat konstruktor berjalan, sehingga array asli boleh diubah setelahnya.
  4. Baris 22-25 (socket.receive(terima)): memblokir sampai satu paket tiba, lalu menyalin isi paket ke penyangga. Setelah pemanggilan selesai, terima.getLength() berisi panjang data aktual yang diterima — jangan memakai penyangga.length karena paket bisa lebih pendek dari penyangga.
  5. Baris 27 (new String(terima.getData(), 0, terima.getLength(), ...)): mengubah potongan array yang benar-benar terisi menjadi teks. Offset 0 dan panjang getLength() memastikan tidak ada sisa penyangga lama yang ikut terbaca.

Pola Kirim dan Balas antar Dua Socket #

Contoh sebelumnya memakai satu socket sebagai pengirim dan penerima sekaligus. Pola nyata biasanya melibatkan dua socket berbeda: server menunggu paket dari siapa pun, lalu membalas ke alamat yang terbaca dari paket masuk — pola request-response tanpa koneksi. Program berikut menjalankan kedua peran dalam satu berkas, dengan tiga putaran kirim-balas secara bergilir:

java ObrolanUdp.java
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.nio.charset.StandardCharsets;

public class ObrolanUdp {
    public static void main(String[] args) throws Exception {
        // Server UDP menunggu paket lalu membalas ke alamat pengirim
        DatagramSocket server = new DatagramSocket(0, InetAddress.getLoopbackAddress());

        Thread pegawaiServer = new Thread(() -> {
            try {
                byte[] penyangga = new byte[1024];
                for (int i = 1; i <= 3; i++) {
                    DatagramPacket masuk = new DatagramPacket(penyangga, penyangga.length);
                    server.receive(masuk);

                    String isi = new String(masuk.getData(), 0, masuk.getLength(),
                            StandardCharsets.UTF_8);
                    String balasan = "gema-" + i + ": " + isi;
                    byte[] dataBalasan = balasan.getBytes(StandardCharsets.UTF_8);
                    DatagramPacket keluar = new DatagramPacket(
                            dataBalasan, dataBalasan.length, masuk.getAddress(), masuk.getPort());
                    server.send(keluar);
                }
            } catch (Exception e) {
                System.out.println("server galat : " + e.getMessage());
            }
        });
        pegawaiServer.start();

        try (DatagramSocket klien = new DatagramSocket(0, InetAddress.getLoopbackAddress())) {
            byte[] penyangga = new byte[1024];
            for (String pesan : new String[]{"ping-1", "ping-2", "ping-3"}) {
                byte[] data = pesan.getBytes(StandardCharsets.UTF_8);
                DatagramPacket kirim = new DatagramPacket(
                        data, data.length, InetAddress.getLoopbackAddress(), server.getLocalPort());
                klien.send(kirim);

                DatagramPacket terima = new DatagramPacket(penyangga, penyangga.length);
                klien.receive(terima);
                String balasan = new String(terima.getData(), 0, terima.getLength(),
                        StandardCharsets.UTF_8);
                System.out.println("klien kirim  : " + pesan);
                System.out.println("server balas : " + balasan);
            }
        }
        pegawaiServer.join();
        server.close();
    }
}

Output yang Dihasilkan:

text
klien kirim  : ping-1
server balas : gema-1: ping-1
klien kirim  : ping-2
server balas : gema-2: ping-2
klien kirim  : ping-3
server balas : gema-3: ping-3

Tiga hal patut digarisbawahi pada program di atas. Pertama, server membalas ke masuk.getAddress() dan masuk.getPort() — alamat pengirim diketahui dari paket itu sendiri, bukan dari koneksi, sehingga balasan selalu menuju sumber yang tepat meski pengirim berganti-ganti. Kedua, nomor urut gema-1, gema-2, gema-3 diproduksi oleh server; UDP tidak memberi nomor urut apa pun, jadi penomoran pesan adalah tanggung jawab protokol aplikasi bila urutan penting. Ketiga, klien mengikuti pola kirim-satu-tunggu-satu secara bergilir sehingga output program selalu terurut sama.

Konsekuensi Tanpa Jaminan #

Menghilangkan handshake membawa konsekuensi yang perlu kita sadari sebelum memilih UDP untuk kasus nyata. Paket dapat hilang di jalur, dapat tiba dua kali, dan dapat tiba dalam urutan berbeda — jaringan tidak berutang apa pun kepada datagram. Paket yang lebih besar dari MTU jaringan (umumnya sekitar 1.500 byte) juga akan dipecah di lapisan IP, dan satu pecahan hilang berarti seluruh paket dibuang tanpa pemberitahuan. Karena itu rancangan UDP yang sehat umumnya: paket kecil, data yang sama dikirim ulang secara berkala, dan penerima yang toleran terhadap paket usang.

Dengan TCP dan UDP kita sudah menguasai lapisan transport lewat socket mentah. Praktik modern jauh lebih sering berbicara di lapisan application dengan protokol HTTP — dan untuk itu Java 11 ke atas menyediakan java.net.http.HttpClient yang akan kita bahas mulai Chapter berikutnya.

Referensi #

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

ObrolanUdp.java

Unduh berkas
java
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.nio.charset.StandardCharsets;

public class ObrolanUdp {
    public static void main(String[] args) throws Exception {
        // Server UDP menunggu paket lalu membalas ke alamat pengirim
        DatagramSocket server = new DatagramSocket(0, InetAddress.getLoopbackAddress());

        Thread pegawaiServer = new Thread(() -> {
            try {
                byte[] penyangga = new byte[1024];
                for (int i = 1; i <= 3; i++) {
                    DatagramPacket masuk = new DatagramPacket(penyangga, penyangga.length);
                    server.receive(masuk);

                    String isi = new String(masuk.getData(), 0, masuk.getLength(),
                            StandardCharsets.UTF_8);
                    String balasan = "gema-" + i + ": " + isi;
                    byte[] dataBalasan = balasan.getBytes(StandardCharsets.UTF_8);
                    DatagramPacket keluar = new DatagramPacket(
                            dataBalasan, dataBalasan.length, masuk.getAddress(), masuk.getPort());
                    server.send(keluar);
                }
            } catch (Exception e) {
                System.out.println("server galat : " + e.getMessage());
            }
        });
        pegawaiServer.start();

        try (DatagramSocket klien = new DatagramSocket(0, InetAddress.getLoopbackAddress())) {
            byte[] penyangga = new byte[1024];
            for (String pesan : new String[]{"ping-1", "ping-2", "ping-3"}) {
                byte[] data = pesan.getBytes(StandardCharsets.UTF_8);
                DatagramPacket kirim = new DatagramPacket(
                        data, data.length, InetAddress.getLoopbackAddress(), server.getLocalPort());
                klien.send(kirim);

                DatagramPacket terima = new DatagramPacket(penyangga, penyangga.length);
                klien.receive(terima);
                String balasan = new String(terima.getData(), 0, terima.getLength(),
                        StandardCharsets.UTF_8);
                System.out.println("klien kirim  : " + pesan);
                System.out.println("server balas : " + balasan);
            }
        }
        pegawaiServer.join();
        server.close();
    }
}

PingUdp.java

Unduh berkas
java
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.nio.charset.StandardCharsets;

public class PingUdp {
    public static void main(String[] args) throws Exception {
        // Satu socket mengirim ke dirinya sendiri lewat loopback
        try (DatagramSocket socket = new DatagramSocket(0, InetAddress.getLoopbackAddress())) {
            System.out.println("socket terikat : " + socket.isBound());
            System.out.println("terhubung      : " + socket.isConnected());

            String pesan = "detak Budi";
            byte[] data = pesan.getBytes(StandardCharsets.UTF_8);
            DatagramPacket paket = new DatagramPacket(
                    data, data.length, InetAddress.getLoopbackAddress(), socket.getLocalPort());
            socket.send(paket);

            byte[] penyangga = new byte[1024];
            DatagramPacket terima = new DatagramPacket(penyangga, penyangga.length);
            socket.receive(terima);

            String hasil = new String(terima.getData(), 0, terima.getLength(), StandardCharsets.UTF_8);
            System.out.println("pesan diterima : " + hasil);
            System.out.println("panjang data   : " + terima.getLength());
        }
    }
}