- Special Edition Using Java, 2nd Edition -

Chapter 24

UDP Sockets


by David W. Baker

For many Internet developers, UDP is a much less-often used protocol when compared to TCP. UDP does not isolate you so neatly from the details of implementing a continuous network communication. However, for many Java applications, choosing UDP as the tool to create a network linkage may be the most prudent option.

Overview of UDP Messaging

Programming with UDP has significant ramifications. Understanding these factors will inform your network programming.

UDP is a good choice for applications in which communications can be separated into discrete messages, where a single query from a client invokes a single response from a server. Time-dependent data is particularly suited to UDP. UDP requires much less overhead, but the burden of engineering any necessary reliability into the system is your responsibility. For instance, if clients never receive responses to their queries—perfectly possible and legitimate with UDP—you might want to program the clients to retransmit the request or perhaps display an informative message indicating communication difficulties.

UDP Socket Characteristics

As discussed in Chapter 22, “Communications and Networking,” UDP behaves very differently than TCP. UDP is described as unreliable, connectionless, and message-oriented. A common analogy that elucidates UDP is that of communicating with postcards.

A dialog with UDP must be quanticized into small messages that fit within a small packet of a specific size, although some packets can hold more data than others. When you send out a message, you can never be certain that you will receive a return message. Unless you do receive a return message, you have no idea if your message was received—your message could have been lost en route, the recipient’s confirmation could have been lost, or the recipient might be ignoring your message.

The postcards you will be exchanging between network programs are referred to as datagrams. Within a datagram, you can store an array of bytes. A receiving application can extract this array and decode your message, possibly sending a return datagram response.

As with TCP, you will program in UDP using the socket programming abstraction. However, UDP sockets are very different from TCP sockets. Extending the analogy, UDP sockets are much like creating a mailbox.

A mailbox is identified by your address, but you don't construct a new one for each person to whom you will be sending a message. (However, you might create a new mailbox to receive newspapers, which shouldn't go into your normal mailbox.) Instead, you place an address on the postcard that indicates to whom the message is intended. You place the postcard in the mailbox and it is (eventually) sent on its way.

When receiving a message, you could potentially wait forever until one arrives in your mailbox. Once one does, you can read the postcard. Meta-information appears on the postcard that identifies the sender through the return address.

As the previous analogies suggest, UDP programming involves the following general tasks:

Java UDP Classes

The java.net package has the tools that are necessary to perform UDP communications. For creating datagrams, Java provides the DatagramPacket class. When receiving a UDP datagram, you also use the DatagramPacket class to read the data, sender, and meta-information.

To create a datagram to send to a remote system, the following constructor is provided:

public DatagramPacket(byte[] ibuf, int length,
InetAddress iaddr, int iport);

ibuf is the array of bytes that encodes the data of the message, while length is the length of the byte array to place into the datagram. This factor determines the size of the datagram. iaddr is an InetAddress object, as explained in Chapter 23, which stores the IP address of the intended recipient. port identifies which port the datagram should be sent to on the receiving host.

See “Java TCP Socket Classes,” Chapter 23 for more information.
 

In order to receive a datagram, you must use another DatagramPacket constructor in which the incoming data will be stored. This constructor has the prototype of:

public DatagramPacket(byte[] ibuff, int ilength);

ibuf is the byte array into which the data portion of the datagram will be copied. ilength is the number of bytes to copy from the datagram into the array corresponding to the size of the datagram.

Datagrams are not limited to certain lengths; you can create very long or very short datagrams. However, notice that there should be some agreement between the sender and the receiver about the size of the message, because both must create an appropriately sized byte array before creating a DatagramPacket for sending or receiving a datagram.
 

After a datagram has been received, as illustrated later in this section, you can read that data. Other methods allow you to obtain meta-information regarding the message.

public int getLength();
public byte[] getData();
public InetAddress getAddress();
public int getPort();

The getLength() method is used to obtain the number of bytes contained within the data portion of the datagram. The getData() method is used to obtain a byte array containing the data received. getAddress() provides an InetAddress object identifying the sender, while getPort() indicates the UDP port used.

Performing the sending and receiving of these datagrams is accomplished with the DatagramSocket class, which creates a UDP socket. Two constructors are available, one allowing the system to assign an unused port dynamically. The other allows you to specify a known port, which is useful for server applications. As with TCP, most systems require super-user privileges in order to bind UDP ports below 1024.

public DatagramSocket() throws SocketException;
public DatagramSocket(int port) throws SocketException;

You can use this socket to send properly addressed DatagramPacket instances created with the first constructor described by using this DatagramSocket method:

public void send(DatagramPacket p) throws IOException;

Once a DatagramPacket has been created with the second constructor described, a datagram can be received:

public synchronized void receive(DatagramPacket p)
throws IOException;

Note that the receive() method blocks until a packet is received. Because UDP is unreliable, your application cannot expect receive() ever to return. Many discussions point out this programming challenge. In the example later, described in “Creating a UDP Client,” one possible solution using threads will be demonstrated that should greatly assist your efforts in UDP programming.

Once communications through the UDP socket are completed, that socket should be closed:

public synchronized void close();

Creating a UDP Server

Because a network server is expected to wait indefinitely until clients connect or the application is interrupted, a basic UDP server is simpler and best explained before a UDP client. The practical example used here is to create a daytime server.

Daytime is a simple service that runs on many systems. For example, most UNIX systems run daytime out of inetd, as listed in /ETC/INETD.CONF. On Windows NT, the daytime server is available through the Simple TCP/IP Services within the Services Control Panel. Daytime is generally run on UDP port 13. When sent a datagram, it responds with a datagram containing the date in a format such as:

Friday, July 30, 1993 19:25:00

Listing 24.1 shows the Java code used to implement this service.

Listing 24.1 DaytimeServer.java

import java.lang.*; // Import the package names used.
import java.net.*;
import java.util.*;
import java.io.*;
/**
* This is an application which runs the
* daytime service.
* @author David W. Baker
* @version 1.1
*/
public class DaytimeServer {
// The daytime service runs on this well-known port.
private static final int TIME_PORT = 13;
private DatagramSocket timeSocket = null;
private static final String[] DAY_NAMES = {
"Sunday","Monday","Tuesday","Wednesday",
"Thursday","Friday","Saturday","Sunday" };
private static final String[] MONTH_NAMES = {
"January","February","March","April","May","June","July",
"August","September","October","November","December" };
private static final int SMALL_ARRAY = 1;
private static final int TIME_ARRAY = 50;
// A boolean to keep the server looping until stopped.
private boolean keepRunning = true;
/**
* This method starts the application, creating and
* instance and telling it to start accepting
* requests.
* @param args Command line arguments - ignored.
*/
public static void main(String[] args) {
DaytimeServer server = new DaytimeServer();
server.startServing();
}
/**
* This constructor creates a datagram socket to
* listen on.
*/
public DaytimeServer() {
try {
timeSocket = new DatagramSocket(TIME_PORT);
} catch(SocketException excpt) {
System.err.println("Unable to open socket: " +
excpt);
}
}
/**
* This method does all of the work of listening for
* and responding to clients.
*/
public void startServing() {
DatagramPacket datagram; // For a UDP datagram.
InetAddress clientAddr; // Address of the client.
int clientPort; // Port of the client.
byte[] dataBuffer; // To construct a datagram.
String timeString; // The time as a string.
// Keep looping while we have a socket.
while(keepRunning) {
try {
// Create a DatagramPacket to receive query.
dataBuffer = new byte[SMALL_ARRAY];
datagram = new DatagramPacket(dataBuffer,
dataBuffer.length);
timeSocket.receive(datagram);
// Get the meta-info on the client.
clientAddr = datagram.getAddress();
clientPort = datagram.getPort();
// Place the time into byte array.
dataBuffer = getTimeBuffer();
// Create and send the datagram.
datagram = new DatagramPacket(dataBuffer,
dataBuffer.length,clientAddr,clientPort);
timeSocket.send(datagram);
} catch(IOException excpt) {
System.err.println("Failed I/O: " + excpt);
}
}
timeSocket.close();
}
/**
* This method is used to create a byte array
* containing the current time in the special daytime
* server format.
* @return The byte array with the time.
*/
protected byte[] getTimeBuffer() {
String timeString;
// Get the current time.
Date currentTime = new Date();
int year, mon, date, day, hours, min, sec;
byte[] timeBuffer = new byte[TIME_ARRAY];
// Get the various portions of the current time.
year = currentTime.getYear();
mon = currentTime.getMonth();
date = currentTime.getDate();
day = currentTime.getDay();
hours = currentTime.getHours();
min = currentTime.getMinutes();
sec = currentTime.getSeconds();
year = year + 1900;
// Create the special time format.
timeString = DAY_NAMES[day] + ", " + MONTH_NAMES[mon]
+ " " + date + ", " + year + " " + hours + ":" +
min + ":" + sec;
// Copy it into the byte array and return that array.
timeString.getBytes(0,timeString.length(),
timeBuffer,0);
return timeBuffer;
}
/**
* This method provides an interface to stopping
* the server.
*/
protected void stop() {
if (keepRunning) {
keepRunning = false;
}
}
}

Starting the Server

The DaytimeServer class uses a number of static final variables (for example, constants), many of which are used to create the date string in the proper format. The main() method creates a DaytimeServer object and then invokes its startServing() method so that it accepts incoming requests.

The DaytimeServer constructor merely creates a UDP socket at the specified port. Note that as written, the server may require super-user privileges in order to run because it binds port 13. If you don't have permission to bind this port, the attempt to create a DatagramSocket will throw an exception. The constructor catches this and fails gracefully, informing you of the problem.

The startServing() Method: Handling Requests

The startServing() method is where the serving logic is implemented. While the application is intended to be running, it loops through a number of steps: It creates a small byte array and uses this array to create a DatagramPacket. The application then receives a datagram from the DatagramSocket. From the datagram, it obtains the IP address and port of the requesting application. The startServing() method need not read any information from the incoming datagram, as the datagram's arrival plus the meta-information it contains is sufficient for the server to understand the request.

The getTimeBuffer() method is called to obtain a byte array that contains the time in an appropriate format. Using this information, this method creates a new DatagramPacket. Finally, it sends this information through the DatagramSocket. The server loops through this process until interrupted externally.

The getTimeBuffer() Method: Creating the Byte Array

This protected method creates an instance of Date class and uses this object to get the various portions of the time. It creates a String that contains the time in the proper daytime format, which is different than the output of the toString() method of the Date class. The method then creates a byte array and copies the timeString into the array. Finally, it returns that array.

Running the Daytime Server

To run the server, first compile it with javac. Then, if necessary, log in as the super-user (for example, "root") and use java to run the server. If this is not possible, modify the TIME_PORT variable so that it binds to a port over 1024.

In the next example, you create a client to connect to this server.

Creating a UDP Client

The example used to create a UDP client makes use of the daytime server demonstrated previously but also illustrates communications with multiple servers through a single UDP socket. TimeCompare is a Java program that requests the time from a series of servers, receives their responses, and displays the difference between the remote system's times and the time of the local machine.

One of the most important aspects of this client is designing it so that an unanswered query does not hang the program. Because the receive() method of the DatagramSocket class blocks until a datagram is received and because a datagram's delivery is unreliable, the application must ensure that TimeCompare interrupts itself after a certain amount of time. TimeCompare accomplishes interruption by using different threads of execution.

Listing 24.2 shows this application.

Listing 24.2 TimeCompare.java

import java.io.*; // Import the package names used.
import java.net.*;
import java.util.*;
/**
* This is an application to obtain the times from
* various remote systems via UDP and then report
* a comparison.
* @author David W. Baker
* @version 1.1
*/
public class TimeCompare extends Thread {
private static final int TIME_PORT = 13; // Daytime port.
private static final int TIMEOUT = 10000; // UDP timeout.
DatagramSocket timeSocket = null;
private InetAddress[] remoteMachines;
private String arguments;
private Date localTime;
// This is the size of the datagram data to send
// for the query - intentially small.
private static final int SMALL_ARRAY = 1;
/**
* This method starts the application.
* @param args Command line arguments - remote hosts.
*/
public static void main(String[] args) {
if (args.length < 1) {
System.out.println(
"Usage: TimeCompare host1 (host2 ... hostn)");
System.exit(1);
}
// Create an instance.
TimeCompare runCompare = new TimeCompare(args);
// Start the thread running.
runCompare.start();
}
/**
* The constructor looks up the remote hosts and
* creates a UDP socket.
* @param hosts The hosts to contact.
*/
public TimeCompare(String[] hosts) {
remoteMachines = new InetAddress[hosts.length];
// Look up all hosts and place in InetAddress[] array.
for(int hostsFound = 0; hostsFound < hosts.length;
hostsFound++) {
try {
remoteMachines[hostsFound] =
InetAddress.getByName(hosts[hostsFound]);
} catch(UnknownHostException excpt) {
remoteMachines[hostsFound] = null;
System.err.println("Unknown host " +
hosts[hostsFound] + ": " + excpt);
}
}
try {
timeSocket = new DatagramSocket();
} catch(SocketException excpt) {
System.err.println("Unable to bind UDP socket: " +
excpt);
System.exit(1);
}
}
/**
* This method is the thread of execution where we
* send out requests for times, control the object
* which will return the responses, and then print
* out the report.
*/
public void run() {
DatagramPacket timeQuery;
DatagramPacket[] timeResponses;
int datagramsSent = 0;
// Send out an empty UDP packet to each machine,
// asking it to respond with its time.
for(int ips = 0;ips < remoteMachines.length; ips++) {
if (remoteMachines[ips] != null) {
try {
byte[] empty = new byte[SMALL_ARRAY];
timeQuery = new DatagramPacket(empty,
empty.length,remoteMachines[ips],TIME_PORT);
timeSocket.send(timeQuery);
datagramsSent++;
} catch(IOException excpt) {
System.err.println("Unable to send to " +
remoteMachines[ips] + ": " + excpt);
}
}
}
// Create an array in which to place responses.
timeResponses = new DatagramPacket[datagramsSent];
// Create a listener thread.
ReceiveDatagrams listener =
new ReceiveDatagrams(timeSocket,timeResponses);
// Get current time to base the comparisons.
localTime = new Date();
// Calculate the timeout time and start listener.
long endWait = System.currentTimeMillis() + TIMEOUT;
listener.start();
do {
yield();
if (System.currentTimeMillis() > endWait) {
listener.stop();
yield();
break;
}
} while(listener.isAlive());
printTimes(timeResponses); // Print the comparison.
System.exit(0); // Exit.
}
/**
* This prints out a report comparing the times
* sent from the remote hosts with the local
* time.
* @param times The datagram responses from the hosts.
*/
protected void printTimes(DatagramPacket[] times) {
Date remoteTime;
String timeString;
long secondsOff;
InetAddress dgAddr;
System.out.print("TIME COMPARISON\n\tCurrent time " +
"is: " + localTime + "\n\n");
// Iterate through each host.
for(int hosts = 0;
hosts < remoteMachines.length; hosts++) {
if (remoteMachines[hosts] != null) {
boolean found = false;
int dataIndex;
// Iterate through each datagram received.
for(dataIndex = 0; dataIndex < times.length;
dataIndex++) {
// If the datagram element isn't null:
if (times[dataIndex] != null) {
dgAddr = times[dataIndex].getAddress();
// See if there's a match.
if(dgAddr.equals(remoteMachines[hosts])) {
found = true;
break;
}
}
}
System.out.println("Host: " +
remoteMachines[hosts]);
// If there was a match, print comparison.
if (found) {
timeString =
new String(times[dataIndex].getData(), 0);
int endOfLine = timeString.indexOf("\n");
if (endOfLine != -1) {
timeString =
timeString.substring(0,endOfLine);
}
remoteTime = new Date(timeString);
secondsOff = (localTime.getTime() -
remoteTime.getTime()) / 1000;
secondsOff = Math.abs(secondsOff);
System.out.println("Time: " + timeString);
System.out.println("Difference: " +
secondsOff + " seconds\n");
} else {
System.out.println("Time: NO RESPONSE FROM "
+ "HOST\n");
}
}
}
}
/**
* This method performs any necessary cleanup.
*/
protected void finalize() {
// If the socket is still open, close it.
if (timeSocket != null) {
timeSocket.close();
}
}
}
/**
* This class is used to receive a number of incoming
* datagrams.
*/
class ReceiveDatagrams extends Thread {
private DatagramSocket receiveSocket;
private DatagramPacket[] receivePackets;
private DatagramPacket newPacket;
private static final int TIME_ARRAY = 50;
/**
* This constructor sets the socket to listen on and
* the array to store the datagrams in.
* @param s The socket to use.
* @param p The array for the storage of datagrams.
*/
public ReceiveDatagrams(DatagramSocket s,
DatagramPacket[] p) {
receiveSocket = s;
receivePackets = p;
}

/**
* This is the thread of execution for this class,
* where is goes through the number of datagrams
* expected and waits to receive each.
*/
public void run() {
for(int got = 0; got < receivePackets.length; got++) {
byte[] emptyBuffer = new byte[TIME_ARRAY];
try {
// Create a new DatagramPacket.
newPacket = new DatagramPacket(emptyBuffer,
emptyBuffer.length);
// Obtain a datagram.
receiveSocket.receive(newPacket);
// Now that its been received, add it to the
// array of received datagrams.
receivePackets[got] = newPacket;
} catch(IOException excpt) {
System.err.println("Failed I/O: " + excpt);
}
yield();
}
receiveSocket.close();
}
}

Starting TimeCompare

The TimeCompare class extends Thread so that it can run in its own path of execution and have access to all of the methods of Thread. The main() method instantiates a TimeCompare object, passing it the command line arguments that correspond to the hosts to query. main() then starts the Thread.

The TimeCompare constructor uses the InetAddress.getByName() static method to look up the set of remote hosts, placing the returned InetAddress instances into an array of these objects. If it is unable to look up one of the hosts, this constructor ensures that the element is set to null and loops through the other hosts. Finally, the constructor creates a DatagramSocket at a dynamically allocated port.

The run() Method: TimeCompare's Execution Path

The run() method is invoked by the Thread superclass when the start() method is called by main(). The first thing this method does is iterate through the remoteMachines array. For each element that is not null, the run() method creates a small byte array, uses it to construct an appropriately addressed DatagramPacket, and then sends the datagram using the UDP socket. Once sent, the method uses datagramsSent to keep track of how many queries were successfully sent.

Now that a datagram has been sent to each remote host, TimeCompare prepares to receive the responses. These responses are to be accepted by another class, which also extends Thread, ReceiveDatagrams. TimeCompare creates an instance of this class, referred to as listener, passing it the UDP socket and an array of type DatagramPacket. The length of this array is equal to the number of successful queries sent, which is the number of expected responses.

At this point, run() collects the current time, used as a basis for comparison against the remote systems' times. The method also sets a timeout time, in milliseconds, for the listener object and starts that object. The do-while loop runs until listener.isAlive() returns false, indicating that the listener has completed its execution. Within this loop, the run() method checks to see if the timeout has been reached and, if so, stops the listener and breaks from the loop.

Critical to this code are the calls to the Thread superclass's yield() method. This ensures that other threads, such as listener, are given an opportunity to execute. Without the invocation of yield(), this loop could possibly execute until the timeout is reached, with the listener never obtaining any datagrams because it never received any processing time.

The printTimes() Method: Showing the Comparison

This method takes an array of UDP packets and prints out a comparison of the times contained therein. The outer for loop iterates through the machines contacted, while the inner for loop matches the host to a received datagram. If a match is found, printTimes() calculates the difference in times and prints the data. If no match is found, printTimes() indicates that a response from that host was not received.

The ReceiveDatagrams Class

This simple class runs in its own execution thread by extending the Thread class. This action enables another thread to observe its progress, interrupting it if necessary. The constructor takes a DatagramSocket to obtain datagrams as well as a DatagramPacket array to store the datagrams.

When the object is started, run() loops to receive a number of datagrams equal to the length of the receivePackets array. It creates a new DatagramPacket and then uses the receive() method of the DatagramSocket to accept the incoming message. If no response is forthcoming, the object will block until interrupted by the TimeCompare object.

Once a datagram is received, ReceiveDatagrams assigns it to an element in the receivePackets array. Note that ReceiveDatagrams does this only once the datagram has been successfully received so that the array element remains null if ReceiveDatagrams is interrupted. Again, the yield() call here is important, allowing the observing TimeCompare thread to continue. If all expected datagrams are received, the run() method closes the socket. The thread will now complete, and the isAlive() method should return false.

Running the Application

Compile TimeCompare.java with the Java compiler and then execute it with the Java interpreter. Each argument to TimeCompare should be a host name of a remote machine to include in the comparison. For instance, to check your machine’s time against www.sgi.com and www.paramount.com, you would type

and you would see a report that appeared as

TIME COMPARISON
Current time is: Mon Aug 19 08:03:09 PDT 1996
Host: www.sgi.com/204.94.214.4
Time: Mon Aug 19 08:02:55 1996
Difference: 14 seconds
Host: www.paramount.com/192.216.189.10
Time: Mon Aug 19 08:07:58 1996
Difference: 288 seconds

Using IP Multicasting

Internet Protocol (IP) is the means by which all information on the Internet is transmitted. UDP datagrams are encapsulated within IP packets in order to send them to the appropriate machines on the network.

See “Internet Protocol (IP),” Chapter 22 for more information.
 

Most uses of IP involve unicasting—sending a packet from one host to another. However, IP is not limited to this mode and includes the ability to multicast. With multicasting, a message is addressed to a targeted set of hosts. One message is sent, and the entire group can receive it.

Java makes it possible to implement multicasting applications easily. At the time of this writing, the necessary code is not part of the Java API but is intended to be incorporated in the near future. For the time being, multicasting is implemented by the sun.net package, distributed with the JDK and residing within the CLASSES.ZIP file.
 

Classes within the sun.net package are not part of the JDK. Java applications cannot expect to always have sun.net classes available on every system. Furthermore, these classes are poorly documented and may have bugs.
 

Multicasting is particularly suited to high-bandwidth applications, such as sending video and audio over the network, because a separate transmission need not be established (which could saturate the network). Other possible applications include chat sessions, distributed data storage, and on-line, interactive games.

In order to support IP multicasting, a certain range of IP addresses are set aside solely for this purpose. These IP addresses are those within the range of 224.0.0.0 and 239.255.255.255. Each of these addresses is referred to as a multicast group. Any IP packet addressed to that group will be received by any machine which has joined that group.

When a machine joins a multicast group, it begins accepting messages sent to that IP multicast address. Extending the previous analogy from the section “UDP Socket Characteristics,” joining a group is similar to constructing a new mailbox that accepts messages intended for the group. Each machine that wants to join the group constructs its own mailbox to receive the same message. If a multicast packet is distributed to a network, any machine that is listening for the message has an opportunity to receive it. That is, with IP multicasting, there is no mechanism for restricting which machines on the same network may join the group.

Multicasting has its limitations, however—particularly the task of routing multicast packets throughout the Internet. A special TCP/IP protocol, Internet Group Management Protocol (IGMP), is used to manage memberships in a multicast group. A router that support multicasting can use IGMP to determine if local machines are subscribed to a particular group; such hosts respond with a report using IGMP. Based on these communications, a multicast router can determine if it is appropriate to forward on a multicast packet.

Realize that there is no formal way of reserving a multicast group for your own use. Certain groups are reserved for particular uses, but other than avoiding these, there are few rules to choosing a group. Try picking an arbitrary address between 224.0.1.27 and 224.0.1.225.
 
If you happen to choose a group already being used, your communications will be disrupted by those other machines. Should this occur, quit your application and try another address.
 

Besides the multicast group, another important facet of a multicast packet is the time-to-live (TTL) parameter. The TTL is used to indicate how many separate networks the sending intends the message to be transmitted over. When a packet is forwarded on by a router, the TTL within the packet is decremented by one. When a TTL reaches zero, the packet is not forwarded on further.

Choose a TTL parameter as small as possible. A large TTL value can cause unnecessary bandwidth use throughout the Internet. Furthermore, you are more likely to disrupt other multicast communications in diverse areas that happen to be using the same group.
 
If your communications should be isolated to machines on the local network, choose a TTL of 1. When communicating with machines that are not on the local network, try to determine how many multicast routers exist along the way and set your TTL to be one more than that value.
 

The Multicast Backbone, or MBONE, is an attempt to create a network of Internet routers that are capable of providing multicast servers. However, multicasting today is by no means ubiquitous. If all participants reside on the same physical network, routers need not be involved, and multicasting is likely to prove successful. For more distributed communications, you may need to contact your network administrator.

Java Multicasting

The Java MulticastSocket class is the key to utilizing this powerful Internet networking feature. MulticastSocket allows you to send or receive UDP datagrams that use multicast IP. To send a datagram, you use the default constructor:

public MulticastSocket() throws SocketException;

Then you must create an appropriately formed DatagramPacket addressed to a multicast group between 224.0.0.0 and 239.255.255.255. Once created, the datagram can be sent with the send() method, which requires a TTL value. The TTL indicates how many routers the packets should be allowed to go through. Avoid setting the TLL to a high value, which could cause the data to propagate through a large portion of the Internet. Here is an example:

int multiPort = 2222;
int ttl = 1;
InetAddress multiAddr =
InetAddress.getByName("239.10.10.10");
byte[] multiBytes = new byte[256];
DatagramPacket multiDatagram =
new DatagramPacket(multiBytes, multiBytes.length,
multiAddr,multiPort);
MulticastSocket multiSocket = new MulticastSocket();
multiSocket.send(multiDatagram, ttl);

Some Java API implementations have a flaw that impedes the use of the getByName() method, as shown here, with the constructor taking a String that contains the IP address of the remote system. As a work-around, you can associate a dummy host name with the desired IP address on your system. For instance, with most UNIX systems, you can associate a new host name with an IP address by adding an entry to the /ETC/HOSTS file. On Windows NT machines, this can be accomplished by editing the \WINNT35\SYSTEM32\DRIVERS\ETC\HOSTS file, although the MulticastSocket implementation under NT currently has problems. Then, call getByName() with the dummy host name.

To receive datagrams, an application must create a socket at a specific UDP port. Then, it must join the group of recipients. Through the socket, the application can then receive UDP datagrams:

MulticastSocket receiveSocket =
new MulticastSocket(multiPort);
receiveSocket.joinGroup(multiAddr);
receiveSocket.receive(multiDatagram);

When the joinGroup() method is invoked, the machine now pays attention to any IP packets transmitted along the network for that particular multicast group. The host should also use IGMP to appropriately report the usage of the group.

To leave a multicast group, the leaveGroup() method is available. A MulticastSocket should be closed when communications are done.

receiveSocket.leaveGroup(multiAddr);
receiveSocket.close();

As is apparent, using the MulticastSocket is very similar to using the normal UDP socket class DatagramSocket. The essential differences are:

Multicast Applications

The following two examples show a very simple use of multicasting. Listing 24.3 is a program that sends datagrams to a specific multicast IP address. The program is run with two arguments: the first specifying the multicast IP address to send the datagrams, and the other specifying the UDP port of the listening applications. The main() method ensures that these arguments have been received and then instantiates a MultiCastSender object.

The constructor creates an InetAddress instance with the String representation of the multicast IP address. It then creates a MulticastSocket at a dynamically allocated port for sending datagrams. The constructor enters a while loop, reading in from standard input line by line. The program packages the first 256 bytes of each line into an appropriately addressed DatagramPacket, sending that datagram through the MulticastSocket.

Listing 24.3 MultCastSender.java

import sun.net.*; // This will move to java.net soon.
import java.net.*; // Import package names used.
import java.lang.*;
import java.io.*;
/**
* This is a program which sends data from the command
* line to a particular multicast group.
* @author David W. Baker
* @version 1.1
*/
class MultiCastSender {
// The number of Internet routers through which this
// message should be passed. Keep this low. 1 is good
// for local LAN communications.
private static final byte TTL = 1;
// The size of the data sent - basically the maximum
// length of each line typed in at a time.
private static final int DATAGRAM_BYTES = 256;
private int mcastPort;
private InetAddress mcastIP;
private DataInputStream input;
private MulticastSocket mcastSocket;
/**
* This starts up the application.
* @param args Program arguments - <ip> <port>
*/
public static void main(String[] args) {
// This must be the same port and IP address used
// by the receivers.
if (args.length != 2) {
System.out.print("Usage: MultiCastSender <IP addr>"
+ " <port>\n\t<IP addr> can be one of 224.x.x.x "
+ "- 239.x.x.x\n");
System.exit(1);
}
MultiCastSender send = new MultiCastSender(args);
System.exit(0);
}
/**
* The constructor does all of the work of opening
* the socket and sending datagrams through it.
* @param args Program arguments - <ip> <port>
*/
public MultiCastSender(String[] args) {
DatagramPacket mcastPacket; // UDP datagram.
String nextLine; // Line from STDIN.
byte[] mcastBuffer; // Buffer for datagram.
int sendLength; // Length of line.
input = new DataInputStream(System.in);
try {
// Create a multicasting socket.
mcastIP = InetAddress.getByName(args[0]);
mcastPort = Integer.parseInt(args[1]);
mcastSocket = new MulticastSocket();
} catch(UnknownHostException excpt) {
System.err.println("Unknown address: " + excpt);
System.exit(1);
} catch(SocketException excpt) {
System.err.println("Unable to obtain socket: "
+ excpt);
System.exit(1);
}
try {
// Loop and read lines from standard input.
while ((nextLine = input.readLine()) != null) {
mcastBuffer = new byte[DATAGRAM_BYTES];
// If line is longer than our buffer, use the
// length of the buffer available.
if (nextLine.length() > mcastBuffer.length) {
sendLength = mcastBuffer.length;
// Otherwise, use the line's length.
} else {
sendLength = nextLine.length();
}
// Create the datagram.
nextLine.getBytes(0,nextLine.length(),
mcastBuffer,0);
mcastPacket = new DatagramPacket(mcastBuffer,
mcastBuffer.length,mcastIP,mcastPort);
// Send the datagram.
try {
System.out.println("Sending:\t" + nextLine);
mcastSocket.send(mcastPacket,TTL);
} catch(IOException excpt) {
System.err.println("Unable to send packet: "
+ excpt);
}
}
} catch(IOException excpt) {
System.err.println("Failed I/O: " + excpt);
}
mcastSocket.close(); // Close the socket.
}
}

Listing 24.4 complements the sender by receiving multicasted datagrams. The application takes two arguments that must correspond to the IP address and port with which the MultiCastSender was invoked. The main() method checks the command line arguments and then creates a MultiCastReceiver object.

The object's constructor creates an InetAddress and then a MulticastSocket at the port used to invoke the application. It joins the multicast group at the address contained within the InetAddress instance and then enters a loop. The object’s constructor receives a datagram from the socket and prints the data contained within the datagram, indicating the machine and port from where the packet was sent.

Listing 24.4 MultiCastReceiver.java

import sun.net.*; // This will move to java.net soon.
import java.net.*; // Import package names used.
import java.lang.*;
import java.io.*;
/**
* This is a program which allows you to listen
* at a particular multicast IP address/port and
* print out incoming UDP datagrams.
* @author David W. Baker
* @version 1.1
*/
class MultiCastReceiver {
// The length of the data portion of incoming
// datagrams.
private static final int DATAGRAM_BYTES = 256;
private int mcastPort;
private InetAddress mcastIP;
private MulticastSocket mcastSocket;
// Boolean to tell the client to keep looping for
// new datagrams.
private boolean keepReceiving = true;
/**
* This starts up the application
* @param args Program arguments - <ip> <port>
*/
public static void main(String[] args) {
// This must be the same port and IP address
// used by the sender.
if (args.length != 2) {
System.out.print("Usage: MultiCastReceiver <IP "
+ "addr> <port>\n\t<IP addr> can be one of "
+ "224.x.x.x - 239.x.x.x\n");
System.exit(1);
}
MultiCastReceiver send = new MultiCastReceiver(args);
System.exit(0);
}
/**
* The constructor does the work of opening a socket,
* joining the multicast group, and printing out
* incoming data.
* @param args Program arguments - <ip> <port>
*/
public MultiCastReceiver(String[] args) {
DatagramPacket mcastPacket; // Packet to receive.
byte[] mcastBuffer; // byte[] array buffer
InetAddress fromIP; // Sender address.
int fromPort; // Sender port.
String mcastMsg; // String of message.
try {
// First, set up our receiving socket.
mcastIP = InetAddress.getByName(args[0]);
mcastPort = Integer.parseInt(args[1]);
mcastSocket = new MulticastSocket(mcastPort);
// Join the multicast group.
mcastSocket.joinGroup(mcastIP);
} catch(UnknownHostException excpt) {
System.err.println("Unknown address: " + excpt);
System.exit(1);
} catch(SocketException excpt) {
System.err.println("Unable to obtain socket: "
+ excpt);
System.exit(1);
}
while (keepReceiving) {
try {
// Create a new datagram.
mcastBuffer = new byte[DATAGRAM_BYTES];
mcastPacket = new DatagramPacket(mcastBuffer,
mcastBuffer.length);
// Receive the datagram.
mcastSocket.receive(mcastPacket);
fromIP = mcastPacket.getAddress();
fromPort = mcastPacket.getPort();
mcastMsg = new String(mcastPacket.getData(),0);
// Print out the data.
System.out.println("Received from " + fromIP +
" on port " + fromPort + ": " + mcastMsg);
} catch(IOException excpt) {
System.err.println("Failed I/O: " + excpt);
}
}
try {
mcastSocket.leaveGroup(mcastIP); // Leave the group.
} catch(SocketException excpt) {
System.err.println("Socket problem leaving group: "
+ excpt);
}
mcastSocket.close(); // Close the socket.
}
/**
* This method provides a way to stop the program.
*/
public void stop() {
if (keepReceiving) {
keepReceiving = false;
}
}
}

To run the applications, first compile MultiCastSender and MultiCastReceiver. Then, transfer the MultCastReceiver to other machines, so that you can demonstrate more than one participant receiving messages. Finally, run the applications with the Java interpreter.

For instance, the following shows the use of MultiCastSender on a UNIX system. mcast is a dummy domain name referring to 224.0.1.30 that has been defined on the system by adding a line to the /ETC/HOSTS file of

224.0.1.30 mcast

(Once the flaw in the InetAddress class implementation has been corrected, this will no longer be necessary.) In this example, you are sending messages to this group destined for port 1111:

~/classes -> java MultiCastSender mcast 1111
This is a test multicast message.
Sending: This is a test multicast message.
Have you received it?
Sending: Have you received it?

To receive these messages, you would run the MultiCastReceiver application on one or more systems. You join the same multicast group, 224.0.1.30, and listen to the same port number, 1111. Again, on this system you have defined mcast to be a host name for this group, to work around the InetAddress bug.

~/classes -> java MultiCastReceiver mcast 1111
Received from 204.160.73.131 on port 32911: This is a test multicast message.
Received from 204.160.73.131 on port 32911: Have you received it?


Previous Page TOC Next Page

| Previous Chapter | Next Chapter |

|Table of Contents | Book Home Page |

| Que Home Page | Digital Bookshelf | Disclaimer |


To order books from QUE, call us at 800-716-0044 or 317-361-5400.

For comments or technical support for our books and software, select Talk to Us.

© 1996, QUE Corporation, an imprint of Macmillan Publishing USA, a Simon and Schuster Company.