TinyHttpd
used a Socket
to create a connection to the client
using the TCP protocol. In that example, TCP itself took care of data
integrity; we didn’t have to worry about data arriving out of
order or incorrect. Now we’ll take a walk on the wild side.
We’ll build 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,
but we didn’t have to worry about those details.)
UDP doesn’t guarantee that the data will get through. If the data packets do get through, they may not arrive in the order in which we sent them; 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 got 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. But it’s significantly more efficient than TCP, particularly if you don’t care about the order in which messages arrive, or whether 100% 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 send another query. It shouldn’t be hard for the client to match responses to its original queries. Some important applications that use UDP are the Domain Name System (DNS) and Sun’s Network Filesystem (NFS).
In this section, we’ll build a simple applet,
HeartBeat
, that sends a datagram to its server
each time it’s started and stopped. We’ll also build a
simple standalone server application, Pulse
, that
receives these datagrams and prints them. By tracking the output, you
could have a crude measure of who is currently looking at your web
page at any given time. This is an ideal application for UDP: we
don’t want the overhead of a TCP socket, and if 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( ); InetAddress addr = InetAddress.getByName( myHost ); DatagramPacket pack = new DatagramPacket(data, data.length, addr, myPort ); DatagramSocket ds = new DatagramSocket( ); ds.send( pack ); 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=10 width=10 code=HeartBeat> <PARAM name="myPort" value="1234"> </APPLET>
Make sure to place the HeartBeat.class
file in the
same directory as the HTML document. If you’re not familiar
with embedding applets in HTML documents, consult Chapter 20.
The myPort
parameter should specify the port
number on which our server application listens for data.
Next, the server-side application,
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( ) ); 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 application might do something
visual as well), but you should get a blip from the
Pulse
application. Leave the page and return to it
a few times. Each time the applet is started or stopped, it sends a
message and Pulse
reports it
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 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.
HeartBeat
overrides the init( )
, start( )
, and stop( )
methods of the Applet
class, and
implements one private method of its own, sendMessage( )
, that sends a datagram. 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 of 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( ); 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 from the
name myHost
. An InetAddress
holds the network address information for a host in a special format.
We get an InetAddress
object for our host by using
the static getByName( )
method of
the
InetAddress
class. (We can’t construct an InetAddress
object directly.) 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.
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()
.
InetAddress.getByName( )
can throw an
UnknownHostException
, which is a type of
IOException
that indicates that the hostname
can’t be resolved. If send( )
throws an
IOException
, it implies a serious client-side
problem in talking to 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 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 size to receive an
incoming datagram. This alternative
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 they can be larger
for local network use.) Finally, Pulse
calls the
DatagramSocket
’s receive( )
method to wait for a packet to arrive. When a packet
arrives, its contents are printed.
As you can see, working with DatagramSocket
is
slightly more tedious than working with Socket
s.
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. It’s easy to imagine
conveniences that would make all of this simpler; perhaps
we’ll have
them in a future release.
Get Learning Java now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.