The ServerSocketChannel class is where NIO really begins to shine. One server thread using a ServerSocketChannel can manage many different clients. The key to this is nonblocking I/O, discussion of which I'll again defer to the next chapter. However, for the moment we can look at the basics of writing a server using the new I/O API. We'll add selectors in the next chapter.
The basic strategy for writing a server with the new I/O API is:
This is very similar to how a server written using traditional I/O works, except that you use buffers and channels instead of streams to communicate. You could move steps 5 and 6 into a separate thread to handle multiple connections simultaneously.
More likely, you'd use the nonblocking I/O introduced in the next chapter, but for the moment let's look at the simpler blocking approach.
There are no public constructors in the ServerSocketChannel class. Instead, a new ServerSocketChannel object is returned by the static open( ) method:
public static SocketChannel open( ) throws IOException
For example, this statement creates a new ServerSocketChannel that is not yet connected to anything:
ServerSocketChannel channel = ServerSocketChannel.open( );
To start listening for incoming connections, you have to bind to the port. This is done not by the ServerSocketChannel itself, but rather by its associated java.net.ServerSocket object. This object is returned by the socket( ) method:
public abstract ServerSocket socket( )
For example:
SocketAddress port = new InetSocketAddress(8000); channel.socket( ).bind(port);
You can now begin accepting connections with the accept( ) method:
public abstract SocketChannel accept( ) throws IOException
This returns a SocketChannel object that you use to communicate with the remote client. The ServerSocketChannel class itself does not have any read( ) or write( ) methods.
Of course, ServerSocketChannel also inherits all the usual methods of any channel, such as isOpen( ) and close( ).
We're now ready to write a simple network server. Let's reproduce the Hello server from Example 15-4, but this time implement it with the new I/O API rather than the traditional stream-based APIs. Recall that this server responds to any client that connects with a message like:
Hello titan.oit.unc.edu/152.2.22.14 on port 50361 This is utopia.poly.edu/128.238.3.21 on port 2345
Neither the ServerSocketChannel class nor the SocketChannel class has methods to determine the IP address of either the local or the remote end of the connection. However, we can use the socket( ) methods to get this information from the associated Socket and ServerSocket objects. Example 15-6 demonstrates.
Example 15-6. The new HelloServer program
import java.net.*; import java.io.*; import java.nio.ByteBuffer; import java.nio.channels.*; public class NewIOHelloServer { public final static int PORT = 2345; public static void main(String[] args) throws IOException { ServerSocketChannel serverChannel = ServerSocketChannel.open( ); SocketAddress port = new InetSocketAddress(PORT); serverChannel.socket( ).bind(port); while (true) { try { SocketChannel clientChannel = serverChannel.accept( ); String response = "Hello " + clientChannel.socket().getInetAddress( ) + " on port " + clientChannel.socket().getPort( ) + " "; response += "This is " + serverChannel.socket( ) + " on port " + serverChannel.socket().getLocalPort( ) + " "; byte[] data = response.getBytes("UTF-8"); ByteBuffer buffer = ByteBuffer.wrap(data); while (buffer.hasRemaining( )) clientChannel.write(buffer); clientChannel.close( ); } catch (IOException ex) { // This is an error on one connection. Maybe the client crashed. // Maybe it broke the connection prematurely. Whatever happened, // it's not worth shutting down the server for. } } // end while } // end main } // end NewIOHelloServer |
Here's some typical output when connecting to this server with telnet:
$ telnet 192.168.254.100 2345 Trying 192.168.254.100... Connected to 192.168.254.100. Escape character is '^]'. Hello /192.168.254.36 on port 4940 This is ServerSocket[addr=/0.0.0.0,localport=2345] on port 2345 Connection closed by foreign host.
To be honest, this is complete overkill for such a simple server. If there's any performance difference between the original stream-based example and this one, I'd expect the original to be faster. There's enough constant overhead in setting up the buffers and channels that speedups become apparent only for larger datasets, and likely then only if you're using nonblocking I/O. However, this example does enable me to demonstrate the relevant points.
Basic I/O
Introducing I/O
Output Streams
Input Streams
Data Sources
File Streams
Network Streams
Filter Streams
Filter Streams
Print Streams
Data Streams
Streams in Memory
Compressing Streams
JAR Archives
Cryptographic Streams
Object Serialization
New I/O
Buffers
Channels
Nonblocking I/O
The File System
Working with Files
File Dialogs and Choosers
Text
Character Sets and Unicode
Readers and Writers
Formatted I/O with java.text
Devices
The Java Communications API
USB
The J2ME Generic Connection Framework
Bluetooth
Character Sets