Datagram Sockets

TinyHttpd used a Socket to create a connection to the client using the TCP protocol. In that example, the TCP protocol took care of data integrity; we didn't have to worry about data arriving out of order or incorrect. Now, let's take a walk on the wild side, building an applet that uses a java.net.DatagramSocket, which uses the UDP protocol. A datagram is sort of like a letter sent via the postal service: it's a discrete chunk of data transmitted in one packet. Unlike the previous example, where we could get a convenient OutputStream from our Socket and write the data as if writing to a file, with a DatagramSocket, we have to work one datagram at a time. (Of course, the TCP protocol was taking our OutputStream and slicing the data into packets, too, but we didn't have to worry about those details.) UDP doesn't guarantee that the data is received. If the data packets are received, they may not arrive in the order in which they were sent; it's even possible for duplicate datagrams to arrive (under rare circumstances). Using UDP is something like cutting the pages out of the encyclopedia, putting them into separate envelopes, and mailing them to your friend. If your friend wants to read the encyclopedia, it's his or her job to put the pages in order. If some pages get lost in the mail, your friend has to send you a letter asking for replacements. Obviously, you wouldn't use UDP to send a huge amount of data without error correction. However, it's significantly more efficient than TCP, particularly if you don't care about the order in which messages arrive or whether 100 percent of their arrival is guaranteed. For example, in a simple periodic database lookup, the client can send a query; the server's response itself constitutes an acknowledgment. If the response doesn't arrive within a certain time, the client can just send another query. It shouldn't be hard for the client to match responses to its original queries. Some important apps that use UDP are the Domain Name System (DNS) and Sun's Network File System (NFS).

The HeartBeat Applet

In this section, we build a simple applet, HeartBeat, that sends a datagram to its server each time it's started and stopped. We also build a simple standalone server app, Pulse, that receives these datagrams and prints them. Tracking the output gives you a crude measure of who is currently looking at your web page at any given time (assuming that firewalls do not block the UDP packets). This is an ideal app for UDP: we don't want the overhead of a TCP socket, and if the datagrams get lost, it's no big deal. First, the HeartBeat applet:

 //file: HeartBeat.java
 import java.net.*;
 import java.io.*;
  
 public class HeartBeat extends java.applet.Applet {
 String myHost;
 int myPort;
  
 public void init( ) {
 myHost = getCodeBase( ).getHost( );
 myPort = Integer.parseInt( getParameter("myPort") );
 }
  
 private void sendMessage( String message ) {
 try {
 byte [] data = message.getBytes("UTF-8");
 InetAddress addr = InetAddress.getByName( myHost );
 DatagramPacket packet =
 new DatagramPacket( data, data.length, addr, myPort );
 DatagramSocket ds = new DatagramSocket( );
 ds.send( packet );
 ds.close( );
 } catch ( IOException e ) {
 System.out.println( e ); // Error creating socket
 }
 }
  
 public void start( ) {
 sendMessage("Arrived");
 }
 public void stop( ) {
 sendMessage("Departed");
 }
 }


Compile the applet and include it in an HTML document with an <applet> tag:

 <applet height="1 " width="1 " code="HeartBeat ">
 <param value="1234">
 </applet>


Make sure to place the compiled HeartBeat.class file in the same directory as the HTML document. If you're not familiar with embedding applets in HTML documents, consult . The myPort parameter should specify the port number on which our server app listens for data. Next, the server-side app, Pulse:

 //file: Pulse.java
 import java.net.*;
 import java.io.*;
  
 public class Pulse {
 public static void main( String [] argv ) throws IOException {
 DatagramSocket s =
 new DatagramSocket( Integer.parseInt(argv[0]) );
  
 while ( true ) {
 DatagramPacket packet =
 new DatagramPacket( new byte [1024], 1024 );
 s.receive( packet );
 String message = new String(
 packet.getData(), 0, packet.getLength( ),"UTF-8" );
 System.out.println( "Heartbeat from: "
 + packet.getAddress( ).getHostName( )
 + " - " + message );
 }
 }
 }


Compile Pulse and run it on your web server, specifying a port number as an argument:

 % java Pulse 1234


The port number should be the same as the one you used in the myPort parameter of the <applet> tag for HeartBeat. Now, pull up the web page in your browser. You won't see anything interesting there (a better app might do something visual as well), but you should get a blip from the Pulse app. Leave the page and return to it a few times. Each time the applet is started or stopped, it sends a message that Pulse reports:

 Heartbeat from: foo.bar.com - Arrived
 Heartbeat from: foo.bar.com - Departed
 Heartbeat from: foo.bar.com - Arrived
 Heartbeat from: foo.bar.com - Departed
 ...


Cool, eh? Just remember that the datagrams are not guaranteed to arrive (although it's highly unlikely you'll ever see them fail on a normal network), and it's possible that you could miss an arrival or a departure. Now let's look at the code.

The HeartBeat applet code

HeartBeat overrides the init( ), start( ), and stop( ) methods of the Applet class and implements one private method of its own, sendMessage( ), which sends a datagram. (We haven't covered applets yet, so if you want more details refer to .) HeartBeat begins its life in init( ), where it determines the destination for its messages. It uses the Applet getCodeBase( ) and getHost( ) methods to find the name of its originating host and fetches the correct port number from the myPort parameter of the <applet> tag. After init( ) has finished, the start( ) and stop( ) methods are called whenever the applet is started or stopped. These methods merely call sendMessage( ) with the appropriate message. sendMessage( ) is responsible for sending a String message to the server as a datagram. It takes the text as an argument, constructs a datagram packet containing the message, and then sends the datagram. All the datagram information is packed into a java.net.DatagramPacket object, including the destination and port number. The DatagramPacket is like an addressed envelope, stuffed with our bytes. After the DatagramPacket is created, sendMessage( ) simply has to open a DatagramSocket and send it. The first five lines of sendMessage( ) build the DatagramPacket:

 try {
 byte [] data = message.getBytes("UTF-8");
 InetAddress addr = InetAddress.getByName( myHost );
 DatagramPacket pack =
 new DatagramPacket(data, data.length, addr, myPort );


First, the contents of message are placed into an array of bytes called data. Next a java.net.InetAddress object is created by looking up the hostname myHost. An InetAddress holds the network IP address for a host. This class also provides the static getByName( ) method for looking up an IP address by hostname using the system's name service. (We'll say more about InetAddress in the next section.) Finally, we call the DatagramPacket constructor with four arguments: the byte array containing our data, the length of the data, the destination address object, and the port number. We ask for the string to be encoded using the UTF-8 charset; we'll use the same character set to decode it. The remaining lines construct a default client DatagramSocket and call its send( ) method to transmit the DatagramPacket. After sending the datagram, we close the socket:

 DatagramSocket ds = new DatagramSocket( );
 ds.send( pack );
 ds.close( );


Two operations throw a type of IOException: the InetAddress.getByName( ) lookup and the DatagramSocket send( ) method. InetAddress.getByName( ) can throw an UnknownHostException, which is a type of IOException that indicates the hostname can't be resolved. If send( ) throws an IOException, it implies a serious client-side communication problem with the network. We need to catch these exceptions; our catch block simply prints a message telling us that something went wrong. If we get one of these exceptions, we can assume the datagram never arrived. However, we can't assume the inverse: even if we don't get an exception, we still don't know that the host is actually accessible or that the data actually arrived; with a DatagramSocket, we never find out from the API.

The Pulse server code

The Pulse server corresponds to the HeartBeat applet. First, it creates a DatagramSocket to listen on our prearranged port. This time, we specify a port number in the constructor; we get the port number from the command line as a string (argv[0]) and convert it to an integer with Integer.parseInt( ). Note the difference between this call to the constructor and the call in HeartBeat. In the server, we need to listen for incoming datagrams on a prearranged port, so we need to specify the port when creating the DatagramSocket. The client just sends datagrams, so we don't have to specify the port in advance; we build the port number into the DatagramPacket itself. Second, Pulse creates an empty DatagramPacket of a fixed maximum size to receive an incoming datagram. This form of the constructor for DatagramPacket takes a byte array and a length as arguments. As much data as possible is stored in the byte array when it's received. (A practical limit on the size of a UDP datagram that can be sent over the Internet is 8K, although datagrams can be larger for local network usetheoretically up to 64K.) Finally, Pulse calls the DatagramSocket's receive( ) method to wait for a packet to arrive. When a packet arrives, its contents are printed by turning them to a string using UTF-8 encoding. We determine the actual number of received bytes from the packet's getLength( ) method. As you can see, DatagramSockets are slightly more tedious than regular Sockets. With datagrams, it's harder to spackle over the messiness of the socket interface. The Java API rather slavishly follows the Unix interface, and that doesn't help. But all in all, it's not that hard to use datagrams for simple tasks.

InetAddress

The java.net.InetAddress class is the lowest-level Java API for working with IP addresses. Instances of InetAddress represent individual addresses and the InetAddress class provides the API for using the platform's name service to map a string hostname to a numeric IP address. Most of our networking examples showed the use of hostnames to identify remote servers, but under the covers, Java utilized the static InetAddress.getByName( ) method to map the name to a physical IP address. Java normally uses the DNS to perform this lookup (and it caches the results for efficiency). Most Java networking APIs (such as Sockets) will accept either a hostname or an InetAddress as a destination. The InetAddress class can also be used to perform reverse IP lookups (get a name for an IP address) as well as to find the primary address of the local host via the static InetAddress.getLocalHost( ) method. Java 5.0 added an important feature to InetAddress. The method isReachable( ) attempts to use the ICMP ping protocol to determine whether a remote address can be reached over the network. The ping protocol is the standard mechanism used to check reachability and latency on networks. It is a low-level IP protocol (along with TCP and UDP) and is not guaranteed to be supported everywhere. If isReachable( ) can't use ICMP, it attempts to use TCP to reach the echo service (port 7) on the remote host. For example:

 InetAddress server = InetAddress.getByName("myserver");
 If ( !server.isReachable( timeout ) ) // milliseconds
 pageSomeone( );


Comments