[ Team LiB ] Previous Section Next Section

6.8 The Daytime Service

daytime is an ancient (by Internet standards) network service that simply sends the current local day and time as an ASCII string to any client that connects to port 13, via either TCP or UDP. Example 6-10 is a simple daytime server that accepts TCP connections using a ServerSocketChannel and sends responses to the client with a SocketChannel. This simple program is followed by Example 6-11, a more robust implementation that handles TCP and UDP connections, and includes exception handling and error logging. This more advanced server demonstrates ServerSocketChannel and DatagramChannel, and shows how a program can wait for activity on both channels simultaneously using a Selector object. This section concludes with Example 6-12, a simple datagram-based client of the daytime service, suitable for testing Example 6-11.

SimpleDaytimeServer is, as its name implies, a simple program. It first creates a ServerSocketChannel, and then binds it to port 13 or some other port specified on the command line. Binding the channel to a port is done by querying the underlying java.net.ServerSocket object and using its bind( ) method. It obtains a CharsetEncoder object that it will use later to encode the characters of the daytime string into ASCII bytes.

Next, the server enters an infinite loop to service clients. This loop is made particularly simple by the fact that there is no need to read or parse client requests: the server sends a date and time string to every client that connects. The server first calls the channel's accept( ), which blocks until a client initiates a connection to the server. The return value of accept( ) is a SocketChannel connected to the client. Next, the server gets the current time, converts it to a string using the local time zone, wraps the string in a CharBuffer, and encodes that CharBuffer to create a ByteBuffer. Finally, it sends the contents of that buffer to the client, using the SocketChannel returned by accept( ), and then closes the connection. The loop then repeats and the server blocks in a call to accept( ) again.

Example 6-10. SimpleDaytimeServer.java
package je3.nio;
import java.nio.*;
import java.nio.channels.*;
import java.nio.charset.*;
import java.net.*;

 * A simple TCP server for the daytime service.  See RFC867 for details.
 * This implementation lacks meaningful exception handling and cannot
 * handle UDP connections.
public class SimpleDaytimeServer {
    public static void main(String args[  ]) throws java.io.IOException {
        // RFC867 specifies port 13 for this service.  On Unix platforms,
        // you need to be running as root to use that port, so we allow
        // this service to use other ports for testing.
        int port = 13;  
        if (args.length > 0) port = Integer.parseInt(args[0]);

        // Create a channel to listen for connections on.
        ServerSocketChannel server = ServerSocketChannel.open( );

        // Bind the channel to a local port.  Note that we do this by obtaining
        // the underlying java.net.ServerSocket and binding that socket.
        server.socket( ).bind(new InetSocketAddress(port));

        // Get an encoder for converting strings to bytes
        CharsetEncoder encoder = Charset.forName("US-ASCII").newEncoder( );

        for(;;) {  // Loop forever, processing client connections
            // Wait for a client to connect
            SocketChannel client = server.accept( );
            // Build response string, wrap, and encode to bytes
            String date = new java.util.Date( ).toString( ) + "\r\n";
            ByteBuffer response = encoder.encode(CharBuffer.wrap(date));

            // Send the response to the client and disconnect.
            client.close( );

6.8.1 Listening for TCP and UDP Connections

Example 6-11 is an improved version of the SimpleDaytimeServer class. This DaytimeServer class adds the exception handling that was missing from the previous example, and uses the java.util.logging package (new in Java 1.4) to log errors. (We'll discuss logging in more detail when we consider Example 6-14.) Most importantly, it provides the daytime service using UDP datagrams as well as TCP connections. It uses the DatagramChannel class to receive and send datagrams, and also demonstrates how to multiplex the ServerSocketChannel and the DatagramChannel.

If we were using the java.io and java.net architecture instead of the java.nio architecture, the server would have to use two threads. One of these threads would block in an accept( ) method of the ServerSocketChannel while waiting for a TCP client to connect; the other thread would block in the receive( ) method of the DatagramChannel while waiting for a UDP client to send a datagram. Instead, our New I/O server places both channels in nonblocking mode and uses a single thread that blocks in the select( ) method of the Selector class. The Selector monitors both channels, and select( ) returns when there is activity of interest (i.e., a connection ready to accept or a datagram ready to receive) on either one.

Example 6-11. DaytimeServer.java
package je3.nio;
import java.nio.*;
import java.nio.channels.*;
import java.nio.charset.*;
import java.net.*;
import java.util.logging.*;
import java.util.*;

 * A more robust daytime service that handles TCP and UDP connections and
 * provides exception handling and error logging.
public class DaytimeServer {
    public static void main(String args[  ]) {
        try {     // Handle startup exceptions at the end of this block
            // Get an encoder for converting strings to bytes
            CharsetEncoder encoder = Charset.forName("US-ASCII").newEncoder( );

            // Allow an alternative port for testing with non-root accounts
            int port = 13;   // RFC867 specifies this port.
            if (args.length > 0) port = Integer.parseInt(args[0]);

            // The port we'll listen on
            SocketAddress localport = new InetSocketAddress(port);

            // Create and bind a TCP channel to listen for connections on.
            ServerSocketChannel tcpserver = ServerSocketChannel.open( );
            tcpserver.socket( ).bind(localport);

            // Also create and bind a DatagramChannel to listen on.
            DatagramChannel udpserver = DatagramChannel.open( );
            udpserver.socket( ).bind(localport);

            // Specify non-blocking mode for both channels, since our 
            // Selector object will be doing the blocking for us.

            // The Selector object is what allows us to block while waiting
            // for activity on either of the two channels.
            Selector selector = Selector.open( );

            // Register the channels with the selector, and specify what
            // conditions (a connection ready to accept, a datagram ready
            // to read) we'd like the Selector to wake up for.
            // These methods return SelectionKey objects, which we don't
            // need to retain in this example.
            tcpserver.register(selector, SelectionKey.OP_ACCEPT);
            udpserver.register(selector, SelectionKey.OP_READ);

            // This is an empty byte buffer to receive empty datagrams with.
            // If a datagram overflows the receive buffer size, the extra bytes
            // are automatically discarded, so we don't have to worry about
            // buffer overflow attacks here.
            ByteBuffer receiveBuffer = ByteBuffer.allocate(0);

            // Now loop forever, processing client connections
            for(;;) {
                try { // Handle per-connection problems below
                    // Wait for a client to connect
                    selector.select( );

                    // If we get here, a client has probably connected, so
                    // put our response into a ByteBuffer.
                    String date = new java.util.Date( ).toString( ) + "\r\n";
                    ByteBuffer response=encoder.encode(CharBuffer.wrap(date));

                    // Get the SelectionKey objects for the channels that have
                    // activity on them. These are the keys returned by the
                    // register( ) methods above. They are returned in a 
                    // java.util.Set.
                    Set keys = selector.selectedKeys( );
                    // Iterate through the Set of keys.
                    for(Iterator i = keys.iterator( ); i.hasNext( ); ) {
                        // Get a key from the set, and remove it from the set
                        SelectionKey key = (SelectionKey)i.next( );
                        i.remove( );

                        // Get the channel associated with the key
                        Channel c = (Channel) key.channel( );

                        // Now test the key and the channel to find out
                        // whether something happened on the TCP or UDP channel
                        if (key.isAcceptable( ) && c == tcpserver) { 
                            // A client has attempted to connect via TCP.
                            // Accept the connection now.
                            SocketChannel client = tcpserver.accept( );
                            // If we accepted the connection successfully,
                            // then send our response back to the client.
                            if (client != null) {
                                client.write(response);  // send response
                                client.close( );          // close connection
                        else if (key.isReadable( ) && c == udpserver) {
                            // A UDP datagram is waiting.  Receive it now, 
                            // noting the address it was sent from.
                            SocketAddress clientAddress =
                            // If we got the datagram successfully, send
                            // the date and time in a response packet.
                            if (clientAddress != null)
                                udpserver.send(response, clientAddress);
                catch(java.io.IOException e) {
                    // This is a (hopefully transient) problem with a single
                    // connection: we log the error, but continue running.
                    // We use our classname for the logger so that a sysadmin
                    // can configure logging for this server independently
                    // of other programs.
                    Logger l = Logger.getLogger(DaytimeServer.class.getName( ));
                    l.log(Level.WARNING, "IOException in DaytimeServer", e);
                catch(Throwable t) {
                    // If anything else goes wrong (out of memory, for example),
                    // then log the problem and exit.
                    Logger l = Logger.getLogger(DaytimeServer.class.getName( ));
                    l.log(Level.SEVERE, "FATAL error in DaytimeServer", t);
        catch(Exception e) { 
            // This is a startup error: there is no need to log it;
            // just print a message and exit

6.8.2 A Daytime Client

Example 6-12 is a simple UDP client for testing the UDP portion of DaytimeServer. It uses java.net.DatagramSocket instead of java.nio.channels.DatagramChannel because DatagramChannel does not honor calls to setSoTimeout( ) on its underlying DatagramSocket. The program does simple datagram networking, and the code is easy to figure out. The most interesting feature of this program is that it specifies a timeout (so that it doesn't hang forever if the server is down) and retries the connection three times before giving up (because UDP is an unreliable protocol and a server's response could be lost in the network).

Example 6-12. DaytimeClient.java
package je3.nio;
import java.net.*;

 * Connect to a daytime server using the UDP protocol.
 * We use java.net instead of java.nio because DatagramChannel doesn't honor
 * the setSoTimeout( ) method on the underlying DatagramSocket
public class DaytimeClient {
    public static void main(String args[  ]) throws java.io.IOException {
        // Figure out the host and port we're going to talk to
        String host = args[0];
        int port = 13;
        if (args.length > 1) port = Integer.parseInt(args[1]);

        // Create a socket to use
        DatagramSocket socket = new DatagramSocket( );
        // Specify a 1-second timeout so that receive( ) does not block forever.

        // This buffer will hold the response.  On overflow, extra bytes are
        // discarded: there is no possibility of a buffer overflow attack here.
        byte[  ] buffer = new byte[512];
        DatagramPacket packet =  new DatagramPacket(buffer, buffer.length,
                                             new InetSocketAddress(host,port));

        // Try three times before giving up
        for(int i = 0; i < 3; i++) {
            try {
                // Send an empty datagram to the specified host (and port)
                packet.setLength(0);  // make the packet empty
                socket.send(packet);  // send it out

                // Wait for a response (or time out after 1 second)
                packet.setLength(buffer.length); // make room for the response
                socket.receive(packet);          // wait for the response

                // Decode and print the response
                System.out.print(new String(buffer, 0, packet.getLength( ),
                // We were successful, so break out of the retry loop
            catch(SocketTimeoutException e) {
                // If the receive call timed out, print error and retry
                System.out.println("No response");

        // We're done with the channel now
        socket.close( );
    [ Team LiB ] Previous Section Next Section