UDP - Java Network Programming, 4th Edition (2013)

Java Network Programming, 4th Edition (2013)

Chapter 12. UDP

Previous chapters discussed network applications that run on top of the TCP transport layer protocol. TCP is designed for reliable transmission of data. If data is lost or damaged in transmission, TCP ensures that the data is resent. If packets of data arrive out of order, TCP puts them back in the correct order. If the data is coming too fast for the connection, TCP throttles the speed back so that packets won’t be lost. A program never needs to worry about receiving data that is out of order or incorrect. However, this reliability comes at a price. That price is speed. Establishing and tearing down TCP connections can take a fair amount of time, particularly for protocols such as HTTP, which tend to require many short transmissions.

The User Datagram Protocol (UDP) is an alternative transport layer protocol for sending data over IP that is very quick, but not reliable. When you send UDP data, you have no way of knowing whether it arrived, much less whether different pieces of data arrived in the order in which you sent them. However, the pieces that do arrive generally arrive quickly.

The UDP Protocol

The obvious question to ask is why anyone would ever use an unreliable protocol. Surely, if you have data worth sending, you care about whether the data arrives correctly? Clearly, UDP isn’t a good match for applications like FTP that require reliable transmission of data over potentially unreliable networks. However, there are many kinds of applications in which raw speed is more important than getting every bit right. For example, in real-time audio or video, lost or swapped packets of data simply appear as static. Static is tolerable, but awkward pauses in the audio stream, when TCP requests a retransmission or waits for a wayward packet to arrive, are unacceptable. In other applications, reliability tests can be implemented in the application layer. For example, if a client sends a short UDP request to a server, it may assume that the packet is lost if no response is returned within an established period of time; this is one way the Domain Name System (DNS) works. (DNS can also operate over TCP.) In fact, you could implement a reliable file transfer protocol using UDP, and many people have: Network File System (NFS), Trivial FTP (TFTP), and FSP, a more distant relative of FTP, all use UDP. (The latest version of NFS can use either UDP or TCP.) In these protocols, the application is responsible for reliability; UDP doesn’t take care of it (the application must handle missing or out-of-order packets). This is a lot of work, but there’s no reason it can’t be done—although if you find yourself writing this code, think carefully about whether you might be better off with TCP.

The difference between TCP and UDP is often explained by analogy with the phone system and the post office. TCP is like the phone system. When you dial a number, the phone is answered and a connection is established between the two parties. As you talk, you know that the other party hears your words in the order in which you say them. If the phone is busy or no one answers, you find out right away. UDP, by contrast, is like the postal system. You send packets of mail to an address. Most of the letters arrive, but some may be lost on the way. The letters probably arrive in the order in which you sent them, but that’s not guaranteed. The farther away you are from your recipient, the more likely it is that mail will be lost on the way or arrive out of order. If this is a problem, you can write sequential numbers on the envelopes, then ask the recipients to arrange them in the correct order and send you mail telling you which letters arrived so that you can resend any that didn’t get there the first time. However, you and your correspondent need to agree on this protocol in advance. The post office will not do it for you.

Both the phone system and the post office have their uses. Although either one could be used for almost any communication, in some cases one is definitely superior to the other. The same is true of UDP and TCP. The past several chapters have all focused on TCP applications, which are more common than UDP applications. However, UDP also has its place; in this chapter, we’ll look at what you can do with UDP. If you want to go further, the next chapter describes multicasting over UDP. A multicast socket is a fairly simple variation on a standard UDP socket.

Java’s implementation of UDP is split into two classes: DatagramPacket and DatagramSocket. The DatagramPacket class stuffs bytes of data into UDP packets called datagrams and lets you unstuff datagrams that you receive. A DatagramSocket sends as well as receives UDP datagrams. To send data, you put the data in a DatagramPacket and send the packet using a DatagramSocket. To receive data, you take a DatagramPacket object from a DatagramSocket and then inspect the contents of the packet. The sockets themselves are very simple creatures. In UDP, everything about a datagram, including the address to which it is directed, is included in the packet itself; the socket only needs to know the local port on which to listen or send.

This division of labor contrasts with the Socket and ServerSocket classes used by TCP. First, UDP doesn’t have any notion of a unique connection between two hosts. One socket sends and receives all data directed to or from a port without any concern for who the remote host is. A single DatagramSocket can send data to and receive data from many independent hosts. The socket isn’t dedicated to a single connection, as it is in TCP. In fact, UDP doesn’t have any concept of a connection between two hosts; it only knows about individual datagrams. Figuring out who sent what data is the application’s responsibility. Second, TCP sockets treat a network connection as a stream: you send and receive data with input and output streams that you get from the socket. UDP doesn’t support this; you always work with individual datagram packets. All the data you stuff into a single datagram is sent as a single packet and is either received or lost as a group. One packet is not necessarily related to the next. Given two packets, there is no way to determine which packet was sent first and which was sent second. Instead of the orderly queue of data that’s necessary for a stream, datagrams try to crowd into the recipient as quickly as possible, like a crowd of people pushing their way onto a bus. And occasionally, if the bus is crowded enough, a few packets, like people, may not squeeze on and will be left waiting at the bus stop.

UDP Clients

Let’s begin with a simple example. As in Reading from Servers with Sockets we will connect to the daytime server at the National Institute for Standards and Technology (NIST) and ask it for the current time. However, this time you’ll use UDP instead of TCP. Recall that the daytime server listens on port 13, and that the server sends the time in a human-readable format and closes the connection.

Now let’s see how to retrieve this same data programmatically using UDP. First, open a socket on port 0:

DatagramSocket socket = new DatagramSocket(0);

This is very different than a TCP socket. You only specify a local port to connect to. The socket does not know the remote host or address. By specifying port 0 you ask Java to pick a random available port for you, much as with server sockets.

The next step is optional but highly recommended. Set a timeout on the connection using the setSoTimeout() method. Timeouts are measured in milliseconds, so this statement sets the socket to time out after 10 seconds of nonresponsiveness:

socket.setSoTimeout(10000);

Timeouts are even more important for UDP than TCP because many problems that would cause an IOException in TCP silently fail in UDP. For example, if the remote host is not listening on the targeted port, you’ll never hear about it.

Next you need to set up the packets. You’ll need two, one to send and one to receive. For the daytime protocol it doesn’t matter what data you put in the packet, but you do need to tell it the remote host and remote port to connect to:

InetAddress host = InetAddress.getByName("time.nist.gov");

DatagramPacket request = new DatagramPacket(new byte[1], 1, host, 13);

The packet that receives the server’s response just contains an empty byte array. This needs to be large enough to hold the entire response. If it’s too small, it will be silently truncated—1k should be enough space:

byte[] data = new byte[1024];

DatagramPacket response = new DatagramPacket(data, data.length);

Now you’re ready. First send the packet over the socket and then receive the response:

socket.send(request);

socket.receive(response);

Finally, extract the bytes from the response and convert them to a string you can show to the end user:

String daytime = new String(response.getData(), 0, response.getLength(),

"US-ASCII");

System.out.println(daytime);

The constructor and send() and receive() methods can each throw an IOException, so you’ll usually wrap all this in a try block. In Java 7, DatagramSocket implements Autocloseable so you can use try-with-resources:

try (DatagramSocket socket = new DatagramSocket(0)) {

// connect to the server...

} catch (IOException ex) {

System.err.println("Could not connect to time.nist.gov");

}

In Java 6 and earlier, you’ll want to explicitly close the socket in a finally block to release resources the socket holds:

DatagramSocket socket = null;

try {

socket = new DatagramSocket(0);

// connect to the server...

} catch (IOException ex) {

System.err.println(ex);

} finally {

if (socket != null) {

try {

socket.close();

} catch (IOException ex) {

// ignore

}

}

}

Example 12-1 puts this all together.

Example 12-1. A daytime protocol client

import java.io.*;

import java.net.*;

public class DaytimeUDPClient {

private final static int PORT = 13;

private static final String HOSTNAME = "time.nist.gov";

public static void main(String[] args) {

try (DatagramSocket socket = new DatagramSocket(0)) {

socket.setSoTimeout(10000);

InetAddress host = InetAddress.getByName(HOSTNAME);

DatagramPacket request = new DatagramPacket(new byte[1], 1, host , PORT);

DatagramPacket response = new DatagramPacket(new byte[1024], 1024);

socket.send(request);

socket.receive(response);

String result = new String(response.getData(), 0, response.getLength(),

"US-ASCII");

System.out.println(result);

} catch (IOException ex) {

ex.printStackTrace();

}

}

}

Typical output is much the same as if you connected with TCP:

$ java DaytimeUDPClient

56375 13-04-11 19:55:22 50 0 0 843.6 UTC(NIST) *

UDP Servers

A UDP server follows almost the same pattern as a UDP client, except that you usually receive before sending and don’t choose an anonymous port to bind to. Unlike TCP, there’s no separate DatagramServerSocket class.

For example, let’s implement a daytime server over UDP. Begin by opening a datagram socket on a well-known port. For daytime, this port is 13:

DatagramSocket socket = new DatagramSocket(13);

As with TCP sockets, on Unix systems (including Linux and Mac OS X) you need to be running as root in order to bind to a port below 1024. You can either use sudo to run the program or simply change the port to something 1024 or higher.

Next, create a packet into which to receive a request. You need to supply both a byte array in which to store incoming data, the offset into the array, and the number of bytes to store. Here you set up a packet with space for 1,024 bytes starting at 0:

DatagramPacket request = new DatagramPacket(new byte[1024], 0, 1024);

Then receive it:

socket.receive(request);

This call blocks indefinitely until a UDP packet arrives on port 13. When it does, Java fills the byte array with data and the receive() method returns.

Next, create a response packet. This has four parts: the raw data to send, the number of bytes of the raw data to send, the host to send to, and the port on that host to address. In this example, the raw data comes from a String form of the current time, and the host and the port are simply the host and port of the incoming packet:

String daytime = new Date().toString() + "\r\n";

byte[] data = daytime.getBytes("US-ASCII");

InetAddress host = request.getAddress();

int port = request.getPort();

DatagramPacket response = new DatagramPacket(data, data.length, host, port);

Finally, send the response back over the same socket that received it:

socket.send(response);

Example 12-2 wraps this sequence up in a while loop, complete with logging and exception handling, so that it can process many incoming requests.

Example 12-2. A daytime protocol server

import java.net.*;

import java.util.Date;

import java.util.logging.*;

import java.io.*;

public class DaytimeUDPServer {

private final static int PORT = 13;

private final static Logger audit = Logger.getLogger("requests");

private final static Logger errors = Logger.getLogger("errors");

public static void main(String[] args) {

try (DatagramSocket socket = new DatagramSocket(PORT)) {

while (true) {

try {

DatagramPacket request = new DatagramPacket(new byte[1024], 1024);

socket.receive(request);

String daytime = new Date().toString();

byte[] data = daytime.getBytes("US-ASCII");

DatagramPacket response = new DatagramPacket(data, data.length,

request.getAddress(), request.getPort());

socket.send(response);

audit.info(daytime + " " + request.getAddress());

} catch (IOException | RuntimeException ex) {

errors.log(Level.SEVERE, ex.getMessage(), ex);

}

}

} catch (IOException ex) {

errors.log(Level.SEVERE, ex.getMessage(), ex);

}

}

}

As you can see in this example, UDP servers tend not to be as multithreaded as TCP servers. They usually don’t do a lot of work for any one client, and they can’t get blocked waiting for the other end to respond because UDP never reports errors. Unless a lot of time-consuming work is required to prepare the response, an iterative approach works just fine for UDP servers.

The DatagramPacket Class

UDP datagrams add very little to the IP datagrams they sit on top of. Figure 12-1 shows a typical UDP datagram. The UDP header adds only eight bytes to the IP header. The UDP header includes source and destination port numbers, the length of everything that follows the IP header, and an optional checksum. Because port numbers are given as two-byte unsigned integers, 65,536 different possible UDP ports are available per host. These are distinct from the 65,536 different TCP ports per host. Because the length is also a two-byte unsigned integer, the number of bytes in a datagram is limited to 65,536 minus the eight bytes for the header. However, this is redundant with the datagram length field of the IP header, which limits datagrams to between 65,467 and 65,507 bytes. (The exact number depends on the size of the IP header.) The checksum field is optional and not used in or accessible from application layer programs. If the checksum for the data fails, the native network software silently discards the datagram; neither the sender nor the receiver is notified. UDP is an unreliable protocol, after all.

The structure of a UDP datagram

Figure 12-1. The structure of a UDP datagram

Although the theoretical maximum amount of data in a UDP datagram is 65,507 bytes, in practice there is almost always much less. On many platforms, the actual limit is more likely to be 8,192 bytes (8K). And implementations are not required to accept datagrams with more than 576 total bytes, including data and headers. Consequently, you should be extremely wary of any program that depends on sending or receiving UDP packets with more than 8K of data. Most of the time, larger packets are simply truncated to 8K of data. For maximum safety, the data portion of a UDP packet should be kept to 512 bytes or less, although this limit can negatively affect performance compared to larger packet sizes. (This is a problem for TCP datagrams too, but the stream-based API provided by Socket and ServerSocket completely shields programmers from these details.)

In Java, a UDP datagram is represented by an instance of the DatagramPacket class:

public final class DatagramPacket extends Object

This class provides methods to get and set the source or destination address from the IP header, to get and set the source or destination port, to get and set the data, and to get and set the length of the data. The remaining header fields are inaccessible from pure Java code.

The Constructors

DatagramPacket uses different constructors depending on whether the packet will be used to send data or to receive data. This is a little unusual. Normally, constructors are overloaded to let you provide different kinds of information when you create an object, not to create objects of the same class that will be used in different contexts. In this case, all six constructors take as arguments a byte array that holds the datagram’s data and the number of bytes in that array to use for the datagram’s data. When you want to receive a datagram, these are the only arguments you provide. When the socket receives a datagram from the network, it stores the datagram’s data in the DatagramPacket object’s buffer array, up to the length you specified.

The second set of DatagramPacket constructors is used to create datagrams you will send over the network. Like the first, these constructors require a buffer array and a length, but they also require an address and port to which the packet will be sent. In this case, you pass to the constructor a byte array containing the data you want to send and the destination address and port to which the packet is to be sent. The DatagramSocket reads the destination address and port from the packet; the address and port aren’t stored within the socket, as they are in TCP.

Constructors for receiving datagrams

These two constructors create new DatagramPacket objects for receiving data from the network:

public DatagramPacket(byte[] buffer, int length)

public DatagramPacket(byte[] buffer, int offset, int length)

If the first constructor is used, when a socket receives a datagram, it stores the datagram’s data part in buffer beginning at buffer[0] and continuing until the packet is completely stored or until length bytes have been written into the buffer. For example, this code fragment creates a new DatagramPacket for receiving a datagram of up to 8,192 bytes:

byte[] buffer = new byte[8192];

DatagramPacket dp = new DatagramPacket(buffer, buffer.length);

If the second constructor is used, storage begins at buffer[offset] instead. Otherwise, these two constructors are identical. length must be less than or equal to buffer.length - offset. If you try to construct a DatagramPacket with a length that will overflow the buffer, the constructor throws an IllegalArgumentException. This is a RuntimeException, so your code is not required to catch it. It is OK to construct a DatagramPacket with a length less than buffer.length - offset. In this case, at most the first length bytes of buffer will be filled when the datagram is received.

The constructor doesn’t care how large the buffer is and would happily let you create a DatagramPacket with megabytes of data. However, the underlying native network software is less forgiving, and most native UDP implementations don’t support more than 8,192 bytes of data per datagram. The theoretical limit for an IPv4 datagram is 65,507 bytes of data, and a DatagramPacket with a 65,507-byte buffer can receive any possible IPv4 datagram without losing data. IPv6 datagrams raise the theoretical limit to 65,536 bytes. In practice, however, many UDP-based protocols such as DNS and TFTP use packets with 512 bytes of data per datagram or fewer. The largest data size in common usage is 8,192 bytes for NFS. Almost all UDP datagrams you’re likely to encounter will have 8K of data or fewer. In fact, many operating systems don’t support UDP datagrams with more than 8K of data and either truncate, split, or discard larger datagrams. If a large datagram is too big and as a result the network truncates or drops it, your Java program won’t be notified of the problem. Consequently, you shouldn’t create DatagramPacket objects with more than 8,192 bytes of data.

Constructors for sending datagrams

These four constructors create new DatagramPacket objects used to send data across the network:

public DatagramPacket(byte[] data, int length,

InetAddress destination, int port)

public DatagramPacket(byte[] data, int offset, int length,

InetAddress destination, int port)

public DatagramPacket(byte[] data, int length,

SocketAddress destination)

public DatagramPacket(byte[] data, int offset, int length,

SocketAddress destination)

Each constructor creates a new DatagramPacket to be sent to another host. The packet is filled with length bytes of the data array starting at offset or 0 if offset is not used. If you try to construct a DatagramPacket with a length that is greater than data.length (or greater thandata.length - offset), the constructor throws an IllegalArgumentException. It’s OK to construct a DatagramPacket object with an offset and a length that will leave extra, unused space at the end of the data array. In this case, only length bytes of data will be sent over the network. The InetAddress or SocketAddress object destination points to the host you want the packet delivered to; the int argument port is the port on that host.

CHOOSING A DATAGRAM SIZE

The correct amount of data to stuff into one packet depends on the situation. Some protocols dictate the size of the packet. For example, rlogin transmits each character to the remote system almost as soon as the user types it. Therefore, packets tend to be short: a single byte of data, plus a few bytes of headers. Other applications aren’t so picky. For example, file transfer is more efficient with large buffers; the only requirement is that you split files into packets no larger than the maximum allowable packet size.

Several factors are involved in choosing the optimal packet size. If the network is highly unreliable, such as a packet radio network, smaller packets are preferable because they’re less likely to be corrupted in transit. On the other hand, very fast and reliable LANs should use the largest packet size possible. Eight kilobytes—that is, 8,192 bytes—is a good compromise for many types of networks.

It’s customary to convert the data to a byte array and place it in data before creating the DatagramPacket, but it’s not absolutely necessary. Changing data after the datagram has been constructed and before it has been sent changes the data in the datagram; the data isn’t copied into a private buffer. In some applications, you can take advantage of this. For example, you could store data that changes over time in data and send out the current datagram (with the most recent data) every minute. However, it’s more important to make sure that the data doesn’t change when you don’t want it to. This is especially true if your program is multithreaded, and different threads may write into the data buffer. If this is the case, copy the data into a temporary buffer before you construct the DatagramPacket.

For instance, this code fragment creates a new DatagramPacket filled with the data “This is a test” in UTF-8. The packet is directed at port 7 (the echo port) of www.ibiblio.org:

String s = "This is a test";

byte[] data = s.getBytes("UTF-8");

try {

InetAddress ia = InetAddress.getByName("www.ibiblio.org");

int port = 7;

DatagramPacket dp = new DatagramPacket(data, data.length, ia, port);

// send the packet...

} catch (IOException ex)

}

Most of the time, the hardest part of creating a new DatagramPacket is translating the data into a byte array. Because this code fragment wants to send a string, it uses the getBytes() method of java.lang.String. The java.io.ByteArrayOutputStream class can also be very useful for preparing data for inclusion in datagrams.

The get Methods

DatagramPacket has six methods that retrieve different parts of a datagram: the actual data plus several fields from its header. These methods are mostly used for datagrams received from the network.

public InetAddress getAddress()

The getAddress() method returns an InetAddress object containing the address of the remote host. If the datagram was received from the Internet, the address returned is the address of the machine that sent it (the source address). On the other hand, if the datagram was created locally to be sent to a remote machine, this method returns the address of the host to which the datagram is addressed (the destination address). This method is most commonly used to determine the address of the host that sent a UDP datagram, so that the recipient can reply.

public int getPort()

The getPort() method returns an integer specifying the remote port. If this datagram was received from the Internet, this is the port on the host that sent the packet. If the datagram was created locally to be sent to a remote host, this is the port to which the packet is addressed on the remote machine.

public SocketAddress getSocketAddress()

The getSocketAddress() method returns a SocketAddress object containing the IP address and port of the remote host. As is the case for getInetAddress(), if the datagram was received from the Internet, the address returned is the address of the machine that sent it (the source address). On the other hand, if the datagram was created locally to be sent to a remote machine, this method returns the address of the host to which the datagram is addressed (the destination address). You typically invoke this method to determine the address and port of the host that sent a UDP datagram before you reply. The net effect is not noticeably different than calling getAddress() and getPort(). Also, if you’re using nonblocking I/O, the DatagramChannel class accepts a SocketAddress but not an InetAddress and port.

public byte[] getData()

The getData() method returns a byte array containing the data from the datagram. It’s often necessary to convert the bytes into some other form of data before they’ll be useful to your program. One way to do this is to change the byte array into a String. For example, given aDatagramPacket dp received from the network, you can convert it to a UTF-8 String like this:

String s = new String(dp.getData(), "UTF-8");

If the datagram does not contain text, converting it to Java data is more difficult. One approach is to convert the byte array returned by getData() into a ByteArrayInputStream. For example:

InputStream in = new ByteArrayInputStream(packet.getData(),

packet.getOffset(), packet.getLength());

You must specify the offset and the length when constructing the ByteArrayInputStream. Do not use the ByteArrayInputStream() constructor that takes only an array as an argument. The array returned by packet.getData() probably has extra space in it that was not filled with data from the network. This space will contain whatever random values those components of the array had when the DatagramPacket was constructed.

The ByteArrayInputStream can then be chained to a DataInputStream:

DataInputStream din = new DataInputStream(in);

The data can then be read using the DataInputStream’s readInt(), readLong(), readChar(), and other methods. Of course, this assumes that the datagram’s sender uses the same data formats as Java; it’s probably the case when the sender is written in Java, and is often (though not necessarily) the case otherwise. (Most modern computers use the same floating-point format as Java, and most network protocols specify two’s complement integers in network byte order, which also matches Java’s formats.)

public int getLength()

The getLength() method returns the number of bytes of data in the datagram. This is not necessarily the same as the length of the array returned by getData() (i.e., getData().length). The int returned by getLength() may be less than the length of the array returned bygetData().

public int getOffset()

This method simply returns the point in the array returned by getData() where the data from the datagram begins.

Example 12-3 uses all the methods covered in this section to print the information in the DatagramPacket. This example is a little artificial; because the program creates a DatagramPacket, it already knows what’s in it. More often, you’ll use these methods on a DatagramPacketreceived from the network, but that will have to wait for the introduction of the DatagramSocket class in the next section.

Example 12-3. Construct a DatagramPacket to receive data

import java.io.*;

import java.net.*;

public class DatagramExample {

public static void main(String[] args) {

String s = "This is a test.";

try {

byte[] data = s.getBytes("UTF-8");

InetAddress ia = InetAddress.getByName("www.ibiblio.org");

int port = 7;

DatagramPacket dp

= new DatagramPacket(data, data.length, ia, port);

System.out.println("This packet is addressed to "

+ dp.getAddress() + " on port " + dp.getPort());

System.out.println("There are " + dp.getLength()

+ " bytes of data in the packet");

System.out.println(

new String(dp.getData(), dp.getOffset(), dp.getLength(), "UTF-8"));

} catch (UnknownHostException | UnsupportedEncodingException ex) {

System.err.println(ex);

}

}

}

Here’s the output:

% java DatagramExample

This packet is addressed to www.ibiblio.org/152.2.254.81 on port 7

There are 15 bytes of data in the packet

This is a test.

The setter Methods

Most of the time, the six constructors are sufficient for creating datagrams. However, Java also provides several methods for changing the data, remote address, and remote port after the datagram has been created. These methods might be important in a situation where the time to create and garbage collect new DatagramPacket objects is a significant performance hit. In some situations, reusing objects can be significantly faster than constructing new ones: for example, in a networked twitch game that sends a datagram for every bullet fired or every centimeter of movement. However, you would have to use a very speedy connection for the improvement to be noticeable relative to the slowness of the network itself.

public void setData(byte[] data)

The setData() method changes the payload of the UDP datagram. You might use this method if you are sending a large file (where large is defined as “bigger than can comfortably fit in one datagram”) to a remote host. You could repeatedly send the same DatagramPacket object, just changing the data each time.

public void setData(byte[] data, int offset, int length)

This overloaded variant of the setData() method provides an alternative approach to sending a large quantity of data. Instead of sending lots of new arrays, you can put all the data in one array and send it a piece at a time. For instance, this loop sends a large array in 512-byte chunks:

int offset = 0;

DatagramPacket dp = new DatagramPacket(bigarray, offset, 512);

int bytesSent = 0;

while (bytesSent < bigarray.length) {

socket.send(dp);

bytesSent += dp.getLength();

int bytesToSend = bigarray.length - bytesSent;

int size = (bytesToSend > 512) ? 512 : bytesToSend;

dp.setData(bigarray, bytesSent, size);

}

On the other hand, this strategy requires either a lot of confidence that the data will in fact arrive or, alternatively, a disregard for the consequences of its not arriving. It’s relatively difficult to attach sequence numbers or other reliability tags to individual packets when you take this approach.

public void setAddress(InetAddress remote)

The setAddress() method changes the address a datagram packet is sent to. This might allow you to send the same datagram to many different recipients. For example:

String s = "Really Important Message";

byte[] data = s.getBytes("UTF-8");

DatagramPacket dp = new DatagramPacket(data, data.length);

dp.setPort(2000);

int network = "128.238.5.";

for (int host = 1; host < 255; host++) {

try {

InetAddress remote = InetAddress.getByName(network + host);

dp.setAddress(remote);

socket.send(dp);

} catch (IOException ex) {

// skip it; continue with the next host

}

}

Whether this is a sensible choice depends on the application. If you’re trying to send to all the stations on a network segment, as in this fragment, you’d probably be better off using the local broadcast address and letting the network do the work. The local broadcast address is determined by setting all bits of the IP address after the network and subnet IDs to 1. For example, Polytechnic University’s network address is 128.238.0.0. Consequently, its broadcast address is 128.238.255.255. Sending a datagram to 128.238.255.255 copies it to every host on that network (although some routers and firewalls may block it, depending on its origin).

For more widely separated hosts, you’re probably better off using multicasting. Multicasting actually uses the same DatagramPacket class described here. However, it uses different IP addresses and a MulticastSocket instead of a DatagramSocket. We’ll discuss this further inChapter 13.

public void setPort(int port)

The setPort() method changes the port a datagram is addressed to. I honestly can’t think of many uses for this method. It could be used in a port scanner application that tried to find open ports running particular UDP-based services such as FSP. Another possibility might be some sort of networked game or conferencing server where the clients that need to receive the same information are all running on different ports as well as different hosts. In this case, setPort() could be used in conjunction with setAddress() to change destinations before sending the same datagram out again.

public void setAddress(SocketAddress remote)

The setSocketAddress() method changes the address and port a datagram packet is sent to. You can use this when replying. For example, this code fragment receives a datagram packet and responds to the same address with a packet containing the string “Hello there”:

DatagramPacket input = new DatagramPacket(new byte[8192], 8192);

socket.receive(input);

DatagramPacket output = new DatagramPacket(

"Hello there".getBytes("UTF-8"), 11);

SocketAddress address = input.getSocketAddress();

output.setAddress(address);

socket.send(output);

You could certainly write the same code using InetAddress objects and ports instead of a SocketAddress. The code would be just a few lines longer.

public void setLength(int length)

The setLength() method changes the number of bytes of data in the internal buffer that are considered to be part of the datagram’s data as opposed to merely unfilled space. This method is useful when receiving datagrams, as we’ll explore later in this chapter. When a datagram is received, its length is set to the length of the incoming data. This means that if you try to receive another datagram into the same DatagramPacket, it’s limited to no more than the number of bytes in the first. That is, once you’ve received a 10-byte datagram, all subsequent datagrams will be truncated to 10 bytes; once you’ve received a 9-byte datagram, all subsequent datagrams will be truncated to 9 bytes; and so on. This method lets you reset the length of the buffer so that subsequent datagrams aren’t truncated.

The DatagramSocket Class

To send or receive a DatagramPacket, you must open a datagram socket. In Java, a datagram socket is created and accessed through the DatagramSocket class:

public class DatagramSocket extends Object

All datagram sockets bind to a local port, on which they listen for incoming data and which they place in the header of outgoing datagrams. If you’re writing a client, you don’t care what the local port is, so you call a constructor that lets the system assign an unused port (an anonymous port). This port number is placed in any outgoing datagrams and will be used by the server to address any response datagrams. If you’re writing a server, clients need to know on which port the server is listening for incoming datagrams; therefore, when a server constructs a DatagramSocket, it specifies the local port on which it will listen. However, the sockets used by clients and servers are otherwise identical: they differ only in whether they use an anonymous (system-assigned) or a well-known port. There’s no distinction between client sockets and server sockets, as there is with TCP. There’s no such thing as a DatagramServerSocket.

The Constructors

The DatagramSocket constructors are used in different situations, much like the DatagramPacket constructors. The first constructor opens a datagram socket on an anonymous local port. The second constructor opens a datagram socket on a well-known local port that listens to all local network interfaces. The last two constructors open a datagram socket on a well-known local port on a specific network interface. All constructors deal only with the local address and port. The remote address and port are stored in the DatagramPacket, not the DatagramSocket. Indeed, one DatagramSocket can send and receive datagrams from multiple remote hosts and ports.

public DatagramSocket() throws SocketException

This constructor creates a socket that is bound to an anonymous port. For example:

try {

DatagramSocket client = new DatagramSocket();

// send packets...

} catch (SocketException ex) {

System.err.println(ex);

}

Pick this constructor for a client that initiates a conversation with a server. In this scenario, you don’t care what port the socket is bound to because the server will send its response to the port from which the datagram originated. Letting the system assign a port means that you don’t have to worry about finding an unused port. If, for some reason, you need to know the local port, you can find out with the getLocalPort() method described later in this chapter.

The same socket can receive the datagrams that a server sends back to it. The constructor throws a SocketException if the socket can’t bind to a port. It’s unusual for this constructor to throw an exception; it’s hard to imagine situations in which the socket could not be opened, because the system gets to choose an available port.

public DatagramSocket(int port) throws SocketException

This constructor creates a socket that listens for incoming datagrams on a particular port, specified by the port argument. Use this constructor to write a server that listens on a well-known port. A SocketException is thrown if the socket can’t be created. There are two common reasons for the constructor to fail: the specified port is already occupied, or you are trying to connect to a port below 1024 and you don’t have sufficient privileges (i.e., you are not root on a Unix system; for better or worse, other platforms allow anyone to connect to low-numbered ports).

TCP ports and UDP ports are not related. Two different programs can use the same port number if one uses UDP and the other uses TCP. Example 12-4 is a port scanner that looks for UDP ports in use on the local host. It decides that the port is in use if the DatagramSocket constructor throws an exception. As written, it looks at ports from 1024 and up to avoid Unix’s requirement that it run as root to bind to ports below 1024. You can easily extend it to check ports below 1024, however, if you have root access or are running it on Windows.

Example 12-4. Look for local UDP ports

import java.net.*;

public class UDPPortScanner {

public static void main(String[] args) {

for (int port = 1024; port <= 65535; port++) {

try {

// the next line will fail and drop into the catch block if

// there is already a server running on port i

DatagramSocket server = new DatagramSocket(port);

server.close();

} catch (SocketException ex) {

System.out.println("There is a server on port " + port + ".");

}

}

}

}

Here are the results from the Linux workstation on which much of the code in this book was written:

% java UDPPortScanner

There is a server on port 2049.

There is a server on port 32768.

There is a server on port 32770.

There is a server on port 32771.

The first port, 2049, is an NFS server. The high-numbered ports in the 30,000 range are Remote Procedure Call (RPC) services. Along with RPC, common protocols that use UDP include NFS, TFTP, and FSP.

It’s much harder to scan UDP ports on a remote system than to scan for remote TCP ports. Whereas there’s always some indication that a listening port, regardless of application layer protocol, has received your TCP packet, UDP provides no such guarantees. To determine that a UDP server is listening, you have to send it a packet it will recognize and respond to.

public DatagramSocket(int port, InetAddress interface) throws SocketException

This constructor is primarily used on multihomed hosts; it creates a socket that listens for incoming datagrams on a specific port and network interface. The port argument is the port on which this socket listens for datagrams. As with TCP sockets, you need to be root on a Unix system to create a DatagramSocket on a port below 1024. The address argument is an InetAddress object matching one of the host’s network addresses. A SocketException is thrown if the socket can’t be created. There are three common reasons for this constructor to fail: the specified port is already occupied, you are trying to connect to a port below 1024 and you’re not root on a Unix system, or address is not the address of one of the system’s network interfaces.

public DatagramSocket(SocketAddress interface) throws SocketException

This constructor is similar to the previous one except that the network interface address and port are read from a SocketAddress. For example, this code fragment creates a socket that only listens on the local loopback address:

SocketAddress address = new InetSocketAddress("127.0.0.1", 9999);

DatagramSocket socket = new DatagramSocket(address);

protected DatagramSocket(DatagramSocketImpl impl) throws SocketException

This constructor enables subclasses to provide their own implementation of the UDP protocol, rather than blindly accepting the default. Unlike sockets created by the other four constructors, this socket is not initially bound to a port. Before using it, you have to bind it to a SocketAddressusing the bind() method:

public void bind(SocketAddress addr) throws SocketException

You can pass null to this method, binding the socket to any available address and port.

Sending and Receiving Datagrams

The primary task of the DatagramSocket class is to send and receive UDP datagrams. One socket can both send and receive. Indeed, it can send and receive to and from multiple hosts at the same time.

public void send(DatagramPacket dp) throws IOException

Once a DatagramPacket is created and a DatagramSocket is constructed, send the packet by passing it to the socket’s send() method. For example, if theSocket is a DatagramSocket object and theOutput is a DatagramPacket object, send theOutput using theSocket like this:

theSocket.send(theOutput);

If there’s a problem sending the data, this method may throw an IOException. However, this is less common with DatagramSocket than Socket or ServerSocket, because the unreliable nature of UDP means you won’t get an exception just because the packet doesn’t arrive at its destination. You may get an IOException if you’re trying to send a larger datagram than the host’s native networking software supports, but then again you may not. This depends heavily on the native UDP software in the OS and the native code that interfaces between this and Java’sDatagramSocketImpl class. This method may also throw a SecurityException if the SecurityManager won’t let you communicate with the host to which the packet is addressed. This is primarily a problem for applets and other remotely loaded code.

Example 12-5 is a UDP-based discard client. It reads lines of user input from System.in and sends them to a discard server, which simply discards all the data. Each line is stuffed in a DatagramPacket. Many of the simpler Internet protocols, such as discard and echo, have both TCP and UDP implementations.

Example 12-5. A UDP discard client

import java.net.*;

import java.io.*;

public class UDPDiscardClient {

public final static int PORT = 9;

public static void main(String[] args) {

String hostname = args.length > 0 ? args[0] : "localhost";

try (DatagramSocket theSocket = new DatagramSocket()) {

InetAddress server = InetAddress.getByName(hostname);

BufferedReader userInput

= new BufferedReader(new InputStreamReader(System.in));

while (true) {

String theLine = userInput.readLine();

if (theLine.equals(".")) break;

byte[] data = theLine.getBytes();

DatagramPacket theOutput

= new DatagramPacket(data, data.length, server, PORT);

theSocket.send(theOutput);

} // end while

} catch (IOException ex) {

System.err.println(ex);

}

}

}

The UDPDiscardClient class should look familiar. It has a single static field, PORT, which is set to the standard port for the discard protocol (port 9), and a single method, main(). The main() method reads a hostname from the command line and converts that hostname to theInetAddress object called server. A BufferedReader is chained to System.in to read user input from the keyboard. Next, a DatagramSocket object called theSocket is constructed. After creating the socket, the program enters an infinite while loop that reads user input line by line using readLine(). Example 12-5 is careful, however, to use only readLine() to read data from the console, the one place where it is guaranteed to work as advertised. Because the discard protocol deals only with raw bytes, it can ignore character encoding issues.

In the while loop, each line is converted to a byte array using the getBytes() method, and the bytes are stuffed in a new DatagramPacket, theOutput. Finally, theOutput is sent over theSocket, and the loop continues. If at any point the user types a period on a line by itself, the program exits. The DatagramSocket constructor may throw a SocketException, so that needs to be caught. Because this is a discard client, you don’t need to worry about data coming back from the server.

public void receive(DatagramPacket dp) throws IOException

This method receives a single UDP datagram from the network and stores it in the preexisting DatagramPacket object dp. Like the accept() method in the ServerSocket class, this method blocks the calling thread until a datagram arrives. If your program does anything besides wait for datagrams, you should call receive() in a separate thread.

The datagram’s buffer should be large enough to hold the data received. If it’s not, receive() places as much data in the buffer as it can hold; the rest is lost. Remember that the maximum size of the data portion of a UDP datagram is 65,507 bytes. (That’s the 65,536-byte maximum size of an IP datagram minus the 20-byte size of the IP header and the 8-byte size of the UDP header.) Some application protocols that use UDP further restrict the maximum number of bytes in a packet; for instance, NFS uses a maximum packet size of 8,192 bytes.

If there’s a problem receiving the data, receive() may throw an IOException. In practice, this is rare because problems like dropped packets that would shut down a TCP stream are silently discarded by the network or network stack before Java ever sees them.

Example 12-6 shows a UDP discard server that receives incoming datagrams. Just for fun, it logs the data in each datagram to System.out so that you can see who’s sending what to your discard server.

Example 12-6. The UDPDiscardServer

import java.net.*;

import java.io.*;

public class UDPDiscardServer {

public final static int PORT = 9;

public final static int MAX_PACKET_SIZE = 65507;

public static void main(String[] args) {

byte[] buffer = new byte[MAX_PACKET_SIZE];

try (DatagramSocket server = new DatagramSocket(PORT)) {

DatagramPacket packet = new DatagramPacket(buffer, buffer.length);

while (true) {

try {

server.receive(packet);

String s = new String(packet.getData(), 0, packet.getLength(), "8859_1");

System.out.println(packet.getAddress() + " at port "

+ packet.getPort() + " says " + s);

// reset the length for the next packet

packet.setLength(buffer.length);

} catch (IOException ex) {

System.err.println(ex);

}

} // end while

} catch (SocketException ex) {

System.err.println(ex);

}

}

}

This is a simple class with a single method, main(). It reads the port the server listens to from the command line. If the port is not specified on the command line, it listens on port 9. It then opens a DatagramSocket on that port and creates a DatagramPacket with a 65,507-byte buffer—large enough to receive any possible packet. Then the server enters an infinite loop that receives packets and prints the contents and the originating host on the console. There’s no particular encoding expected for discard packets. Indeed, there’s no particular reason these packets have to be text at all. I somewhat arbitrarily picked the Latin-1 ISO 8859-1 encoding because it’s ASCII compatible and defines a character for every byte.

As each datagram is received, the length of packet is set to the length of the data in that datagram. Consequently, as the last step of the loop, the length of the packet is reset to the maximum possible value. Otherwise, the incoming packets would be limited to the minimum size of all previous packets. You can run the discard client on one machine and connect to the discard server on a second machine to verify that the network is working.

public void close()

Calling a DatagramSocket object’s close() method frees the port occupied by that socket. As with streams and TCP sockets, you’ll want to take care to close the datagram socket in a finally block:

DatagramSocket server = null

try {

server = new DatagramSocket();

// use the socket...

} catch (IOException ex) {

System.err.println(ex);

} finally {

try {

if (server != null) server.close();

} catch (IOException ex) {

}

}

In Java 7, DatagramSocket implements AutoCloseable so you can use try-with-resources:

try (DatagramSocket server = new DatagramSocket()) {

// use the socket...

}

It’s never a bad idea to close a DatagramSocket when you’re through with it; it’s particularly important to close an unneeded socket if the program will continue to run for a significant amount of time. For example, the close() method was essential in Example 12-4, UDPPortScanner: if this program did not close the sockets it opened, it would tie up every UDP port on the system for a significant amount of time. On the other hand, if the program ends as soon as you’re through with the DatagramSocket, you don’t need to close the socket explicitly; the socket is automatically closed upon garbage collection. However, Java won’t run the garbage collector just because you’ve run out of ports or sockets, unless by lucky happenstance you run out of memory at the same time. Closing unneeded sockets never hurts and is good programming practice.

public int getLocalPort()

A DatagramSocket’s getLocalPort() method returns an int that represents the local port on which the socket is listening. Use this method if you created a DatagramSocket with an anonymous port and want to find out what port the socket has been assigned. For example:

DatagramSocket ds = new DatagramSocket();

System.out.println("The socket is using port " + ds.getLocalPort());

public InetAddress getLocalAddress()

A DatagramSocket’s getLocalAddress() method returns an InetAddress object that represents the local address to which the socket is bound. It’s rarely needed in practice. Normally, you either already know or simply don’t care which address a socket is listening to.

public SocketAddress getLocalSocketAddress()

The getLocalSocketAddress() method returns a SocketAddress object that wraps the local interface and port to which the socket is bound. Like getLocalAddress(), it’s a little hard to imagine a realistic use case here. This method probably exists mostly for parallelism withsetLocalSocketAddress().

Managing Connections

Unlike TCP sockets, datagram sockets aren’t very picky about whom they’ll talk to. In fact, by default they’ll talk to anyone; but this is often not what you want. For instance, applets are only allowed to send datagrams to and receive datagrams from the applet host. An NFS or FSP client should accept packets only from the server it’s talking to. A networked game should listen to datagrams only from the people playing the game. The next five methods let you choose which host you can send datagrams to and receive datagrams from, while rejecting all others’ packets.

public void connect(InetAddress host, int port)

The connect() method doesn’t really establish a connection in the TCP sense. However, it does specify that the DatagramSocket will only send packets to and receive packets from the specified remote host on the specified remote port. Attempts to send packets to a different host or port will throw an IllegalArgumentException. Packets received from a different host or a different port will be discarded without an exception or other notification.

A security check is made when the connect() method is invoked. If the VM is allowed to send data to that host and port, the check passes silently. If not, a SecurityException is thrown. However, once the connection has been made, send() and receive() on that DatagramSocketno longer make the security checks they’d normally make.

public void disconnect()

The disconnect() method breaks the “connection” of a connected DatagramSocket so that it can once again send packets to and receive packets from any host and port.

public int getPort()

If and only if a DatagramSocket is connected, the getPort() method returns the remote port to which it is connected. Otherwise, it returns –1.

public InetAddress getInetAddress()

If and only if a DatagramSocket is connected, the getInetAddress() method returns the address of the remote host to which it is connected. Otherwise, it returns null.

public InetAddress getRemoteSocketAddress()

If a DatagramSocket is connected, the getRemoteSocketAddress() method returns the address of the remote host to which it is connected. Otherwise, it returns null.

Socket Options

Java supports six socket options for UDP:

§ SO_TIMEOUT

§ SO_RCVBUF

§ SO_SNDBUF

§ SO_REUSEADDR

§ SO_BROADCAST

§ IP_TOS

SO_TIMEOUT

SO_TIMEOUT is the amount of time, in milliseconds, that receive() waits for an incoming datagram before throwing an InterruptedIOException, which is a subclass of IOException. Its value must be nonnegative. If SO_TIMEOUT is 0, receive() never times out. This value can be changed with the setSoTimeout() method and inspected with the getSoTimeout() method:

public void setSoTimeout(int timeout) throws SocketException

public int getSoTimeout() throws IOException

The default is to never time out, and indeed there are few situations in which you need to set SO_TIMEOUT. You might need it if you were implementing a secure protocol that required responses to occur within a fixed amount of time. You might also decide that the host you’re communicating with is dead (unreachable or not responding) if you don’t receive a response within a certain amount of time.

The setSoTimeout() method sets the SO_TIMEOUT field for a datagram socket. When the timeout expires, a blocked receive() method throws a SocketTimeoutException. Set this option before you call receive(). You cannot change it while receive() is waiting for a datagram. The timeout argument must be greater than or equal to zero. For example:

try {

byte[] buffer = new byte[2056];

DatagramPacket dp = new DatagramPacket(buffer, buffer.length);

DatagramSocket ds = new DatagramSocket(2048);

ds.setSoTimeout(30000); // block for no more than 30 seconds

try {

ds.receive(dp);

// process the packet...

} catch (SocketTimeoutException ex) {

ss.close();

System.err.println("No connection within 30 seconds");

}

} catch (SocketException ex) {

System.err.println(ex);

} catch (IOException ex) {

System.err.println("Unexpected IOException: " + ex);

}

The getSoTimeout() method returns the current value of this DatagramSocket object’s SO_TIMEOUT field. For example:

public void printSoTimeout(DatagramSocket ds) {

int timeout = ds.getSoTimeOut();

if (timeout > 0) {

System.out.println(ds + " will time out after "

+ timeout + "milliseconds.");

} else if (timeout == 0) {

System.out.println(ds + " will never time out.");

} else {

System.out.println("Something is seriously wrong with " + ds);

}

}

SO_RCVBUF

The SO_RCVBUF option of DatagramSocket is closely related to the SO_RCVBUF option of Socket. It determines the size of the buffer used for network I/O. Larger buffers tend to improve performance for reasonably fast (say, Ethernet-speed) connections because they can store more incoming datagrams before overflowing. Sufficiently large receive buffers are even more important for UDP than for TCP, because a UDP datagram that arrives when the buffer is full will be lost, whereas a TCP datagram that arrives at a full buffer will eventually be retransmitted. Furthermore, SO_RCVBUF sets the maximum size of datagram packets that can be received by the application. Packets that won’t fit in the receive buffer are silently discarded.

DatagramSocket has methods to set and get the suggested receive buffer size used for network input:

public void setReceiveBufferSize(int size) throws SocketException

public int getReceiveBufferSize() throws SocketException

The setReceiveBufferSize() method suggests a number of bytes to use for buffering input from this socket. However, the underlying implementation is free to ignore this suggestion. For instance, many 4.3 BSD-derived systems have a maximum receive buffer size of about 52K and won’t let you set a limit higher than this. My Linux box was limited to 64K. Other systems raise this to about 240K. The details are highly platform-dependent. Consequently, you may wish to check the actual size of the receive buffer with getReceiveBufferSize() after setting it. ThegetReceiveBufferSize() method returns the number of bytes in the buffer used for input from this socket.

Both methods throw a SocketException if the underlying socket implementation does not recognize the SO_RCVBUF option. This might happen on a non-POSIX operating system. The setReceiveBufferSize() method will throw an IllegalArgumentException if its argument is less than or equal to zero.

SO_SNDBUF

DatagramSocket has methods to get and set the suggested send buffer size used for network output:

public void setSendBufferSize(int size) throws SocketException

public int getSendBufferSize() throws SocketException

The setSendBufferSize() method suggests a number of bytes to use for buffering output on this socket. Once again, however, the operating system is free to ignore this suggestion. Consequently, you’ll want to check the result of setSendBufferSize() by immediately following it with a call to getSendBufferSize() to find out the real buffer size.

Both methods throw a SocketException if the underlying native network software doesn’t understand the SO_SNDBUF option. The setSendBufferSize() method also throws an IllegalArgumentException if its argument is less than or equal to zero.

SO_REUSEADDR

The SO_REUSEADDR option does not mean the same thing for UDP sockets as it does for TCP sockets. For UDP, SO_REUSEADDR controls whether multiple datagram sockets can bind to the same port and address at the same time. If multiple sockets are bound to the same port, received packets will be copied to all bound sockets. This option is controlled by these two methods:

public void setReuseAddress(boolean on) throws SocketException

public boolean getReuseAddress() throws SocketException

For this to work reliably, setReuseAddress() must be called before the new socket binds to the port. This means the socket must be created in an unconnected state using the protected constructor that takes a DatagramImpl as an argument. In other words, it won’t work with a plain vanilla DatagramSocket. Reusable ports are most commonly used for multicast sockets, which will be discussed in the next chapter. Datagram channels also create unconnected datagram sockets that can be configured to reuse ports, as you’ll see later in this chapter.

SO_BROADCAST

The SO_BROADCAST option controls whether a socket is allowed to send packets to and receive packets from broadcast addresses such as 192.168.254.255, the local network broadcast address for the network with the local address 192.168.254.*. UDP broadcasting is often used for protocols such as DHCP that need to communicate with servers on the local net whose addresses are not known in advance. This option is controlled with these two methods:

public void setBroadcast(boolean on) throws SocketException

public boolean getBroadcast() throws SocketException

Routers and gateways do not normally forward broadcast messages, but they can still kick up a lot of traffic on the local network. This option is turned on by default, but if you like you can disable it thusly:

socket.setBroadcast(false);

This option can be changed after the socket has been bound.

TIP

On some implementations, sockets bound to a specific address do not receive broadcast packets. In other words, you should use the DatagramPacket(int port) constructor, not the DatagramPacket(InetAddress address, int port) constructor to listen to broadcasts. This is necessary in addition to setting the SO_BROADCAST option to true.

IP_TOS

Because the traffic class is determined by the value of the IP_TOS field in each IP packet header, it is essentially the same for UDP as it is for TCP. After all, packets are actually routed and prioritized according to IP, which both TCP and UDP sit on top of. There’s really no difference between the setTrafficClass() and getTrafficClass() methods in DatagramSocket and those in Socket. They just have to be repeated here because DatagramSocket and Socket don’t have a common superclass. These two methods let you inspect and set the class of service for a socket using these two methods:

public int getTrafficClass() throws SocketException

public void setTrafficClass(int trafficClass) throws SocketException

The traffic class is given as an int between 0 and 255. Because this value is copied to an eight-bit field in the TCP header, only the low-order byte of this int is used; and values outside this range cause IllegalArgumentExceptions.

WARNING

The JavaDoc for these options is severely out of date, and describes a quality of service scheme based on bit fields for four traffic classes: low cost, high reliability, maximum throughput, and minimum delay. This scheme was never widely implemented and probably hasn’t been used in this century.

This code fragment sets a socket to use Expedited Forwarding by setting the traffic class to 10111000:

DatagramSocket s = new DatagramSocket();

s.setTrafficClass(0xB8); // 10111000 in binary

For details of the individual traffic classes, refer to IP_TOS Class of Service.

The underlying socket implementation is not required to respect any of these requests. Some implementations ignore these values completely. Android in particular treats the setTrafficClass() method as a noop. If the native network stack is unable to provide the requested class of service, Java may but is not required to throw a SocketException.

Some Useful Applications

In this section, you’ll see several Internet servers and clients that use DatagramPacket and DatagramSocket. Some of these will be familiar from previous chapters because many Internet protocols have both TCP and UDP implementations. When an IP packet is received by a host, the host determines whether the packet is a TCP packet or a UDP datagram by inspecting the IP header. As I said earlier, there’s no connection between UDP and TCP ports; TCP and UDP servers can share the same port number without problems. By convention, if a service has both TCP and UDP implementations, it uses the same port for both, although there’s no technical reason this has to be the case.

Simple UDP Clients

Several Internet services need to know only the client’s address and port; they ignore any data the client sends in its datagrams. Daytime, quote of the day, time, and chargen are four such protocols. Each of these responds the same way, regardless of the data contained in the datagram, or indeed regardless of whether there actually is any data in the datagram. Clients for these protocols simply send a UDP datagram to the server and read the response that comes back. Therefore, let’s begin with a simple client called UDPPoke , shown in Example 12-7, which sends an empty UDP packet to a specified host and port and reads a response packet from the same host.

The UDPPoke class has four private fields. The bufferSize field specifies how large a return packet is expected. An 8,192-byte buffer is large enough for most of the protocols that UDPPoke is useful for, but it can be increased by passing a different value to the constructor. The timeoutfield specifies how long to wait for a response. The host and the port fields specify the remote host to connect to.

If the buffer length is not specified, 8,192 bytes is used. If the timeout is not given, 30 seconds (30,000 milliseconds) is used. The host, port, and buffer size are also used to construct the outgoing DatagramPacket. Although in theory you should be able to send a datagram with no data at all, bugs in some Java implementations require that you add at least one byte of data to the datagram. The simple servers we’re currently considering ignore this data.

Once a UDPPoke object has been constructed, clients call its poke() method to send an empty outgoing datagram to the target and read its response. The response is initially set to null. When the expected datagram appears, its data is copied into the response field. This method returns null if the response doesn’t come quickly enough or never comes at all.

The main() method merely reads the host and port to connect to from the command line, constructs a UDPPoke object, and pokes it. Most of the simple protocols that this client suits will return ASCII text, so this example attempts to convert the response to an ASCII string and print it.

Example 12-7. The UDPPoke class

import java.io.*;

import java.net.*;

public class UDPPoke {

private int bufferSize; // in bytes

private int timeout; // in milliseconds

private InetAddress host;

private int port;

public UDPPoke(InetAddress host, int port, int bufferSize, int timeout) {

this.bufferSize = bufferSize;

this.host = host;

if (port < 1 || port > 65535) {

throw new IllegalArgumentException("Port out of range");

}

this.port = port;

this.timeout = timeout;

}

public UDPPoke(InetAddress host, int port, int bufferSize) {

this(host, port, bufferSize, 30000);

}

public UDPPoke(InetAddress host, int port) {

this(host, port, 8192, 30000);

}

public byte[] poke() {

try (DatagramSocket socket = new DatagramSocket(0)) {

DatagramPacket outgoing = new DatagramPacket(new byte[1], 1, host, port);

socket.connect(host, port);

socket.setSoTimeout(timeout);

socket.send(outgoing);

DatagramPacket incoming

= new DatagramPacket(new byte[bufferSize], bufferSize);

// next line blocks until the response is received

socket.receive(incoming);

int numBytes = incoming.getLength();

byte[] response = new byte[numBytes];

System.arraycopy(incoming.getData(), 0, response, 0, numBytes);

return response;

} catch (IOException ex) {

return null;

}

}

public static void main(String[] args) {

InetAddress host;

int port = 0;

try {

host = InetAddress.getByName(args[0]);

port = Integer.parseInt(args[1]);

} catch (RuntimeException | UnknownHostException ex) {

System.out.println("Usage: java UDPPoke host port");

return;

}

try {

UDPPoke poker = new UDPPoke(host, port);

byte[] response = poker.poke();

if (response == null) {

System.out.println("No response within allotted time");

return;

}

String result = new String(response, "US-ASCII");

System.out.println(result);

} catch (UnsupportedEncodingException ex) {

// Really shouldn't happen

ex.printStackTrace();

}

}

}

For example, this connects to a daytime server over UDP:

$ java UDPPoke rama.poly.edu 13

Sun Oct 3 13:04:22 2009

This connects to a chargen server:

$ java UDPPoke rama.poly.edu 19

123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuv

Given this class, UDP daytime, time, chargen, and quote of the day clients are almost trivial. A time client is only slightly harder, and only because you need to convert the four raw bytes returned by the server to a java.util.Date object. The same algorithm as in Example 8-3 will accomplish that as demonstrated in Example 12-8.

Example 12-8. A UDP time client

import java.net.*;

import java.util.*;

public class UDPTimeClient {

public final static int PORT = 37;

public final static String DEFAULT_HOST = "time.nist.gov";

public static void main(String[] args) {

InetAddress host;

try {

if (args.length > 0) {

host = InetAddress.getByName(args[0]);

} else {

host = InetAddress.getByName(DEFAULT_HOST);

}

} catch (RuntimeException | UnknownHostException ex) {

System.out.println("Usage: java UDPTimeClient [host]");

return;

}

UDPPoke poker = new UDPPoke(host, PORT);

byte[] response = poker.poke();

if (response == null) {

System.out.println("No response within allotted time");

return;

} else if (response.length != 4) {

System.out.println("Unrecognized response format");

return;

}

// The time protocol sets the epoch at 1900,

// the Java Date class at 1970. This number

// converts between them.

long differenceBetweenEpochs = 2208988800L;

long secondsSince1900 = 0;

for (int i = 0; i < 4; i++) {

secondsSince1900

= (secondsSince1900 << 8) | (response[i] & 0x000000FF);

}

long secondsSince1970

= secondsSince1900 - differenceBetweenEpochs;

long msSince1970 = secondsSince1970 * 1000;

Date time = new Date(msSince1970);

System.out.println(time);

}

}

UDPServer

Clients aren’t the only programs that benefit from a reusable implementation. The servers for these protocols are also very similar. They all wait for UDP datagrams on a specified port and reply to each datagram with another datagram. The servers differ only in the content of the datagram that they return. Example 12-9 is a simple iterative UDPServer class that can be subclassed to provide specific servers for different protocols.

The UDPServer class has two fields, the int bufferSize and the DatagramSocket socket, the latter of which is protected so it can be used by subclasses. The constructor opens a datagram socket on a specified local port to receive datagrams of no more than bufferSize bytes.

UDPServer implements Runnable so that multiple instances can run in parallel. Its run() method contains a loop that repeatedly receives an incoming datagram and responds by passing it to the abstract respond() method. This method will be overridden by particular subclasses in order to implement different kinds of servers.

Assuming this class may be used as part of other programs that do more than just run one server, you need a way to shut it down. This is provided by the shutDown() method, which sets a flag. The main loop checks this flag each pass to see if it should exit. Because the receive() call can block indefinitely if there’s no traffic, you also set a timeout on the socket. This will wake it up once every 10 seconds to check for shutdown whether there’s traffic or not.

UDPServer is a very flexible class. Subclasses can send zero, one, or many datagrams in response to each incoming datagram. If a lot of processing is required to respond to a packet, the respond() method can spawn a thread to do it. However, UDP servers tend not to have extended interactions with a client. Each incoming packet is treated independently of other packets, so the response can usually be handled directly in the respond() method without spawning a thread.

Example 12-9. The UDPServer class

import java.io.*;

import java.net.*;

import java.util.logging.*;

public abstract class UDPServer implements Runnable {

private final int bufferSize; // in bytes

private final int port;

private final Logger logger = Logger.getLogger(UDPServer.class.getCanonicalName());

private volatile boolean isShutDown = false;

public UDPServer(int port, int bufferSize) {

this.bufferSize = bufferSize;

this.port = port;

}

public UDPServer(int port) {

this(port, 8192);

}

@Override

public void run() {

byte[] buffer = new byte[bufferSize];

try (DatagramSocket socket = new DatagramSocket(port)) {

socket.setSoTimeout(10000); // check every 10 seconds for shutdown

while (true) {

if (isShutDown) return;

DatagramPacket incoming = new DatagramPacket(buffer, buffer.length);

try {

socket.receive(incoming);

this.respond(socket, incoming);

} catch (SocketTimeoutException ex) {

if (isShutDown) return;

} catch (IOException ex) {

logger.log(Level.WARNING, ex.getMessage(), ex);

}

} // end while

} catch (SocketException ex) {

logger.log(Level.SEVERE, "Could not bind to port: " + port, ex);

}

}

public abstract void respond(DatagramSocket socket, DatagramPacket request)

throws IOException;

public void shutDown() {

this.isShutDown = true;

}

}

The easiest protocol to handle is discard. All that’s needed is a main() method that sets the port and starts the thread. respond() is a do-nothing method. Example 12-10 is a high-performance UDP discard server that does nothing with incoming packets.

Example 12-10. A UDP discard server

import java.net.*;

public class FastUDPDiscardServer extends UDPServer {

public final static int DEFAULT_PORT = 9;

public FastUDPDiscardServer() {

super(DEFAULT_PORT);

}

public static void main(String[] args) {

UDPServer server = new FastUDPDiscardServer();

Thread t = new Thread(server);

t.start();

}

@Override

public void respond(DatagramSocket socket, DatagramPacket request) {

}

}

It isn’t much harder to implement an echo server, as Example 12-11 shows. Unlike a stream-based TCP echo server, multiple threads are not required to handle multiple clients.

Example 12-11. A UDP echo server

import java.io.*;

import java.net.*;

public class UDPEchoServer extends UDPServer {

public final static int DEFAULT_PORT = 7;

public UDPEchoServer() {

super(DEFAULT_PORT);

}

@Override

public void respond(DatagramSocket socket, DatagramPacket packet)

throws IOException {

DatagramPacket outgoing = new DatagramPacket(packet.getData(),

packet.getLength(), packet.getAddress(), packet.getPort());

socket.send(outgoing);

}

public static void main(String[] args) {

UDPServer server = new UDPEchoServer();

Thread t = new Thread(server);

t.start();

}

}

A UDP Echo Client

The UDPPoke class implemented earlier isn’t suitable for all protocols. In particular, protocols that require multiple datagrams require a different implementation. The echo protocol has both TCP and UDP implementations. Implementing the echo protocol with TCP is simple; it’s more complex with UDP because you don’t have I/O streams or the concept of a connection to work with. A TCP-based echo client can send a message and wait for a response on the same connection. However, a UDP-based echo client has no guarantee that the message it sent was received. Therefore, it cannot simply wait for the response; it needs to be prepared to send and receive data asynchronously.

This behavior is fairly simple to implement using threads, however. One thread can process user input and send it to the echo server, while a second thread accepts input from the server and displays it to the user. The client is divided into three classes: the main UDPEchoClient class, theSenderThread class, and the ReceiverThread class.

The UDPEchoClient class should look familiar. It reads a hostname from the command line and converts it to an InetAddress object. UDPEchoClient uses this object and the default echo port to construct a SenderThread object. This constructor can throw a SocketException, so the exception must be caught. Then the SenderThread starts. The same DatagramSocket that the SenderThread uses is used to construct a ReceiverThread, which is then started. It’s important to use the same DatagramSocket for both sending and receiving data because the echo server will send the response back to the port the data was sent from. Example 12-12 shows the code for the UDPEchoClient.

Example 12-12. The UDPEchoClient class

import java.net.*;

public class UDPEchoClient {

public final static int PORT = 7;

public static void main(String[] args) {

String hostname = "localhost";

if (args.length > 0) {

hostname = args[0];

}

try {

InetAddress ia = InetAddress.getByName(hostname);

DatagramSocket socket = new DatagramSocket();

SenderThread sender = new SenderThread(socket, ia, PORT);

sender.start();

Thread receiver = new ReceiverThread(socket);

receiver.start();

} catch (UnknownHostException ex) {

System.err.println(ex);

} catch (SocketException ex) {

System.err.println(ex);

}

}

}

The SenderThread class reads input from the console a line at a time and sends it to the echo server. It’s shown in Example 12-13. The input is provided by System.in, but a different client could include an option to read input from a different stream—perhaps opening aFileInputStream to read from a file. The fields of this class define the server to which it sends data, the port on that server, and the DatagramSocket that does the sending, all set in the single constructor. The DatagramSocket is connected to the remote server to make sure all datagrams received were in fact sent by the right server. It’s rather unlikely that some other server on the Internet is going to bombard this particular port with extraneous data, so this is not a big flaw. However, it’s a good habit to make sure that the packets you receive come from the right place, especially if security is a concern.

The run() method processes user input a line at a time. To do this, the BufferedReader userInput is chained to System.in. An infinite loop reads lines of user input. Each line is stored in theLine. A period on a line by itself signals the end of user input and breaks out of the loop. Otherwise, the bytes of data are stored in the data array using the getBytes() method from java.lang.String. Next, the data array is placed in the payload part of the DatagramPacket output, along with information about the server, the port, and the data length. This packet is then sent to its destination by socket. This thread then yields to give other threads an opportunity to run.

Example 12-13. The SenderThread class

import java.io.*;

import java.net.*;

class SenderThread extends Thread {

private InetAddress server;

private DatagramSocket socket;

private int port;

private volatile boolean stopped = false;

SenderThread(DatagramSocket socket, InetAddress address, int port) {

this.server = address;

this.port = port;

this.socket = socket;

this.socket.connect(server, port);

}

public void halt() {

this.stopped = true;

}

@Override

public void run() {

try {

BufferedReader userInput

= new BufferedReader(new InputStreamReader(System.in));

while (true) {

if (stopped) return;

String theLine = userInput.readLine();

if (theLine.equals(".")) break;

byte[] data = theLine.getBytes("UTF-8");

DatagramPacket output

= new DatagramPacket(data, data.length, server, port);

socket.send(output);

Thread.yield();

}

} catch (IOException ex) {

System.err.println(ex);

}

}

}

The ReceiverThread class shown in Example 12-14 waits for datagrams to arrive from the network. When a datagram is received, it is converted to a String and printed on System.out for display to the user. A more advanced echo client could include an option to send the output elsewhere.

This class has two fields. The more important is the DatagramSocket, theSocket, which must be the same DatagramSocket used by the SenderThread. Data arrives on the port used by that DatagramSocket; any other DatagramSocket would not be allowed to connect to the same port. The second field, stopped, is a boolean used to halt this thread without invoking the deprecated stop() method.

The run() method is an infinite loop that uses socket’s receive() method to wait for incoming datagrams. When an incoming datagram appears, it is converted into a String with the same length as the incoming data and printed on System.out. As in the input thread, this thread then yields to give other threads an opportunity to execute.

Example 12-14. The ReceiverThread class

import java.io.*;

import java.net.*;

class ReceiverThread extends Thread {

private DatagramSocket socket;

private volatile boolean stopped = false;

ReceiverThread(DatagramSocket socket) {

this.socket = socket;

}

public void halt() {

this.stopped = true;

}

@Override

public void run() {

byte[] buffer = new byte[65507];

while (true) {

if (stopped) return;

DatagramPacket dp = new DatagramPacket(buffer, buffer.length);

try {

socket.receive(dp);

String s = new String(dp.getData(), 0, dp.getLength(), "UTF-8");

System.out.println(s);

Thread.yield();

} catch (IOException ex) {

System.err.println(ex);

}

}

}

}

You can run the echo client on one machine and connect to the echo server on a second machine to verify that the network is functioning properly between them.

DatagramChannel

The DatagramChannel class is used for nonblocking UDP applications, in the same way as SocketChannel and ServerSocketChannel are used for nonblocking TCP applications. Like SocketChannel and ServerSocketChannel, DatagramChannel is a subclass ofSelectableChannel that can be registered with a Selector. This is useful in servers where one thread can manage communications with multiple clients. However, UDP is by its nature much more asynchronous than TCP so the net effect is smaller. In UDP, a single datagram socket can process requests from multiple clients for both input and output. What the DatagramChannel class adds is the ability to do this in a nonblocking fashion, so methods return quickly if the network isn’t immediately ready to receive or send data.

Using DatagramChannel

DatagramChannel is a near-complete alternate API for UDP. In Java 6 and earlier, you still need to use the DatagramSocket class to bind a channel to a port. However, you do not have to use it thereafter, and you don’t have to use it all in Java 7 and later. Nor do you ever useDatagramPacket. Instead, you read and write byte buffers, just as you do with a SocketChannel.

Opening a socket

The java.nio.channels.DatagramChannel class does not have any public constructors. Instead, you create a new DatagramChannel object using the static open() method For example:

DatagramChannel channel = DatagramChannel.open();

This channel is not initially bound to any port. To bind it, you access the channel’s peer DatagramSocket object using the socket() method. For example, this binds a channel to port 3141:

SocketAddress address = new InetSocketAddress(3141);

DatagramSocket socket = channel.socket();

socket.bind(address);

Java 7 adds a convenient bind() method directly to DatagramChannel, so you don’t have to use a DatagramSocket at all. For example:

SocketAddress address = new InetSocketAddress(3141);

channel.bind(address);

Receiving

The receive() method reads one datagram packet from the channel into a ByteBuffer. It returns the address of the host that sent the packet:

public SocketAddress receive(ByteBuffer dst) throws IOException

If the channel is blocking (the default), this method will not return until a packet has been read. If the channel is nonblocking, this method will immediately return null if no packet is available to read.

If the datagram packet has more data than the buffer can hold, the extra data is thrown away with no notification of the problem. You do not receive a BufferOverflowException or anything similar. Again you see that UDP is unreliable. This behavior introduces an additional layer of unreliability into the system. The data can arrive safely from the network and still be lost inside your own program.

Using this method, you can reimplement the discard server to log the host sending the data as well as the data sent. Example 12-15 demonstrates. It avoids the potential loss of data by using a buffer that’s big enough to hold any UDP packet and clearing it before it’s used again.

Example 12-15. A UDPDiscardServer based on channels

import java.io.*;

import java.net.*;

import java.nio.*;

import java.nio.channels.*;

public class UDPDiscardServerWithChannels {

public final static int PORT = 9;

public final static int MAX_PACKET_SIZE = 65507;

public static void main(String[] args) {

try {

DatagramChannel channel = DatagramChannel.open();

DatagramSocket socket = channel.socket();

SocketAddress address = new InetSocketAddress(PORT);

socket.bind(address);

ByteBuffer buffer = ByteBuffer.allocateDirect(MAX_PACKET_SIZE);

while (true) {

SocketAddress client = channel.receive(buffer);

buffer.flip();

System.out.print(client + " says ");

while (buffer.hasRemaining()) System.out.write(buffer.get());

System.out.println();

buffer.clear();

}

} catch (IOException ex) {

System.err.println(ex);

}

}

}

Sending

The send() method writes one datagram packet into the channel from a ByteBuffer to the address specified as the second argument:

public int send(ByteBuffer src, SocketAddress target) throws IOException

The source ByteBuffer can be reused if you want to send the same data to multiple clients. Just don’t forget to rewind it first.

The send() method returns the number of bytes written. This will either be the number of bytes that were available in the buffer to be written or zero, nothing in between. It is zero if the channel is in nonblocking mode and the data can’t be sent immediately. Otherwise, if the channel is not in nonblocking mode, send() simply waits to return until it can send all the data in the buffer.

Example 12-16 demonstrates with a simple echo server based on channels. Just as it did in Example 12-15, the receive() method reads a packet. However, this time, rather than logging the packet on System.out, it returns the same data to the client that sent it.

Example 12-16. A UDPEchoServer based on channels

import java.io.*;

import java.net.*;

import java.nio.*;

import java.nio.channels.*;

public class UDPEchoServerWithChannels {

public final static int PORT = 7;

public final static int MAX_PACKET_SIZE = 65507;

public static void main(String[] args) {

try {

DatagramChannel channel = DatagramChannel.open();

DatagramSocket socket = channel.socket();

SocketAddress address = new InetSocketAddress(PORT);

socket.bind(address);

ByteBuffer buffer = ByteBuffer.allocateDirect(MAX_PACKET_SIZE);

while (true) {

SocketAddress client = channel.receive(buffer);

buffer.flip();

channel.send(buffer, client);

buffer.clear();

}

} catch (IOException ex) {

System.err.println(ex);

}

}

}

This program is iterative, blocking, and synchronous. This is much less of a problem for UDP-based protocols than for TCP protocols. The unreliable, packet-based, connectionless nature of UDP means that the server at most has to wait for the local buffer to clear. It does not wait for the client to be ready to receive data. There’s much less opportunity for one client to get held up behind a slower client.

Connecting

Once you’ve opened a datagram channel, you connect it to a particular remote address using the connect() method:

SocketAddress remote = new InetSocketAddress("time.nist.gov", 37);

channel.connect(remote);

The channel will only send data to or receive data from this host. Unlike the connect() method of SocketChannel, this method alone does not send or receive any packets across the network because UDP is a connectionless protocol. It merely establishes the host it will send packets to when there’s data ready to be sent. Thus, connect() returns fairly quickly, and doesn’t block in any meaningful sense. There’s no need here for a finishConnect() or isConnectionPending() method. There is an isConnected() method that returns true if and only if theDatagramSocket is connected:

public boolean isConnected()

This tells you whether the DatagramChannel is limited to one host. Unlike SocketChannel, a DatagramChannel doesn’t have to be connected to transmit or receive data.

Finally, the disconnect() method breaks the connection:

public DatagramChannel disconnect() throws IOException

This doesn’t really close anything because nothing was really open in the first place. It just allows the channel to be connected to a different host in the future.

Reading

Besides the special-purpose receive() method, DatagramChannel has the usual three read() methods:

public int read(ByteBuffer dst) throws IOException

public long read(ByteBuffer[] dsts) throws IOException

public long read(ByteBuffer[] dsts, int offset, int length)

throws IOException

However, these methods can only be used on connected channels. That is, before invoking one of these methods, you must invoke connect() to glue the channel to a particular remote host. This makes them more suitable for use with clients that know who they’ll be talking to than for servers that must accept input from multiple hosts at the same time that are normally not known prior to the arrival of the first packet.

Each of these three methods only reads a single datagram packet from the network. As much data from that datagram as possible is stored in the argument ByteBuffer(s). Each method returns the number of bytes read or –1 if the channel has been closed. This method may return 0 for any of several reasons, including:

§ The channel is nonblocking and no packet was ready.

§ A datagram packet contained no data.

§ The buffer is full.

As with the receive() method, if the datagram packet has more data than the ByteBuffer(s) can hold, the extra data is thrown away with no notification of the problem. You do not receive a BufferOverflowException or anything similar.

Writing

Naturally, DatagramChannel has the three write methods common to all writable, scattering channels, which can be used instead of the send() method:

public int write(ByteBuffer src) throws IOException

public long write(ByteBuffer[] dsts) throws IOException

public long write(ByteBuffer[] dsts, int offset, int length)

throws IOException

These methods can only be used on connected channels; otherwise, they don’t know where to send the packet. Each of these methods sends a single datagram packet over the connection. None of these methods are guaranteed to write the complete contents of the buffer(s). Fortunately, the cursor-based nature of buffers enables you to easily call this method again and again until the buffer is fully drained and the data has been completely sent, possibly using multiple datagram packets. For example:

while (buffer.hasRemaining() && channel.write(buffer) != -1) ;

You can use the read and write methods to implement a simple UDP echo client. On the client side, it’s easy to connect before sending. Because packets may be lost in transit (always remember UDP is unreliable), you don’t want to tie up the sending while waiting to receive a packet. Thus, you can take advantage of selectors and nonblocking I/O. These work for UDP pretty much exactly like they worked for TCP in Chapter 11. This time, though, rather than sending text data, let’s send one hundred ints from 0 to 99. You’ll print out the values returned so it will be easy to figure out if any packets are being lost. Example 12-17 demonstrates.

Example 12-17. A UDP echo client based on channels

import java.io.*;

import java.net.*;

import java.nio.*;

import java.nio.channels.*;

import java.util.*;

public class UDPEchoClientWithChannels {

public final static int PORT = 7;

private final static int LIMIT = 100;

public static void main(String[] args) {

SocketAddress remote;

try {

remote = new InetSocketAddress(args[0], PORT);

} catch (RuntimeException ex) {

System.err.println("Usage: java UDPEchoClientWithChannels host");

return;

}

try (DatagramChannel channel = DatagramChannel.open()) {

channel.configureBlocking(false);

channel.connect(remote);

Selector selector = Selector.open();

channel.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE);

ByteBuffer buffer = ByteBuffer.allocate(4);

int n = 0;

int numbersRead = 0;

while (true) {

if (numbersRead == LIMIT) break;

// wait one minute for a connection

selector.select(60000);

Set<SelectionKey> readyKeys = selector.selectedKeys();

if (readyKeys.isEmpty() && n == LIMIT) {

// All packets have been written and it doesn't look like any

// more are will arrive from the network

break;

}

else {

Iterator<SelectionKey> iterator = readyKeys.iterator();

while (iterator.hasNext()) {

SelectionKey key = (SelectionKey) iterator.next();

iterator.remove();

if (key.isReadable()) {

buffer.clear();

channel.read(buffer);

buffer.flip();

int echo = buffer.getInt();

System.out.println("Read: " + echo);

numbersRead++;

}

if (key.isWritable()) {

buffer.clear();

buffer.putInt(n);

buffer.flip();

channel.write(buffer);

System.out.println("Wrote: " + n);

n++;

if (n == LIMIT) {

// All packets have been written; switch to read-only mode

key.interestOps(SelectionKey.OP_READ);

}

}

}

}

}

System.out.println("Echoed " + numbersRead + " out of " + LIMIT +

" sent");

System.out.println("Success rate: " + 100.0 * numbersRead / LIMIT +

"%");

} catch (IOException ex) {

System.err.println(ex);

}

}

}

There is one major difference between selecting TCP channels and selecting datagram channels. Because datagram channels are truly connectionless (despite the connect() method), you need to notice when the data transfer is complete and shut down. In this example, you assume the data is finished when all packets have been sent and one minute has passed since the last packet was received. Any expected packets that have not been received by this point are assumed to be lost in the ether.

A typical run produced output like this:

Wrote: 0

Read: 0

Wrote: 1

Wrote: 2

Read: 1

Wrote: 3

Read: 2

Wrote: 4

Wrote: 5

Wrote: 6

Wrote: 7

Wrote: 8

Wrote: 9

Wrote: 10

Wrote: 11

Wrote: 12

Wrote: 13

Wrote: 14

Wrote: 15

Wrote: 16

Wrote: 17

Wrote: 18

Wrote: 19

Wrote: 20

Wrote: 21

Wrote: 22

Read: 3

Wrote: 23

...

Wrote: 97

Read: 72

Wrote: 98

Read: 73

Wrote: 99

Read: 75

Read: 76

...

Read: 97

Read: 98

Read: 99

Echoed 92 out of 100 sent

Success rate: 92.0%

Connecting to a remote server a couple of miles and seven hops away (according to traceroute), I saw between 90% and 98% of the packets make the round trip.

Closing

Just as with regular datagram sockets, a channel should be closed when you’re done with it to free up the port and any other resources it may be using:

public void close() throws IOException

Closing an already closed channel has no effect. Attempting to write data to or read data from a closed channel throws an exception. If you’re uncertain whether a channel has been closed, check with isOpen():

public boolean isOpen()

This returns false if the channel is closed, true if it’s open.

Like all channels, in Java 7 DatagramChannel implements AutoCloseable so you can use it in try-with-resources statements. Prior to Java 7, close it in a finally block if you can. By now the pattern should be quite familiar. In Java 6 and earlier:

DatagramChannel channel = null;

try {

channel = DatagramChannel.open();

// Use the channel...

} catch (IOException ex) {

// handle exceptions...

} finally {

if (channel != null) {

try {

channel.close();

} catch (IOException ex) {

// ignore

}

}

}

and in Java 7 and later:

try (DatagramChannel channel = DatagramChannel.open()) {

// Use the channel...

} catch (IOException ex) {

// handle exceptions...

}

Socket Options // Java 7

In Java 7 and later, DatagramChannel supports eight socket options listed in Table 12-1.

Table 12-1. Socket options supported by datagram sockets

Option

Type

Constant

Purpose

SO_SNDBUF

StandardSocketOptions.

Integer

Size of the buffer used for sending datagram packets

SO_RCVBUF

StandardSocketOptions.SO_RCVBUF

Integer

Size of the buffer used for receiving datagram packets

SO_REUSEADDR

StandardSocketOptions.SO_REUSEADDR

Boolean

Enable/disable address reuse

SO_BROADCAST

StandardSocketOptions.SO_BROADCAST

Boolean

Enable/disable broadcast messages

IP_TOS

StandardSocketOptions.IP_TOS

Integer

Traffic class

IP_MULTICAST_IF

StandardSocketOptions.IP_MULTICAST_IF

NetworkInterface

Local network interface to use for multicast

IP_MULTICAST_TTL

StandardSocketOptions.IP_MULTICAST_TTL

Integer

Time-to-live value for multicast datagrams

IP_MULTICAST_LOOP

StandardSocketOptions.IP_MULTICAST_LOOP

Boolean

Enable/disable loopback of multicast datagrams

The first five options have the same meanings as they do for datagram sockets as described in Socket Options. The last three are used by multicast sockets, which we’ll take up in Chapter 13.

These are inspected and configured by just three methods:

public <T> DatagramChannel setOption(SocketOption<T> name, T value)

throws IOException

public <T> T getOption(SocketOption<T> name) throws IOException

public Set<SocketOption<?>> supportedOptions()

The supportedOptions() method lists the available socket options. The getOption() method tells you the current value of any of these. And setOption() lets you change the value. For example, suppose you want to send a broadcast message. SO_BROADCAST is usually turned off by default, but you can switch it on like so:

try (DatagramChannel channel = DatagramChannel.open()) {

channel.setOption(StandardSocketOptions.SO_BROADCAST, true);

// Send the broadcast message...

} catch (IOException ex) {

// handle exceptions...

}

Example 12-18 opens a channel just to check the default values of these options.

Example 12-18. Default socket option values

import java.io.IOException;

import java.net.SocketOption;

import java.nio.channels.DatagramChannel;

public class DefaultSocketOptionValues {

public static void main(String[] args) {

try (DatagramChannel channel = DatagramChannel.open()) {

for (SocketOption<?> option : channel.supportedOptions()) {

System.out.println(option.name() + ": " + channel.getOption(option));

}

} catch (IOException ex) {

ex.printStackTrace();

}

}

}

Here’s the output I got on my Mac:

IP_MULTICAST_TTL: 1

SO_BROADCAST: false

SO_REUSEADDR: false

SO_RCVBUF: 196724

IP_MULTICAST_LOOP: true

SO_SNDBUF: 9216

IP_MULTICAST_IF: null

IP_TOS: 0

It’s a bit surprising that my send buffer is so much larger than my receive buffer.