Java Non-Blocking I/O Concepts: Difference between revisions

From NovaOrdis Knowledge Base
Jump to navigation Jump to search
Line 71: Line 71:


==Buffer==
==Buffer==
=Channel=
A Channel represents an open connection to an entity such as a hardware device, a file, a network socket or a program component that is capable of performing I/O operations. The Channel is essentially a source of I/O events. The application does not read or write data from/to the Channel directly, it does so via [[#Buffer|Buffers]], after being notified of data availability via a [[#Selector|selector]]. For more details se [[#Channel.2FBuffer_Interaction|Channel/Buffer Interaction]] below.
For more details about Channels see:
<blockquote style="background-color: #f9f9f9; border: solid thin lightgrey;">
:[[NIO Channels]]
</blockquote>
=Buffer=
<tt>java.nio.Buffer</tt> is a container for a fixed amount of data. More details on Buffers is available in:
{{Internal|NIO Buffer Mechanics|NIO Buffer Mechanics}}
=Channel/Buffer Interaction=
==Reading from a Channel==
To read from a channel use:
<pre>
ReadableByteChannel channel = ...;
ByteBuffer buffer = ...;
channel.read(buffer);
</pre>
The invocation of the <tt>read()</tt> method initiates an attempt to transfer data from the channel into the buffer. The read operation might not fill the buffer, and it fact might not read any bytes at all - if none are available on the channel. In the best case, it fills all the space available in the buffer. The result is the number of bytes read, possibly zero, or -1 if the channel has reached end-of-stream. The buffer's internal accounting variables are modified correspondingly.
If the channel is in blocking mode, the method will block until at least one byte is read.
Also see https://docs.oracle.com/javase/8/docs/api/java/nio/channels/ReadableByteChannel.html


=Java NIO and TCP Connections=
=Java NIO and TCP Connections=

Revision as of 22:15, 25 July 2018

External

Internal

Overview

Until NIO, all that was available for I/O in Java were Streams (java.io.*). All operations with Streams are blocking: a thread waits until there is data to read from the Stream instance or until it can write to the Stream instance. The disadvantage of this approach is that multiple threads are required when we need to simultaneously handle multiple sources of data, such as concurrent network connections. These threads usually spend most of their time blocked waiting on I/O events. This is the I/O and threading model Tomcat is built on. Another particularity of the Stream API is that it reads or writes data one byte at a time, which is not the most efficient way of doing I/O - the O/S handle I/O in blocks.

NIO addresses both of this issues: it offers a method of handing I/O in an asynchronous manner, referred to as "multiplexed non-blocking I/O facility", described below. It also comes with APIs that allow block-oriented I/O operations.

Multiplexed Non-Blocking I/O Facility

Selectors, selection keys and selectable channels work together to provide a multiplexed, non-blocking I/O facility, whose main advantage is that is much more scalable than thread-oriented blocking I/O.

The facility works as follows: a Selector instance is created using the Selector.open() static method. The Selector instance is then used to register selectable channels, so the Selector instance acts as a multiplexor of selectable channels. A selectable channel is a special type of channel that can be put into non-blocking mode, and which has to be put in blocking mode if it is to be multiplexed under a selector.

The set of channel I/O operations to be tested for readiness by the selector, also referred to as the "interest set", are also specified during the registration procedure.

The procedure returns a selection key that represents that registration.

After the registration, a selection operation can be performed to discover which channels, if any, have become ready to perform one or more of the operations in which interest was previously declared. The underlying operating system is queried for an update on registered channels' readiness. If a channel is ready, the key returned when it was registered will be added to the selector's selected-key set. The set is returned by Selector.selectedKeys(). The keys of the selected-key set can be examined to determine the operations for which each channel is ready. The key also gives access to the channel instance, which then can be used to perform the I/O operation.

java.nio.channels provides selectable channel classes corresponding to DatagramSocket, ServerSocket and Socket. If the channel needs an associated socket, the socket will be created as a side effect on this operation.

Channels cannot be deregistered directly, instead, the key representing their registration must be cancelled.

Stream-Oriented vs. Block-Oriented I/O Operations

A stream-oriented I/O system deals with data one byte at a time: an input stream produces a byte of data and an output stream consumes a byte of data. Stream-oriented API allow data to be easily filtered. They also allow multiple streams to be easily chained. However, moving data this way is rather slow. A block-oriented I/O system deals with data in blocks - each operation produced or consumes a block of data in one step. This could move data faster, but the block-oriented APIs lack the elegance and simplicity of the stream-oriented APIs.





NIO (java.nio.*) offers access to underlying O/S non-blocking I/O facilities and offers a selectors mechanism through which a single thread could be notified and process I/O events arriving from multiple sources.

Also, because the underlying operating system I/O facilities are block-oriented for efficiency - hardware usually transfers data in blocks rather than byte by byte - NIO exposes block-level access via Channels and Buffers. Data can be read and written in blocks via Buffers into and from Channels. Streams only offer sequential access - once a byte has been read, it is gone from the Stream and it is the application's responsibility to cache it locally if it needs it later. With Buffer, the caching is already done, efficiently, by the O/S.

Channels are also sources of asynchronous I/O events that are routed by selectors into the application. The data transfer between Channels and Buffers is done transparently, without requiring application threads to move bytes around. An application thread is notified once the transfer had completed via an I/O event. The NIO API does not do anything that the Stream API can't do - essentially reading and writing data from/to I/O devices - but it does it faster and using less threads.

Block-access and non-blocking I/O read and write allow Java applications to implement high-speed I/O without having to write native code that would access O/S specific facilities.

Primitives

Selector

Selector

A multiplexor that allows registration of multiple selectable channels so they can be serviced by a single selector thread. The selector thread will block in select() and it will be notified by the selector's implementation only when an I/O event, such as data becoming available or a new connection being established, occurs.

Channel

Selectable channel.

ServerSocketChannel

ServerSocketChannel

A selectable channel used to listen for incoming network connections and create new SocketChannels for each TCP connection. The ServerSocketChannel delegates to a ServerSocket to do the actual listening.

SocketChannel

SocketChannel

Buffer

Channel

A Channel represents an open connection to an entity such as a hardware device, a file, a network socket or a program component that is capable of performing I/O operations. The Channel is essentially a source of I/O events. The application does not read or write data from/to the Channel directly, it does so via Buffers, after being notified of data availability via a selector. For more details se Channel/Buffer Interaction below.

For more details about Channels see:

NIO Channels

Buffer

java.nio.Buffer is a container for a fixed amount of data. More details on Buffers is available in:

NIO Buffer Mechanics

Channel/Buffer Interaction

Reading from a Channel

To read from a channel use:

ReadableByteChannel channel = ...;
ByteBuffer buffer = ...;
channel.read(buffer);

The invocation of the read() method initiates an attempt to transfer data from the channel into the buffer. The read operation might not fill the buffer, and it fact might not read any bytes at all - if none are available on the channel. In the best case, it fills all the space available in the buffer. The result is the number of bytes read, possibly zero, or -1 if the channel has reached end-of-stream. The buffer's internal accounting variables are modified correspondingly.

If the channel is in blocking mode, the method will block until at least one byte is read.

Also see https://docs.oracle.com/javase/8/docs/api/java/nio/channels/ReadableByteChannel.html

Java NIO and TCP Connections

A working example that shows various Java NIO primitives collaborating in establishing a bidirectional TCP connection:

Java NIO and TCP Connections