Server Socket Channels

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:

  1. Open a ServerSocketChannel using the open( ) method.
  2. Retrieve the channel's ServerSocket using the socket( ) method.
  3. Bind the ServerSocket to a port.
  4. Accept an incoming connection to get a socket channel.
  5. Communicate over the SocketChannel.
  6. Close the SocketChannel.
  7. Go to step 4.

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.

Категории