Oct 20, 1999 1:00 AM PT

Multicast the chatwaves

Use IP multicast along with custom stream classes to implement a cool, yet simple, peer-to-peer networked chat system

By and large, the majority of Internet traffic is unicast traffic, consisting of unique conversations between pairs of parties on the network, as shown in Figure 1 below. When you send email to someone, for example, a network connection is established between you and a mail exchanger, some commands are exchanged, and the body of your message is transmitted. When you visit a Web site, a network connection is established between you and the Web server, some commands are exchanged, and your computer receives the body of the page. In these scenarios, unicast makes sense; it is quite probable that your client and the remote server are engaging in a unique conversation on the network at that instant.

Figure 1. Unicast data transmission

Consider, however, the case of a streaming Internet radio station. In this case, the server will simultaneously transmit the same data to many different receivers. In other words, it will send the same packets of data multiple times, once for each connected client. It would potentially be much more efficient if the server could place a single packet onto the network and deliver that packet to all recipients without duplication. This scenario is obviously not practical, though; different clients would be on different networks, and each packet would have to trace a tortuous route to reach everyone along a single path.

Instead, consider a scenario, depicted in Figure 2, in which the server places a single packet onto the network and the network determines an optimal route to all clients, duplicating the packet only when necessary. The packet will thus travel outwards in a tree pattern, duplicating itself at branches in the network path from the server to all clients. This is IP multicast.

Figure 2. Multicast data transmission

This article is not going to show you how to create an Internet radio station; that would be far too complicated. Instead, the particular application that we will look at is a networked chat system. Here again, multicast will serve the goal of efficiency: when the system transmits a message to a chat room, rather than unicast it once to each client, the system can multicast it in a single operation to all clients, and the network will take care of optimal distribution.

Given a network that is capable of multicasting data, there are in fact two ways to design this chat system. One option, shown in Figure 3 below, would employ a central server to which clients unicast their messages and which then multicasts the messages back out. This is perhaps the most obvious redesign of a traditional Web-based chat system; however, it is not necessarily the best.

Figure 3. Server-based multicast

Multicast is in no way restricted to a traditional server-based architecture. At any given time, any member of the group is equally likely to take the duty of multicasting to all others. This capability lends itself to a much neater architecture for our chat system than the server-based system that I just described.

Consider, instead, a serverless peer-to-peer architecture, illustrated in Figure 4 below. In this architecture, members of the group multicast their messages directly to each other, simulating much more accurately a normal conversation among a group of people. This is the architecture that I will employ for this article. It should, of course, be noted that converting this to a server-based architecture is comparatively trivial, for those who are interested.

Figure 4. Peer-to-peer multicast

I'm not planning on going into great depth here about the internals of multicast; others have done more than I could ever hope to. If you'd like more multicast details, I've included some helpful links in the Resources section at the end of this article. However, I do have to present two big caveats about multicast up front: multicasting is, by itself, neither reliable nor pervasive.

If you multicast some data, it may be lost or delivered out of order; this represents multicasting's main problem with reliability. Also, losses and misordering may vary by recipient, which means that some recipients may receive the data as intended while others may experience various losses. It is possible to layer protocols that add reliability on top of raw multicast; the Java shared data toolkit employs one such protocol, LRMP (Lightweight Reliable Multicast Protocol). However, without the use of such a higher-level protocol, applications have to be able to tolerate data loss.

The second problem, multicasting's lack of pervasiveness, is due to the fact that multicast is not deployed on the Internet at large. There are islands (for example, Ethernet wires, corporations, university campuses, and various ISPs) that support multicast, and there are technologies (the MBone, for example, or multicast-enabled routers) that can link multicast-enabled islands across the Internet. But, by and large, random machines on the Internet cannot communicate using IP multicast. As a result, multicast is currently suitable only for controlled environments.

I'm also going to skimp on a detailed introduction to Java's specific multicast-related classes. For the purposes of this article, however, I've included a brief sidebar on them to get you started.

Design

To keep things manageable, our chat system will be purely text-based: clients will communicate among themselves using simple strings of text. For universality, of course, we will use Java's character streams to support the characters of any (supported) language. Also, as stated earlier, our chat system will be purely peer-to-peer; unlike most traditional client/server chat systems, there will be no server. All clients are created equal in the eyes of this system.

Multicast groups

Some questions naturally follow from the use of this architecture. How, using the multicast model, do we define a chat room? And how do interested persons gather there to chat? In a client-server system, you name the server, port, and, if your protocol demands it, a room. In this system, however, there is no server to identify. In the world of multicast, the same idea of a central gathering point is implemented with the concept of a multicast group. Under the current version of IP, there are 268 million multicast groups, identified by the IP addresses 224.0.0.0 through 239.255.255.255. When you join a particular group, you announce to the underlying network infrastructure that you are interested in messages sent to that group. The network will then attempt to ensure that you receive all messages sent to that group across the entire multicast-enabled archipelago to which you are connected. To send a message to a group, simply drop it onto the network, addressed to the appropriate group. The network will then attempt to deliver your message to all members of that group. Indeed, you do not even need to be a member of a group to send messages to it.

So, how do you know which group is yours? Well, that's a big picture issue. You can apply to the Internet Architecture and Naming Authority (IANA), if you want; however, it's doubtful that IANA would assign you a group number just for a chat room. More often, if the group will be restricted to just your organization, you can ask your system administrator to assign one of the 16 million private-use group numbers (those beginning with 239).

To make administration easier, multicast supports a special time-to-live feature whereby you can limit how far messages will travel. You can restrict a chat group to just your department intranet, for instance, or perhaps to your local Ethernet wire. There are two aspects to this. First, you must configure the routers and tunnels of your network appropriately. Usually, this is performed by your system administrator. Second, you must choose an appropriate time-to-live for the messages that you send; the value will depend on your local network configuration. A time-to-live of 1 will restrict packets to your local Ethernet wire, while 63 typically restricts it to a single multicast island. You can find more details in the Resources.

We now have an overall design for the chat system:

  • Configure your network to be multicast-enabled. If you have multiple machines on a single hub or switch, they should be able to communicate using multicast without any configuration.

  • Choose a multicast group and a time-to-live that lets all the desired users connect to your chat session, but prevents messages from travelling too far. For testing purposes, any multicast group in the 239.x.x.x range will do, as will a time-to-live of 1.

  • Join the multicast group so that you receive messages destined for it. Start a thread that listens for these messages and display them in a text area.

  • Open a text field for the user to enter messages. To transmit a message to the multicast group, simply drop it on the network bound to the multicast address.

Data communications

IP multicast is built on top of the User Datagram Protocol (UDP). This type of network protocol is packet-based; you construct a packet of information (up to 65,508 bytes long) and dispatch the packet (a datagram) into the network, bound for a particular destination (identified by an IP address and a port). The network will then try to deliver the packet to the remote address. Upon successful delivery, the recipient will receive the exact payload that you initially dispatched. However, the network sometimes loses the datagrams, sometimes duplicates them, and sometimes misorders them (that is, packets may arrive in a different order than the one in which they were sent). Neither the sender nor the recipient will receive notification of any such network errors. Rather, it is up to the application to identify and surmount such problems. This is one of the complexities of datagram-based networking. The datagram protocols only guarantee that, if a packet is delivered, it will be delivered intact.

For many applications -- file transfer, downloading Web pages, and so on -- datagram protocols are completely inappropriate. These data types must be transferred intact, so they require a reliable protocol (such as TCP) to guarantee successful transfer. In other cases -- streaming audio, for example -- datagram protocols are fine; occasional problems, such as data loss, are easily overcome or ignored. In the case of our chat system, we will simply ignore these problems. For our purposes, the occasional loss of a message is quite acceptable. Protocols (see Resources) that implement reliable transfer on top of multicast do exist. However, these go beyond the scope of the problem that I wish to address here.

Now we come down to the issue of the datagram format. You could support sequence numbers to detect loss and misordering, include information about the sender, and so on. I want to keep things simple, however, so the contents of our datagrams are simply going to be a sequence of UTF 8-encoded characters. If packets could be corrupted, the decoding could fail and cause problems. However, datagram protocols guarantee payload integrity, so this is not an issue here.

The next question concerns the method by which we send the messages. One option would be for the application to simply take a message, convert it to a byte array, create a datagram from this array, and deliver the datagram to the network. To receive messages, the application would receive datagrams from the network, extract the payloads, convert these to strings, and display them.

This is too low-level for my liking. Instead, I'm going to define a pair of simple stream classes, one that translates a stream of data into datagrams that are placed on the network, and another that translates datagrams from the network into a stream of data. Our application can thus use a convenient high-level streams interface to communicate with the multicast network.

Of course, there are advantages and disadvantages to this approach. On the plus side, it is easy to use. It also lets us potentially build in some degree of reliability underneath our application without changing the public API. On the minus side, however, it does hide the datagram nature of the underlying protocol. We are discarding some information in the process as well: the origins of a message, what the boundary of a particular message is, and so on. When you notice that there is nothing to stop a random machine multicast-connected to our chat system from sending any data to our multicast group, the potential problems become clear. However, regardless of the advantages and disadvantages, this approach serves my whim.

Implementation

Let's get down to the nitty-gritty. This entire chat system consists of three classes:

  • DatagramOutputStream: An output byte stream that, whenever its flush() method is invoked, collects data into a byte array for transmission as a datagram packet.

  • DatagramInputStream: An input byte stream that receives packets from the network and translates them into a continuous stream of data.

  • MulticastChat: The main chat application. It consists of a Frame containing an output TextArea and an input TextField. Messages entered in the TextField transmit to the multicast group. A listener thread receives messages from the multicast group and displays them in the TextArea.

Class DatagramOutputStream

We start with our DatagramOutputStream:

import java.io.*;
import java.net.*;
public class DatagramOutputStream extends ByteArrayOutputStream {
  DatagramSocket socket;
  DatagramPacket packet;
  
  public DatagramOutputStream
  (DatagramSocket socket, InetAddress address, int port) {
    this (socket, address, port, 512);
  }
  public DatagramOutputStream
  (DatagramSocket socket, InetAddress address, int port, int initialSize) {
    super (initialSize);
    this.socket = socket;
    packet = new DatagramPacket (buf, 0, address, port);
  }
  public synchronized void flush () throws IOException {
    if (count >= 65508)
      throw new IOException ("Packet overflow (" + count + ") bytes");
    packet.setData (buf, 0, count);
    socket.send (packet);
    reset ();
  }
}

In the code above, we extend the ByteArrayOutputStream class because it provides a convenient foundation on which to build; it buffers all data written to it into an expanding byte array.

In our constructor, we accept the DatagramSocket through which messages should be sent, the address and port to which messages should be addressed, and, optionally, an initial size for the internal buffer. We then create a DatagramPacket with the specified destination, and an empty payload. For efficiency and convenience, we will use this single packet for all messages that we transmit.

We override the flush() method to actually package the buffered data into a datagram and deliver this to the network. If too much data has been written to this stream before flushing (i.e., more than can fit in a datagram packet), we throw an exception; we cannot guarantee that it would be acceptable to split the data into multiple datagrams. Otherwise, we modify the DatagramPacket payload to be our internal buffer (buf and count are inherited from the superclass), and we deliver this to the network before calling reset() to reset the internal buffer.

We have chosen the standard flush() method to be the mechanism by which messages are delivered. We could have chosen instead to add a new method called send(); however, overriding flush() makes the operation of this stream just more transparent.

Note that we don't override the close() method, so closing this stream will have no effect on the associated socket.

Class DatagramInputStream

Now, let's look at our DatagramInputStream:

import java.io.*;
import java.net.*;
public class DatagramInputStream extends InputStream {
  byte[] buffer;
  int index, count;
  DatagramSocket socket;
  DatagramPacket packet;
  
  public DatagramInputStream (DatagramSocket socket) {
    this.socket = socket;
    buffer = new byte[65508];
    packet = new DatagramPacket (buffer, 0);
  }
  public synchronized int read () throws IOException {
    while (index >= count)
      receive ();
    return (int) buffer[index ++];
  }
  public synchronized int read (byte[] data, int offset, int length)
  throws IOException {
    if (length <= 0)
      return 0;
    while (index >= count)
      receive ();
    if (count - index < length)
      length = count - index;
    System.arraycopy (buffer, index, data, offset, length);
    index += length;
    return length;
  }
  public synchronized long skip (long amount) throws IOException {
    if (amount <= 0)
      return 0;
    while (index >= count)
      receive ();
    if (count - index < amount)
      amount = count - index;
    index += amount;
    return amount;
  }
  public synchronized int available () throws IOException {
    return count - index;
  }
  void receive () throws IOException {
    packet.setLength (buffer.length);
    socket.receive (packet);
    index = 0;
    count = packet.getLength ();
  }
}

DatagramInputStream is a bit more complex because there is no convenient existing stream that we can override. Instead, we must effectively reimplement the ByteArrayInputStream class.

In the constructor, we accept the DatagramSocket through which we should receive packets and create a DatagramPacket to hold these packets. We create a 65,508 byte buffer into which messages will be written and from which the resulting data will be read out.

All of our I/O methods (read(), skip(), and available()) follow a standard pattern. Each one checks to see if the internal buffer is empty. If it is, it calls receive() to receive a new packet. Otherwise, it returns some data from our internal buffer. As a result, the internal receive() method is the only interesting method.

We automatically call the receive() method whenever this stream needs to obtain more data to return to the user. We expand the DatagramPacket so that it can receive a maximum-sized packet from the network. Then we wait to receive a packet from the network. After this, we update the value of our count variable (the amount of valid data in our buffer) to be equal to the length of the packet just received, and we reset index.

A word of warning

After seeing these classes, you may think that they are the bee's knees, providing a clean, stream-based interface to datagram connections. However, remember that datagrams are not reliable. For stateless streams of data (for example, a stream of uncompressed audio), loss or duplication of datagrams is relatively inconsequential. However, in the case of a stateful stream, a single error can be catastrophic. Consider, for example, a stream encrypted with a stream or chaining block-cipher. If, in such an environment, a single packet is lost or duplicated, it will render all future communications wholly unintelligible.

Closer to home, the object streams are another example of stateful streams. The ObjectOutputStream class maintains a record of every object that you transmit through it, so that, if you write the same object again, only a back reference to the original is transmitted. This is necessary in order to preserve the referential integrity of interconnected object structures.

If you employ the object streams across an unreliable connection, it is possible that the packet containing the actual object data will be lost, and a later packet containing a back reference will be meaningless to the recipient. Similarly, duplication of packets may cause the recipient great distress. The problem of loss can be overcome by calling reset() on the object stream each time you flush it. This will clear it of all back reference information. However, if you actually require long-term referential equality of transmitted objects, this technique may introduce semantic errors to the data that you transmit. The problem of duplication, on the other hand, is only resolvable by adding some intelligence to these streams, so that duplicate packets can be discarded.

Class MulticastChat

This final class is the chat client itself:

import java.io.*;
import java.net.*;
import java.awt.*;
import java.awt.event.*;
public class MulticastChat {
  static final String DEFAULT_GROUP = "239.1.2.3";
  static final int DEFAULT_PORT = 1234;
  static final int DEFAULT_TTL = 1;
  
  InetAddress group;
  int port;
  
  public MulticastChat (InetAddress group, int port, int ttl) {
    this.group = group;
    this.port = port;
    this.ttl = ttl;
    initAWT ();
  }
  Frame frame;
  TextArea area;
  TextField field;
  public void initAWT () {
    frame = new Frame
      ("MulticastChat [" + group.getHostAddress () + ":" + port + "]");
    frame.addWindowListener (new WindowAdapter () {
      public void windowOpened (WindowEvent event) {
        field.requestFocus ();
      }
      public void windowClosing (WindowEvent event) {
        try {
          stop ();
        } catch (IOException ignored) {
        }
      }
    });
    area = new TextArea ("", 12, 24, TextArea.SCROLLBARS_VERTICAL_ONLY);
    area.setEditable (false);
    frame.add (area, "Center");
    field = new TextField ("");
    field.addActionListener (new ActionListener () {
      public void actionPerformed (ActionEvent event) {
        netSend (event.getActionCommand ());
        field.selectAll ();
      }
    });
    frame.add (field, "South");
    frame.pack ();
  }
      
  public void start () throws IOException {
    netStart ();
    frame.setVisible (true);
  }
  public void stop () throws IOException {
    netStop ();
    frame.setVisible (false);
  }
  MulticastSocket socket;
  BufferedReader in;
  OutputStreamWriter out;
  Thread listener;
  void netStart () throws IOException {
    socket = new MulticastSocket (port);
    socket.setTimeToLive (ttl);
    socket.joinGroup (group);
    in = new BufferedReader
      (new InputStreamReader (new DatagramInputStream (socket), "UTF8"));
    out =
      new OutputStreamWriter (new DatagramOutputStream (socket, group, port), "UTF8");
    listener = new Thread () {
      public void run () {
        netReceive ();
      }
    };
    listener.start ();
  }
  void netStop () throws IOException {
    listener.interrupt ();
    listener = null;
    socket.leaveGroup (group);
    socket.close ();
  }
  void netSend (String message) {
    try {
      out.write (message + "\n");
      out.flush ();
    } catch (IOException ex) {
      ex.printStackTrace ();
    }
  }
  void netReceive () {
    try {
      Thread myself = Thread.currentThread ();
      while (listener == myself) {
        String message = in.readLine ();
        area.append (message + "\n");
      }
    } catch (IOException ex) {
      area.append ("- listener stopped");
      ex.printStackTrace ();
    }
  }
  public static void main (String[] args) throws IOException {
    if ((args.length > 3) || ((args.length > 0) && args[1].endsWith ("help"))) {
      System.out.println
        ("Syntax: MulticastChat [<group:" + DEFAULT_GROUP +
         "> [<port:" + DEFAULT_PORT + ">] [<ttl:" + DEFAULT_TT + ">]]");
      System.exit (0);
    }
    String groupStr = (args.length > 0) ? args[0] : DEFAULT_GROUP;
    InetAddress group = InetAddress.getByName (groupStr);
    int port = (args.length > 1) ? Integer.parseInt (args[1]) : DEFAULT_PORT;
    int ttl = (args.length > 2) ? Integer.parseInt (args[2]) : DEFAULT_TTL;
    MulticastChat chat = new MulticastChat (group, port, ttl);
    chat.start ();
  }
}

In the MulticastChat constructor above, we set up the user interface, which consists of a Frame, a TextArea, and a TextField. We use an anonymous class to handle the basic window events appropriately, and another anonymous class to translate the user-pressed Return keystroke in the TextField into an invocation of the netSend() method.

The start() method invokes the netStart() method to initialize the network, then shows the Frame. To initialize the network, we create a MulticastSocket listening on the chosen port, set the time-to-live of packets that it sends to the user-specified value (the default is 1, remember), and then create our datagram streams. Note that MulticastSocket is a subclass of DatagramSocket, so it can be used by our datagram streams. To make textual communications easier, we attach an InputStreamReader and an OutputStreamWriter using UTF 8 encoding on top of these byte streams. Then we create an anonymous thread that invokes our netReceive() method to receive messages from the network.

The stop() method invokes the netStop() method to close down the network, then hides the Frame. To close down the network, we interrupt our listener thread, then close the MulticastSocket. You may find that, in practice, this results in an IOException being thrown whenever the chat system is shut down. A little bit of glue can catch these exceptions and ignore them as appropriate.

The netSend() and netReceive() methods behave as expected, sending and receiving messages using our datagram streams. To send a message, we simply write it out and flush the stream; to receive a message, we simply read from the stream. Note that, if you multicast a message to a group of which you are a member, you will automatically receive a copy of your own message. As a result, we don't need to explicitly display messages entered by the user; that will be handled naturally by the message-receiving thread. Of course, error handling here is very crude. You might wish to handle exceptions more gracefully in a real application.

Finally, we provide a main() method that starts up the chat client. If you specify -help as an argument, a brief usage message is displayed. Otherwise, we parse the command-line arguments to determine the host, port, and time-to-live to use, with defaults if an argument is omitted, and then start up an instance of the chat client.

In practice

OK, so we now have a streams interface on top of multicast datagrams, and a peer-to-peer multicast chat system built on these streams. How do we try it all out?

The simplest option is to run the application twice on your current machine. Your IP stack should successfully cater to multiple applications listening on the same multicast group and port at the same time. Every message that you enter in the entry field should be displayed by both clients. To get more adventurous, try multiple machines on the same Ethernet bus, hub, or multicast-aware switch. If you have a multicast-capable routed network, you should be able to communicate across networks if you increase the time-to-live. However, you should probably check with your system administrator first.

Running the chat system as an applet is more complex, as Figure 5 below demonstrates. First, there is the question of whether or not the machines running the applet are multicast-connected. Second, there is the issue of whether or not the browser JVMs support multicast; the Java plugin, of course, does. Third, there is the problem of security restrictions. The Java security model does not let an applet receive messages from, or transmit messages to, arbitrary members of a multicast group. It is, in fact, easy (without using applet signing) to overcome this problem; applets are allowed to unicast datagram packets to their codebase server, and to receive multicast messages from that server. As a result, you can adapt the chat system to include a central server to which clients unicast their messages, and which then multicasts the messages back to all clients. But that's an exercise for you, the reader.

Figure 5. An applet-based multicast chat system

Conclusion

Multicast is very cool. The underlying network technology that can determine the best (for some definition of best) way to transfer a single packet to multiple recipients is fascinating, and the potential applications of multicast are quite impressive. Here, we've shown a simple serverless chat system. The simplicity and aesthetic of pure peer-to-peer systems of this nature is very alluring. Furthermore, server-based multicast systems operating efficiently across existing networks are potentially quite powerful. If you follow some of the links in the Resources section below, you'll find some remarkable applications of multicast.

And, to top it all off, Java's elegant I/O architecture lets you hide some of multicast's rough edges -- at a price -- and produce a clean, streams-based interface to the multicast network. All in all, if you compare this chat system with the TCP-based one that I described a few years ago (see the Resources section), I think you'll agree that multicast is infinitely more pleasing. Of course, multicast's nonpervasiveness is a problem; it renders it mildly impractical on the Internet. However, with the imminence of IPv6 (Internet Protocol version 6) and its potential for a wide scale roll-out of multicast, I hope we'll see more applications of multicast in the future.

SIDEBAR:

SIDEBAR_HEAD: Java's multicast-related classes

Because multicast is an extension of UDP (the User Datagram Protocol), Java's multicast classes are closely associated with its unicast datagram classes. The following are the four major related classes:

  • InetAddress: This class represents an IP address; it can be used to resolve a host name into an IP address, and vice versa. This is used to address hosts on a network.

  • DatagramPacket: This class represents an actual datagram; for example, a packet of data that is to be transmitted onto the network, or that has been received from the network.

  • DatagramSocket: This class represents a UDP socket. It lets you place unicast datagrams onto the network for delivery to a remote host, and to receive unicast datagrams from the network.

  • MulticastSocket: This class, a subclass of DatagramSocket, provides specialized support for multicast, specifically the ability to join and leave multicast groups and to set the time-to-live of transmitted datagrams.

The following is a brief listing of the major methods and constructors of these classes. Note that I have, for brevity's sake, omitted the majority of methods and constructors. Only those of immediate concern to this article are included.

Class InetAddress

The InetAddress class is used to represent the address of a host on the network. The following methods are of most common use:

  • static InetAddress getByName(String host): This static method looks up the IP address of a host by name, or constructs an InetAddress object for a specific IP address.

  • boolean isMulticastAddress(): The value of the boolean value returned by this method depends on whether or not an InetAddress object corresponds to a multicast address.

  • String getHostName(): This method returns the host name associated with this InetAddress object; this may involve asking the domain name system to reverse search for the IP address.

  • byte[] getAddress(): This method returns the raw IP address of this InetAddress as an array of bytes.

  • String getHostAddress(): This method returns the raw IP address of this InetAddress in human-readable textual format.

Ports

Note that, when you address a server on the network, you identify not only the IP address (under IPv4, a 4-byte value; under IPv6, a 16-byte value) but also the port (a 16-bit value). A port is, in effect, a subaddress of a particular host. There are 65,535 TCP/IP ports (and, similarly, 65,535 UDP/IP ports) on every machine; however, the UDP ports are shared with IP multicast. And, to further complicate things, ports 1 through 1,024 are reserved for system services.

Thus, when you are running a server (or any other application that accepts incoming network connections), you must choose a port on which to listen. If you are a user application, this port must be above 1,024. Then, when you run the client, you must identify the server IP address (in the case of a client/server system) or the multicast IP address (in the case of a multicast system) and the port on which the server is listening.

Class DatagramPacket

The DatagramPacket class represents a packet of data that is to be transmitted onto or has been received from the network. The following methods are of most common use:

  • DatagramPacket(byte[] data, int length, InetAddress address, int port): This method creates a new DatagramPacket for transmission to the network, consisting of the specified payload, addressed to the specified host and port.

  • DatagramPacket(byte[] data, int length): This method creates a new DatagramPacket for receiving a packet from the network. The packet will be read into the specified buffer. At most, length bytes will be read in, and any additional data discarded.

  • InetAddress getAddress(): For a transmitted packet, this method returns the destination host; for a received packet, this returns the source host.

  • int getPort(): For a transmitted packet, this method returns the destination port; for a received packet, this returns the source port.

  • byte[] getData(): This method returns the data array specified in the constructor.

  • int getLength(): This method returns the length specified in the constructor, or, if a packet has been received from the network, the amount of data read from that packet.

  • void setAddress(InetAddress address): This method reassigns the address of this DatagramPacket.

  • void setPort(Inetint port): This method reassigns the port of this DatagramPacket.

  • void setData(byte[] data): This method reassigns the data array of this DatagramPacket.

  • void setLength(int length): This method reassigns the length of this DatagramPacket.

Observe that a DatagramPacket is simply a container for four fields: address, port, data, and length. In fact, Java 2 has also introduced a fifth offset field to allow more flexibility in access to the byte array. As such, you can easily reuse DatagramPacket for multiple operations; this can be important for efficiency in high-performance servers. However, you must be aware of the effect of previous operations on the internal state of the object. When you receive a packet from the network, use the length field to limit the amount of data that will be read from the received packet. The length field is then updated to reflect the amount of data that has actually been read from the received packet. If you try to receive multiple packets in sequence from the network into a single DatagramPacket, you will find that each subsequent packet is limited in length to the size of the previous packet. To overcome this, you must reset the fields of your DatagramPacket as appropriate between uses.

Class DatagramSocket

The DatagramSocket class transmits and receives datagram (UDP) packets onto and from the network. Note that there is no Java support for raw IP datagrams.

  • DatagramSocket(): This method, primarily used by client applications, creates a new DatagramSocket bound to a random, unused local port.

  • DatagramSocket(int port): This method creates a new DatagramSocket bound to the specified local port port.

  • void send(DatagramPacket packet): This method transmits the specified packet onto the network. When it transmits the packet, the UDP protocol includes the IP address and port of the socket through which it was transmitted. However, the DatagramPacket is not updated with this information. There is no guarantee that you will receive an error if there is no application listening on the destination host and port. In practice, however, in an environment without firewalls, you may receive an error after you transmit a few packets. This error propagation is handled by low-level ICMP (Internet Control Message Protocol) packets.

  • void receive(DatagramPacket packet): This method receives a packet from the network. The method blocks (sleeps) until a packet arrives from the network at this socket. The DatagramPacket is filled with the contents of the received packet, updated with its length, source address, and port.

  • void close(): This method closes this socket.

Java 2 includes a special connected feature that you can use for efficiency if you will be communicating a lot of data with a single remote host in a security-restricted environment. However, it is not of particular interest for this application, since we will communicate with multiple hosts.

Class MulticastSocket

The MulticastSocket class, a subclass of DatagramSocket, provides specialized support for multicast.

  • MulticastSocket(int port): This method creates a new MulticastSocket bound to the specified local port, port.

  • void joinGroup(InetAddress group): This method announces to the underlying network layers that you are interested in receiving packets addressed to the specified multicast group, group. The IGMP (Internet Group Management Protocol) will be used to notify routers as necessary. Subsequently, packets addressed to the multicast group should become available for reception through the receive() method. A single socket can be a member of many multicast groups, although there are often OS-specific restrictions.

  • void leaveGroup(InetAddress group): This method announces to the underlying network layers that you are no longer interested in being a member of the specified group.

  • void setTimeToLive(int ttl): This method sets the default time-to-live of packets transmitted through this socket to the specified value (between 1 and 255). The meaning of the time-to-live is network specific.

  • int getTimeToLive(): This method returns the current time-to-live of packets transmitted through this socket.

Other multicast-specific methods are also provided, as are methods inherited from the superclass.

Summary

To summarize, there are two ways to use these classes:

  • To transmit a UDP or multicast packet, first get an InetAddress object corresponding to the destination host or group. Then, construct a DatagramPacket containing the target host and port, and the data to be sent. Next, open a DatagramSocket. Finally, transmit your packet through the socket.

  • To receive a UDP or multicast packet, first create a DatagramPacket containing a byte array into which you will receive packets from the network. Next, open a DatagramSocket on the specific port to which the packets will be addressed. If you are receiving a reply to a transmission of yours, then you should use the same DatagramSocket that you used for the initial transmission. Next, if you will be listening to a multicast conversation, construct an InetAddress for the appropriate group, and then join that group. Finally, call receive() to wait for a packet to arrive. When it does, you can query the DatagramPacket for details.

And, as always, when you are finished with your network connection, close the DatagramSocket so that your operating system network resources can be freed up.

:END_SIDEBAR

It is through execution of the algorithm that Merlin achieves consciousness. He's also lead author of Java Network Programming, Second Edition, in all good bookstores now.

Learn more about this topic