13.4 Some Useful Applications
In this section, you'll see several Internet servers
and clients that use DatagramPacket and
DatagramSocket. Some of these will be familiar
from previous chapters because many Internet protocols have both TCP
and UDP implementations. When an IP packet is received by a host, the
host determines whether the packet is a TCP packet or a UDP datagram
by inspecting the IP header. As I said earlier,
there's no connection between UDP and TCP ports; TCP
and UDP servers can share the same port number without problems. By
convention, if a service has both TCP and UDP implementations, it
uses the same port for both, although there's no
technical reason this has to be the case.
13.4.1 Simple UDP Clients
Several Internet services need to know
only the client's address and port; they ignore any
data the client sends in its datagrams. Daytime, quote of the day,
time, and chargen are four such protocols. Each of these responds the
same way, regardless of the data contained in the datagram, or indeed
regardless of whether there actually is any data in the datagram.
Clients for these protocols simply send a UDP datagram to the server
and read the response that comes back. Therefore,
let's begin with a simple client called
UDPPoke, shown in Example 13-5,
which sends an empty UDP packet to a specified host and port and
reads a response packet from the same host.The UDPPoke
class has three private fields. The bufferSize
field specifies how large a return packet is expected. An 8,192-byte
buffer is large enough for most of the protocols that
UDPPoke is useful for, but it can be increased by
passing a different value to the constructor. The
DatagramSocket object socket
will be used to both send and receive datagrams. Finally, the
DatagramPacket object outgoing
is the message sent to the individual servers.The constructors initialize all three fields using an
InetAddress for the host and
ints for the port, the buffer length, and the
number of milliseconds to wait before timing out. These last three
become part of the DatagramSocket field
socket. If the buffer length is not specified,
8,192 bytes is used. If the timeout is not given, 30 seconds (30,000
milliseconds) is used. The host, port, and buffer size are also used
to construct the outgoing
DatagramPacket. Although in theory you should be
able to send a datagram with no data at all, bugs in some Java
implementations require that you add at least one byte of data to the
datagram. The simple servers we're currently
considering ignore this data.Once a UDPPoke object has been constructed,
clients will call its poke(
) method to send an empty
outgoing datagram to the target and read its
response. The response is initially set to null. When the expected
datagram appears, its data is copied into the
response field. This method returns null if the
response doesn't come quickly enough or never comes
at all.The main( )
method merely reads the host and port to connect to from the command
line, constructs a UDPPoke object, and pokes it.
Most of the simple protocols that this client suits will return ASCII
text, so we'll attempt to convert the response to an
ASCII string and print it. Not all VMs support the ASCII character
encoding, so we'll provide the possibility of using
the ASCII superset Latin-1 (8859-1) as a backup.
Example 13-5. The UDPPoke class
import java.net.*;For example, this connects to a daytime server over UDP:
import java.io.*;
public class UDPPoke {
private int bufferSize; // in bytes
private DatagramSocket socket;
private DatagramPacket outgoing;
public UDPPoke(InetAddress host, int port, int bufferSize,
int timeout) throws SocketException {
outgoing = new DatagramPacket(new byte[1], 1, host, port);
this.bufferSize = bufferSize;
socket = new DatagramSocket(0);
socket .connect(host, port); // requires Java 2
socket .setSoTimeout(timeout);
}
public UDPPoke(InetAddress host, int port, int bufferSize)
throws SocketException {
this(host, port, bufferSize, 30000);
}
public UDPPoke(InetAddress host, int port)
throws SocketException {
this(host, port, 8192, 30000);
}
public byte[] poke( ) throws IOException {
byte[] response = null;
try {
socket .send(outgoing);
DatagramPacket incoming
= new DatagramPacket(new byte[bufferSize], bufferSize);
// next line blocks until the response is received
socket .receive(incoming);
int numBytes = incoming.getLength( );
response = new byte[numBytes];
System.arraycopy(incoming.getData( ), 0, response, 0, numBytes);
}
catch (IOException ex) {
// response will be null
}
// may return null
return response;
}
public static void main(String[] args) {
InetAddress host;
int port = 0;
try {
host = InetAddress.getByName(args[0]);
port = Integer.parseInt(args[1]);
if (port < 1 || port > 65535) throw new Exception( );
}
catch (Exception ex) {
System.out.println("Usage: java UDPPoke host port");
return;
}
try {
UDPPoke poker = new UDPPoke(host, port);
byte[] response = poker.poke( );
if (response == null) {
System.out.println("No response within allotted time");
return;
}
String result = ";
try {
result = new String(response, "ASCII");
}
catch (UnsupportedEncodingException e) {
// try a different encoding
result = new String(response, "8859_1");
}
System.out.println(result);
}
catch (Exception ex) {
System.err.println(ex);
ex.printStackTrace( );
}
} // end main
}
D:\JAVA\JNP3\examples\13>java UDPPoke rama.poly.edu 13This connects to a chargen server:
Sun Oct 3 13:04:22 1999
D:\JAVA\JNP3\examples\13>java UDPPoke rama.poly.edu 19Given this class, UDP daytime, time, chargen, and quote of the day
123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuv
clients are almost trivial. Example 13-6 demonstrates
a time client. The most complicated part is converting the four raw
bytes returned by the server to a java.util.Date
object. The same algorithm as in Example 10-5 is used here, so I
won't repeat that discussion. The other protocols
are left as exercises for the reader.
Example 13-6. A UDP time client
import java.net.*;
import java.util.*;
public class UDPTimeClient {
public final static int DEFAULT_PORT = 37;
public final static String DEFAULT_HOST = "time-a.nist.gov";
public static void main(String[] args) {
InetAddress host;
int port = DEFAULT_PORT;
try {
if (args.length > 0) {
host = InetAddress.getByName(args[0]);
}
else {
host = InetAddress.getByName(DEFAULT_HOST);
}
}
catch (Exception ex) {
System.out.println("Usage: java UDPTimeClient host port");
return;
}
if (args.length > 1) {
try {
port = Integer.parseInt(args[1]);
if (port <= 0 || port > 65535) port = DEFAULT_PORT;;
}
catch (Exception ex){
}
}
try {
UDPPoke poker = new UDPPoke(host, port);
byte[] response = poker.poke( );
if (response == null) {
System.out.println("No response within allotted time");
return;
}
else if (response.length != 4) {
System.out.println("Unrecognized response format");
return;
}
// The time protocol sets the epoch at 1900,
// the Java Date class at 1970. This number
// converts between them.
long differenceBetweenEpochs = 2208988800L;
long secondsSince1900 = 0;
for (int i = 0; i < 4; i++) {
secondsSince1900
= (secondsSince1900 << 8) | (response[i] & 0x000000FF);
}
long secondsSince1970
= secondsSince1900 - differenceBetweenEpochs;
long msSince1970 = secondsSince1970 * 1000;
Date time = new Date(msSince1970);
System.out.println(time);
}
catch (Exception ex) {
System.err.println(ex);
ex.printStackTrace( );
}
}
}
13.4.2 UDPServer
Clients aren't the only
programs that benefit from a reusable implementation. The servers for
these protocols are very similar. They all wait for UDP datagrams on
a specified port and reply to each datagram with another datagram.
The servers differ only in the content of the datagram that they
return. Example 13-7 is a simple
UDPServer class that can be subclassed to provide
specific servers for different protocols.The UDPServer class has
two fields, the int bufferSize
and the DatagramSocket socket,
the latter of which is protected so it can be used by subclasses. The
constructor opens the DatagramSocket
socket on a specified local port to receive
datagrams of no more than bufferSize bytes.UDPServer extends Thread so
that multiple instances can run in parallel. Its run(
) method contains an infinite loop that repeatedly receives
an incoming datagram and responds by passing it to the abstract
respond( ) method. This method will be overridden
by particular subclasses in order to implement different kinds of
servers.UDPServer is a very flexible class. Subclasses can
send zero, one, or many datagrams in response to each incoming
datagram. If a lot of processing is required to respond to a packet,
the respond( ) method can spawn a thread to do it.
However, UDP servers tend not to have extended interactions with a
client. Each incoming packet is treated independently of other
packets, so the response can usually be handled directly in the
respond( ) method without spawning a thread.
Example 13-7. The UDPServer class
import java.net.*;The easiest protocol to handle is discard. All we need to do is write
import java.io.*;
public abstract class UDPServer extends Thread {
private int bufferSize; // in bytes
protected DatagramSocket socket;
public UDPServer(int port, int bufferSize)
throws SocketException {
this.bufferSize = bufferSize;
this.socket = new DatagramSocket(port);
}
public UDPServer(int port) throws SocketException {
this(port, 8192);
}
public void run( ) {
byte[] buffer = new byte[bufferSize];
while (true) {
DatagramPacket incoming = new DatagramPacket(buffer, buffer.length);
try {
socket.receive(incoming);
this.respond(incoming);
}
catch (IOException ex) {
System.err.println(ex);
}
} // end while
} // end run
public abstract void respond(DatagramPacket request);
}
a main( ) method that sets the port and start the
thread. respond( ) is a do-nothing method. Example 13-8 is a high-performance UDP discard server that
does nothing with incoming packets.
Example 13-8. A high-performance UDP discard server
import java.net.*;Example 13-9 is a slightly more interesting discard
public class FastUDPDiscardServer extends UDPServer {
public final static int DEFAULT_PORT = 9;
public FastUDPDiscardServer( ) throws SocketException {
super(DEFAULT_PORT);
}
public void respond(DatagramPacket packet) {}
public static void main(String[] args) {
try {
UDPServer server = new FastUDPDiscardServer( );
server.start( );
}
catch (SocketException ex) {
System.err.println(ex);
}
}
}
server that prints the incoming packets on
System.out.
Example 13-9. A UDP discard server
import java.net.*;It isn't much harder to implement an echo server, as
public class LoggingUDPDiscardServer extends UDPServer {
public final static int DEFAULT_PORT = 9999;
public LoggingUDPDiscardServer( ) throws SocketException {
super(DEFAULT_PORT);
}
public void respond(DatagramPacket packet) {
byte[] data = new byte[packet.getLength( )];
System.arraycopy(packet.getData( ), 0, data, 0, packet.getLength( ));
try {
String s = new String(data, "8859_1");
System.out.println(packet.getAddress( ) + " at port "
+ packet.getPort( ) + " says " + s);
}
catch (java.io.UnsupportedEncodingException ex) {
// This shouldn't happen
}
}
public static void main(String[] args) {
try {
UDPServer erver = new LoggingUDPDiscardServer( );
server.start( );
}
catch (SocketException ex) {
System.err.println(ex);
}
}
}
Example 13-10 shows. Unlike a stream-based TCP echo
server, multiple threads are not required to handle multiple clients.
Example 13-10. A UDP echo server
import java.net.*;A daytime server is only slightly more complex. The server listens
import java.io.*;
public class UDPEchoServer extends UDPServer {
public final static int DEFAULT_PORT = 7;
public UDPEchoServer( ) throws SocketException {
super(DEFAULT_PORT);
}
public void respond(DatagramPacket packet) {
try {
DatagramPacket outgoing = new DatagramPacket(packet.getData( ),
packet.getLength( ), packet.getAddress( ), packet.getPort( ));
socket.send(outgoing);
}
catch (IOException ex) {
System.err.println(ex);
}
}
public static void main(String[] args) {
try {
UDPServer server = new UDPEchoServer( );
server.start( );
}
catch (SocketException ex) {
System.err.println(ex);
}
}
}
for incoming UDP datagrams on port 13. When it detects an incoming
datagram, it returns the current date and time at the server as a
one-line ASCII string. Example 13-11 demonstrates
this.
Example 13-11. The UDP daytime server
import java.net.*;
import java.io.*;
import java.util.*;
public class UDPDaytimeServer extends UDPServer {
public final static int DEFAULT_PORT = 13;
public UDPDaytimeServer( ) throws SocketException {
super(DEFAULT_PORT);
}
public void respond(DatagramPacket packet) {
try {
Date now = new Date( );
String response = now.toString( ) + "\r\n";
byte[] data = response.getBytes("ASCII");
DatagramPacket outgoing = new DatagramPacket(data,
data.length, packet.getAddress( ), packet.getPort( ));
socket.send(outgoing);
}
catch (IOException ex) {
System.err.println(ex);
}
}
public static void main(String[] args) {
try {
UDPServer server = new UDPDaytimeServer( );
server.start( );
}
catch (SocketException ex) {
System.err.println(ex);
}
}
}
13.4.3 A UDP Echo Client
The UDPPoke class
implemented earlier isn't suitable for all
protocols. In particular, protocols that require multiple datagrams
require a different implementation. The echo protocol has both TCP
and UDP implementations. Implementing the echo protocol with TCP is
simple; it's more complex with UDP because you
don't have I/O streams or the concept of a
connection to work with. A TCP-based echo client can send a message
and wait for a response on the same connection. However, a UDP-based
echo client has no guarantee that the message it sent was received.
Therefore, it cannot simply wait for the response; it needs to be
prepared to send and receive data asynchronously.This behavior is fairly simple to implement using threads, however.
One thread can process user input and send it to the echo server,
while a second thread accepts input from the server and displays it
to the user. The client is divided into three classes: the main
UDPEchoClient class, the
SenderThread class, and the
ReceiverThread class.The UDPEchoClient class should look familiar. It
reads a hostname from the command line and converts it to an
InetAddress object.
UDPEchoClient uses this object and the default
echo port to construct a SenderThread object. This
constructor can throw a SocketException, so the
exception must be caught. Then the SenderThread
starts. The same DatagramSocket that the
SenderThread uses is used to construct a
ReceiverThread, which is then started.
It's important to use the same
DatagramSocket for both sending and receiving data
because the echo server will send the response back to the port the
data was sent from. Example 13-12 shows the code for
the UDPEchoClient.
Example 13-12. The UDPEchoClient class
import java.net.*;The SenderThread class reads input from the console a line
import java.io.*;
public class UDPEchoClient {
public final static int DEFAULT_PORT = 7;
public static void main(String[] args) {
String hostname = "localhost";
int port = DEFAULT_PORT;
if (args.length > 0) {
hostname = args[0];
}
try {
InetAddress ia = InetAddress.getByName(hostname);
Thread sender = new SenderThread(ia, DEFAULT_PORT);
sender.start( );
Thread receiver = new ReceiverThread(sender.getSocket( ));
receiver.start( );
}
catch (UnknownHostException ex) {
System.err.println(ex);
}
catch (SocketException ex) {
System.err.println(ex);
}
} // end main
}
at a time and sends it to the echo server. It's
shown in Example 13-13. The input is provided by
System.in, but a different client could include an
option to read input from a different streamperhaps opening a
FileInputStream to read from a file. The three
fields of this class define the server to which it sends data, the
port on that server, and the DatagramSocket that
does the sending, all set in the single constructor. The
DatagramSocket is connected to the remote server
to make sure all datagrams received were in fact sent by the right
server. It's rather unlikely that some other server
on the Internet is going to bombard this particular port with
extraneous data, so this is not a big flaw. However,
it's a good habit to make sure that the packets you
receive come from the right place, especially if security is a
concern.The run( ) method processes user input a line at a
time. To do this, the BufferedReader
userInput is chained to
System.in. An infinite loop reads lines of user
input. Each line is stored in theLine. A period on
a line by itself signals the end of user input and breaks out of the
loop. Otherwise, the bytes of data are stored in the data array using
the getBytes( ) method from
java.lang.String. Next, the data array is placed
in the payload part of the DatagramPacket
output, along with information about the server,
the port, and the data length. This packet is then sent to its
destination by socket. This thread then yields to
give other threads an opportunity to run.
Example 13-13. The SenderThread class
import java.net.*;The
import java.io.*;
public class SenderThread extends Thread {
private InetAddress server;
private DatagramSocket socket;
private boolean stopped = false;
private int port;
public SenderThread(InetAddress address, int port)
throws SocketException {
this.server = address;
this.port = port;
this.socket = new DatagramSocket( );
this.socket.connect(server, port);
}
public void halt( ) {
this.stopped = true;
}
public DatagramSocket getSocket( ) {
return this.socket;
}
public void run( ) {
try {
BufferedReader userInput
= new BufferedReader(new InputStreamReader(System.in));
while (true) {
if (stopped) return;
String theLine = userInput.readLine( );
if (theLine.equals(".")) break;
byte[] data = theLine.getBytes( );
DatagramPacket output
= new DatagramPacket(data, data.length, server, port);
socket.send(output);
Thread.yield( );
}
} // end try
catch (IOException ex) {
System.err.println(ex);
}
} // end run
}
ReceiverThread class shown in Example 13-14 waits for datagrams to arrive from the
network. When a datagram is received, it is converted to a
String and printed on
System.out for display to the user. A more
advanced EchoClient could include an option to
send the output elsewhere.This class has two fields. The more important is the
DatagramSocket, theSocket,
which must be the same DatagramSocket used by the
SenderThread. Data arrives on the port used by
that DatagramSocket; any other
DatagramSocket would not be allowed to connect to
the same port. The second field, stopped, is a
boolean used to halt this thread without invoking the deprecated
stop( ) method.The run( ) method is an infinite loop that uses
socket's receive(
) method to wait for incoming datagrams. When an incoming
datagram appears, it is converted into a String
with the same length as the incoming data and printed on
System.out. As in the input thread, this thread
then yields to give other threads an opportunity to execute.
Example 13-14. The ReceiverThread class
import java.net.*;You can run the echo client on one machine and connect to the echo
import java.io.*;
class ReceiverThread extends Thread {
DatagramSocket socket;
private boolean stopped = false;
public ReceiverThread(DatagramSocket ds) throws SocketException {
this.socket = ds;
}
public void halt( ) {
this.stopped = true;
}
public void run( ) {
byte[] buffer = new byte[65507];
while (true) {
if (stopped) return;
DatagramPacket dp = new DatagramPacket(buffer, buffer.length);
try {
socket.receive(dp);
String s = new String(dp.getData( ), 0, dp.getLength( ));
System.out.println(s);
Thread.yield( );
}
catch (IOException ex) {
System.err.println(ex);
}
}
}
}
server on a second machine to verify that the network is functioning
properly between them.
• Table of Contents• Index• Reviews• Reader Reviews• Errata• AcademicJava Network Programming, 3rd EditionBy
Elliotte Rusty Harold Publisher: O'ReillyPub Date: October 2004ISBN: 0-596-00721-3Pages: 706
Thoroughly revised to cover all the 100+ significant updates
to Java Developers Kit (JDK) 1.5, Java Network
Programming is a complete introduction to
developing network programs (both applets and applications)
using Java, covering everything from networking fundamentals
to remote method invocation (RMI). It includes chapters on
TCP and UDP sockets, multicasting protocol and content
handlers, servlets, and the new I/O API. This is the
essential resource for any serious Java developer.