Sockets for Clients - Java Network Programming, 4th Edition (2013)

Java Network Programming, 4th Edition (2013)

Chapter 8. Sockets for Clients

Data is transmitted across the Internet in packets of finite size called datagrams. Each datagram contains a header and a payload. The header contains the address and port to which the packet is going, the address and port from which the packet came, a checksum to detect data corruption, and various other housekeeping information used to ensure reliable transmission. The payload contains the data itself. However, because datagrams have a finite length, it’s often necessary to split the data across multiple packets and reassemble it at the destination. It’s also possible that one or more packets may be lost or corrupted in transit and need to be retransmitted or that packets arrive out of order and need to be reordered. Keeping track of this—splitting the data into packets, generating headers, parsing the headers of incoming packets, keeping track of what packets have and haven’t been received, and so on—is a lot of work and requires a lot of intricate code.

Fortunately, you don’t have to do the work yourself. Sockets allow the programmer to treat a network connection as just another stream onto which bytes can be written and from which bytes can be read. Sockets shield the programmer from low-level details of the network, such as error detection, packet sizes, packet splitting, packet retransmission, network addresses, and more.

Using Sockets

A socket is a connection between two hosts. It can perform seven basic operations:

§ Connect to a remote machine

§ Send data

§ Receive data

§ Close a connection

§ Bind to a port

§ Listen for incoming data

§ Accept connections from remote machines on the bound port

Java’s Socket class, which is used by both clients and servers, has methods that correspond to the first four of these operations. The last three operations are needed only by servers, which wait for clients to connect to them. They are implemented by the ServerSocket class, which is discussed in the next chapter. Java programs normally use client sockets in the following fashion:

§ The program creates a new socket with a constructor.

§ The socket attempts to connect to the remote host.

Once the connection is established, the local and remote hosts get input and output streams from the socket and use those streams to send data to each other. This connection is full-duplex. Both hosts can send and receive data simultaneously. What the data means depends on the protocol; different commands are sent to an FTP server than to an HTTP server. There will normally be some agreed-upon handshaking followed by the transmission of data from one to the other.

When the transmission of data is complete, one or both sides close the connection. Some protocols, such as HTTP 1.0, require the connection to be closed after each request is serviced. Others, such as FTP and HTTP 1.1, allow multiple requests to be processed in a single connection.

Investigating Protocols with Telnet

In this chapter, you’ll see clients that use sockets to communicate with a number of well-known Internet services such as time, dict, and more. The sockets themselves are simple enough; however, the protocols to communicate with different servers make life complex.

To get a feel for how a protocol operates, you can use Telnet to connect to a server, type different commands to it, and watch its responses. By default, Telnet attempts to connect to port 23. To connect to servers on different ports, specify the port you want to connect to like this:

$ telnet localhost 25

This requests a connection to port 25, the SMTP port, on the local machine; SMTP is the protocol used to transfer email between servers or between a mail client and a server. If you know the commands to interact with an SMTP server, you can send email without going through a mail program. This trick can be used to forge email. For example, some years ago, the summer students at the National Solar Observatory in Sunspot, New Mexico, made it appear that the party one of the scientists was throwing after the annual volleyball match between the staff and the students was in fact a victory party for the students. (Of course, the author of this book had absolutely nothing to do with such despicable behavior. ;-) ) The interaction with the SMTP server went something like this; input the user types is shown in bold (the names have been changed to protect the gullible):

flare% telnet localhost 25

Trying 127.0.0.1 ...

Connected to localhost.sunspot.noao.edu.

Escape character is '^]'.

220 flare.sunspot.noao.edu Sendmail 4.1/SMI-4.1 ready at

Fri, 5 Jul 93 13:13:01 MDT

HELO sunspot.noao.edu

250 flare.sunspot.noao.edu Hello localhost [127.0.0.1], pleased to meet you

MAIL FROM: bart

250 bart... Sender ok

RCPT TO: local@sunspot.noao.edu

250 local@sunspot.noao.edu... Recipient ok

DATA

354 Enter mail, end with "." on a line by itself

In a pitiful attempt to reingratiate myself with the students

after their inevitable defeat of the staff on the volleyball

court at 4:00 P.M., July 24, I will be throwing a victory

party for the students at my house that evening at 7:00.

Everyone is invited.

Beer and Ben-Gay will be provided so the staff may drown

their sorrows and assuage their aching muscles after their

public humiliation.

Sincerely,

Bart

.

250 Mail accepted

QUIT

221 flare.sunspot.noao.edu delivering mail

Connection closed by foreign host.

Several members of the staff asked Bart why he, a staff member, was throwing a victory party for the students. The moral of this story is that you should never trust email, especially patently ridiculous email like this, without independent verification.

In the 20 years since this happened, most SMTP servers have added a little more security than shown here. They tend to require usernames and passwords, and only accept connections from clients in the local networks and other trusted mail servers. However, it’s still the case that you can use Telnet to simulate a client, see how the client and the server interact, and thus learn what your Java program needs to do. Although this session doesn’t demonstrate all the features of the SMTP protocol, it’s sufficient to enable you to deduce how a simple email client talks to a server.

Reading from Servers with Sockets

Let’s begin with a simple example. You’re going to connect to the daytime server at the National Institute for Standards and Technology (NIST) and ask it for the current time. This protocol is defined in RFC 867. Reading that, you see that the daytime server listens on port 13, and that the server sends the time in a human-readable format and closes the connection. You can test the daytime server with Telnet like this:

$ telnet time.nist.gov 13

Trying 129.6.15.28...

Connected to time.nist.gov.

Escape character is '^]'.

56375 13-03-24 13:37:50 50 0 0 888.8 UTC(NIST) *

Connection closed by foreign host.

The line “56375 13-03-24 13:37:50 50 0 0 888.8 UTC(NIST)” is sent by the daytime server. When you read the Socket’s InputStream, this is what you will get. The other lines are produced either by the Unix shell or by the Telnet program.

RFC 867 does not specify any particular format for the output other than that it be human readable. In this case, you can see this connection was made on March 24, 2013, at 1:37: 50 P.M., Greenwich Meantime. More specifically, the format is defined as JJJJJ YY-MM-DD HH:MM:SS TT L H msADV UTC(NIST) OTM where:

§ JJJJJ is the “Modified Julian Date” (i.e., it is the number of whole days since midnight on November 17, 1858).

§ YY-MM-DD is the last two digits of the year, the month, and the current day of month.

§ HH:MM:SS is the time in hours, minutes, and seconds in Coordinated Universal Time (UTC, essentially Greenwich Mean Time).

§ TT indicates whether the United States is currently observing on Standard Time or Daylight Savings Time: 00 means standard time; 50 means daylight savings time. Other values count down the number of days until the switchover.

§ L is a one-digit code that indicates whether a leap second will be added or subtracted at midnight on the last day of the current month: 0 for no leap second, 1 to add a leap second, and 2 to subtract a leap second.

§ H represents the health of the server: 0 means healthy, 1 means up to 5 seconds off, 2 means more than 5 seconds off, 3 means an unknown amount of inaccuracy, and 4 is maintenance mode.

§ msADV is a number of milliseconds that NIST adds to the time it sends to roughly compensate for network delays. In the preceding code, you can see that it added 888.8 milliseconds to this result, because that’s how long it estimates it’s going to take for the response to return.

§ The string UTC(NIST) is a constant, and the OTM is almost a constant (an asterisk unless something really weird has happened).

These details are all NIST specific. They are not part of the daytime standard. Although they do offer a lot of data, if you have a real programmatic need to sync with a network time server, you’re better off using the NTP protocol defined in RFC 5905 instead.

NOTE

I’m not sure how long this example is going to work as shown here. These servers are overloaded, and I did have intermittent problems connecting while writing this chapter. In early 2013, NIST announced, “Users of the NIST DAYTIME protocol on tcp port 13 are also strongly encouraged to upgrade to the network time protocol, which provides greater accuracy and requires less network bandwidth. The NIST time client (nistime-32bit.exe) supports both protocols. We expect to replace the tcp version of this protocol with a udp-based version near the end of 2013.” I’ll show you how to access this service over UDP in Chapter 11.

Now let’s see how to retrieve this same data programmatically using sockets. First, open a socket to time.nist.gov on port 13:

Socket socket = new Socket("time.nist.gov", 13);

This doesn’t just create the object. It actually makes the connection across the network. If the connection times out or fails because the server isn’t listening on port 13, then the constructor throws an IOException, so you’ll usually wrap this in a try block. In Java 7, Socket implementsAutocloseable so you can use try-with-resources:

try (Socket socket = new Socket("time.nist.gov", 13)) {

// read from the socket...

} catch (IOException ex) {

System.err.println("Could not connect to time.nist.gov");

}

In Java 6 and earlier, you’ll want to explicitly close the socket in a finally block to release resources the socket holds:

Socket socket = null;

try {

socket = new Socket(hostname, 13);

// read from the socket...

} catch (IOException ex) {

System.err.println(ex);

} finally {

if (socket != null) {

try {

socket.close();

} catch (IOException ex) {

// ignore

}

}

}

The next step is optional but highly recommended. Set a timeout on the connection using the setSoTimeout() method. Timeouts are measured in milliseconds, so this statement sets the socket to time out after 15 seconds of nonresponsiveness:

socket.setSoTimeout(15000);

Although a socket should throw a ConnectException pretty quickly if the server rejects the connection, or a NoRouteToHostException if the routers can’t figure out how to send your packets to the server, neither of these help you with the case where a misbehaving server accepts the connection and then stops talking to you without actively closing the connection. Setting a timeout on the socket means that each read from or write to the socket will take at most a certain number of milliseconds. If a server hangs while you’re connected to it, you will be notified with aSocketTimeoutException. Exactly how long a timeout to set depends on the needs of your application and how responsive you expect the server to be. Fifteen seconds is a long time for a local intranet server to respond, but it’s rather short for an overloaded public server liketime.nist.gov.

Once you’ve opened the socket and set its timeout, call getInputStream() to return an InputStream you can use to read bytes from the socket. In general, a server can send any bytes at all; but in this specific case, the protocol specifies that those bytes must be ASCII:

InputStream in = socket.getInputStream();

StringBuilder time = new StringBuilder();

InputStreamReader reader = new InputStreamReader(in, "ASCII");

for (int c = reader.read(); c != -1; c = reader.read()) {

time.append((char) c);

}

System.out.println(time);

Here I’ve stored the bytes in a StringBuilder. You can, of course, use any data structure that fits your problem to hold the data that comes off the network.

Example 8-1 puts this all together in a program that also allows you to choose a different daytime server.

Example 8-1. A daytime protocol client

import java.net.*;

import java.io.*;

public class DaytimeClient {

public static void main(String[] args) {

String hostname = args.length > 0 ? args[0] : "time.nist.gov";

Socket socket = null;

try {

socket = new Socket(hostname, 13);

socket.setSoTimeout(15000);

InputStream in = socket.getInputStream();

StringBuilder time = new StringBuilder();

InputStreamReader reader = new InputStreamReader(in, "ASCII");

for (int c = reader.read(); c != -1; c = reader.read()) {

time.append((char) c);

}

System.out.println(time);

} catch (IOException ex) {

System.err.println(ex);

} finally {

if (socket != null) {

try {

socket.close();

} catch (IOException ex) {

// ignore

}

}

}

}

}

Typical output is much the same as if you connected with Telnet:

$ java DaytimeClient

56375 13-03-24 15:05:42 50 0 0 843.6 UTC(NIST) *

As far as network-specific code goes, that’s pretty much it. In most network programs like this, the real effort is in speaking the protocol and comprehending the data formats. For instance, rather than simply printing out the text the server sends you, you might want to parse it into ajava.util.Date object instead. Example 8-2 shows you how to do this. For variety, I also wrote this example taking advantage of Java 7’s AutoCloseable and try-with-resources.

Example 8-2. Construct a Date by talking to time.nist.gov

import java.net.*;

import java.text.*;

import java.util.Date;

import java.io.*;

public class Daytime {

public Date getDateFromNetwork() throws IOException, ParseException {

try (Socket socket = new Socket("time.nist.gov", 13)) {

socket.setSoTimeout(15000);

InputStream in = socket.getInputStream();

StringBuilder time = new StringBuilder();

InputStreamReader reader = new InputStreamReader(in, "ASCII");

for (int c = reader.read(); c != -1; c = reader.read()) {

time.append((char) c);

}

return parseDate(time.toString());

}

}

static Date parseDate(String s) throws ParseException {

String[] pieces = s.split(" ");

String dateTime = pieces[1] + " " + pieces[2] + " UTC";

DateFormat format = new SimpleDateFormat("yy-MM-dd hh:mm:ss z");

return format.parse(dateTime);

}

}

Notice, however, this class doesn’t actually do anything with the network that Example 8-1 didn’t do. It just added a bunch of code to turn strings into dates.

When reading data from the network, it’s important to keep in mind that not all protocols use ASCII or even text. For example, the time protocol specified in RFC 868 specifies that the time be sent as the number of seconds since midnight, January 1, 1900, Greenwich Mean Time. However, this is not sent as an ASCII string like 2,524,521,600 or –1297728000. Rather, it is sent as a 32-bit, unsigned, big-endian binary number.

TIP

The RFC never actually comes out and says that this is the format used. It specifies 32 bits and assumes you know that all network protocols use big-endian numbers. The fact that the number is unsigned can be determined only by calculating the wraparound date for signed and unsigned integers and comparing it to the date given in the specification (2036). To make matters worse, the specification gives an example of a negative time that can’t actually be sent by time servers that follow the protocol. Time is a relatively old protocol, standardized in the early 1980s before the IETF was as careful about such issues as it is today. Nonetheless, if you find yourself implementing a not particularly well-specified protocol, you may have to do a significant amount of testing against existing implementations to figure out what you need to do. In the worst case, different implementations may behave differently.

Because the time protocol doesn’t send back text, you can’t easily use Telnet to test such a service, and your program can’t read the server response with a Reader or any sort of readLine() method. A Java program that connects to time servers must read the raw bytes and interpret them appropriately. In this example, that job is complicated by Java’s lack of a 32-bit unsigned integer type. Consequently, you have to read the bytes one at a time and manually convert them into a long using the bitwise operators << and |. Example 8-3 demonstrates. When speaking other protocols, you may encounter data formats even more alien to Java. For instance, a few network protocols use 64-bit fixed-point numbers. There’s no shortcut to handle all possible cases. You simply have to grit your teeth and code the math you need to handle the data in whatever format the server sends.

Example 8-3. A time protocol client

import java.net.*;

import java.text.*;

import java.util.Date;

import java.io.*;

public class Time {

private static final String HOSTNAME = "time.nist.gov";

public static void main(String[] args) throws IOException, ParseException {

Date d = Time.getDateFromNetwork();

System.out.println("It is " + d);

}

public static Date getDateFromNetwork() throws IOException, ParseException {

// The time protocol sets the epoch at 1900,

// the Java Date class at 1970. This number

// converts between them.

long differenceBetweenEpochs = 2208988800L;

// If you'd rather not use the magic number, uncomment

// the following section which calculates it directly.

/*

TimeZone gmt = TimeZone.getTimeZone("GMT");

Calendar epoch1900 = Calendar.getInstance(gmt);

epoch1900.set(1900, 01, 01, 00, 00, 00);

long epoch1900ms = epoch1900.getTime().getTime();

Calendar epoch1970 = Calendar.getInstance(gmt);

epoch1970.set(1970, 01, 01, 00, 00, 00);

long epoch1970ms = epoch1970.getTime().getTime();

long differenceInMS = epoch1970ms - epoch1900ms;

long differenceBetweenEpochs = differenceInMS/1000;

*/

Socket socket = null;

try {

socket = new Socket(HOSTNAME, 37);

socket.setSoTimeout(15000);

InputStream raw = socket.getInputStream();

long secondsSince1900 = 0;

for (int i = 0; i < 4; i++) {

secondsSince1900 = (secondsSince1900 << 8) | raw.read();

}

long secondsSince1970

= secondsSince1900 - differenceBetweenEpochs;

long msSince1970 = secondsSince1970 * 1000;

Date time = new Date(msSince1970);

return time;

} finally {

try {

if (socket != null) socket.close();

}

catch (IOException ex) {}

}

}

}

Here’s the output of this program from a sample run:

$ java Time

It is Sun Mar 24 12:22:17 EDT 2013

The time protocol actually specifies Greenwich Mean Time, but the toString() method in Java’s Date class, implicitly invoked by System.out.println(), converts this into the time zone of the local host, Eastern Daylight Time in this case.

Writing to Servers with Sockets

Writing to a server is not noticeably harder than reading from one. You simply ask the socket for an output stream as well as an input stream. Although it’s possible to send data over the socket using the output stream at the same time you’re reading data over the input stream, most protocols are designed so that the client is either reading or writing over a socket, not both at the same time. In the most common pattern, the client sends a request. Then the server responds. The client may send another request, and the server responds again. This continues until one side or the other is done, and closes the connection.

One simple bidirectional TCP protocol is dict, defined in RFC 2229. In this protocol, the client opens a socket to port 2628 on the dict server and sends commands such as “DEFINE eng-lat gold”. This tells the server to send a definition of the word gold using its English-to-Latin dictionary. (Different servers have different dictionaries installed.) After the first definition is received, the client can ask for another. When it’s done it sends the command “quit”. You can explore dict with Telnet like this:

$ telnet dict.org 2628

Trying 216.18.20.172...

Connected to dict.org.

Escape character is '^]'.

220 pan.alephnull.com dictd 1.12.0/rf on Linux 3.0.0-14-server

<auth.mime> <499772.29595.1364340382@pan.alephnull.com>

DEFINE eng-lat gold

150 1 definitions retrieved

151 "gold" eng-lat "English-Latin Freedict dictionary"

gold [gould]

aurarius; aureus; chryseus

aurum; chrysos

.

250 ok [d/m/c = 1/0/10; 0.000r 0.000u 0.000s]

DEFINE eng-lat computer

552 no match [d/m/c = 0/0/9; 0.000r 0.000u 0.000s]

quit

221 bye [d/m/c = 0/0/0; 42.000r 0.000u 0.000s]

You can see that control response lines begin with a three-digit code. The actual definition is plain text, terminated with a period on a line by itself. If the dictionary doesn’t contain the word you asked for, it returns 552 no match. Of course, you could also find this out, and a lot more, by reading the RFC.

It’s not hard to implement this protocol in Java. First, open a socket to a dict server—_dict.org__ is a good one—on port 2628:

Socket socket = new Socket("dict.org", 2628);

Once again you’ll want to set a timeout in case the server hangs while you’re connected to it:

socket.setSoTimeout(15000);

In the dict protocol, the client speaks first, so ask for the output stream using getOutputStream():

OutputStream out = socket.getOutputStream();

The getOutputStream() method returns a raw OutputStream for writing data from your application to the other end of the socket. You usually chain this stream to a more convenient class like DataOutputStream or OutputStreamWriter before using it. For performance reasons, it’s a good idea to buffer it as well. Because the dict protocol is text based, more specifically UTF-8 based, it’s convenient to wrap this in a Writer:

Writer writer = new OutputStreamWriter(out, "UTF-8");

Now write the command over the socket:

writer.write("DEFINE eng-lat gold\r\n");

Finally, flush the output so you’ll be sure the command is sent over the network:

writer.flush();

The server should now respond with a definition. You can read that using the socket’s input stream:

InputStream in = socket.getInputStream();

BufferedReader reader = new BufferedReader(

new InputStreamReader(in, "UTF-8"));

for (String line = reader.readLine();

!line.equals(".");

line = reader.readLine()) {

System.out.println(line);

}

When you see a period on a line by itself, you know the definition is complete. You can then send the quit over the output stream:

writer.write("quit\r\n");

writer.flush();

Example 8-4 shows a complete dict client. It connects to dict.org, and translates any words the user enters on the command line into Latin. It filters out all the metadata lines that begin with response codes such as 150 or 220. However, it does specifically check for a line that begins “552 no match” in case the server doesn’t recognize the word.

Example 8-4. A network-based English-to-Latin translator

import java.io.*;

import java.net.*;

public class DictClient {

public static final String SERVER = "dict.org";

public static final int PORT = 2628;

public static final int TIMEOUT = 15000;

public static void main(String[] args) {

Socket socket = null;

try {

socket = new Socket(SERVER, PORT);

socket.setSoTimeout(TIMEOUT);

OutputStream out = socket.getOutputStream();

Writer writer = new OutputStreamWriter(out, "UTF-8");

writer = new BufferedWriter(writer);

InputStream in = socket.getInputStream();

BufferedReader reader = new BufferedReader(

new InputStreamReader(in, "UTF-8"));

for (String word : args) {

define(word, writer, reader);

}

writer.write("quit\r\n");

writer.flush();

} catch (IOException ex) {

System.err.println(ex);

} finally { // dispose

if (socket != null) {

try {

socket.close();

} catch (IOException ex) {

// ignore

}

}

}

}

static void define(String word, Writer writer, BufferedReader reader)

throws IOException, UnsupportedEncodingException {

writer.write("DEFINE eng-lat " + word + "\r\n");

writer.flush();

for (String line = reader.readLine(); line != null; line = reader.readLine()) {

if (line.startsWith("250 ")) { // OK

return;

} else if (line.startsWith("552 ")) { // no match

System.out.println("No definition found for " + word);

return;

}

else if (line.matches("\\d\\d\\d .*")) continue;

else if (line.trim().equals(".")) continue;

else System.out.println(line);

}

}

}

Here’s a sample run:

$ java DictClient gold uranium silver copper lead

gold [gould]

aurarius; aureus; chryseus

aurum; chrysos

No definition found for uranium

silver [silvər]

argenteus

argentum

copper [kɔpər]

æneus; aheneus; ærarius; chalceus

æs

lead [led]

ducere

molybdus; plumbum

Example 8-4 is line oriented. It reads a line of input from the console, sends it to the server, and waits to read a line of output it gets back.

Half-closed sockets

The close() method shuts down both input and output from the socket. On occasion, you may want to shut down only half of the connection, either input or output. The shutdownInput() and shutdownOutput() methods close only half the connection:

public void shutdownInput() throws IOException

public void shutdownOutput() throws IOException

Neither actually closes the socket. Instead, they adjust the stream connected to the socket so that it thinks it’s at the end of the stream. Further reads from the input stream after shutting down input return –1. Further writes to the socket after shutting down output throw an IOException.

Many protocols, such as finger, whois, and HTTP, begin with the client sending a request to the server, then reading the response. It would be possible to shut down the output after the client has sent the request. For example, this code fragment sends a request to an HTTP server and then shuts down the output, because it won’t need to write anything else over this socket:

try (Socket connection = new Socket("www.oreilly.com", 80)) {

Writer out = new OutputStreamWriter(

connection.getOutputStream(), "8859_1");

out.write("GET / HTTP 1.0\r\n\r\n");

out.flush();

connection.shutdownOutput();

// read the response...

} catch (IOException ex) {

ex.printStackTrace();

}

Notice that even though you shut down half or even both halves of a connection, you still need to close the socket when you’re through with it. The shutdown methods simply affect the socket’s streams. They don’t release the resources associated with the socket, such as the port it occupies.

The isInputShutdown() and isOutputShutdown() methods tell you whether the input and output streams are open or closed, respectively. You can use these (rather than isConnected() and isClosed()) to more specifically ascertain whether you can read from or write to a socket:

public boolean isInputShutdown()

public boolean isOutputShutdown()

Constructing and Connecting Sockets

The java.net.Socket class is Java’s fundamental class for performing client-side TCP operations. Other client-oriented classes that make TCP network connections such as URL, URLConnection, Applet, and JEditorPane all ultimately end up invoking the methods of this class. This class itself uses native code to communicate with the local TCP stack of the host operating system.

Basic Constructors

Each Socket constructor specifies the host and the port to connect to. Hosts may be specified as an InetAddress or a String. Remote ports are specified as int values from 1 to 65535:

public Socket(String host, int port) throws UnknownHostException, IOException

public Socket(InetAddress host, int port) throws IOException

These constructors connect the socket (i.e., before the constructor returns, an active network connection is established to the remote host). If the connection can’t be opened for some reason, the constructor throws an IOException or an UnknownHostException. For example:

try {

Socket toOReilly = new Socket("www.oreilly.com", 80);

// send and receive data...

} catch (UnknownHostException ex) {

System.err.println(ex);

} catch (IOException ex) {

System.err.println(ex);

}

In this constructor, the host argument is just a hostname expressed as a String. If the domain name server cannot resolve the hostname or is not functioning, the constructor throws an UnknownHostException. If the socket cannot be opened for some other reason, the constructor throws an IOException. There are many reasons a connection attempt might fail: the host you’re trying to reach may not accept connections on that port, the hotel WiFi service may be blocking you until you log in to its website and pay $14.95, or routing problems may be preventing your packets from reaching their destination.

Because this constructor doesn’t just create a Socket object but also tries to connect the socket to the remote host, you can use the object to determine whether connections to a particular port are allowed, as in Example 8-5.

Example 8-5. Find out which of the first 1024 ports seem to be hosting TCP servers on a specified host

import java.net.*;

import java.io.*;

public class LowPortScanner {

public static void main(String[] args) {

String host = args.length > 0 ? args[0] : "localhost";

for (int i = 1; i < 1024; i++) {

try {

Socket s = new Socket(host, i);

System.out.println("There is a server on port " + i + " of "

+ host);

s.close();

} catch (UnknownHostException ex) {

System.err.println(ex);

break;

} catch (IOException ex) {

// must not be a server on this port

}

}

}

}

Here’s the output this program produces on my local host (your results will vary, depending on which ports are occupied):

$ java LowPortScanner

There is a server on port 21 of localhost

There is a server on port 22 of localhost

There is a server on port 23 of localhost

There is a server on port 25 of localhost

There is a server on port 37 of localhost

There is a server on port 111 of localhost

There is a server on port 139 of localhost

There is a server on port 210 of localhost

There is a server on port 515 of localhost

There is a server on port 873 of localhost

If you’re curious about what servers are running on these ports, try experimenting with Telnet. On a Unix system, you may be able to find out which services reside on which ports by looking in the file /etc/services. If LowPortScanner finds any ports that are running servers but are not listed in /etc/services, then that’s interesting.

Although this program looks simple, it’s not without its uses. The first step to securing a system is understanding it. This program helps you understand what your system is doing so you can find (and close) possible entrance points for attackers. You may also find rogue servers: for example,LowPortScanner might tell you that there’s a server on port 800, which, on further investigation, turns out to be an HTTP server somebody is running to serve MP3 files, and which is saturating your T1.

Three constructors create unconnected sockets. These provide more control over exactly how the underlying socket behaves, for instance by choosing a different proxy server or an encryption scheme:

public Socket()

public Socket(Proxy proxy)

protected Socket(SocketImpl impl)

Picking a Local Interface to Connect From

Two constructors specify both the host and port to connect to and the interface and port to connect from:

public Socket(String host, int port, InetAddress interface, int localPort)

throws IOException, UnknownHostException

public Socket(InetAddress host, int port, InetAddress interface, int localPort)

throws IOException

This socket connects to the host and port specified in the first two arguments. It connects from the local network interface and port specified by the last two arguments. The network interface may be either physical (e.g., an Ethernet card) or virtual (a multihomed host with more than one IP address). If 0 is passed for the localPort argument, Java chooses a random available port between 1024 and 65535.

Selecting a particular network interface from which to send data is uncommon, but a need does come up occasionally. One situation where you might want to explicitly choose the local address would be on a router/firewall that uses dual Ethernet ports. Incoming connections would be accepted on one interface, processed, and forwarded to the local network from the other interface. Suppose you were writing a program to periodically dump error logs to a printer or send them over an internal mail server. You’d want to make sure you used the inward-facing network interface instead of the outward-facing network interface. For example:

try {

InetAddress inward = InetAddress.getByName("router");

Socket socket = new Socket("mail", 25, inward, 0);

// work with the sockets...

} catch (IOException ex) {

System.err.println(ex);

}

By passing 0 for the local port number, I say that I don’t care which port is used but I do want to use the network interface bound to the local hostname router.

This constructor can throw an IOException or an UnknownHostException for the same reasons as the previous constructors. In addition, it throws an IOException (probably a BindException, although again that’s just a subclass of IOException and not specifically declared in thethrows clause of this method) if the socket is unable to bind to the requested local network interface. For instance, a program running on a.example.com can’t connect from b.example.org. You could take deliberate advantage of this to restrict a compiled program to run on only a predetermined host. It would require customizing distributions for each computer and is certainly overkill for cheap products. Furthermore, Java programs are so easy to disassemble, decompile, and reverse engineer that this scheme is far from foolproof. Nonetheless, it might be part of a scheme to enforce a software license.

Constructing Without Connecting

All the constructors we’ve talked about so far both create the socket object and open a network connection to a remote host. Sometimes you want to split those operations. If you give no arguments to the Socket constructor, it has nowhere to connect to:

public Socket()

You can connect later by passing a SocketAddress to one of the connect() methods. For example:

try {

Socket socket = new Socket();

// fill in socket options

SocketAddress address = new InetSocketAddress("time.nist.gov", 13);

socket.connect(address);

// work with the sockets...

} catch (IOException ex) {

System.err.println(ex);

}

You can pass an int as the second argument to specify the number of milliseconds to wait before the connection times out:

public void connect(SocketAddress endpoint, int timeout) throws IOException

The default, 0, means wait forever.

The raison d'être for this constructor is to enable different kinds of sockets. You also need to use it to set a socket option that can only be changed before the socket connects. I’ll discuss this in Setting Socket Options. However, the prime benefit I find is that it enables me to clean up the code in try-catch-finally blocks, especially prior to Java 7. The noargs constructor throws no exceptions so it enables you to avoid the annoying null check when closing a socket in a finally block. With the original constructor, most code looks like this:

Socket socket = null;

try {

socket = new Socket(SERVER, PORT);

// work with the socket...

} catch (IOException ex) {

System.err.println(ex);

} finally {

if (socket != null) {

try {

socket.close();

} catch (IOException ex) {

// ignore

}

}

}

With the noargs constructor, it looks like this:

Socket socket = new Socket();

SocketAddress address = new InetSocketAddress(SERVER, PORT);

try {

socket.connect(address);

// work with the socket...

} catch (IOException ex) {

System.err.println(ex);

} finally {

try {

socket.close();

} catch (IOException ex) {

// ignore

}

}

That’s not quite as nice as the autoclosing version in Java 7, but it is an improvement.

Socket Addresses

The SocketAddress class represents a connection endpoint. It is an empty abstract class with no methods aside from a default constructor. At least theoretically, the SocketAddress class can be used for both TCP and non-TCP sockets. In practice, only TCP/IP sockets are currently supported and the socket addresses you actually use are all instances of InetSocketAddress.

The primary purpose of the SocketAddress class is to provide a convenient store for transient socket connection information such as the IP address and port that can be reused to create new sockets, even after the original socket is disconnected and garbage collected. To this end, theSocket class offers two methods that return SocketAddress objects (getRemoteSocketAddress() returns the address of the system being connected to and getLocalSocketAddress() returns the address from which the connection is made):

public SocketAddress getRemoteSocketAddress()

public SocketAddress getLocalSocketAddress()

Both of these methods return null if the socket is not yet connected. For example, first you might connect to Yahoo! then store its address:

Socket socket = new Socket("www.yahoo.com", 80);

SocketAddress yahoo = socket.getRemoteSocketAddress();

socket.close();

Later, you could reconnect to Yahoo! using this address:

Socket socket2 = new Socket();

socket2.connect(yahoo);

The InetSocketAddress class (which is the only subclass of SocketAddress in the JDK, and the only subclass I’ve ever encountered) is usually created with a host and a port (for clients) or just a port (for servers):

public InetSocketAddress(InetAddress address, int port)

public InetSocketAddress(String host, int port)

public InetSocketAddress(int port)

You can also use the static factory method InetSocketAddress.createUnresolved() to skip looking up the host in DNS:

public static InetSocketAddress createUnresolved(String host, int port)

InetSocketAddress has a few getter methods you can use to inspect the object:

public final InetAddress getAddress()

public final int getPort()

public final String getHostName()

Proxy Servers

The last constructor creates an unconnected socket that connects through a specified proxy server:

public Socket(Proxy proxy)

Normally, the proxy server a socket uses is controlled by the socksProxyHost and socksProxyPort system properties, and these properties apply to all sockets in the system. However, a socket created by this constructor will use the specified proxy server instead. Most notably, you can pass Proxy.NO_PROXY for the argument to bypass all proxy servers completely and connect directly to the remote host. Of course, if a firewall prevents direct connections, there’s nothing Java can do about it; and the connection will fail.

To use a particular proxy server, specify it by address. For example, this code fragment uses the SOCKS proxy server at myproxy.example.com to connect to the host login.ibiblio.org:

SocketAddress proxyAddress = new InetSocketAddress("myproxy.example.com", 1080);

Proxy proxy = new Proxy(Proxy.Type.SOCKS, proxyAddress)

Socket s = new Socket(proxy);

SocketAddress remote = new InetSocketAddress("login.ibiblio.org", 25);

s.connect(remote);

SOCKS is the only low-level proxy type Java understands. There’s also a high-level Proxy.Type.HTTP that works in the application layer rather than the transport layer and a Proxy.Type.DIRECT that represents proxyless connections.

Getting Information About a Socket

Socket objects have several properties that are accessible through getter methods:

§ Remote address

§ Remote port

§ Local address

§ Local port

Here are the getter methods for accessing these properties:

public InetAddress getInetAddress()

public int getPort()

public InetAddress getLocalAddress()

public int getLocalPort()

There are no setter methods. These properties are set as soon as the socket connects, and are fixed from there on.

The getInetAddress() and getPort() methods tell you the remote host and port the Socket is connected to; or, if the connection is now closed, which host and port the Socket was connected to when it was connected. The getLocalAddress() and getLocalPort() methods tell you the network interface and port the Socket is connected from.

Unlike the remote port, which (for a client socket) is usually a “well-known port” that has been preassigned by a standards committee, the local port is usually chosen by the system at runtime from the available unused ports. This way, many different clients on a system can access the same service at the same time. The local port is embedded in outbound IP packets along with the local host’s IP address, so the server can send data back to the right port on the client.

Example 8-6 reads a list of hostnames from the command line, attempts to open a socket to each one, and then uses these four methods to print the remote host, the remote port, the local address, and the local port.

Example 8-6. Get a socket’s information

import java.net.*;

import java.io.*;

public class SocketInfo {

public static void main(String[] args) {

for (String host : args) {

try {

Socket theSocket = new Socket(host, 80);

System.out.println("Connected to " + theSocket.getInetAddress()

+ " on port " + theSocket.getPort() + " from port "

+ theSocket.getLocalPort() + " of "

+ theSocket.getLocalAddress());

} catch (UnknownHostException ex) {

System.err.println("I can't find " + host);

} catch (SocketException ex) {

System.err.println("Could not connect to " + host);

} catch (IOException ex) {

System.err.println(ex);

}

}

}

}

Here’s the result of a sample run. I included www.oreilly.com on the command line twice in order to demonstrate that each connection was assigned a different local port, regardless of the remote host; the local port assigned to any connection is unpredictable and depends mostly on what other ports are in use. The connection to login.ibiblio.org failed because that machine does not run any servers on port 80:

$ java SocketInfo www.oreilly.com www.oreilly.com www.elharo.com

login.ibiblio.org

Connected to www.oreilly.com/208.201.239.37 on port 80 from port 49156 of

/192.168.254.25

Connected to www.oreilly.com/208.201.239.37 on port 80 from port 49157 of

/192.168.254.25

Connected to www.elharo.com/216.254.106.198 on port 80 from port 49158 of

/192.168.254.25

Could not connect to login.ibiblio.org

Closed or Connected?

The isClosed() method returns true if the socket is closed, false if it isn’t. If you’re uncertain about a socket’s state, you can check it with this method rather than risking an IOException. For example:

if (socket.isClosed()) {

// do something...

} else {

// do something else...

}

However, this is not a perfect test. If the socket has never been connected in the first place, isClosed() returns false, even though the socket isn’t exactly open.

The Socket class also has an isConnected() method. The name is a little misleading. It does not tell you if the socket is currently connected to a remote host (like if it is unclosed). Instead, it tells you whether the socket has ever been connected to a remote host. If the socket was able to connect to the remote host at all, this method returns true, even after that socket has been closed. To tell if a socket is currently open, you need to check that isConnected() returns true and isClosed() returns false. For example:

boolean connected = socket.isConnected() && ! socket.isClosed();

Finally, the isBound() method tells you whether the socket successfully bound to the outgoing port on the local system. Whereas isConnected() refers to the remote end of the socket, isBound() refers to the local end. This isn’t very important yet. Binding will become more important when we discuss server sockets in Chapter 9.

toString()

The Socket class overrides only one of the standard methods from java.lang.Object: toString(). The toString() method produces a string that looks like this:

Socket[addr=www.oreilly.com/198.112.208.11,port=80,localport=50055]

This is useful primarily for debugging. Don’t rely on this format; it may change in the future. All parts of this string are accessible directly through other methods (specifically getInetAddress(), getPort(), and getLocalPort()).

NOTE

Because sockets are transitory objects that typically last only as long as the connection they represent, there’s not much reason to store them in hash tables or compare them to each other. Therefore, Socket does not override equals() or hashCode(), and the semantics for these methods are those of the Object class. Two Socket objects are equal to each other if and only if they are the same object.

Setting Socket Options

Socket options specify how the native sockets on which the Java Socket class relies send and receive data. Java supports nine options for client-side sockets:

§ TCP_NODELAY

§ SO_BINDADDR

§ SO_TIMEOUT

§ SO_LINGER

§ SO_SNDBUF

§ SO_RCVBUF

§ SO_KEEPALIVE

§ OOBINLINE

§ IP_TOS

The funny-looking names for these options are taken from the named constants in the C header files used in Berkeley Unix where sockets were invented. Thus, they follow classic Unix C naming conventions rather than the more legible Java naming conventions. For instance, SO_SNDBUF really means “Socket Option Send Buffer Size.”

TCP_NODELAY

public void setTcpNoDelay(boolean on) throws SocketException

public boolean getTcpNoDelay() throws SocketException

Setting TCP_NODELAY to true ensures that packets are sent as quickly as possible regardless of their size. Normally, small (one-byte) packets are combined into larger packets before being sent. Before sending another packet, the local host waits to receive acknowledgment of the previous packet from the remote system. This is known as Nagle’s algorithm. The problem with Nagle’s algorithm is that if the remote system doesn’t send acknowledgments back to the local system fast enough, applications that depend on the steady transfer of small parcels of information may slow down. This issue is especially problematic for GUI programs such as games or network computer applications where the server needs to track client-side mouse movement in real time. On a really slow network, even simple typing can be too slow because of the constant buffering. Setting TCP_NODELAY to true defeats this buffering scheme, so that all packets are sent as soon as they’re ready.

setTcpNoDelay(true) turns off buffering for the socket. setTcpNoDelay(false) turns it back on. getTcpNoDelay() returns true if buffering is off and false if buffering is on. For example, the following fragment turns off buffering (that is, it turns on TCP_NODELAY) for the socket s if it isn’t already off:

if (!s.getTcpNoDelay()) s.setTcpNoDelay(true);

These two methods are each declared to throw a SocketException, which will happen if the underlying socket implementation doesn’t support the TCP_ NODELAY option.

SO_LINGER

public void setSoLinger(boolean on, int seconds) throws SocketException

public int getSoLinger() throws SocketException

The SO_LINGER option specifies what to do with datagrams that have not yet been sent when a socket is closed. By default, the close() method returns immediately; but the system still tries to send any remaining data. If the linger time is set to zero, any unsent packets are thrown away when the socket is closed. If SO_LINGER is turned on and the linger time is any positive value, the close() method blocks while waiting the specified number of seconds for the data to be sent and the acknowledgments to be received. When that number of seconds has passed, the socket is closed and any remaining data is not sent, acknowledgment or no.

These two methods each throw a SocketException if the underlying socket implementation does not support the SO_LINGER option. The setSoLinger() method can also throw an IllegalArgumentException if you try to set the linger time to a negative value. However, thegetSoLinger() method may return –1 to indicate that this option is disabled, and as much time as is needed is taken to deliver the remaining data; for example, to set the linger timeout for the Socket s to four minutes, if it’s not already set to some other value:

if (s.getTcpSoLinger() == -1) s.setSoLinger(true, 240);

The maximum linger time is 65,535 seconds, and may be smaller on some platforms. Times larger than that will be reduced to the maximum linger time. Frankly, 65,535 seconds (more than 18 hours) is much longer than you actually want to wait. Generally, the platform default value is more appropriate.

SO_TIMEOUT

public void setSoTimeout(int milliseconds) throws SocketException

public int getSoTimeout() throws SocketException

Normally when you try to read data from a socket, the read() call blocks as long as necessary to get enough bytes. By setting SO_TIMEOUT, you ensure that the call will not block for more than a fixed number of milliseconds. When the timeout expires, an InterruptedIOException is thrown, and you should be prepared to catch it. However, the socket is still connected. Although this read() call failed, you can try to read from the socket again. The next call may succeed.

Timeouts are given in milliseconds. Zero is interpreted as an infinite timeout; it is the default value. For example, to set the timeout value of the Socket object s to 3 minutes if it isn’t already set, specify 180,000 milliseconds:

if (s.getSoTimeout() == 0) s.setSoTimeout(180000);

These two methods each throw a SocketException if the underlying socket implementation does not support the SO_TIMEOUT option. The setSoTimeout() method also throws an IllegalArgumentException if the specified timeout value is negative.

SO_RCVBUF and SO_SNDBUF

TCP uses buffers to improve network performance. Larger buffers tend to improve performance for reasonably fast (say, 10Mbps and up) connections whereas slower, dial-up connections do better with smaller buffers. Generally, transfers of large, continuous blocks of data, which are common in file transfer protocols such as FTP and HTTP, benefit from large buffers, whereas the smaller transfers of interactive sessions, such as Telnet and many games, do not. Relatively old operating systems designed in the age of small files and slow networks, such as BSD 4.2, use two-kilobyte buffers. Windows XP used 17,520 byte buffers. These days, 128 kilobytes is a common default.

Maximum achievable bandwidth equals buffer size divided by latency. For example, on Windows XP suppose the latency between two hosts is half a second (500 ms). Then the bandwidth is 17520 bytes / 0.5 seconds = 35040 bytes / second = 273.75 kilobits / second. That’s the maximumspeed of any socket, regardless of how fast the network is. That’s plenty fast for a dial-up connection, and not bad for ISDN, but not really adequate for a DSL line or FIOS.

You can increase speed by decreasing latency. However, latency is a function of the network hardware and other factors outside the control of your application. On the other hand, you do control the buffer size. For example, if you increase the buffer size from 17,520 bytes to 128 kilobytes, the maximum bandwidth increases to 2 megabits per second. Double the buffer size again to 256 kilobytes, and the maximum bandwidth doubles to 4 megabits per second. Of course, the network itself has limits on maximum bandwidth. Set the buffer too high and your program will try to send and receive data faster than the network can handle, leading to congestion, dropped packets, and slower performance. Thus, when you want maximum bandwidth, you need to match the buffer size to the latency of the connection so it’s a little less than the bandwidth of the network.

TIP

You can use ping to check the latency to a particular host manually, or you can time a call to InetAddress.isReachable() from inside your program.

The SO_RCVBUF option controls the suggested send buffer size used for network input. The SO_SNDBUF option controls the suggested send buffer size used for network output:

public void setReceiveBufferSize(int size)

throws SocketException, IllegalArgumentException

public int getReceiveBufferSize() throws SocketException

public void setSendBufferSize(int size)

throws SocketException, IllegalArgumentException

public int getSendBufferSize() throws SocketException

Although it looks like you should be able to set the send and receive buffers independently, the buffer is usually set to the smaller of these two. For instance, if you set the send buffer to 64K and the receive buffer to 128K, you’ll have 64K as both the send and receive buffer size. Java will report that the receive buffer is 128K, but the underlying TCP stack will really be using 64K.

The setReceiveBufferSize()/setSendBufferSize methods suggest a number of bytes to use for buffering output on this socket. However, the underlying implementation is free to ignore or adjust this suggestion. In particular, Unix and Linux systems often specify a maximum buffer size, typically 64K or 256K, and do not allow any socket to have a larger one. If you attempt to set a larger value, Java will just pin it to the maximum possible buffer size. On Linux, it’s not unheard of for the underlying implementation to double the requested size. For example, if you ask for a 64K buffer, you may get a 128K buffer instead.

These methods throw an IllegalArgumentException if the argument is less than or equal to zero. Although they’re also declared to throw SocketException, they probably won’t in practice, because a SocketException is thrown for the same reason asIllegalArgumentException and the check for the IllegalArgumentException is made first.

In general, if you find your application is not able to fully utilize the available bandwidth (e.g., you have a 25 Mbps Internet connection, but your data is transferring at a piddling 1.5 Mbps) try increasing the buffer sizes. By contrast, if you’re dropping packets and experiencing congestion, try decreasing the buffer size. However, most of the time, unless you’re really taxing the network in one direction or the other, the defaults are fine. In particular, modern operating systems use TCP window scaling (not controllable from Java) to dynamically adjust buffer sizes to fit the network. As with almost any performance tuning advice, the rule of thumb is not to do it until you’ve measured a problem. And even then you may well get more speed by increasing the maximum allowed buffer size at the operating system level than by adjusting the buffer sizes of individual sockets.

SO_KEEPALIVE

If SO_KEEPALIVE is turned on, the client occasionally sends a data packet over an idle connection (most commonly once every two hours), just to make sure the server hasn’t crashed. If the server fails to respond to this packet, the client keeps trying for a little more than 11 minutes until it receives a response. If it doesn’t receive a response within 12 minutes, the client closes the socket. Without SO_KEEPALIVE, an inactive client could live more or less forever without noticing that the server had crashed. These methods turn SO_KEEPALIVE on and off and determine its current state:

public void setKeepAlive(boolean on) throws SocketException

public boolean getKeepAlive() throws SocketException

The default for SO_KEEPALIVE is false. This code fragment turns SO_KEEPALIVE off, if it’s turned on:

if (s.getKeepAlive()) s.setKeepAlive(false);

OOBINLINE

TCP includes a feature that sends a single byte of “urgent” data out of band. This data is sent immediately. Furthermore, the receiver is notified when the urgent data is received and may elect to process the urgent data before it processes any other data that has already been received. Java supports both sending and receiving such urgent data. The sending method is named, obviously enough, sendUrgentData():

public void sendUrgentData(int data) throws IOException

This method sends the lowest-order byte of its argument almost immediately. If necessary, any currently cached data is flushed first.

How the receiving end responds to urgent data is a little confused, and varies from one platform and API to the next. Some systems receive the urgent data separately from the regular data. However, the more common, more modern approach is to place the urgent data in the regular received data queue in its proper order, tell the application that urgent data is available, and let it hunt through the queue to find it.

By default, Java ignores urgent data received from a socket. However, if you want to receive urgent data inline with regular data, you need to set the OOBINLINE option to true using these methods:

public void setOOBInline(boolean on) throws SocketException

public boolean getOOBInline() throws SocketException

The default for OOBINLINE is false. This code fragment turns OOBINLINE on, if it’s turned off:

if (!s.getOOBInline()) s.setOOBInline(true);

Once OOBINLINE is turned on, any urgent data that arrives will be placed on the socket’s input stream to be read in the usual way. Java does not distinguish it from nonurgent data. That makes it less than ideally useful, but if you have a particular byte (e.g., a Ctrl-C) that has special meaning to your program and never shows up in the regular data stream, then this would enable you to send it more quickly.

SO_REUSEADDR

When a socket is closed, it may not immediately release the local port, especially if a connection was open when the socket was closed. It can sometimes wait for a small amount of time to make sure it receives any lingering packets that were addressed to the port that were still crossing the network when the socket was closed. The system won’t do anything with any of the late packets it receives. It just wants to make sure they don’t accidentally get fed into a new process that has bound to the same port.

This isn’t a big problem on a random port, but it can be an issue if the socket has bound to a well-known port because it prevents any other socket from using that port in the meantime. If the SO_REUSEADDR is turned on (it’s turned off by default), another socket is allowed to bind to the port even while data may be outstanding for the previous socket.

In Java this option is controlled by these two methods:

public void setReuseAddress(boolean on) throws SocketException

public boolean getReuseAddress() throws SocketException

For this to work, setReuseAddress() must be called before the new socket binds to the port. This means the socket must be created in an unconnected state using the noargs constructor; then setReuseAddress(true) is called, and the socket is connected using the connect() method. Both the socket that was previously connected and the new socket reusing the old address must set SO_REUSEADDR to true for it to take effect.

IP_TOS Class of Service

Different types of Internet service have different performance needs. For instance, video chat needs relatively high bandwidth and low latency for good performance, whereas email can be passed over low-bandwidth connections and even held up for several hours without major harm. VOIP needs less bandwidth than video but minimum jitter. It might be wise to price the different classes of service differentially so that people won’t ask for the highest class of service automatically. After all, if sending an overnight letter cost the same as sending a package via media mail, we’d all just use FedEx overnight, which would quickly become congested and overwhelmed. The Internet is no different.

The class of service is stored in an eight-bit field called IP_TOS in the IP header. Java lets you inspect and set the value a socket places in this field using these two methods:

public int getTrafficClass() throws SocketException

public void setTrafficClass(int trafficClass) throws SocketException

The traffic class is given as an int between 0 and 255. Because this value is copied to an eight-bit field in the TCP header, only the low order byte of this int is used; and values outside this range cause IllegalArgumentExceptions.

In 21st-century TCP stacks, the high-order six bits of this byte contain a Differentiated Services Code Point (DSCP) value and the low-order two bits contain an Explicit Congestion Notification (ECN) value. The DSCP thus has room for up to 26 different traffic classes. However, it’s up to individual networks and routers to specify exactly what the 64 different possible DSCP values mean. The four values shown in Table 8-1 are fairly common.

Table 8-1. Common DSCP values and interpretations

PHB (Per Hop Behavior)

Binary value

Purpose

Default

00000

Best-effort traffic.

Expedited Forwarding (EF)

101110

Low-loss, low-delay, low-jitter traffic. Often limited to 30% or less of network capacity.

Assured Forwarding (AF)

multiple

Assured delivery up to a specified rate.

Class Selector

xxx000

Backward compatibility with the IPv4 TOS header, as stored in the first three bits.

For example, the Expedited Forwarding PHB is a good choice for for VOIP. EF traffic is often given strict priority queuing above all other traffic classes. This code fragment sets a socket to use Expedited Forwarding by setting the traffic class to 10111000:

Socket s = new Socket("www.yahoo.com", 80);

s.setTrafficClass(0xB8); // 10111000 in binary

Remember the low-order two bits of this number are Explicit Congestion Notification, and should be set to zero.

Assured Forwarding is actually 12 different DSCP values divided into four classes as shown in Table 8-2. The purpose here is to allow a sender to express relative preferences for which packets to drop when the network is congested. Within a class, packets with lower priority are dropped before packets with a higher priority. Between classes, packest from a higher-priority class are given preference, though lower-priority classes are not starved completely.

Table 8-2. Assured forwarding priority classes

Class 1 (lowest priority)

Class 2

Class 3

Class 4 (highest priority)

Low Drop Rate

AF11 (001010)

AF21 (010010)

AF31 (011010)

AF41 (100010)

Medium Drop Rate

AF12 (001100)

AF22 (010100)

AF32 (011100)

AF42 (100100)

High Drop Rate

AF13 (001110)

AF23 (010110)

AF33 (011110)

AF43 (100110)

For example, the following code fragment sets up three sockets with different forwarding characteristics. If the network gets congested enough, socket 3, in class 4 with a high drop rate, will send most of its data. Socket 1, in class 1 with a low drop rate, will also get to send data, though not as quickly as socket 1; and socket 3, with a high drop rate also in class 1 will be blocked completely until the congestion eases up enough that socket 2 is no longer dropping packets:

Socket s1 = new Socket("www.example.com", 80);

s1.setTrafficClass(0x26); // 00100110 in binary

Socket s2 = new Socket("www.example.com", 80);

s2.setTrafficClass(0x0A); // 00001010 in binary

Socket s3 = new Socket("www.example.com", 80);

s3.setTrafficClass(0x0E); // 00001110 in binary

DSCP values are not hard and fast guarantees of service. In practice, although DSCP values are respected on some networks internally, any time a packet crosses ISPs, this information is almost always ignored.

WARNING

The JavaDoc for these options is severely out of date, and describes a quality of service scheme based on bit fields for four traffic classes: low cost, high reliability, maximum throughput, and minimum delay. This scheme was never widely implemented and probably hasn’t been used in this century. The specific TCP header where these values were stored has been repurposed for the DSCP and EN values described here. However, in the unlikely event you need it, you can put these values in the high-order three bits of a class selector PHB, followed by zero bits.

The underlying socket implementation is not required to respect any of these requests. They only provide a hint to the TCP stack about the desired policy. Many implementations ignore these values completely. Android in particular treats the setTrafficClass() method as a no-op. If the TCP stack is unable to provide the requested class of service, it may, but is not required to, throw a SocketException.

As an alternative way to express preferences, the setPerformancePreferences() method assigns relative preferences to connection time, latency, and bandwidth:

public void setPerformancePreferences(int connectionTime,

int latency, int bandwidth)

For instance, if connectionTime is 2, latency is 1, and bandwidth is 3, then maximum bandwidth is the most important characteristic, minimum latency is the least important, and connection time is in the middle. If connectionTime is 2, latency is 2, and bandwidth is 3, then maximum bandwidth is the most important characteristic, while minimum latency and connection time are equally important. Exactly how any given VM implements this is implementation dependent. Indeed, it may be a no-op in some implementations.

Socket Exceptions

Most methods of the Socket class are declared to throw IOException or its subclass, java.net.SocketException:

public class SocketException extends IOException

However, knowing that a problem occurred is often not sufficient to deal with the problem. Did the remote host refuse the connection because it was busy? Did the remote host refuse the connection because no service was listening on the port? Did the connection attempt timeout because of network congestion or because the host was down? There are several subclasses of SocketException that provide more information about what went wrong and why:

public class BindException extends SocketException

public class ConnectException extends SocketException

public class NoRouteToHostException extends SocketException

A BindException is thrown if you try to construct a Socket or ServerSocket object on a local port that is in use or that you do not have sufficient privileges to use. A ConnectException is thrown when a connection is refused at the remote host, which usually happens because the host is busy or no process is listening on that port. Finally, a NoRouteToHostException indicates that the connection has timed out.

The java.net package also includes ProtocolException, which is a direct subclass of IOException:

public class ProtocolException extends IOException

This is thrown when data is received from the network that somehow violates the TCP/IP specification.

None of these exception classes have any special methods you wouldn’t find in any other exception class, but you can take advantage of these subclasses to provide more informative error messages or to decide whether retrying the offending operation is likely to be successful.

Sockets in GUI Applications

The HotJava web browser was the first large-scale Java GUI network client. HotJava has been discontinued, but there are still numerous network-aware client applications written in Java, including the Eclipse IDE and the Frostwire BitTorrent client. It is completely possible to write commercial-quality client applications in Java; and it is especially possible to write network-aware applications, both clients and servers. This section demonstrates a network client, whois, to illustrate this point; and to discuss the special considerations that arise when integrating networking code with Swing applications. The example stops short of what could be done, but only in the user interface. All the necessary networking code is present. Indeed, once again you find out that network code is easy; it’s user interfaces that are hard.

Whois

Whois is a simple directory service protocol defined in RFC 954; it was originally designed to keep track of administrators responsible for Internet hosts and domains. A whois client connects to one of several central servers and requests directory information for a person or persons; it can usually give you a phone number, an email address, and a snail mail address (not necessarily current ones, though). With the explosive growth of the Internet, flaws have become apparent in the whois protocol, most notably its centralized nature. A more complex replacement called whois++ is documented in RFCs 1913 and 1914 but has not been widely implemented.whois directory service protocol)

Let’s begin with a simple client to connect to a whois server. The basic structure of the whois protocol is:

1. The client opens a TCP socket to port 43 on the server.

2. The client sends a search string terminated by a carriage return/linefeed pair (\r\n). The search string can be a name, a list of names, or a special command, as discussed shortly. You can also search for domain names, like www.oreilly.com or netscape.com, which give you information about a network.

3. The server sends an unspecified amount of human-readable information in response to the command and closes the connection.

4. The client displays this information to the user.

The search string the client sends has a fairly simple format. At its most basic, it’s just the name of the person you’re searching for. Here’s a simple whois search for “Harold”:

$ telnet whois.internic.net 43

Trying 199.7.50.74...

Connected to whois.internic.net.

Escape character is '^]'.

Harold

Whois Server Version 2.0

Domain names in the .com and .net domains can now be registered

with many different competing registrars. Go to http://www.internic.net

for detailed information.

HAROLD.LUCKYLAND.ORG

HAROLD.FRUGAL.COM

HAROLD.NET

HAROLD.COM

To single out one record, look it up with "xxx", where xxx is one of the

of the records displayed above. If the records are the same, look them up

with "=xxx" to receive a full display for each record.

>>> Last update of whois database: Sat, 30 Mar 2013 15:15:05 UTC <<<

...

Connection closed by foreign host.

Although the previous input has a pretty clear format, that format is regrettably nonstandard. Different whois servers can and do send decidedly different output. For example, here are the first couple of results from the same search at the main French whois server, whois.nic.fr:

% telnet whois.nic.fr 43

telnet whois.nic.fr 43

Trying 192.134.4.18...

Connected to winter.nic.fr.

Escape character is '^]'.

Harold

Tous droits reserves par copyright.

Voir http://www.nic.fr/outils/dbcopyright.html

Rights restricted by copyright.

See http://www.nic.fr/outils/dbcopyright.html

person: Harold Potier

address: ARESTE

address: 154 Avenue Du Brezet

address: 63000 Clermont-Ferrand

address: France

phone: +33 4 73 42 67 67

fax-no: +33 4 73 42 67 67

nic-hdl: HP4305-FRNIC

mnt-by: OLEANE-NOC

changed: hostmaster@oleane.net 20000510

changed: migration-dbm@nic.fr 20001015

source: FRNIC

person: Harold Israel

address: LE PARADIS LATIN

address: 28 rue du Cardinal Lemoine

address: Paris, France 75005 FR

phone: +33 1 43252828

fax-no: +33 1 43296363

e-mail: info@cie.fr

nic-hdl: HI68-FRNIC

notify: info@cie.fr

changed: registrar@ns.il 19991011

changed: migration-dbm@nic.fr 20001015

source: FRNIC

Here each complete record is returned rather than just a list of sites. Other whois servers may use still other formats. This protocol is not at all designed for machine processing. You pretty much have to write new code to handle the output of each different whois server. However, regardless of the output format, each response likely contains a handle, which in the Internic output is a domain name, and in the nic.fr output is in the nic-hdl field. Handles are guaranteed to be unique, and are used to get more specific information about a person or a network. If you search for a handle, you will get at most one match. If your search only has one match, either because you’re lucky or you’re searching for a handle, the server returns a more detailed record. Here’s a search for oreilly.com. Because there is only one oreilly.com in the database, the server returns all the information it has on this domain:

% telnet whois.internic.net 43

Trying 198.41.0.6...

Connected to whois.internic.net.

Escape character is '^]'.

oreilly.com

Whois Server Version 1.3

Domain names in the .com and .net domains can now be registered

with many different competing registrars. Go to http://www.internic.net

for detailed information.

Domain Name: OREILLY.COM

Registrar: BULKREGISTER, LLC.

Whois Server: whois.bulkregister.com

Referral URL: http://www.bulkregister.com

Name Server: NS1.SONIC.NET

Name Server: NS.OREILLY.COM

Status: ACTIVE

Updated Date: 17-oct-2002

Creation Date: 27-may-1997

Expiration Date: 26-may-2004

>>> Last update of whois database: Tue, 16 Dec 2003 18:36:16 EST <<<

...

Connection closed by foreign host.

The whois protocol supports several flags you can use to restrict or expand your search. For example, if you know you want to search for a person named “Elliott” but you aren’t sure whether he spells his name “Elliot,” “Elliott,” or perhaps even something as unlikely as “Elliotte,” you would type:

% whois Person Partial Elliot

This tells the whois server that you want only matches for people (not domains, gateways, groups, or the like) whose names begin with the letters “Elliot.” Unfortunately, you need to do a separate search if you want to find someone who spells his name “Eliot.” The rules for modifying a search are summarized in Table 8-3. Each prefix should be placed before the search string on the command line.

Table 8-3. Whois prefixes

Prefix

Meaning

Domain

Find only domain records.

Gateway

Find only gateway records.

Group

Find only group records.

Host

Find only host records.

Network

Find only network records.

Organization

Find only organization records.

Person

Find only person records.

ASN

Find only autonomous system number records.

Handle or !

Search only for matching handles.

Mailbox or @

Search only for matching email addresses.

Name or :

Search only for matching names.

Expand or *

Search only for group records and show all individuals in that group.

Full or =

Show complete record for each match.

Partial or suffix

Match records that start with the given string.

Summary or $

Show just the summary, even if there’s only one match.

SUBdisplay or %

Show the users of the specified host, the hosts on the specified network, etc.

These keywords are all useful, but they’re way too much trouble to remember. In fact, most people don’t even know that they exist. They just type “whois Harold” at the command line and sort through the mess that comes back. A good whois client doesn’t rely on users remembering arcane keywords; rather, it shows them the options. Supplying this requires a graphical user interface for end users and a better API for client programmers.

A Network Client Library

It’s best to think of network protocols like whois in terms of the bits and bytes that move across the network, whether as packets, datagrams, or streams. No network protocol neatly fits into a GUI (with the arguable exception of the Remote Framebuffer Protocol used by VNC and X11). It’s usually best to encapsulate the network code into a separate library that the GUI code can invoke as needed.

Example 8-7 is a reusable Whois class. Two fields define the state of each Whois object: host, an InetAddress object, and port, an int. Together, these define the server that this particular Whois object connects to. Five constructors set these fields from various combinations of arguments. Furthermore, the host can be changed using the setHost() method.

The main functionality of the class is in one method, lookUpNames(). The lookUpNames() method returns a String containing the whois response to a given query. The arguments specify the string to search for, what kind of record to search for, which database to search in, and whether an exact match is required. I could have used strings or int constants to specify the kind of record to search for and the database to search in, but because there are only a small number of valid values, lookUpNames() defines enums with a fixed number of members instead. This solution provides much stricter compile-time type-checking and guarantees the Whois class won’t have to handle an unexpected value.

Example 8-7. The Whois class

import java.net.*;

import java.io.*;

public class Whois {

public final static int DEFAULT_PORT = 43;

public final static String DEFAULT_HOST = "whois.internic.net";

private int port = DEFAULT_PORT;

private InetAddress host;

public Whois(InetAddress host, int port) {

this.host = host;

this.port = port;

}

public Whois(InetAddress host) {

this(host, DEFAULT_PORT);

}

public Whois(String hostname, int port)

throws UnknownHostException {

this(InetAddress.getByName(hostname), port);

}

public Whois(String hostname) throws UnknownHostException {

this(InetAddress.getByName(hostname), DEFAULT_PORT);

}

public Whois() throws UnknownHostException {

this(DEFAULT_HOST, DEFAULT_PORT);

}

// Items to search for

public enum SearchFor {

ANY("Any"), NETWORK("Network"), PERSON("Person"), HOST("Host"),

DOMAIN("Domain"), ORGANIZATION("Organization"), GROUP("Group"),

GATEWAY("Gateway"), ASN("ASN");

private String label;

private SearchFor(String label) {

this.label = label;

}

}

// Categories to search in

public enum SearchIn {

ALL(""), NAME("Name"), MAILBOX("Mailbox"), HANDLE("!");

private String label;

private SearchIn(String label) {

this.label = label;

}

}

public String lookUpNames(String target, SearchFor category,

SearchIn group, boolean exactMatch) throws IOException {

String suffix = "";

if (!exactMatch) suffix = ".";

String prefix = category.label + " " + group.label;

String query = prefix + target + suffix;

Socket socket = new Socket();

try {

SocketAddress address = new InetSocketAddress(host, port);

socket.connect(address);

Writer out

= new OutputStreamWriter(socket.getOutputStream(), "ASCII");

BufferedReader in = new BufferedReader(new

InputStreamReader(socket.getInputStream(), "ASCII"));

out.write(query + "\r\n");

out.flush();

StringBuilder response = new StringBuilder();

String theLine = null;

while ((theLine = in.readLine()) != null) {

response.append(theLine);

response.append("\r\n");

}

return response.toString();

} finally {

socket.close();

}

}

public InetAddress getHost() {

return this.host;

}

public void setHost(String host)

throws UnknownHostException {

this.host = InetAddress.getByName(host);

}

}

Figure 8-1 shows one possible interface for a graphical whois client that depends on Example 8-7 for the actual network connections. This interface has a text field to enter the name to be searched for and a checkbox to determine whether the match should be exact or partial. A group of radio buttons lets users specify which group of records they want to search. Another group of radio buttons chooses the fields that should be searched. By default, this client searches all fields of all records for an exact match.

A graphical whois client

Figure 8-1. A graphical whois client

When a user enters a string in the Whois: search box and presses Enter or clicks the Find button, the program makes a connection to the whois server and retrieves records that match that string. These are placed in the text area in the bottom of the window. Initially, the server is set towhois.internic.net, but the user is free to change this setting. Example 8-8 is the program that produces this interface.

Example 8-8. A graphical Whois client interface

import java.awt.*;

import java.awt.event.*;

import java.net.*;

import javax.swing.*;

public class WhoisGUI extends JFrame {

private JTextField searchString = new JTextField(30);

private JTextArea names = new JTextArea(15, 80);

private JButton findButton = new JButton("Find");;

private ButtonGroup searchIn = new ButtonGroup();

private ButtonGroup searchFor = new ButtonGroup();

private JCheckBox exactMatch = new JCheckBox("Exact Match", true);

private JTextField chosenServer = new JTextField();

private Whois server;

public WhoisGUI(Whois whois) {

super("Whois");

this.server = whois;

Container pane = this.getContentPane();

Font f = new Font("Monospaced", Font.PLAIN, 12);

names.setFont(f);

names.setEditable(false);

JPanel centerPanel = new JPanel();

centerPanel.setLayout(new GridLayout(1, 1, 10, 10));

JScrollPane jsp = new JScrollPane(names);

centerPanel.add(jsp);

pane.add("Center", centerPanel);

// You don't want the buttons in the south and north

// to fill the entire sections so add Panels there

// and use FlowLayouts in the Panel

JPanel northPanel = new JPanel();

JPanel northPanelTop = new JPanel();

northPanelTop.setLayout(new FlowLayout(FlowLayout.LEFT));

northPanelTop.add(new JLabel("Whois: "));

northPanelTop.add("North", searchString);

northPanelTop.add(exactMatch);

northPanelTop.add(findButton);

northPanel.setLayout(new BorderLayout(2,1));

northPanel.add("North", northPanelTop);

JPanel northPanelBottom = new JPanel();

northPanelBottom.setLayout(new GridLayout(1,3,5,5));

northPanelBottom.add(initRecordType());

northPanelBottom.add(initSearchFields());

northPanelBottom.add(initServerChoice());

northPanel.add("Center", northPanelBottom);

pane.add("North", northPanel);

ActionListener al = new LookupNames();

findButton.addActionListener(al);

searchString.addActionListener(al);

}

private JPanel initRecordType() {

JPanel p = new JPanel();

p.setLayout(new GridLayout(6, 2, 5, 2));

p.add(new JLabel("Search for:"));

p.add(new JLabel(""));

JRadioButton any = new JRadioButton("Any", true);

any.setActionCommand("Any");

searchFor.add(any);

p.add(any);

p.add(this.makeRadioButton("Network"));

p.add(this.makeRadioButton("Person"));

p.add(this.makeRadioButton("Host"));

p.add(this.makeRadioButton("Domain"));

p.add(this.makeRadioButton("Organization"));

p.add(this.makeRadioButton("Group"));

p.add(this.makeRadioButton("Gateway"));

p.add(this.makeRadioButton("ASN"));

return p;

}

private JRadioButton makeRadioButton(String label) {

JRadioButton button = new JRadioButton(label, false);

button.setActionCommand(label);

searchFor.add(button);

return button;

}

private JRadioButton makeSearchInRadioButton(String label) {

JRadioButton button = new JRadioButton(label, false);

button.setActionCommand(label);

searchIn.add(button);

return button;

}

private JPanel initSearchFields() {

JPanel p = new JPanel();

p.setLayout(new GridLayout(6, 1, 5, 2));

p.add(new JLabel("Search In: "));

JRadioButton all = new JRadioButton("All", true);

all.setActionCommand("All");

searchIn.add(all);

p.add(all);

p.add(this.makeSearchInRadioButton("Name"));

p.add(this.makeSearchInRadioButton("Mailbox"));

p.add(this.makeSearchInRadioButton("Handle"));

return p;

}

private JPanel initServerChoice() {

final JPanel p = new JPanel();

p.setLayout(new GridLayout(6, 1, 5, 2));

p.add(new JLabel("Search At: "));

chosenServer.setText(server.getHost().getHostName());

p.add(chosenServer);

chosenServer.addActionListener( new ActionListener() {

@Override

public void actionPerformed(ActionEvent event) {

try {

server = new Whois(chosenServer.getText());

} catch (UnknownHostException ex) {

JOptionPane.showMessageDialog(p,

ex.getMessage(), "Alert", JOptionPane.ERROR_MESSAGE);

}

}

} );

return p;

}

private class LookupNames implements ActionListener {

@Override

public void actionPerformed(ActionEvent event) {

names.setText("");

SwingWorker<String, Object> worker = new Lookup();

worker.execute();

}

}

private class Lookup extends SwingWorker<String, Object> {

@Override

protected String doInBackground() throws Exception {

Whois.SearchIn group = Whois.SearchIn.ALL;

Whois.SearchFor category = Whois.SearchFor.ANY;

String searchForLabel = searchFor.getSelection().getActionCommand();

String searchInLabel = searchIn.getSelection().getActionCommand();

if (searchInLabel.equals("Name")) group = Whois.SearchIn.NAME;

else if (searchInLabel.equals("Mailbox")) {

group = Whois.SearchIn.MAILBOX;

} else if (searchInLabel.equals("Handle")) {

group = Whois.SearchIn.HANDLE;

}

if (searchForLabel.equals("Network")) {

category = Whois.SearchFor.NETWORK;

} else if (searchForLabel.equals("Person")) {

category = Whois.SearchFor.PERSON;

} else if (searchForLabel.equals("Host")) {

category = Whois.SearchFor.HOST;

} else if (searchForLabel.equals("Domain")) {

category = Whois.SearchFor.DOMAIN;

} else if (searchForLabel.equals("Organization")) {

category = Whois.SearchFor.ORGANIZATION;

} else if (searchForLabel.equals("Group")) {

category = Whois.SearchFor.GROUP;

} else if (searchForLabel.equals("Gateway")) {

category = Whois.SearchFor.GATEWAY;

} else if (searchForLabel.equals("ASN")) {

category = Whois.SearchFor.ASN;

}

server.setHost(chosenServer.getText());

return server.lookUpNames(searchString.getText(),

category, group, exactMatch.isSelected());

}

@Override

protected void done() {

try {

names.setText(get());

} catch (Exception ex) {

JOptionPane.showMessageDialog(WhoisGUI.this,

ex.getMessage(), "Lookup Failed", JOptionPane.ERROR_MESSAGE);

}

}

}

public static void main(String[] args) {

try {

Whois server = new Whois();

WhoisGUI a = new WhoisGUI(server);

a.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);

a.pack();

EventQueue.invokeLater(new FrameShower(a));

} catch (UnknownHostException ex) {

JOptionPane.showMessageDialog(null, "Could not locate default host "

+ Whois.DEFAULT_HOST, "Error", JOptionPane.ERROR_MESSAGE);

}

}

private static class FrameShower implements Runnable {

private final Frame frame;

FrameShower(Frame frame) {

this.frame = frame;

}

@Override

public void run() {

frame.setVisible(true);

}

}

}

The main() method is the usual block of code to start up a standalone application. It constructs a Whois object and then uses that to construct a WhoisGUI object. Then the WhoisGUI() constructor sets up the Swing interface. There’s a lot of redundant code here, so it’s broken out into the private methods initSearchFields(), initServerChoice(), makeSearchInRadioButton(), and makeSearchForRadioButton(). As usual with LayoutManager-based interfaces, the setup is fairly involved. Because you’d probably use a visual designer to build such an application, I won’t describe it in detail here.

When the constructor returns, the main() method attaches an anonymous inner class to the window that will close the application when the window is closed. (This isn’t in the constructor because other programs that use this class may not want to exit the program when the window closes.)main() then packs and shows the window. To avoid an obscure race condition that can lead to deadlock this needs to be done in the event dispatch thread; hence the FrameShower inner class that implements Runnable and the call to EventQueue.invokeLater(). From that point on, all activity takes place in the event dispatch thread.

The first event this program must respond to is the user typing a name in the Whois: search box and either clicking the Find button or hitting Enter. In this case, the LookupNames inner class sets the main text to the empty string and executes a SwingWorker to make the network connection.SwingWorker (introduced in Java 6) is a really important class to learn if you’re going to write GUI applications that access the network, or for that matter perform any I/O at all.

The problem SwingWorker solves is this. In any Java GUI application there are two rules you must follow in order to avoid deadlock and slowness:

§ All updates to Swing components happen on the event dispatch thread.

§ No slow blocking operations, especially I/O, happen on the event dispatch thread. Otherwise a slow-to-respond server can hang the entire application.

These two rules are at loggerheads for network- and I/O-heavy code because the part of the code that performs the I/O can’t update the GUI and vice versa. These have to happen in two different threads.

There are several ways to sidestep this paradox, but prior to Java 6 they’re all quite complex. In Java 6 and later, however, the solution is easy. Define a subclass of SwingWorker and override two methods:

1. The doInBackground() method performs the long-running, I/O-heavy operation. It does not interact with the GUI. It can return any convenient type and throw any exception.

2. The done() method is automatically invoked on the event dispatch thread after the doInBackground() method returns, so it can update the GUI. This method can call the get() method to retrieve the return value calculated by doInBackground().

Example 8-8 uses an inner class named Lookup as its SwingWorker. The doInBackground() method talks to the whois server, and returns the server’s response as a String. The done() method updates the names text area with the server’s response.

The second event this program must respond to is the user typing a new host in the server text field. In this case, an anonymous inner class tries to construct a new Whois object and stores it in the server field. If it fails (e.g., because the user mistyped the hostname), the old server is restored. An alert box informs the user of this event.

This is not a perfect client by any means. The most glaring omission is that it doesn’t provide a way to save the data and quit the program. However, it does demonstrate how to safely make network connections from a GUI program without blocking the event dispatch thread.