Java Networking

 


 

Overview

Every time you execute an applet in a browser, you are accessing a network resource. Unlike Java applications, which must be run on the local machine from the command-line, applets can reside on the internet, and thus can be located anywhere in the world.

On the browser side, the address of the applet is encoded within the <APPLET> tag.

On the server side, java.net classes are used to parse and find the addresses of network resources. The URL, URLConnection, Socket, and ServerSocket classes all use TCP. The DatagramPacket, DatagramSocket, and MulticastSocket classes use UDP.

 


 

Creating a URL

URL objects are generally created using Strings. For example....
http://www.yahoo.com/

...can be built using...

URL yahoo = new URL("http://www.yahoo.com/");

In your Java programs, you can create a URL object from a relative URL specification. For example, suppose you know two URLs at the Yahoo site:

http://www.yahoo.com/pages/Yahoo.game.html
http://www.yahoo.com/pages/Yahoo.net.html

You can create URL objects for these pages relative to their common base URL: http://www.yahoo.com/pages/ like this:

URL yahoo = new URL("http://www.yahoo.com/pages/");
URL yahooGames = new URL(yahoo, "Yahoo.game.html");
URL yahooNetwork = new URL(yahoo, "Yahoo.net.html");

This code snippet uses the URL constructor that lets you create a URL object from another URL object (the base) and a relative URL specification. The general form of this constructor is:

URL URL(baseURL, String relativeURL)

The first argument is a URL object that specifies the base of the new URL. The second argument is a String that specifies the rest of the resource name relative to the base. If baseURL is null, then this constructor treats relativeURL like an absolute URL specification. Conversely, if relativeURL is an absolute URL specification, then the constructor ignores baseURL.

This constructor is also useful for creating URL objects for named anchors (also called references) within a file. For example, suppose the Yahoo.network.html file has a named anchor called BOTTOM at the bottom of the file. You can use the relative URL constructor to create a URL object for it like this:

URL yahooNetworkBottom = new URL(yahooNetwork, "#BOTTOM");

 

 

Other URL Constructors

The URL class provides two additional constructors for creating a URL object. These constructors are useful when you are working with URLs such as HTTP URLs, that have host name, filename, port number, and reference components in the resource name portion of the URL. These two constructors are useful when you do not have a String containing the complete URL specification, but you do know various components of the URL.

For example, suppose you design a network browsing panel similar to a file browsing panel that allows users to choose the protocol, host name, port number, and filename. You can construct a URL from the panel's components. The first constructor creates a URL object from a protocol, host name, and filename. The following code snippet creates a URL to the Yahoo.net.html file at the Yahoo site:

new URL("http", "www.yahoo.com", "/pages/Yahoo.net.html");

This is equivalent to

new URL("http://www.yahoo.com/pages/Yahoo.net.html");

The first argument is the protocol, the second is the host name, and the last is the pathname of the file. Note that the filename contains a forward slash at the beginning. This indicates that the filename is specified from the root of the host.

of arguments used in the previous constructor:

URL yahoo = new URL("http", "www.yahoo.com", 80,
                       "pages/Yahoo.network.html");

This creates a URL object for the following URL:

http://www.yahoo.com:80/pages/Yahoo.network.html

If you construct a URL object using one of these constructors, you can get a String containing the complete URL address by using the URL object's toString method or the equivalent toExternalForm method.

 

 

MalformedURLException

Each of the four URL constructors throws a MalformedURLException if the arguments to the constructor refer to a null or unknown protocol. Typically, you want to catch and handle this exception by embedding your URL constructor statements in a try/catch pair, like this:

try {
    URL myURL = new URL(. . .)
} catch  MalformedURLException(e) {
    . . .
    // exception handler code here
    . . .
}

URLs are "write-once" objects. Once you've created a URL object, you cannot change any of its attributes (protocol, host name, filename, or port number).

 


 

Parsing a URL

The URL class provides several methods that let you query URL objects. You can get the protocol, host name, port number, and filename from a URL using these accessor methods:

getProtocol
Returns the protocol identifier component of the URL.
getHost
Returns the host name component of the URL.
getPort
Returns the port number component of the URL. The getPort method returns an integer that is the port number. If the port is not set, getPort returns -1.
getFile
Returns the filename component of the URL.
getRef
Returns the reference component of the URL.

Remember that not all URL addresses contain these components. The URL class provides these methods because HTTP URLs do contain these components and are perhaps the most commonly used URLs The URL class is somewhat HTTP-centric.

You can use these getXXX methods to get information about the URL regardless of the constructor that you used to create the URL object.

The URL class, along with these accessor methods, frees you from ever having to parse URLs again! Given any string specification of a URL, just create a new URL object and call any of the accessor methods for the information you need. This small example program creates a URL from a string specification and then uses the URL object's accessor methods to parse the URL:

import java.net.*;
import java.io.*;

public class ParseURL 
{
    public static void main(String[] args) throws Exception 
    {
        URL aURL = new URL("http://java.sun.com:80/docs/books/"
                           + "tutorial/index.html#DOWNLOADING");
        System.out.println("protocol = " + aURL.getProtocol());
        System.out.println("host = " + aURL.getHost());
        System.out.println("filename = " + aURL.getFile());
        System.out.println("port = " + aURL.getPort());
        System.out.println("ref = " + aURL.getRef());
    }
}

Here's the output displayed by the program:

protocol = http
host = java.sun.com
filename = /docs/books/tutorial/index.html
port = 80
ref = DOWNLOADING

 


 

Reading Directly from a URL

After you've successfully created a URL, you can call the URL's openStream() method to get a stream from which you can read the contents of the URL. The openStream() method returns a java.io.InputStream object, so reading from a URL is as easy as reading from an input stream.

The following small Java program uses openStream() to get an input stream on the URL http://www.yahoo.com/. It then opens a BufferedReader on the input stream and reads from the BufferedReader thereby reading from the URL. Everything read is copied to the standard output stream:

import java.net.*;
import java.io.*;

public class URLReader 
{
    public static void main(String[] args) throws Exception 
    {
	URL yahoo = new URL("http://www.yahoo.com/");
	BufferedReader in = new BufferedReader(
				new InputStreamReader(
				yahoo.openStream()));

	String inputLine;

	while ((inputLine = in.readLine()) != null)
	    System.out.println(inputLine);

	in.close();
    }
}

When you run the program, you should see, scrolling by in your command window, the HTML commands and textual content from the HTML file located at http://www.yahoo.com/. Alternatively, the program might hang or you might see an exception stack trace. If either of the latter two events occurs, you may have to set the proxy host so that the program can find the Yahoo server.

 


 

Connecting to a URL

After you've successfully created a URL object, you can call the URL object's openConnection method to connect to it. When you connect to a URL, you are initializing a communication link between your Java program and the URL over the network. For example, you can open a connection to the Yahoo site with the following code:

try {
    URL yahoo = new URL("http://www.yahoo.com/");
    URLConnection yahooConnection = yahoo.openConnection();

} catch  MalformedURLException(e) {     // new URL() failed
    . . .
} catch  IOException(e) {               // openConnection() failed
    . . .
}

If possible, the openConnection method creates a new URLConnection (if an appropriate one does not already exist), initializes it, connects to the URL, and returns the URLConnection object. If something goes wrong--for example, the Yahoo server is down--then the openConnection method throws an IOException.

Now that you've successfully connected to your URL, you can use the URLConnection object to perform actions such as reading from or writing to the connection.

 


 

Reading from and Writing to a URLConnection

If you've successfully used openConnection to initiate communications with a URL, then you have a reference to a URLConnection object. The URLConnection class contains many methods that let you communicate with the URL over the network. URLConnection is an HTTP-centric class; that is, many of its methods are useful only when you are working with HTTP URLs However, most URL protocols allow you to read from and write to the connection.

 

 

Reading from a URLConnection

The following program performs the same function as the URLReader program shown in Reading Directly from a URL.

However, rather than getting an input stream directly from the URL, this program explicitly opens a connection to a URL and gets an input stream from the connection. Then, like URLReader, this program creates a BufferedReader on the input stream and reads from it. The bold statements highlight the differences between this example and the previous

import java.net.*;
import java.io.*;

public class URLConnectionReader {
    public static void main(String[] args) throws Exception {
        URL yahoo = new URL("http://www.yahoo.com/");
        URLConnection yc = yahoo.openConnection();
        BufferedReader in = new BufferedReader(
                                new InputStreamReader(
                                yc.getInputStream()));
        String inputLine;

        while ((inputLine = in.readLine()) != null) 
            System.out.println(inputLine);
        in.close();
    }
}

The output from this program is identical to the output from the program that opens a stream directly from the URL. You can use either way to read from a URL. However, reading from a URLConnection instead of reading directly from a URL might be more useful. This is because you can use the URLConnection object for other tasks (like writing to the URL) at the same time.

Again, if the program hangs or you see an error message, you may have to set the proxy host so that the program can find the Yahoo server. Many HTML pages contain forms-- text fields and other GUI objects that let you enter data to send to the server. After you type in the required information and initiate the query by clicking a button, your Web browser writes the data to the URL over the network. At the other end, a cgi-bin script (usually) on the server receives the data, processes it, and then sends you a response, usually in the form of a new HTML page.

Many cgi-bin scripts use the POST METHOD for reading the data from the client. Thus writing to a URL is often called posting to a URL. Server-side scripts use the POST METHOD to read from their standard input.

Some server-side cgi-bin scripts use the GET METHOD to read your data. The POST METHOD is quickly making the GET METHOD obsolete because it's more versatile and has no limitations on the amount of data that can be sent through the connection.

A Java program can interact with cgi-bin scripts also on the server side. It simply must be able to write to a URL, thus providing data to the server. It can do this by following these steps:

  1. Create a URL.
  2. Open a connection to the URL.
  3. Set output capability on the URLConnection.
  4. Get an output stream from the connection. This output stream is connected to the standard input stream of the cgi-bin script on the server.
  5. Write to the output stream.
  6. Close the output stream.

Hassan Schroeder, a member of the Java development team, wrote a small cgi-bin script named backwards and made it available at the Java Web site, http://java.sun.com/cgi-bin/backwards. You can use this script to test the following example program. You can also put the script on your network, name it backwards, and test the program locally.

The script at our Web site reads a string from its standard input, reverses the string, and writes the result to its standard output. The script requires input of the form string=string_to_reverse, where string_to_reverse is the string whose characters you want displayed in reverse order.

Here's an example program that runs the backwards script over the network through a URLConnection:

import java.io.*;
import java.net.*;

public class Reverse {
    public static void main(String[] args) throws Exception {

	if (args.length != 1) {
	    System.err.println("Usage:  java Reverse "
                               + "string_to_reverse");
	    System.exit(1);
	}

	String stringToReverse = URLEncoder.encode(args[0]);

	URL url = new URL("http://java.sun.com/cgi-bin/backwards");
	URLConnection connection = url.openConnection();
	connection.setDoOutput(true);

	PrintWriter out = new PrintWriter(
                              connection.getOutputStream());
	out.println("string=" + stringToReverse);
	out.close();

	BufferedReader in = new BufferedReader(
				new InputStreamReader(
				connection.getInputStream()));
	String inputLine;

	while ((inputLine = in.readLine()) != null)
	    System.out.println(inputLine);

	in.close();
    }
}

Let's examine the program and see how it works. First, the program processes its command-line arguments:

if (args.length != 1) {
    System.err.println("Usage:  java Reverse " +
                       "string_to_reverse");
    System.exit(-1);
}
String stringToReverse = URLEncoder.encode(args[0]);

These statements ensure that the user provides one and only one command-line argument to the program, and then encodes it. The command-line argument is the string that will be reversed by the cgi-bin script backwards. It may contain spaces or other non-alphanumeric characters. These characters must be encoded because the string is processed on its way to the server. The URLEncoder class methods encode the characters.

Next, the program creates the URL object--the URL for the backwards script on java.sun.com--opens a URLConnection, and sets the connection so that it can write to it:

URL url = new URL("http://java.sun.com/cgi-bin/backwards");
URLConnection c = url.openConnection();
c.setDoOutput(true);

The program then creates an output stream on the connection and opens a PrintWriter on it:

PrintWriter out = new PrintWriter(c.getOutputStream());

If the URL does not support output, getOutputStream method throws an UnknownServiceException. If the URL does support output, then this method returns an output stream that is connected to the standard input stream of the URL on the server side--the client's output is the server's input.

Next, the program writes the required information to the output stream and closes the stream:

out.println("string=" + stringToReverse);
out.close();

This code writes to the output stream using the println method. So you can see that writing data to a URL is as easy as writing data to a stream. The data written to the output stream on the client side is the input for the backwards script on the server side. The Reverse program constructs the input in the form required by the script by concatenating string= to the encoded string to be reversed.

Often, when you are writing to a URL, you are passing information to a cgi-bin script, as in this example. This script reads the information you write, performs some action, and then sends information back to you via the same URL. So it's likely that you will want to read from the URL after you've written to it. The Reverse program does this:

BufferReader in = new BufferedReader(
                      new InputStreamReader(c.getInputStream()));
String inputLine;

while ((inputLine = in.readLine()) != null)
    System.out.println(inputLine);
in.close();

When you run the Reverse program using "Reverse Me" as an argument (including the double quote marks), you should see this output:

Reverse Me
 reversed is: 
eM esreveR

 


 

Sockets

URLs and URLConnections provide a relatively high-level mechanism for accessing resources on the Internet. Sometimes your programs require lower-level network communication, for example, when you want to write a client-server application.

In client-server applications, the server provides some service, such as processing database queries or sending out current stock prices. The client uses the service provided by the server, either displaying database query results to the user or making stock purchase recommendations to an investor. The communication that occurs between the client and the server must be reliable. That is, no data can be dropped and it must arrive on the client side in the same order in which the server sent it.

TCP provides a reliable, point-to-point communication channel that client-server applications on the Internet use to communicate with each other. To communicate over TCP, a client program and a server program establish a connection to one another. Each program binds a socket to its end of the connection. To communicate, the client and the server each reads from and writes to the socket bound to the connection.

 

 

What Is a Socket?

A socket is one end-point of a two-way communication link between two programs running on the network. Socket classes are used to represent the connection between a client program and a server program. The java.net package provides two classes--Socket and ServerSocket--that implement the client side of the connection and the server side of the connection, respectively. Let's look at a simple example that illustrates how a program can establish a connection to a server program using the Socket class and then, how the client can send data to and receive data from the server through the socket.

The example program implements a client, EchoClient, that connects to the Echo server. The Echo server simply receives data from its client and echoes it back. The Echo server is a well-known service that clients can rendezvous with on port 7.

EchoClient creates a socket thereby getting a connection to the Echo server. It reads input from the user on the standard input stream, and then forwards that text to the Echo server by writing the text to the socket. The server echoes the input back through the socket to the client. The client program reads and displays the data passed back to it from the server:

import java.io.*;
import java.net.*;

public class EchoClient {
    public static void main(String[] args) throws IOException {

        Socket echoSocket = null;
        PrintWriter out = null;
        BufferedReader in = null;

        try {
            echoSocket = new Socket("taranis", 7);
            out = new PrintWriter(echoSocket.getOutputStream(), true);
            in = new BufferedReader(new InputStreamReader(
                                        echoSocket.getInputStream()));
        } catch  UnknownHostException(e) {
            System.err.println("Don't know about host: taranis.");
            System.exit(1);
        } catch  IOException(e) {
            System.err.println("Couldn't get I/O for "
                               + "the connection to: taranis.");
            System.exit(1);
        }

	BufferedReader stdIn = new BufferedReader(
                                   new InputStreamReader(System.in));
	String userInput;

	while ((userInput = stdIn.readLine()) != null) {
	    out.println(userInput);
	    System.out.println("echo: " + in.readLine());
	}

	out.close();
	in.close();
	stdIn.close();
	echoSocket.close();
    }
}

Note that EchoClient both writes to and reads from its socket, thereby sending data to and receiving data from the Echo server.

Let's walk through the program and investigate the interesting parts. The three statements in the try block of the main method are critical. These lines establish the socket connection between the client and the server and open a PrintWriter and a BufferedReader on the socket:

echoSocket = new Socket("taranis", 7);
out = new PrintWriter(echoSocket.getOutputStream(), true);
in = new BufferedReader(new InputStreamReader(
                             echoSocket.getInputStream()));

The first statement in this sequence creates a new Socket object and names it echoSocket. The Socket constructor used here requires the name of the machine and the port number to which you want to connect. The example program uses the host name taranis. This is the name of a hypothetical machine on our local network. When you type in and run this program on your machine, change the host name to the name of a machine on your network. Make sure that the name you use is the fully qualified IP name of the machine to which you want to connect. The second argument is the port number. Port number 7 is the port on which the Echo server listens.

The second statement gets the socket's output stream and opens a PrintWriter on it. Similarly, the third statement gets the socket's input stream and opens a BufferedReader on it. The example uses readers and writers so that it can write Unicode characters over the socket.

To send data through the socket to the server, EchoClient simply needs to write to the PrintWriter. To get the server's response, EchoClient reads from the BufferedReader.

The next interesting part of the program is the while loop. The loop reads a line at a time from the standard input stream and immediately sends it to the server by writing it to the PrintWriter connected to the socket:

String userInput;

while ((userInput = stdIn.readLine()) != null) {
    out.println(userInput);
    System.out.println("echo: " + in.readLine());
}

The last statement in the while loop reads a line of information from the BufferedReader connected to the socket. The readLine method waits until the server echoes the information back to EchoClient. When readline returns, EchoClient prints the information to the standard output.

The while loop continues until the user types an end-of-input character. That is, EchoClient reads input from the user, sends it to the Echo server, gets a response from the server, and displays it, until it reaches the end-of-input. The while loop then terminates and the program continues, executing the next four lines of code:

out.close();
in.close();
stdIn.close();
echoSocket.close();

These lines of code fall into the category of housekeeping. A well-behaved program always cleans up after itself, and this program is well-behaved. These statements close the readers and writers connected to the socket and to the standard input stream, and close the socket connection to the server. The order here is important. You should close any streams connected to a socket before you close the socket itself.

This client program is straightforward and simple because the Echo server implements a simple protocol. The client sends text to the server, and the server echoes it back. When your client programs are talking to a more complicated server such as an HTTP server, your client program will also be more complicated. However, the basics are much the same as they are in this program:

  1. Open a socket.
  2. Open an input stream and output stream to the socket.
  3. Read from and write to the stream according to the server's protocol.
  4. Close the streams.
  5. Close the socket.

Only step 3 differs from client to client, depending on the server. The other steps remain largely the same.

 


 

Writing the Server Side of a Socket

This section shows you how to write a server and the client that goes with it. The server in the client/server pair serves up Knock Knock jokes. Knock Knock jokes are favored by children and are usually vehicles for bad puns. They go like this:

Server: "Knock knock!"
Client: "Who's there?"
Server: "Dexter."
Client: "Dexter who?"
Server: "Dexter halls with boughs of holly."
Client: "Groan."

The example consists of two independently running Java programs: the client program and the server program. The client program is implemented by a single class, KnockKnockClient, and is very similar to the EchoClient example. The server program is implemented by two classes: KnockKnockServer and KnockKnockProtocol, KnockKnockServer contains the main method for the server program and performs the work of listening to the port, establishing connections, and reading from and writing to the socket. KnockKnockProtocol serves up the jokes. It keeps track of the current joke, the current state (sent knock knock, sent clue, and so on), and returns the various text pieces of the joke depending on the current state. This object implements the protocol-the language that the client and server have agreed to use to communicate.

The following section looks in detail at each class in both the client and the server and then shows you how to run them.

 

 

The Knock Knock Server

This section walks through the code that implements the Knock Knock server program. Here is the complete source for the KnockKnockServer class.

The server program begins by creating a new ServerSocket object to listen on a specific port (see the statement in bold in the following code segment). When writing a server, choose a port that is not already dedicated to some other service. KnockKnockServer listens on port 4444 because 4 happens to be my favorite number and port 4444 is not being used for anything else in my environment:

try {
    serverSocket = new ServerSocket(4444);
} catch  IOException(e) {
    System.out.println("Could not listen on port: 4444");
    System.exit(-1);
}

ServerSocket is a java.net class that provides a system-independent implementation of the server side of a client/server socket connection. The constructor for ServerSocket throws an exception if it can't listen on the specified port (for example, the port is already being used). In this case, the KnockKnockServer has no choice but to exit.

If the server successfully connects to its port, then the ServerSocket object is successfully created and the server continues to the next step--accepting a connection from a client (shown in bold):

Socket clientSocket = null;
try {
    clientSocket = serverSocket.accept();
} catch  IOException(e) {
    System.out.println("Accept failed: 4444");
    System.exit(-1);
}

The accept method waits until a client starts up and requests a connection on the host and port of this server (in this example, the server is running on the hypothetical machine taranis on port 4444). When a connection is requested and successfully established, the accept method returns a new Socket object which is bound to a new port. The server can communicate with the client over this new Socket and continue to listen for client connection requests on the ServerSocket bound to the original, predetermined port. This particular version of the program doesn't listen for more client connection requests. However, a modified version of the program is provided in Supporting Multiple Clients.

After the server successfully establishes a connection with a client, it communicates with the client using this code:

PrintWriter out = new PrintWriter(
                      clientSocket.getOutputStream(), true);
BufferedReader in = new BufferedReader(
                        new InputStreamReader(
                            clientSocket.getInputStream()));
String inputLine, outputLine;

// initiate conversation with client
KnockKnockProtocol kkp = new KnockKnockProtocol();
outputLine = kkp.processInput(null);
out.println(outputLine);

while ((inputLine = in.readLine()) != null) {	
    outputLine = kkp.processInput(inputLine);
    out.println(outputLine);
    if outputLine.equals("Bye."))
        break;
}

This code:

  1. Gets the socket's input and output stream and opens readers and writers on them.
  2. Initiates communication with the client by writing to the socket (shown in bold).
  3. Communicates with the client by reading from and writing to the socket (the while loop).

Step 1 is already familiar. Step 2 is shown in bold and is worth a few comments. The bold statements in the code segment above initiate the conversation with the client. The code creates a KnockKnockProtocol object-the object that keeps track of the current joke, the current state within the joke, and so on.

After the KnockKnockProtocol is created, the code calls KnockKnockProtocol's processInput method to get the first message that the server sends to the client. For this example, the first thing that the server says is "Knock! Knock!" Next, the server writes the information to the PrintWriter connected to the client socket, thereby sending the message to the client.

Step 3 is encoded in the while loop. As long as the client and server still have something to say to each other, the server reads from and writes to the socket, sending messages back and forth between the client and the server.

The server initiated the conversation with a "Knock! Knock!" so afterwards the server must wait for the client to say "Who's there?" As a result, the while loop iterates on a read from the input stream. The readLine method waits until the client responds by writing something to its output stream (the server's input stream). When the client responds, the server passes the client's response to the KnockKnockProtocol object and asks the KnockKnockProtocol object for a suitable reply. The server immediately sends the reply to the client via the output stream connected to the socket, using a call to println. If the server's response generated from the KnockKnockServer object is "Bye." this indicates that the client doesn't want any more jokes and the loop quits.

The KnockKnockServer class is a well-behaved server, so the last several lines of this section of KnockKnockServer clean up by closing all of the input and output streams, the client socket, and the server socket:

out.close();
in.close();
clientSocket.close();
serverSocket.close();

 

 

The Knock Knock Protocol

The KnockKnockProtocol class implements the protocol that the client and server use to communicate. This class keeps track of where the client and the server are in their conversation and serves up the server's response to the client's statements. The KnockKnockServer object contains the text of all the jokes and makes sure that the client gives the proper response to the server's statements. It wouldn't do to have the client say "Dexter who?" when the server says "Knock! Knock!"

All client/server pairs must have some protocol by which they speak to each other; otherwise, the data that passes back and forth would be meaningless. The protocol that your own clients and servers use depends entirely on the communication required by them to accomplish the task.

The KnockKnockClient class implements the client program that speaks to the KnockKnockServer. KnockKnockClient is based on the EchoClient program in the previous section, Reading from and Writing to a Socket and should be somewhat familiar to you. But we'll go over the program anyway and look at what's happening in the client in the context of what's going on in the server.

When you start the client program, the server should already be running and listening to the port, waiting for a client to request a connection. So, the first thing the client program does is to open a socket that is connected to the server running on the hostname and port specified:

kkSocket = new Socket("taranis", 4444);
out = new PrintWriter(kkSocket.getOutputStream(), true);
in = new BufferedReader(new InputStreamReader(
                            kkSocket.getInputStream()));

When creating its socket, KnockKnockClient uses the host name taranis, the name of a hypothetical machine on our network. When you type in and run this program, change the host name to the name of a machine on your network. This is the machine on which you will run the KnockKnockServer.

The KnockKnockClient program also specifies the port number 4444 when creating its socket. This is a remote port number--the number of a port on the server machine--and is the port to which KnockKnockServer is listening. The client's socket is bound to any available local port--a port on the client machine. Remember that the server gets a new socket as well. That socket is bound to a local port number (not port 4444) on its machine. The server's socket and the client's socket are connected.

Next comes the while loop that implements the communication between the client and the server. The server speaks first, so the client must listen first. The client does this by reading from the input stream attached to the socket. If the server does speak, it says "Bye." and the client exits the loop. Otherwise, the client displays the text to the standard output and then reads the response from the user, who types into the standard input. After the user types a carriage return, the client sends the text to the server through the output stream attached to the socket.

while ((fromServer = in.readLine()) != null) {
    System.out.println("Server: " + fromServer);
    if (fromServer.equals("Bye."))
        break;
    fromUser = stdIn.readLine();
    if (fromUser != null) {
        System.out.println("Client: " + fromUser);
        out.println(fromUser);
    }
}

The communication ends when the server asks if the client wishes to hear another joke, the client says no, and the server says "Bye."

In the interest of good housekeeping, the client closes its input and output streams and the socket:

out.close();
in.close();
stdIn.close();
kkSocket.close();

 

 

Running the Programs

You must start the server program first. To do this, run the server program using the Java interpreter, just as you would any other Java application. Remember to run the server on the machine that the client program specifies when it creates the socket.

Next, run the client program. Note that you can run the client on any machine on your network; it does not have to run on the same machine as the server.

If you are too quick, you might start the client before the server has a chance to initialize itself and begin listening on the port. If this happens, you will see a stack trace from the client. If this happens, just restart the client.

If you forget to change the host name in the source code for the KnockKnockClient program, you will see the following error message:

Don't know about host: taranis

To fix this, modify the KnockKnockClient program and provide a valid host name for your network. Recompile the client program and try again.

If you try to start a second client while the first client is connected to the server, the second client just hangs. The next section, Supporting Multiple Clients, talks about supporting multiple clients.

When you successfully get a connection between the client and server, you will see the following text displayed on your screen:

Server: Knock! Knock!

Now, you must respond with:

Who's there?

The client echoes what you type and sends the text to the server. The server responds with the first line of one of the many Knock Knock jokes in its repertoire. Now your screen should contain this (the text you typed is in bold):

Server: Knock! Knock!
Who's there?
Client: Who's there?
Server: Turnip

Now, you respond with:

Turnip who?"

Again, the client echoes what you type and sends the text to the server. The server responds with the punch line. Now your screen should contain this:

Server: Knock! Knock!
Who's there?
Client: Who's there?
Server: Turnip
Turnip who?
Client: Turnip who?
Server: Turnip the heat, it's cold in here! Want another? (y/n)

If you want to hear another joke, type y; if not, type n. If you type y, the server begins again with "Knock! Knock!" If you type n, the server says "Bye." thus causing both the client and the server to exit.

If at any point you make a typing mistake, the KnockKnockServer object catches it and the server responds with a message similar to this:

Server: You're supposed to say "Who's there?"!

The server then starts the joke over again:

Server: Try again. Knock! Knock!

Note that the KnockKnockProtocol object is particular about spelling and punctuation but not about capitalization.

 

 

Supporting Multiple Clients

To keep the KnockKnockServer example simple, we designed it to listen for and handle a single connection request. However, multiple client requests can come into the same port and, consequently, into the same ServerSocket. Client connection requests are queued at the port, so the server must accept the connections sequentially. However, the server can service them simultaneously through the use of threads - one thread per each client connection.

The basic flow of logic in such a server is this:

while (true) {
    accept a connection ;
    create a thread to deal with the client ;
end while

The thread reads from and writes to the client connection as necessary.

Modify the KnockKnockServer so that it can service multiple clients at the same time. Two classes compose our solution: KKMultiServer and KKMultiServerThread KKMultiServer loops forever, listening for client connection requests on a ServerSocket. When a request comes in, KKMultiServer accepts the connection, creates a new KKMultiServerThread object to process it, hands it the socket returned from accept, and starts the thread. Then the server goes back to listening for connection requests. The KKMultiServerThread object communicates to the client by reading from and writing to the socket. Run the new Knock Knock server and then run several clients in succession.

 


 

All About Datagrams

Some applications that you write to communicate over the network will not require the reliable, point-to-point channel provided by TCP. Rather, your applications might benefit from a mode of communication that delivers independent packages of information whose arrival and order of arrival are not guaranteed.

The UDP protocol provides a mode of network communication whereby applications send packets of data, called datagrams, to one another. A datagram is an independent, self-contained message sent over the network whose arrival, arrival time, and content are not guaranteed. The DatagramPacket and DatagramSocket classes in the java.net package implement system-independent datagram communication using UDP.

 


 

What Is a Datagram?

Clients and servers that communicate via a reliable channel, such as a URL or a socket, have a dedicated point-to-point channel between themselves, or at least the illusion of one. To communicate, they establish a connection, transmit the data, and then close the connection. All data sent over the channel is received in the same order in which it was sent. This is guaranteed by the channel.

In contrast, applications that communicate via datagrams send and receive completely independent packets of information. These clients and servers do not have and do not need a dedicated point-to-point channel. The delivery of datagrams to their destinations is not guaranteed. Nor is the order of their arrival.

A datagram is an independent, self-contained message sent over the network whose arrival, arrival time, and content are not guaranteed.

The java.net package contains two classes to help you write Java programs that use datagrams to send and receive packets over the network: DatagramSocket, DatagramPacket, and MulticastSocket An application can send and receive DatagramPackets through a DatagramSocket. In addition, DatagramPackets can be broadcast to multiple recipients all listening to a MulticastSocket.

 


 

Writing a Datagram Client and Server

The example featured in this section consists of two applications: a client and a server. The server continuously receives datagram packets over a datagram socket. Each datagram packet received by the server indicates a client request for a quotation. When the server receives a datagram, it replies by sending a datagram packet that contains a one-line "quote of the moment" back to the client.

The client application in this example is fairly simple. It sends a single datagram packet to the server indicating that the client would like to receive a quote of the moment. The client then waits for the server to send a datagram packet in response.

Two classes implement the server application: QuoteServer and QuoteServerThread. A single class implements the client application: QuoteClient.

Let's investigate these classes, starting with class that contains the main method for the server application. contains an applet version of the QuoteClient class.

 

 

The QuoteServer Class

The QuoteServer class, shown here in its entirety, contains a single method: the main method for the quote server application. The main method simply creates a new QuoteServerThread object and starts it:

import java.io.*;

public class QuoteServer 
{
    public static void main(String[] args) throws IOException 
    {
        new QuoteServerThread().start();
    }
}

The QuoteServerThread class implements the main logic of the quote server.

 

 

The QuoteServerThread Class

When created, the QuoteServerThread creates a DatagramSocket on port 4445 (arbitrarily chosen). This is the DatagramSocket through which the server communicates with all of its clients.

public QuoteServerThread() throws IOException {
    this("QuoteServer");
}
public QuoteServerThread(String name) throws IOException {
    super(name);
    socket = new DatagramSocket(4445);

    try {
        in = new BufferedReader(
                  new FileReader("one-liners.txt"));	
    } catch  FileNotFoundException(e)
        System.err.println("Couldn't open quote file. " + 
                          "Serving time instead.");
    }
}  

Remember that certain ports are dedicated to well-known services and you cannot use them. If you specify a port that is in use, the creation of the DatagramSocket will fail.

The constructor also opens a BufferedReader on a file named one-liners.txt which contains a list of quotes. Each quote in the file is on a line by itself.

Now for the interesting part of the QuoteServerThread: its run method. The run method overrides run in the Thread class and provides the implementation for the thread.

The run method contains a while loop that continues as long as there are more quotes in the file. During each iteration of the loop, the thread waits for a DatagramPacket to arrive over the DatagramSocket. The packet indicates a request from a client. In response to the client's request, the QuoteServerThread gets a quote from the file, puts it in a DatagramPacket and sends it over the DatagramSocket to the client that asked for it.

Let's look first at the section that receives the requests from clients:

byte[] buf = new byte[256];
DatagramPacket packet = new DatagramPacket(buf, buf.length);
socket.receive(packet);

The first statement creates an array of bytes which is then used to create a DatagramPacket. The DatagramPacket will be used to receive a datagram from the socket because of the constructor used to create it. This constructor requires only two arguments: a byte array that contains client-specific data and the length of the byte array. When constructing a DatagramPacket to send over the DatagramSocket, you also must supply the Internet address and port number of the packet's destination. You'll see this later when we discuss how the server responds to a client request.

The last statement in the previous code snippet receives a datagram from the socket (the information received from the client gets copied into the packet). The receive method waits forever until a packet is received. If no packet is received, the server makes no further progress and just waits.

Now assume that, the server has received a request from a client for a quote. Now the server must respond. This section of code in the run method constructs the response:

String dString = null;
if (in == null)
    dString = new Date().toString();
else
    dString = getNextQuote();
buf = dString.getBytes();

If the quote file did not get opened for some reason, then in equals null. If this is the case, the quote server serves up the time of day instead. Otherwise, the quote server gets the next quote from the already opened file. Finally, the code converts the string to an array of bytes.

Now, the run method sends the response to the client over the DatagramSocket with this code:

InetAddress address = packet.getAddress();
int port = packet.getPort();
packet = new DatagramPacket(buf, buf.length, address, port);
socket.send(packet);

The first two statements in this code segment get the Internet address and the port number, respectively, from the datagram packet received from the client. The Internet address and port number indicate where the datagram packet came from. This is where the server must send its response. In this example, the byte array of the datagram packet contains no relevant information. The arrival of the packet itself indicates a request from a client that can be found at the Internet address and port number indicated in the datagram packet.

The third statement creates a new DatagramPacket object intended for sending a datagram message over the datagram socket. You can tell that the new DatagramPacket is intended to send data over the socket because of the constructor used to create it. This constructor requires four arguments. The first two arguments are the same required by the constructor used to create receiving datagrams: a byte array containing the message from the sender to the receiver and the length of this array. The next two arguments are different: an Internet address and a port number. These two arguments are the complete address of the destination of the datagram packet and must be supplied by the sender of the datagram. The last line of code sends the DatagramPacket on its way.

When the server has read all the quotes from the quote file, the while loop terminates and the run method cleans up:

socket.close();

 

 

The QuoteClient Class

The QuoteClient class implements a client application for the QuoteServer. This application sends a request to the QuoteServer, waits for the response, and, when the response is received, displays it to the standard output. Let's look at the code in detail.

The QuoteClient class contains one method, the main method for the client application. The top of the main method declares several local variables for its use:

int port;
InetAddress address;
DatagramSocket socket = null;
DatagramPacket packet;
byte[] sendBuf = new byte[256];

First, the main method processes the command-line arguments used to invoke the QuoteClient application:

if (args.length != 1) {
     System.out.println("Usage: java QuoteClient <hostname>");
     return;
}

The QuoteClient application requires one command-line arguments: the name of the machine on which the QuoteServer is running.

Next, the main method creates a DatagramSocket:

DatagramSocket socket = new DatagramSocket();

The client uses a constructor that does not require a port number. This constructor just binds the DatagramSocket to any available local port. It doesn't matter what port the client is connected to because the DatagramPackets contain the addressing information. The server gets the port number from the DatagramPackets and send its response to that port.

Next, the QuoteClient program sends a request to the server:

byte[] buf = new byte[256];
InetAddress address = InetAddress.getByName(args[0]);
DatagramPacket packet = new DatagramPacket(buf, buf.length, 
                                           address, 4445);
socket.send(packet);

The code segment gets the Internet address for the host named on the command line (presumably the name of the machine on which the server is running). This InetAddress and the port number 4445 (the port number that the server used to create its DatagramSocket) are then used to create DatagramPacket destined for that Internet address and port number. Therefore the DatagramPacket will be delivered to the quote server.

Note that the code creates a DatagramPacket with an empty byte array. The byte array is empty because this datagram packet is simply a request to the server for information. All the server needs to know to send a response--the address and port number to which reply--is automatically part of the packet.

Next, the client gets a response from the server and displays it:

packet = new DatagramPacket(buf, buf.length);
socket.receive(packet);
String received = new String(packet.getData());
System.out.println("Quote of the Moment: " + received);

To get a response from the server, the client creates a "receive" packet and uses the DatagramSocket receive method to receive the reply from the server. The receive method waits until a datagram packet destined for the client comes through the socket. Note that if the server's reply is somehow lost, the client will wait forever because of the no-guarantee policy of the datagram model. Normally, a client sets a timer so that it doesn't wait forever for a reply; if no reply arrives, the timer goes off and the client retransmits.

When the client receives a reply from the server, the client uses the getData method to retrieve that data from the packet. The client then converts the data to a string and displays it.

 

 

Running the Server and Client

After you've successfully compiled the server and the client programs, you run them. You have to run the server program first. Just use the Java interpreter and specify the QuoteServer class name.

Once the server has started, you can run the client program. Remember to run the client program with one command-line argument: the name of the host on which the QuoteServer is running.

After the client sends a request and receives a response from the server, you should see output similar to this:

Quote of the Moment:
Good programming is 99% sweat and 1% coffee.

 


 

Broadcasting to Multiple Recipients

In addition to DatagramSocket, which lets programs send packets to one another, java.net includes a class called MulticastSocket. This kind of socket is used on the client-side to listen for packets that the server broadcasts to multiple clients.

Let's rewrite the quote server so that it broadcasts DatagramPackets to multiple recipients. Instead of sending quotes to a specific client that makes a request, the new server now needs to broadcast quotes at a regular interval. The client needs to be modified so that it passively listens for quotes and does so on a MulticastSocket.

This example is comprised of three classes which are modifications of the three classes from the previous example: MulticastServer, MulticastServerThread, and MulticastClient.

Here is the new version of the server's main program. The differences between this code and the previous version, QuoteServer, are shown in bold:

import java.io.*;
public class MulticastServer {
    public static void main(String[] args) throws IOException {
        new MulticastServerThread().start();
    }
}

Basically, the server got a new name and creates a MulticastServerThread instead of a QuoteServerThread. Now let's look at the MulticastServerThread which contains the heart of the server. Here's its class declaration:

public class MulticastServerThread extends QuoteServerThread {
    ...
}

We've made this class a subclass of QuoteServerThread so that it can use the constructor, and inherit some member variable and the getNextQuote method. Recall that QuoteServerThread creates a DatagramSocket bound to port 4445 and opens the quote file. The DatagramSocket's port number doesn't actually matter in this example because the client never send anything to the server.

The only method explicitly implemented in MulticastServerThread is its run method. The differences between this run method and the one in QuoteServerThread are shown in bold:

public void run() {
    while (moreQuotes) {
        try {
            byte[] buf new byte[256];
            // don't wait for request...just send a quote

            String dString = null;
            if (in == null)
                dString = new Date().toString();
            else
                dString = getNextQuote();
            buf = dString.getBytes();

            InetAddress group = InetAddress.getByName(
                                            "230.0.0.1");
            DatagramPacket packet;
            packet = new DatagramPacket(buf, buf.length, 
                                        group, 4446);
            socket.send(packet);

            try {
                sleep((long)Math.random() * FIVE_SECONDS);
            } catch (InterruptedException e) { }
        } catch  IOException(e) {
            e.printStackTrace();
            moreQuotes = false;
        }
    }
    socket.close();
}

The interesting change is how the DatagramPacket is constructed, in particular, the InetAddress and port used to construct the DatagramPacket. Recall that the previous example retrieved the InetAddress and port number from the packet sent to the server from the client. This was because the server needed to reply directly to the client. Now, the server needs to address multiple clients. So this time both the InetAddress and the port number are hard-coded.

The hard-coded port number is 4446 (the client must have a MulticastSocket bound to this port). The hard-coded InetAddress of the DatagramPacket is "230.0.0.1" and is a group identifier (rather than the Internet address of the machine on which a single client is running). This particular address was arbitrarily chosen from the reserved for this purpose.

Created in this way, the DatagramPacket is destined for all clients listening to port number 4446 who are member of the "230.0.0.1" group.

To listen to port number 4446, the new client program just created its MulticastSocket with that port number. To become a member of the "230.0.0.1" group, the client calls the MulticastSocket's joinGroup method with the InetAddress that identifies the group. Now, the client is set up to receive DatagramPackets destined for the port and group specified. Here's the relevant code from the new client program (which was also rewritten to passively receive quotes rather than actively request them). The bold statements are the ones that interact with the MulticastSocket:

MulticastSocket socket = new MulticastSocket(4446);
InetAddress group = InetAddress.getByName("230.0.0.1");
socket.joinGroup(group);

DatagramPacket packet;
for (int i = 0; i < 5; i++) {
    byte[] buf = new byte[256];
    packet = new DatagramPacket(buf, buf.length);
    socket.receive(packet);

    String received = new String(packet.getData());
    System.out.println("Quote of the Moment: " + received);
}
socket.leaveGroup(group);
socket.close();

Notice that the server uses a DatagramSocket to broadcast packet received by the client over a MulticastSocket. Alternatively, it could have used a MulticastSocket. The socket used by the server to send the DatagramPacket is not important. What's important when broadcasting packets is the addressing information contained in the DatagramPacket, and the socket used by the client to listen for it

Run the MulticastServer and several clients. Watch how the clients all get the same quotes.

 


 

Set the Proxy Host

To set the proxy host through the command-line: java -Dhttp.proxyHost=proxyhost [-Dhttp.proxyPort=portNumber] URLReader


 

Home