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 applications that use UDP are the Domain Name System (DNS) and Sun’s Network File System (NFS).
In this section, we build a simple applet, HeartBeat
, that runs in a web browser and
sends a datagram to its server each time it’s started and stopped. We
also build a simple standalone server application, Pulse
, that receives these datagrams and
prints them. Tracking the output could give 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 the kind of thing UDP
is good for: 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:
<
html
><
body
>
<
h1
>
Heartbeat
!</
h1
>
<
applet
height
=
"1 "
width
=
"1 "
code
=
"HeartBeat "
>
<
param
name
=
"myPort"
value
=
"1234"
>
</
applet
>
</
body
></
html
>
Make sure to place the compiled
HeartBeat.class file in the same directory as the
HTML document (which we’ll refer to as heartbeat.html
). We talk more about embedding
applets in HTML documents in Chapter 23.
The myPort
parameter should
specify the port number on which our server application listens for data
(“1234” as just shown).
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
(),
0
,
packet
.
getLength
(),
"UTF-8"
);
System
.
out
.
println
(
"Heartbeat from: "
+
packet
.
getAddress
().
getHostName
()
+
" - "
+
message
);
}
}
}
Compile Pulse
and run it on
your web server, specifying the 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 to run the example we’re going to need a web server. Opening
the file directly in your browser will not work here because, as we
mentioned earlier, applets are only allowed to talk to the host that
served them and when no web server is involved, the security manager
doesn’t allow any network communications at all. Fortunately, we wrote a
satisfactory web server, TinyHttpd
,
earlier in this chapter! Just run TinyHttpd
in the directory with your
heartbeat.html file—being careful to specify a
different port number than our HeartBeat
client and Pulse
server—and use it to serve up the page.
We tested this in Safari on a Mac at the time of this writing and it
worked as expected. But if you have issues you can try a “real” web
server and we won’t be offended.
Now, pull up the web page in your browser. You won’t see much
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 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.
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
Chapter 23.) 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
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 use—theoretically
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, DatagramSocket
s are slightly more tedious
than regular 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. But all in all, it’s not that hard to use
datagrams for simple tasks.
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.
A useful feature of InetAddress
is the method isReachable()
, which
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
();
Get Learning Java, 4th Edition 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.