Easy to Learn Java: Programming Articles, Examples and Tips

Start with Java in a few days with Java Lessons or Lectures

Home

Code Examples

Java Tools

More Java Tools!

Java Forum

All Java Tips

Books

Submit News
Search the site here...
Search...
 
Search the JavaFAQ.nu
1000 Java Tips ebook

1000 Java Tips - Click here for the high resolution copy!1000 Java Tips - Click here for the high resolution copy!

Java Screensaver, take it here

Free "1000 Java Tips" eBook is here! It is huge collection of big and small Java programming articles and tips. Please take your copy here.

Take your copy of free "Java Technology Screensaver"!.

New NIO Tutorial with working examples: the client, code. part II

JavaFAQ Home » Networking Go to all tips in Networking


Bookmark and Share

The Rox Java NIO Tutorial, part II

Part I was published here.

Published here by courteous permission from James Greenfield.

Contents

 

  1. Introduction (in part I)
  2. Credits (in part I)
  3. General principles (in part I)
  4. The server (in part I)
  5. The client
  6. NIO and SSL on 1.4
  7. The code
  8. About the author

 

 

 

The client

Before getting started it's worth mentioning that the client implementation is going to end up looking a lot like the server implementation we just finished writing. There's going to be a lot of common functionality that can be factored out and shared between the client and server implementation. For the most part I'm going to totally ignore that and write the client as a standalone piece of code to avoid clouding the picture with layer upon layer of abstraction. I'll leave that as an exercise to the reader. However, there are one or two little bits of code we've already written that will be shared between the implementations, because not doing so may reduce clarity. It's a judgement call on my part so if you disagree, forgive me and try not to let it bother you too much.

That said, let's get on with the client code. Once again, we're going to need some infrastructure. We can actually borrow a lot from the server code, since once a connection is accepted/established there's not a lot of difference between a client and server. A client still needs to read and write data, so at the very least we're going to need that logic. Given the similarities I'm going to build the client implementation by starting with the server implementation and tweaking it as needed.

First, let's remove some bits we don't need. The server socket channel can go, we won't be needing that. We can also remove the accept() method and the logic in the run() method that invokes it.

Next we can throw out most of the logic in the initSelector() method. In fact, we only need the first line, leaving us with a method that looks like this.

  private Selector initSelector() throws IOException {
    // Create a new selector
    return SelectorProvider.provider().openSelector();
  }

We're still missing the most important piece of client-side logic: establishing a connection. This usually goes hand in hand with wanting to send some data. There are scenarios where that's not true: you might, for example, want to establish a bunch of connections to a remote server up front and then send all traffic over those connections. I'm not going to take that approach but it's not hard to get there from here. While we're on the topic of what I'm not going to do, I'm also not going to implement connection pooling. I'm not going to implement a client that talks to a bunch of remote servers on different addresses; the client here will talk to one remote server (albeit using multiple simultaneous connections). What I am going to implement is a client that establishes a new connection, sends a message, waits for a response and then disconnects (oh, and I'm not going to handle the case where a response isn't received). Anything beyond that adds very little beyond more code to confuse the issue.

Phew, that paragraph really got away from me. Back to the code! What we'll need is a send() method that requests a new connection and queues data to be sent on that connection when it comes up. We'll also need some way of notifying the caller when the response is received, but one step at a time.

But before we can put together the send() method we really need a method for initiating a new connection. The send() method will make more sense if we introduce initiateConnection() first.

  private SocketChannel initiateConnection() throws IOException {
    // Create a non-blocking socket channel
    SocketChannel socketChannel = SocketChannel.open();
    socketChannel.configureBlocking(false);
  
    // Kick off connection establishment
    socketChannel.connect(new InetSocketAddress(this.hostAddress, this.port));
  
    // Queue a channel registration since the caller is not the 
    // selecting thread. As part of the registration we'll register
    // an interest in connection events. These are raised when a channel
    // is ready to complete connection establishment.
    synchronized(this.pendingChanges) {
      this.pendingChanges.add(new ChangeRequest(socketChannel, ChangeRequest.REGISTER, SelectionKey.OP_CONNECT));
    }
    
    return socketChannel;
  }

In a nutshell, initiating a connection means creating a new non-blocking socket channel, initiating a (non-blocking) connection and registering an interest in finishing the connection. The only wrinkle is that, since the calling thread is not the selecting thread, the last step must be deferred. We don't wake the selecting thread up because the method that calls this will want to queue some data to be written when the connection is completed. Waking the selecting thread up here opens us up to a race condition where the connection is completed by the selecting thread before the calling thread queues the data and OP_WRITE is never set on the channel's selection key.

Given the requirements we discussed earlier and the initiateConnection() above, our send() method needs to look something like this (with an additional instance member which will be clarified later).

  
  // Maps a SocketChannel to a RspHandler
  private Map rspHandlers = Collections.synchronizedMap(new HashMap());
  

  public void send(byte[] data, RspHandler handler) throws IOException {
    // Start a new connection
    SocketChannel socket = this.initiateConnection();
    
    // Register the response handler
    this.rspHandlers.put(socket, handler);
    
    // And queue the data we want written
    synchronized (this.pendingData) {
      List queue = (List) this.pendingData.get(socket);
      if (queue == null) {
        queue = new ArrayList();
        this.pendingData.put(socket, queue);
      }
      queue.add(ByteBuffer.wrap(data));
    }

    // Finally, wake up our selecting thread so it can make the required changes
    this.selector.wakeup();
  }

It's important to note that nowhere in the two methods just introduced do we request that the OP_CONNECT flag be set on the socket channel's selection key. If we did that we'd overwrite the OP_CONNECT flag and never complete the connection. And if we combined them then we'd run the risk of trying to write on an unconnected channel (or at least having to deal with that case). Instead what we'll do is set the OP_WRITE flag when the connection is established (we could do this based on whether or not data was queued but for our scenario it's acceptable to do it this way).

Which brings us to the second change to our run() method. Remember that we started out with the server implementation, so we still have the logic for handling a pending change to the interest operation set for a selection key. To that we need to handle a pending channel registration. Voila...

            switch (change.type) {
            case ChangeRequest.CHANGEOPS:
              SelectionKey key = change.socket.keyFor(this.selector);
              key.interestOps(change.ops);
              break;
            case ChangeRequest.REGISTER:
              change.socket.register(this.selector, change.ops);
              break;
            }

And, of course, we'll need to handle the connectable event...

          // Check what event is available and deal with it
          if (key.isConnectable()) {
            this.finishConnection(key);
          else if (key.isReadable()) {
            this.read(key);
          else if (key.isWritable()) {
            this.write(key);
          }
 

And of course we need an implementation for finishConnection().

  private void finishConnection(SelectionKey key) throws IOException {
    SocketChannel socketChannel = (SocketChannel) key.channel();
  
    // Finish the connection. If the connection operation failed
    // this will raise an IOException.
    try {
      socketChannel.finishConnection();
    catch (IOException e) {
      // Cancel the channel's registration with our selector
      key.cancel();
      return;
    }
  
    // Register an interest in writing on this channel
    key.interestOps(SelectionKey.OP_WRITE);
  }

As I mentioned before, once the connection is complete we immediately register an interest in writing on the channel. Data has already been queued (or we wouldn't be establishing a connection in the first place). Since the current thread in this case is the selecting thread we're free to modify the selection key directly.

One last change before we're ready to put it all together. Our server implementation's read() handed the data off to a worker thread (that just echoed it back). For the client side we really want to hand the data (the response) back to whoever initiated the original send (the request). Scroll back up the page until you find mention of a RspHandler (it was passed as a parameter to send()). That's our conduit back to the original caller and it replaces our call to the EchoServer in the read() method. So the last line looks like this:

    // Handle the response
    this.handleResponse(socketChannel, this.readBuffer.array(), numRead);

Which begs an implementation of handleResponse. All this method has to do is look up the handler we stashed for this channel and pass it the data we've read. We let the handler indicate whether or not it's seen enough. If it has we'll close the connection.

  private void handleResponse(SocketChannel socketChannel, byte[] data, int numRead) throws IOException {
    // Make a correctly sized copy of the data before handing it
    // to the client
    byte[] rspData = new byte[numRead];
    System.arraycopy(data, 0, rspData, 0, numRead);
    
    // Look up the handler for this channel
    RspHandler handler = (RspHandler) this.rspHandlers.get(socketChannel);
    
    // And pass the response to it
    if (handler.handleResponse(rspData)) {
      // The handler has seen enough, close the connection
      socketChannel.close();
      socketChannel.keyFor(this.selector).cancel();
    }
  }

The response handler itself is pretty simple but for it to make complete sense we need to see how all of this hangs together in the form of our client's main() method.

  public static void main(String[] args) {
    try {
      NioClient client = new NioClient(InetAddress.getByName("localhost"), 9090);
      Thread t = new Thread(client);
      t.setDaemon(true);
      t.start();
      RspHandler handler = new RspHandler();
      client.send("Hello World".getBytes(), handler);
      handler.waitForResponse();
    catch (Exception e) {
      e.printStackTrace();
    }
  }

The response handler implementation is shown below. I waited until last so that the waitForResponse() would make sense, along with some of the synchronization logic. It's entirely possible to pull this logic into the send() method, effectively making it a synchronous call. But this tutorial is illustrating asynchronous I/O so that seems kind of silly. It's not as silly as it sounds, mind you, since you might want to handle a large number of active connections even if the client is forced to use them one at a time. But this illustrates how to perform the send asynchronously. Everything else is merely details.

NIO and SSL on 1.4

I have a working implementation of SSL over NIO under Java 1.4 with no external libraries. I'm prepared to write this up too, or even just to share the source code. Drop me a line if that appeals or just wait until I feel inspired again.

The code

The code samples here are not entirely complete. I've left out a few minor details for clarity. Things like import statements. Complete working source code is available here:

 

About the author

My name is James Greenfield. I'm currently employed by Amazon in Cape Town, South Africa.

I enjoy writing code that other people actually use. Most of all though, I enjoy writing code for other programmer's. When the mood takes me I also enjoy writing documentation for other programmer's. I'm happiest when I can take something complicated, or unpleasant, or opaque, or just plain hard, and make it a non-issue.

I can be reached at nio @flat502.com (I sense a lame "For a good time..." joke lurking in there).

Original article is here.

Related publications at http://JavaFAQ.nu:

Java sockets are not responsive: ways to fix it (with code example)

Java code examples by package and class name on http://JavaFAQ.nu:

ServerSocketSocket, SocketAddress, SocketException, java.nio, java.nio.channels, java.nio.channels.spi, java.nio.charset, java.nio.charset.spi

 


 Printer Friendly Page  Printer Friendly Page
 Send to a Friend  Send to a Friend

.. Bookmark and Share

Search here again if you need more info!
Custom Search



Home Code Examples Java Forum All Java Tips Books Submit News, Code... Search... Offshore Software Tech Doodling

RSS feed Java FAQ RSS feed Java FAQ News     

    RSS feed Java Forums RSS feed Java Forums

All logos and trademarks in this site are property of their respective owner. The comments are property of their posters, all the rest 1999-2006 by Java FAQs Daily Tips.

Interactive software released under GNU GPL, Code Credits, Privacy Policy