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


Writing a Datagram Client and Server

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 which 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 super class (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 contiously waiting for requests from clients and responding to those requests. There are two critical sections of code within this loop: 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 which will contain 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 gets the internet address and the port number from the datagram packet. The internet address and port number indicate where the datagram packet initiated. This is where the server must repond to. The byte array of this datagram packet contains no relevant information. Just the arrival of the packet itself indicates a request from a client who can be found at the internet address and port number attached to 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 will be null. If this is true, 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 send() method uses the destination address from the datagram packet to route the datagram packet correctly.

The last method of interest in the 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;
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 will display 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. Note that this is different than using Sockets. When communicating through sockets, connecting to the server at the other end is done by establishing a connection between the sockets at either end. When communicating through DatagramSockets the DatagramPacket contains the internet address and port number of the destination of the packet itself.

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() to get a reply from the server. The receive() blocks forever 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. The client should probably set a timer so that it doesn't wait forever for a reply.

When the client does receive a reply from the server, the client uses the getData() method to retrieve that data from the packet. The client 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 has successfully bound to its DatagramSocket, it will display a message similar to this one:
QuoteServer listening on port: portNumber
portNumber is the number of the port that the server's DatagramSocket is bound to. 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 start up.

After the client has sent a request and received a response from the server, you should see output similar to this:

Client sent request packet.
Client received packet: Sun Feb 18 15:40:34 PST 1996

Security Considerations

Please note that communications over a DatagramSocket are subject to approval by the current security manager. The two example programs are stand-alone applications that get the default security manager which implements a lenient security policy. If you were to convert these applications to applets they may be unable to communicate over a DatagramSocket depending on the browser or viewer they were running in.

See Providing Your Own Security Manager(in the Networking trail)for general information about security managers. Also, see Understanding Applet Capabilities and Restrictions(in the Writing Applets trail)for information about the security restrictions placed on applets.

See also

java.net.DatagramPacket
java.net.DatagramSocket


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