Datagrams

Bruce Eckel's Thinking in Java Contents | Prev | Next

You might think that the two constructors create two different objects: one for receiving datagrams and one for sending them. Good OO design would suggest that these should be two different classes, rather than one class with different behavior depending on how you construct the object. This is probably true, but fortunately the use of DatagramPackets is simple enough that you’re not tripped up by the problem, as you can see in the following example. This example is similar to the MultiJabberServer and MultiJabberClient example for TCP sockets. Multiple clients will send datagrams to a server, which will echo them back to the same client that sent the message.

To simplify the creation of a DatagramPacket from a String and vice-versa, the example begins with a utility class, Dgram, to do the work for you:

//: Dgram.java
// A utility class to convert back and forth
// Between Strings and DataGramPackets.
import java.net.*;
 
public class Dgram {
  public static DatagramPacket toDatagram(
    String s, InetAddress destIA, int destPort) {
    // Deprecated in Java 1.1, but it works:
    byte[] buf = new byte[s.length() + 1];
    s.getBytes(0, s.length(), buf, 0);
    // The correct Java 1.1 approach, but it's
    // Broken (it truncates the String):
    // byte[] buf = s.getBytes();
    return new DatagramPacket(buf, buf.length, 
      destIA, destPort);
  }
  public static String toString(DatagramPacket p){
    // The Java 1.0 approach:
    // return new String(p.getData(), 
    //  0, 0, p.getLength());
    // The Java 1.1 approach:
    return 
      new String(p.getData(), 0, p.getLength());
  }
} ///:~ 

The first method of Dgram takes a String, an InetAddress, and a port number and builds a DatagramPacket by copying the contents of the String into a byte buffer and passing the buffer into the DatagramPacket constructor. Notice the “+1” in the buffer allocation – this was necessary to prevent truncation. The getBytes( ) method of String is a special operation that copies the chars of a String into a byte buffer. This method is now deprecated; Java 1.1 has a “better” way to do this but it’s commented out here because it truncates the String. So you’ll get a deprecation message when you compile it under Java 1.1, but the behavior will be correct. (This bug might be fixed by the time you read this.)

//: ChatterServer.java
// A server that echoes datagrams
import java.net.*;
import java.io.*;
import java.util.*;
 
public class ChatterServer {
  static final int INPORT = 1711;
  private byte[] buf = new byte[1000];
  private DatagramPacket dp = 
    new DatagramPacket(buf, buf.length);
  // Can listen & send on the same socket:
  private DatagramSocket socket;
 
  public ChatterServer() {
    try {
      socket = new DatagramSocket(INPORT);
      System.out.println("Server started");
      while(true) {
        // Block until a datagram appears:
        socket.receive(dp);
        String rcvd = Dgram.toString(dp) +
          ", from address: " + dp.getAddress() +
          ", port: " + dp.getPort();
        System.out.println(rcvd);
        String echoString = 
          "Echoed: " + rcvd;
        // Extract the address and port from the
        // received datagram to find out where to
        // send it back:
        DatagramPacket echo = 
          Dgram.toDatagram(echoString,
            dp.getAddress(), dp.getPort());
        socket.send(echo);
      }
    } catch(SocketException e) {
      System.err.println("Can't open socket");
      System.exit(1);
    } catch(IOException e) {
      System.err.println("Communication error");
      e.printStackTrace();
    }
  }
  public static void main(String[] args) {
    new ChatterServer();
  }
} ///:~ 

The ChatterServer contains a single DatagramSocket for receiving messages, instead of creating one each time you’re ready to receive a new message. The single DatagramSocket can be used repeatedly. This DatagramSocket has a port number because this is the server and the client must have an exact address where it wants to send the datagram. It is given a port number but not an Internet address because it resides on “this” machine so it knows what its Internet address is (in this case, the default localhost). In the infinite while loop, the socket is told to receive( ), whereupon it blocks until a datagram shows up, and then sticks it into our designated receiver, the DatagramPacket dp . The packet is converted to a String along with information about the Internet address and socket where the packet came from. This information is displayed, and then an extra string is added to indicate that it is being echoed back from the server.

Now there’s a bit of a quandary. As you will see, there are potentially many different Internet addresses and port numbers that the messages might come from – that is, the clients can reside on any machine. (In this demonstration they all reside on the localhost, but the port number for each client is different.) To send a message back to the client that originated it, you need to know that client’s Internet address and port number. Fortunately, this information is conveniently packaged inside the DatagramPacket that sent the message, so all you have to do is pull it out using getAddress( ) and getPort( ), which are used to build the DatagramPacket echo that is sent back through the same socket that’s doing the receiving. In addition, when the socket sends the datagram, it automatically adds the Internet address and port information of this machine, so that when the client receives the message, it can use getAddress( ) and getPort( ) to find out where the datagram came from. In fact, the only time that getAddress( ) and getPort( ) don’t tell you where the datagram came from is if you create a datagram to send and you call getAddress( ) and getPort( ) before you send the datagram (in which case it tells the address and port of this machine, the one the datagram is being sent from). This is an essential part of datagrams: you don’t need to keep track of where a message came from because it’s always stored inside the datagram. In fact, the most reliable way to program is if you don’t try to keep track, but instead always extract the address and port from the datagram in question (as is done here).

//: ChatterClient.java
// Tests the ChatterServer by starting multiple 
// clients, each of which sends datagrams.
import java.lang.Thread;
import java.net.*;
import java.io.*;
 
public class ChatterClient extends Thread {
  // Can listen & send on the same socket:
  private DatagramSocket s;
  private InetAddress hostAddress;
  private byte[] buf = new byte[1000];
  private DatagramPacket dp = 
    new DatagramPacket(buf, buf.length);
  private int id;
 
  public ChatterClient(int identifier) {
    id = identifier;
    try {
      // Auto-assign port number:
      s = new DatagramSocket();
      hostAddress = 
        InetAddress.getByName("localhost");
    } catch(UnknownHostException e) {
      System.err.println("Cannot find host");
      System.exit(1);
    } catch(SocketException e) {
      System.err.println("Can't open socket");
      e.printStackTrace();
      System.exit(1);
    } 
    System.out.println("ChatterClient starting");
  }
  public void run() {
    try {
      for(int i = 0; i < 25; i++) {
        String outMessage = "Client #" +
          id + ", message #" + i;
        // Make and send a datagram:
        s.send(Dgram.toDatagram(outMessage,
          hostAddress, 
          ChatterServer.INPORT));
        // Block until it echoes back:
        s.receive(dp);
        // Print out the echoed contents:
        String rcvd = "Client #" + id +
          ", rcvd from " + 
          dp.getAddress() + ", " + 
          dp.getPort() + ": " +
          Dgram.toString(dp);
        System.out.println(rcvd);
      }
    } catch(IOException e) {
      e.printStackTrace();
      System.exit(1);
    }
  }
  public static void main(String[] args) {
    for(int i = 0; i < 10; i++)
      new ChatterClient(i).start();
  }
} ///:~ 

ChatterClient is created as a Thread so that multiple clients can be made to bother the server. Here you can see that the receiving DatagramPacket looks just like the one used for ChatterServer. In the constructor, the DatagramSocket is created with no arguments since it doesn’t need to advertise itself as being at a particular port number. The Internet address used for this socket will be “this machine” (for the example, localhost) and the port number will be automatically assigned, as you will see from the output. This DatagramSocket, like the one for the server, will be used both for sending and receiving.

The hostAddress is the Internet address of the host machine you want to talk to. The one part of the program in which you must know an exact Internet address and port number is the part in which you make the outgoing DatagramPacket. As is always the case, the host must be at a known address and port number so that clients can originate conversations with the host.

Each thread is given a unique identification number (although the port number automatically assigned to the thread would also provide a unique identifier). In run( ), a message String is created that contains the thread’s identification number and the message number this thread is currently sending. This String is used to create a datagram that is sent to the host at its address; the port number is taken directly from a constant in ChatterServer. Once the message is sent, receive( ) blocks until the server replies with an echoing message. All of the information that’s shipped around with the message allows you to see that what comes back to this particular thread is derived from the message that originated from it. In this example, even though UDP is an “unreliable” protocol, you’ll see that all of the datagrams get where they’re supposed to. (This will be true for localhost and LAN situations, but you might begin to see some failures for non-local connections.)

When you run this program, you’ll see that each of the threads finishes, which means that each of the datagram packets sent to the server is turned around and echoed to the correct recipient; otherwise one or more threads would hang, blocking until their input shows up.

You might think that the only right way to, for example, transfer a file from one machine to another is through TCP sockets, since they’re “reliable.” However, because of the speed of datagrams they can actually be a better solution. You simply break the file up into packets and number each packet. The receiving machine takes the packets and reassembles them; a “header packet” tells the machine how many to expect and any other important information. If a packet is lost, the receiving machine sends a datagram back telling the sender to retransmit.


[64] TCP and UDP ports are considered unique. That is, you can simultaneously run a TCP and UDP server on port 8080 without interference.



Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Mobile is introducing sweeping changes throughout your workplace. As a senior stakeholder driving mobile initiatives in your organization, you may be lost in a sea of technologies and claims from vendors promising rapid delivery of applications to your employees, customers, and partners. To help explain some of the topics you will need to be aware of, and to separate the must-haves from the nice-to-haves, this reference guide can help you with applying a mobile strategy in the context of application …

  • Savvy enterprises are discovering that the cloud holds the power to transform IT processes and support business objectives. IT departments can use the cloud to redefine the continuum of development and operations—a process that is becoming known as DevOps. Download the Executive Brief DevOps: Why IT Operations Managers Should Care About the Cloud—prepared by Frost & Sullivan and sponsored by IBM—to learn how IBM SmartCloud Application services provide a robust platform that streamlines …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds