Previous | Next | Trail Map | Custom Networking and Security | All About Datagrams


Writing a Datagram Client and Server (1.1notes)

The example featured in this section is comprised 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 famous 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 which indicates 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.

Working with a Server-Side Application(in the Writing Applets trail)contains an applet version of the QuoteClient class.

The QuoteServer Class

The QuoteServer class contains a single method: the main() method for the quote server application. The main() method simply creates a new QuoteServerThread object and starts it.
class QuoteServer {
    public static void main(String[] args) {
        new QuoteServerThread().start();
    }
}
The QuoteServerThread implements the main logic of the quote server.

The QuoteServerThread Class

The QuoteServerThread is a Thread that runs continuously waiting for requests over a datagram socket.

QuoteServerThread has two private instance variables. The first, named socket, is a reference to a DatagramSocket object. This variable is initialized to null. The second, qfs, is a DataInputStream object that is opened onto an ASCII text file containing a list of quotes. Whenever a request for a quote arrives in the server, the server retrieves the next line from this input stream.

When the main program creates the QuoteServerThread it uses the only constructor available:

QuoteServerThread() {
    super("QuoteServer");
    try {
        socket = new DatagramSocket();
        System.out.println("QuoteServer listening on port: " + socket.getLocalPort());
    } catch (java.net.SocketException e) {
        System.err.println("Could not create datagram socket.");
    }
    this.openInputFile();
}  
The first line of this constructor calls the superclass (Thread) constructor to initialize the Thread with the name "QuoteServer". The next section of code is the critical part of the QuoteServerThread constructor--it creates a DatagramSocket. The QuoteServerThread uses this DatagramSocket to listen for and respond to client requests for a quote.

The socket is created using the DatagramSocket constructor that requires no arguments:

socket = new DatagramSocket();
When created using this constructor, the new DatagramSocket binds to any locally available port. The DatagramSocket class has another constructor that allows you to specify the port that you want the new DatagramSocket to bind to. You should note 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.

After the DatagramSocket is successfully created, the QuoteServerThread displays a message indicating which port the DatagramSocket is bound to. The QuoteClient needs this port number to construct datagram packets destined for this port. So, you must use this port number when running the QuoteClient.

The last line of the QuoteServerThread constructor calls a private method, openInputFile(), within QuoteServerThread to open a file named one-liners.txt that contains a list of quotes. Each quote in the file must be 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. For information about Threads, see Threads of Control(in the Writing Java Programs trail).

QuoteServerThread's run() method first checks to verify that a valid DatagramSocket was created during construction. If socket is null, then the QuoteServerThread could not bind to the DatagramSocket. Without the socket, the server cannot operate, and the run() method returns.

Otherwise, the run() method enters into an infinite loop. The infinite loop is continuously waiting for requests from clients and responding to those requests. This loop contains two critical sections of code: the section that listens for requests and the section that responds to them. Let's look at first at the section that receives requests:

packet = new DatagramPacket(buf, 256);
socket.receive(packet);
address = packet.getAddress();
port = packet.getPort();
The first line of code creates a new DatagramPacket object intended to receive a datagram message over the datagram socket. You can tell that the new DatagramPacket is intended to receive data 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 must also supply the Internet address and port number of the destination of the packet. You'll see this later when we discuss how the server responds to a client request.

The second line of code in the above code snippet receives a datagram from the socket. The information contained within the datagram message gets copied into the packet created on the previous line. The receive() blocks forever until a packet is received. If no packet is received, the server makes no further progress and just waits.

The next two lines get the Internet address and the port number from the received datagram packet. The Internet address and port number indicate where the datagram packet came from. This is where the server must respond to. In this example, the byte array of the datagram packet contains no relevant information. Just 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.

At this point, the server has received a request from a client for a quote. Now the server must respond. The next six lines of code construct the response and send it.

if (qfs == null)
    dString = new Date().toString();
else
    dString = getNextQuote();
dString.getBytes(0, dString.length(), buf, 0);
packet = new DatagramPacket(buf, buf.length, address, port);
socket.send(packet);
If the quote file did not get opened for some reason, then qfs is 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. The line of code following the if statement converts the string to an array of bytes.

The third line of code 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 mesage 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 fourth line of code sends the DatagramPacket on its way.

The last method of interest in QuoteServerThread is the finalize() method. This method cleans up when the QuoteServerThread is garbage collected by closing the DatagramSocket. Ports are limited resources and sockets bound to ports should be closed when not in use.

The QuoteClient Class

The QuoteClient class implements a client application for the QuoteServer. This application simply 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() declares several local variables for its use:

int port;
InetAddress address;
DatagramSocket socket = null;
DatagramPacket packet;
byte[] sendBuf = new byte[256];
The next section of code processes the command line arguments used to invoke the QuoteClient application.
if (args.length != 2) {
     System.out.println("Usage: java DatagramClient <hostname> <port#>");
     return;
}
The QuoteClient application requires two command line arguments: the name of the machine that the QuoteServer is running on, and the port that the QuoteServer is listening to. When you start the QuoteServer it displays a port number. This is the port number you must use on the command line when starting up the QuoteClient.

Next, the main() method contains a try block that contains the main logic of the client program. This try block contains three main sections: a section that creates a DatagramSocket, a section that sends a request to the server, and a section that gets the response from the server.

First, let's look at the code that creates a DatagramSocket:

socket = new DatagramSocket();
The client uses the same constructor to create a DatagramSocket as the server. The DatagramSocket is bound to any available local port.

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

address = InetAddress.getByName(args[0]);
port = Integer.parseInt(args[1]);
packet = new DatagramPacket(sendBuf, 256, address, port);
socket.send(packet);
System.out.println("Client sent request packet.");
The first line of code gets the Internet address for the host named on the command line. The second line of code gets the port number from the command line. These two pieces of information are used to create a DatagramPacket destined for that Internet address and port number. The Internet address and port number should indicate the machine you started the server on and the port that the server is listening to.

The third line in the previous code snippet creates a DatagramPacket intended for sending data. The packet is constructed with an empty byte array, its length, and the Internet address and port number for the destination of the packet. 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 reply--the address and port number to reply to--is automatically part of the packet.

Next, the client gets a response from the server:

packet = new DatagramPacket(sendBuf, 256);
socket.receive(packet);
String received = new String(packet.getData(), 0);
System.out.println("Client received packet: " + 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 blocks 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 block 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.

Run the Server

After you've successfully compiled the server and the client programs, you can run them. You have to run the server program first because you need the port number that it displays before you can start the client. When the server successfully binds to its DatagramSocket, it displays a message similar to this one:
QuoteServer listening on port: portNumber
portNumber is the number of the port to which the server's DatagramSocket is bound. Use this number to start the client.

Run the Client

Once the server has started and displayed a message indicating which port its listening to, you can run the client program. Remember to run the client program with two command line arguments: the name of the host on which the QuoteServer is running, and the port number that it displayed on startup.

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

Quote of the Moment: Life is wonderful. Without it we'd all be dead.

See also

java.net.DatagramPacket
java.net.DatagramSocket


Previous | Next | Trail Map | Custom Networking and Security | All About Datagrams