Memra

DatagramPacket and DatagramSocket

◈ 7 cards

The two constructor families, send and receive, why getLength() and not getData().length, timeouts that matter more than in TCP, connect() as a filter, SO_BROADCAST, and the truncation trap on a reused packet.

Two constructor families, one class

DatagramPacket is unusual: its constructors are overloaded not to offer different information but to build objects for different jobs.

A receive packet takes only a buffer and a length — new DatagramPacket(byte[] buffer, int length), or with an offset. It has no destination because it is going to be filled in by an arriving datagram, which brings its own source address along.

A send packet takes the same buffer and length plus the destination — new DatagramPacket(byte[] data, int length, InetAddress host, int port). The socket reads the destination off the packet at send time; unlike TCP, the socket itself never holds it.

After a receive, the packet is fully populated: getAddress() and getPort() give you the sender, getData() the buffer, getLength() the byte count, and getSocketAddress() the pair in one object — which is the neat way to address a reply.

Sending and receiving

DatagramSocket binds a local port and does both directions. new DatagramSocket(0) asks the system for a free anonymous port, which is what a client wants; new DatagramSocket(13) binds a known port, which is what a server wants. Below 1024 still needs privilege on Unix. It implements AutoCloseable, so try-with-resources applies.

There are exactly two verbs. send(DatagramPacket) launches the packet at whatever address the packet carries. receive(DatagramPacket) blocks until a datagram arrives, copies it into the packet's buffer, and sets the packet's length, address and port from what arrived.

getData() is the buffer; getLength() is the message

This is the distinction the whole lesson turns on. getData() returns the byte array you supplied — all 1,024 or 8,192 bytes of it, however many actually arrived. getLength() returns how many of those bytes are real.

So the only correct way to turn a received datagram into text is:

String s = new String(packet.getData(), 0, packet.getLength(),
        StandardCharsets.US_ASCII);

Decoding getData() whole appends a tail of zero bytes to every message. It survives casual testing, because a trailing run of NUL characters prints as nothing in most terminals, and then breaks the moment a comparison, a hash or a length check touches the string.

Timeouts matter more here than in TCP

In TCP a dead peer eventually produces an exception. In UDP the failure modes are all silent: nothing listening on the target port, a datagram lost in the network, a checksum failure discarded by the stack. In every case receive() simply blocks forever, because nothing has gone wrong as far as UDP is concerned.

So setSoTimeout(ms) is close to mandatory in a client. After that many milliseconds a blocked receive() throws SocketTimeoutException, and you decide whether to retry, give up, or fall back. Set it before you call receive(); it cannot be changed while a receive is in flight. The default, zero, means never time out.

connect() is a filter, not a connection

DatagramSocket.connect(InetAddress, int) establishes nothing on the wire — no handshake, no state at the far end, no notification. It sets a local filter: this socket will now refuse to send anywhere else (IllegalArgumentException) and will silently discard datagrams arriving from anywhere else. Useful when you know you are talking to exactly one peer and want stray traffic dropped for you. disconnect() removes the filter.

Broadcast: one datagram, every host on the subnet

A datagram sent to a broadcast address is delivered to every host on a local network at once, whether or not any of them asked for it. IPv4 offers two forms. 255.255.255.255 is the limited broadcast: no router ever forwards it, so it reaches exactly the hosts on the wire you sent it from — which is how a booting machine finds a DHCP server whose address it cannot yet know. A directed broadcast sets the host bits of a named network to ones, so 192.168.1.255 addresses everything on 192.168.1.0/24; routers have defaulted to dropping these since RFC 2644, because forwarding them turns any network into an amplifier.

Java gates the capability behind a socket option. setBroadcast(true) enables SO_BROADCAST on a DatagramSocket and getBroadcast() reads it back. Do not rely on the default: StandardSocketOptions.SO_BROADCAST documents its initial value as false, some platforms additionally require privilege before a broadcast may leave the host, and a send the platform refuses surfaces as an IOException from send() rather than as a silent drop. Enable it explicitly and you always know which case you are in. Receiving a broadcast needs no option at all — bind the port and they arrive.

Broadcast is a blunt instrument. Every host on the segment pays the interrupt and the decode even when one of them cares, and there is no way to reach a second subnet at all. That cost is precisely what multicast fixes, and it is the next lesson. Note too that broadcast is IPv4-only: IPv6 removed it outright, replacing it with well-known multicast groups such as ff02::1, "all nodes on this link".

How big can a datagram be

The two-byte length field caps a datagram at 65,535 bytes, and header overhead brings the practical IPv4 ceiling to about 65,507 bytes of payload. That is the theory. In practice many stacks will not carry more than 8 KB, and a datagram too large for the path is truncated or dropped without notifying anyone. Protocols that have to work everywhere stay well under: DNS and TFTP historically used 512 bytes. Size your receive buffer generously — a datagram longer than the buffer is silently truncated too — and size what you send conservatively.

Worked example — a daytime client and an echo server

The client. Open new DatagramSocket(0) in a try-with-resources and set a five-second timeout. Resolve the host with InetAddress.getByName. Build a send packet whose payload is a single byte — the daytime protocol ignores the content, it only needs the packet to arrive — addressed to port 13. Build a receive packet over a 512-byte buffer. send, then receive, then decode with the three-argument String constructor using getLength(). Catch SocketTimeoutException separately from IOException: a timeout here means "no answer", which is a normal UDP outcome, not a crash.

The echo server. Bind new DatagramSocket(7) and allocate one 1,024-byte buffer and one receive packet outside the loop. Inside the loop: receive(request), then build a reply from request.getData(), request.getLength(), request.getAddress() and request.getPort(), and send it. One socket, every client — the role inversion from the last lesson made concrete.

Then the line that makes it keep working. receive() set the request packet's length to the size of the datagram that arrived. If a 12-byte datagram comes in first, the packet's length is now 12, and every subsequent receive on that packet is capped at 12 bytes no matter how large the buffer is. So the last statement in the loop body must be request.setLength(buffer.length), restoring the packet's capacity before you go round again.

ready to goaddressedmay vanishone datagramor truncatefill a byte[]the bytes to sendnew DatagramPacketplus address and portsocket.send(dp)no delivery promisesocket.receive(dp)blocks until SO_TIMEOUTread getLength()not getData().lengthsetLength(buf.length)before reusing the packetThe last box is the oneeveryone forgets.
The middle box is the one UDP will not promise: the datagram may simply vanish, and nothing reports it. The last box is the one people forget — receive() overwrites the packet length with the size of whatever arrived, so a reused packet quietly caps every later datagram at that size.

source JDK javadoc java.net.DatagramSocket#setBroadcast; java.net.StandardSocketOptions SO_BROADCAST; RFC 2644 (directed broadcast forwarding off by default)

NORMAL ~/memra/learn/comp-348/datagram-packet-and-socket utf-8 LF