Friday, June 28, 2013

JAVA NIO tutorial

Most programmers are familiar with java.io package which offers a variety of classes for input/output operations to files or the network. In this "conventional IO", programs read from and write to streams. A stream is a sequence of bytes.You can either write to a stream or read from it, but you cannot do both on the same stream. While the operating system and the JVM are reading or writing the data, the thread might block. This is not very scalable for servers that are servicing a large numbers of clients. Many threads might just be waiting for IO to complete.

java.nio is a package that offers an alternative approach to IO that is considered more scalable. Even though this has been around for several jdk releases, many Java programmers are not familiar with it. The main difference is that with the nio package the programmer works channels and buffers rather than streams. We describe some basic concepts of the java.nio package

Buffers

Buffer and its sub classes are used as containers of data. They can hold data that needs to be written to a channel or that is read from a channel.

// allocate a character buffer
CharBuffer charBuf = CharBuffer.allocate(128) ;
// put some data into the buffer
charBuf.put("abcdef") ;
 // read data from a buffer
charBuf.flip() ;
char c = charBuf.get() ;

Channels

A channel is an interface that represents a connection to a device or entity that is used for input or output such as a file or a socket. There are many implementations of Channel. For example, FileChannel is for reading and writing file, while SocketChannel is for reading/writing socket.

Unlike with streams, you can read and write to the same channel.From a channel, you can write to a buffer or read from a buffer.

// create a channel to a file
RandomAccessFile thefile = new RandomAccessFile("afile.txt","rw") ;
FileChannel theChannel = thefile.getChannel() ;

// read from a channel
ByteBuffer b = ByteBuffer.allocate(128) ;
int numBytesRead = theChannel.read(b) ;

// write to a channel
String data = "here is some data to write" ;
ByteBuffer bBuf = ByteBuffer.allocate(1024) ;
// put some data into the buffer
bBuf.put(data.getBytes()) ;
 // read data from a buffer
bBuf.flip() ;

while(bBuf.hasRemaining()) {
    channel.write(bBuf) ;
}

The buffer has to be flipped because the channel is going to be reading from it. Write has to be done in a loop because the channel may not write all the bytes at one time.

Selector

A Selector is a component that lets you do non blocking IO. In conventional network programming IO, you create 1 thread per connection. The threads blocks waiting for IO to happen. That is waste of resources.

With Selector, a single thread can express interest in events (like connect, read, write) from multiple channels.  

// Creating a selector
Selector selector = selector.open() ;

You register one of more channels with a Selector , also indicating what event you are interested in.

// Register channel with selector
channel.configureBlocking(false) ;
selector.register(channel,SelectionKey.OP_READ) ;

We are interested in non blocking IO. Hence set blocking to false. SocketChannels can be non blocking but not FileChannels. That is alright because that main use case for non blocking IO is in network programming. For example, a web server that needs to do IO with thousands of clients.

Once the channels are registered, you call the select method which will block till an event occurs on one of the registered channels. This is the only thread the needs to block. 

// sit in a loop waiting for something to happen
while (true) {

int ready = selector.select() ;



}


When there is an event, the select call returns. The selector give the list of selectionkeys that had an event. You can iterate over the selectionkeys and handle the keys appropriately. When necessary, the processing of the event can handed of to a worker thread.

while (true) {

int ready = selector.select() ;

Set selected = selector.selectedKeys() ;
Iterator iter = selected.iterator() ;

    while(iter.hasNext()) {

            SelectionKey key = iter.next() ;

            if (key.isAcceptable()) {

                     // server socket channel accepted a connection

            } else if (key.isConnectable()) {
                     // connected to a server

            } else if (key.isReadable()) {
                    // channel is ready for reading
                   

            } else if (key.isWritable()) {

                    // channel is ready for writing
           }

 
    }
    iter.remove() ;
 } 

From the SelectionKey, you can get the channel on which the event occurs with a simple call.

Channel c = SelectionKey.getChannel() ; 

Once you get the channel , you can read or write to it. Once data is read, it can be offloaded to a worker thread for processing. For writes, you should queue data that needs to be written somewhere, so that as soon as the channel is ready for write, you can get the data and write it.

Most server applications use non blocking IO when the need to scale. Tomcat comes with a NIO adapter which should be use when you need to scale to thousands of clients.

Conclusion

In Summary, the main difference between IO and NIO packages is that IO is stream oriented where as NIO is based on buffers and channels. Buffers give a little more flexibility and ease is reading/writing data. For Network programming, NIO support non blocking mode, which lets you scale by ensuring that every thread does not block waiting for input/output.