A popular feature of the java.nio package is its support for nonblocking input and output over a networking connection. In Java, blocking refers to a statement that must complete execution before anything else happens in the program. All the socket programming you have done up to this point has used blocking methods exclusively. For example, in the TimeServer application, when the server socket’s accept() method is called, nothing else happens in the program until a client makes a connection. As you can imagine, it’s problematic for a networking program to wait until a particular statement is executed because numerous things can go wrong. Connections can be broken. A server could go offline. A socket connection could appear to be stalled because a blocked statement is waiting for something to happen.
For example, a client application that reads and buffers data over HTTP might be waiting for a buffer to be filled even though no more data remains to be sent. The program will appear to have halted because the blocked statement never finishes executing. With the java.nio package, you can create networking connections and read and write from them using nonblocking methods.Here’s how it works: Associate a socket channel with an input or output stream. Configure the channel to recognize the kind of networking events you want to monitor, such as new connections, attempts to read data over the channel, an attempts to write data
· Call a method to open the channel.
· Because the method is nonblocking, the program continues execution so that you can handle other tasks.
· If one of the networking events you are monitoring takes place, your program is notified—a method associated with the event is called.
This is comparable to how user-interface components are programmed in Swing. An interface component is associated with one or more event listeners and placed in a container. If the interface component receives input being monitored by a listener, an eventhandling method is called. Until that happens, the program can handle other tasks. To use nonblocking input and output, you must work with channels instead of streams.
Nonblocking Socket Clients and Servers The first step in the development of a nonblocking client or server is the creation of an object that represents the Internet address to which you are making a connection. This task is handled by the InetSocketAddress class in the java.net package. If the server is identified by a hostname, call InetSocketAddress(String, int) with two arguments: the name of the server and its port number.
If the server is identified by its IP address, use the InetAddress class in java.net to identify the host. Call the static method InetAddress.getByName(String) with the IP address of the host as the argument. The method returns an InetAddress object representing the address, which you can use in calling InetSocketAddress(InetAddress, int). The second argument is the server’s port number. Nonblocking connections require a socket channel, another of the new classes in the java.nio package. Call the open() static method of the SocketChannel class to create the channel.
A socket channel can be configured for blocking or nonblocking communication. To set up a nonblocking channel, call the channel’s configureBlocking(boolean) method with an argument of false. Calling it with true makes it a blocking channel. After the channel is configured, call its connect(InetSocketAddress) method to connect the socket. On a blocking channel, the connect() method attempts to establish a connection to the server and waits until it is complete, returning a value of true to indicate success.On a nonblocking channel, the connect() method returns immediately with a value of false. To figure out what’s going on over the channel and respond to events, you must use a channel-listening object called a Selector. A Selector is an object that keeps track of things that happen to a socket channel (or another channel in the package that is a subclass of SelectableChannel). To create a Selector, call its open() method, as in the following statement:
Selector monitor = Selector.open();
When you use a Selector, you must indicate the events you are interested in monitoring. This is handled by calling a channel’s register(Selector, int, Object) method. The three arguments to register() are the following:
· The Selector object you have created to monitor the channel
· An int value that represents the events being monitored (also called selection keys)
· An Object that can be delivered along with the key, or null otherwise
Instead of using an integer value as the second argument, it’s easier to use one or more class variables from the SelectionKey class: SelectionKey.OP_CONNECT to monitor connections, SelectionKey.OP_READ to monitor attempts to read data, and SelectionKey.OP_WRITE to monitor attempts to write data. The following statements create a Selector to monitor a socket channel called wire for reading data:
Selector spy = Selector.open();
channel.register(spy, SelectionKey.OP_READ, null);
To monitor more than one kind of key, add the SelectionKey class variables together. For example:
Selector spy = Selector.open();
channel.register(spy, SelectionKey.OP_READ + SelectionKey.OP_WRITE, null);
After the channel and selector have been set up, you can wait for events by calling the selector’s select() or select(long) methods. The select() method is a blocking method that waits until something has happened on the channel. The select(long) method is a blocking method that waits until something has happened or the specified number of milliseconds has passed, whichever comes first. Both select() methods return the number of events that have taken place, or 0 if nothing has happened. You can use a while loop with a call to the select() method as a way to loop until something happens on the channel.
After an event has taken place, you can find out more about it by calling the selector’s selectedKeys() method, which returns a Set object containing details on each of the events. Use this Set object as you would any other set, creating an Iterator to move through the set by using its hasNext() and next() methods. The call to the set’s next() method returns an object that should be cast to a SelectionKey object. This object represents an event that took place on the channel. Three methods in the SelectionKey class can be used to identify the key in a client program: isReadable(), isWritable(), and isConnectible(). Each returns a boolean value. (A fourth method is used when you’re writing a server: isAcceptable().)
After you retrieve a key from the set, call the key’s remove() method to indicate that you are going to do something with it. The last thing to find out about the event is the channel on which it took place. Call the key’s channel() method, which returns the associated SocketChannel. If one of the events identifies a connection, you must make sure that the connection has been completed before using the channel. Call the key’s isConnectionPending() method, which returns true if the connection is still in progress and false if it is complete. To deal with a connection that is still in progress, you can call the socket’s finishConnect() method, which makes an attempt to complete the connection. Using a nonblocking socket channel involves the interaction of numerous new classes from the java.nio and java.net packages. To give you a more complete picture of how these classes work together, the day’s final project is NewFingerServer, a web application that uses a nonblocking socket channel to handle finger requests. Enter the text of Listing 17.5, save it as NewFingerServer.java, and compile the application.
The finger server requires one or more user .plan files stored in text files. These files should have names that take the form username.plan—for example, linus.plan, lucy.plan, and franklin.plan. Before running the server, create one or more plan files in the same folder as NewFingerServer.class. When you’re done, run the finger server with no arguments:
java NewFingerServer
The application waits for incoming finger requests, creating a nonblocking server socket channel and registering one kind of key for a selector to look for: connection events. Inside a while loop that begins on line 25, the server calls the Selector object’s select() method to see whether the selector has received any keys, which would occur when a finger client makes a connection. When it has, select() returns the number of keys, and the statements inside the loop are executed. After the connection is made, a buffered reader is created to hold a request for a .plan file. The syntax for the command is simply the username of the .plan file being requested.