Java UDP编程 - DatagramSocket

Published on 2017 - 05 - 07

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. 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 1 puts this all together.

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 2 wraps this sequence up in a while loop, complete with logging and exception handling, so that it can process many incoming requests.

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 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.

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 than data.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.

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 a DatagramPacket 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 by getData().

public int getOffset()

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

Example 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 DatagramPacket received from the network, but that will have to wait for the introduction of the DatagramSocket class in the next section.

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.

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.

Reference