UDPの仕組み

UDP(User Datagram Protocol)は、ポート番号を使ってプロセスと プロセス(⇒実行中のプログラム)間の通信を可能とする トランスポート層(レイヤー4)の最もシンプルなプロトコルです。(早く送ること重視して、確実に届ける能力はありません。)
送受信で使うデータは、次の構造になっています。

上記イメージが、
右のレイヤー3で使うIPパケット中の
データ(可変長)の部分に
セットされて運ばれます。

受信側は宛先ポート番号で待機し、送信側ではこの宛先ポート番号を指定して、 送信します。
実験で確認すると分かりますが、 受信相手が正しく動作していなくても送出できます。
つまり、送信側では、相手が正しく受け取ったかどうかは判断できません。
(送信の順番通りに、相手へ届いていない可能性もあります。)

これらの確認する規則が無い分だけ、プログラム的に軽量なプロトコルと言えますが、 その信頼性を確保する場合は、 アプリケーション側のプログラムで負担が増えることになります。
よって信頼性がより、 負荷の軽い処理や連続性や要求されるアプリケーション向けのプロトコルです。
(データ伝達を速くでなく、タイミング的に早く届けたい処理に向きます)

これを使う上位プロトコルに、DNSの問い合わせ、NetBIOSブロードキャストなどがあります。

なお、受信する側では、受信したチェックサムで、 受信したデータの誤りがチェックされ、 エラーなら、そのデータグラムパケットは破棄されるだけで、 使われることはありません。
また、エラーがあったことを知らせる働きもありません。
言い方を変えると、UDPに対するインターフェイス用APIで取得できたパケットは、 エラーになってないデータだけを取り扱えるようになっています。
(途中でエラーデータグラムを失っているかもしれないが、受信できたデータはエラーでない情報と言えます)

例えば、 UDPRecTestで行っているreceiveメソッドで得られるデータは、 エラーの無い正しいデータとして扱うことができます。

UDPデータ構造より分かるように、データグラムパケットの中に、 送信元ポート番号と、受信元ポート番号の2つ情報があります。
これらの情報は、プログラムで取得できます。

送信側では、送信に使うデータグラムのソケットより、 自身の送信元ポート番号が決定されるので、 そのgetLocalPortメソッド分かります。
例えば、UDPSndTest.java の送信を行っている直前に次のコードを 追加して確認できます。
System.out.println(sendSocket.getLocalPort() + "のポートを使って送信します。");

受信側では、受信したデータグラムパケットより、自身の送信元ポート番号が 記憶されるので、そのgetPortメソッド分かります。
UDPRecTest.java の受信メッセージを表示している直後に 次のコードを追加して確認できます。
System.out.println("パケット送信元のポート番号は" + packet.getPort() + "です。");

また、このパケットから送信相手のIPアドレスが、 getAddressメソッドで分かります。