p. 226, Example 9-7: In Java 1.1 the reuse of the same
DatagramPacket
object dp
causes the
UDPDiscardServer
to act
unexpectedly on at least some platforms.
(Exact details appear to be dependent on native code.)
dp
is initialized to a length of 65,507 bytes but
the DatagramSocket.receive()
method resets this the length to reflect
the number of data bytes received. When dp
is reused, its length
has been reset to a new and potentially smaller value.
This seems to be something that changed from 1.0 to 1.1. However,
the example can be written in a fashion that supports all cases in both Java 1.0 and 1.1
by moving the line that constructs the DatagramPacket
into the while
loop.
import java.net.*;
import java.io.*;
public class UDPDiscardServer {
public final static int discardPort = 9;
static byte[] buffer = new byte[65507];
public static void main(String[] args) {
int port;
try {
port = Integer.parseInt(args[0]);
}
catch (Exception e) {
port = discardPort;
}
try {
DatagramSocket ds = new DatagramSocket(port);
while (true) {
try {
DatagramPacket dp = new DatagramPacket(buffer, buffer.length);
ds.receive(dp);
String s = new String(dp.getData(), 0, 0, dp.getLength());
System.out.println(dp.getAddress() + " at port " + dp.getPort() + " says " + s);
}
catch (IOException e) {
System.err.println(e);
}
} // end while
} // end try
catch (SocketException se) {
System.err.println(se);
} // end catch
} // end main
}
p. 237, Example 9-12: Same as above. In Java 1.1 the reuse of the same
DatagramPacket
object dp
causes the
UDPDiscardServer
to act
unexpectedly on at least some platforms.
(Exact details appeuar to be dependent on native code.)
dp
is initialized to a length of 65,507 bytes but
the DatagramSocket.receive()
method resets this the length to reflect
the number of data bytes received. When dp
is reused, its length
has been reset to a new and potentially smaller value.
This seems to be something that changed from 1.0 to 1.1. However,
the example can be written in a fashion that supports all cases in both Java 1.0 and 1.1
by moving the line that constructs the DatagramPacket
into the while
loop.
import java.net.*;
import java.io.*;
public class UDPServer {
protected static int defaultPort = 0;
protected static int defaultBufferLength = 65507;
public static void main(String[] args) {
DatagramPacket incoming;
int port;
int len;
try {
port = Integer.parseInt(args[0]);
}
catch (Exception e) {
port = defaultPort;
}
try {
len = Integer.parseInt(args[1]);
}
catch (Exception e) {
len = defaultBufferLength;
}
try {
DatagramSocket ds = new DatagramSocket(port);
byte[] buffer = new byte[len];
while (true) {
incoming = new DatagramPacket(buffer, buffer.length);
try {
ds.receive(incoming);
respond(ds, incoming);
}
catch (IOException e) {
System.err.println(e);
}
} // end while
} // end try
catch (SocketException se) {
System.err.println(se);
} // end catch
} // end main
public static void respond(DatagramSocket ds, DatagramPacket dp) {
;
}
}
pp. 233-239: The examples used here depend on overriding protected
static fields in subclasses. In fact the fields are not overridden but rather
shadowed. Also, Example 9-12 on p. 237 has the same problem
noted above where DatagramSocket.receive()
resets the length
of the DatagramPacket object.
Consequently it's necessary to rewrite pretty much all the examples to account for these problems as follows.
Several Internet services only need to know the clients address and port; they discard any data the client sends in its datagrams. Daytime, quote of the day, and chargen are three 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, lets begin with a simple client that sends an empty UDP packet to a specified host and port; we will design this class so that it can be subclassed to provide specific clients for different protocols.
The UDPPoke
class has two fields, both of which are protected
so their values may be changed by subclasses. The defaultPort
field holds the port to which the client sends data, if no other port is specified. In this class its set to 0. Subclasses change this field to the default port appropriate for their protocol. bufferLength
is the length of the buffer needed for incoming data. An 8192 byte buffer is large enough for most of the protocols that UDPPoke
is useful for, but it can be increased in a subclass or set from the command line if necessary.
The main()
method is invoked from the command line with the hostname, the port, and the buffer length passed as command line arguments. If the hostname is not included, the localhost is used. If the port is not included, the defaultPort
is used. If the buffer length is not specified, 8192 bytes is used. For example, to send a packet to the daytime server on port 13 of metalab.unc.edu you would type:
% java UDPPoke metalab.unc.edu 13 Sat May 18 15:56:36 1996
Once the port and host are known, a new DatagramSocket
object ds
is created on an anonymous local port. We use the hostname to construct an InetAddress
, which we then use to create a new DatagramPacket
called outgoing
aimed at the host and port specified on the command line. 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 were currently considering ignore this data.
Next we create a new DatagramPacket
with a 8192 byte buffer for the returning datagram. An 8192 byte buffer is large enough for most protocols. However, you can override it in a subclass if necessary. The DatagramSocket
ds
sends the outgoing packet and then waits to receive the response. When the response is received, UDPPoke
converts it into a String
and prints it on System.out
.
Example 9-10 The UDPPoke class
import java.net.*;
import java.io.*;
public class UDPPoke {
protected static int defaultPort = 0;
protected static int bufferLength = 8192;
public static void main(String[] args) {
String hostname;
int port;
int len;
if (args.length > 0) {
hostname = args[0];
}
else {
hostname = "localhost";
port = defaultPort;
len = bufferLength;
}
try {
port = Integer.parseInt(args[1]);
}
catch (Exception e) {
port = defaultPort;
}
try {
len = Integer.parseInt(args[2]);
}
catch (Exception e) {
len = bufferLength;
}
try {
DatagramSocket ds = new DatagramSocket(0);
InetAddress ia = InetAddress.getByName(hostname);
DatagramPacket outgoing = new DatagramPacket(new byte[512], 1, ia, port);
DatagramPacket incoming = new DatagramPacket(new byte[len], len);
ds.send(outgoing);
ds.receive(incoming);
System.out.println(new String(incoming.getData(), 0, 0, incoming.getLength()));
} // end try
catch (UnknownHostException e) {
System.err.println(e);
} // end catch
catch (SocketException e) {
System.err.println(e);
} // end catch
catch (IOException e) {
System.err.println(e);
} // end catch
} // end main
}
A daytime client is a trivial extension of UDPPoke
; all you need to do is change the default port, as in Example 9-11. chargen and quote of the day are equally trivial, and are left as exercises for the reader.
Example 9-11 The UDP daytime client
public class UDPdaytime extends UDPPoke {
static { defaultPort = 13; }
}
Clients arent the only programs that benefit from an object-oriented implementation. The servers for these protocols are also 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 9-12 is a UDPServer
class. It will be subclassed to provide specific servers for different protocols. The only reason it isnt implemented as an abstract class is the difficulty of combining both static and abstract methods in one class. (In brief, a static method needs to instantiate a class before it can call a method in that class; but an abstract class cant be instantiated.)
The UDPServer
class has two fields, defaultPort
and defaultBufferLength
, which are protected
so they can be accessed by subclasses. The main()
method is invoked from the command line, with the port and the buffer length passed as command line arguments. If the port is not included, the defaultPort
is used (though this will be of more use for subclasses). If the buffer length is not specified, 512 bytes is used. Once the port and buffer length are known, UDPServer
creates a new DatagramSocket
ds
on the specified port. Then UDPServer
enters an infinite loop in which the DatagramSocket
receives packets. When UDPServer
receives a packet, both the packet and the DatagramSocket
ds
are passed to the respond()
method.
The respond()
method sends the response to the originating host, using the DatagramSocket
ds
. In this class, the respond()
method does nothing. It should be overridden in subclasses that implement particular protocols. It would not be a bad idea to make respond()
an abstract method, except that methods may not be both abstract and static.
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 9-12 The UDPServer class
import java.net.*;
import java.io.*;
public class UDPServer {
protected static int defaultPort = 0;
protected static int defaultBufferLength = 65507;
public static void main(String[] args) {
DatagramPacket incoming;
int port;
int len;
try {
port = Integer.parseInt(args[0]);
}
catch (Exception e) {
port = defaultPort;
}
try {
len = Integer.parseInt(args[1]);
}
catch (Exception e) {
len = defaultBufferLength;
}
try {
DatagramSocket ds = new DatagramSocket(port);
byte[] buffer = new byte[len];
while (true) {
incoming = new DatagramPacket(buffer, buffer.length);
try {
ds.receive(incoming);
respond(ds, incoming);
}
catch (IOException e) {
System.err.println(e);
}
} // end while
} // end try
catch (SocketException se) {
System.err.println(se);
} // end catch
} // end main
public static void respond(DatagramSocket ds, DatagramPacket dp) {
;
}
}
As written, UDPServer
is almost a functional discard server. All that needs to be changed is the port. Example 9-13 adds the proper defaultPort
for a high-performance UDP discard server that does nothing with incoming packets.
Example 9-13 A High Performance UDP discard server
public class FastUDPDiscardServer extends UDPServer {
static { defaultPort = 9; }
}
Example 9-14 is a discard server that prints the incoming packets on System.out
.
Example 9-14 A UDP discard server
import java.net.*;
public class UDPDiscardServer extends UDPServer {
static { defaultPort = 9; }
public static void respond(DatagramSocket ds, DatagramPacket dp) {
String s = new String(dp.getData(), 0, 0, dp.getLength());
System.out.println(dp.getAddress() + " at port " + dp.getPort() + " says " + s);
}
}
It isnt much harder to implement an echo server, as Example 9-15 shows:
Example 9-15 A UDP echo server
import java.net.*;
import java.io.*;
public class UDPEchoServer extends UDPServer {
static { defaultPort = 7; }
public static void respond(DatagramSocket ds, DatagramPacket dp) {
DatagramPacket outgoing;
try {
outgoing = new DatagramPacket(dp.getData(), dp.getLength(),
dp.getAddress(), dp.getPort());
ds.send(outgoing);
}
catch (IOException e) {
System.err.println(e);
}
}
}
A daytime server is only mildly more complex. The server listens for incoming UDP datagrams on port 13. When a datagram is detected, a response is returned with the date and time at the server in a one-line ASCII string. Example 9-16 demonstrates this.
Example 9-16 The UDP daytime server
import java.net.*;
import java.io.*;
import java.util.Date;
public class UDPDaytimeServer extends UDPServer {
static { defaultPort = 13; }
public static void respond(DatagramSocket ds, DatagramPacket dp) {
DatagramPacket outgoing;
Date now = new Date();
String s = now.toString();
byte[] data = new byte[s.length()];
s.getBytes(0, s.length(), data, 0);
try {
outgoing = new DatagramPacket(data, data.length, dp.getAddress(), dp.getPort());
ds.send(outgoing);
}
catch (IOException e) {
System.err.println(e);
}
}
}
p. 242: Delete the line eot.stop();
in Example 9-18.
This means the program won't terminate of its own free will.
I'll fix that when I get a minute, but this temporary fix will at least
let the program compile and run.
p. 243, Example 9-19
Change
buffer = new byte[65507];
to
byte[] buffer = new byte[65507];