JBoss.orgCommunity Documentation
In NIO terminology, a channel represents a connection to an entity capable of performing I/O operations. XNIO shares this definition, and in fact the channel API in XNIO has many similarities to the NIO channel API. Many operations are intercompatible between NIO and XNIO for this reason.
There are many types of channels; in fact, the entire org.jboss.xnio.channels
package
is dedicated to hosting the interface hierarchy therefor. The complete diagram of this hierarchy
may be viewed in the online API documentation.
While there are a multitude of interfaces, only a relatively small number of them are generally required to perform most tasks.
The key XNIO channel types for most network applications are:
StreamChannel
- a basic, bidirectional byte-oriented data channel.
It extends from both of the two unidirectional parent types StreamSourceChannel
and StreamSinkChannel
. These types in turn extend the NIO ScatteringByteChannel
and GatheringByteChannel
interface types.
TcpChannel
- a subtype of StreamChannel
which corresponds to a single TCP connection.
SslTcpChannel
- a subtype of TcpChannel
which corresponds to
a TCP connection encapsulated with SSL or TLS.
UdpChannel
- a message-oriented channel which represents a bound UDP socket.
In addition, the NIO FileChannel
type may also be used and can interoperate with XNIO
channel types.
Like NIO, reading and writing channels in XNIO amounts to using one of the read
and
write
or receive
and send
, depending on whether the channel
is a stream channel or a message (datagram) channel.
The close
method exists on all channel types. Its purpose is to release all resources
associated with a channel, and ensure that the channel's close listener is invoked exactly one time.
This method should always be called when a channel will no longer be used, in
order to prevent resource starvation and possibly long-term leakage.
When a program is done sending data on a channel, its shutdownOutput
method may be invoked
to terminate output and send an end-of-file condition to the remote side. Since this amounts to a
write operation on some channel types, calling this method may not be immediately successful, so the return value
must be checked, like any non-blocking operation. If the method returns true
, then
the shutdown was successful; if false
, then the shutdown cannot proceed until the channel
is writable again. Be aware that once this method is called, no further writes may take place, even
if the method call was not immediately successful. Even when this method returns false
,
some transmission may have occurred; the output side of the channel should be considered to be in a
"shutting down" state.
When a program does not wish to receive any more input, the shutdownInput
method may be invoked.
This will cause any future data received on the channel to be rejected or ignored. This method always
returns immediately. Many applications will not need to call this method, however, as they will want
to consume all input. When all input has been read, subsequent read
or receive
method invocations will return an EOF value such as -1.
The application program is notified of events on a channel by way of the ChannelListener
interface. A class which implements this interface is known as a channel listener.
The interface's single method, handleEvent
, is invoked when a specific event occurs on a channel.
By default, XNIO uses only a small number of dedicated threads to handle events. This means that
in general, channel listeners are expected to run only for a brief period of time (in other words, the
listener should be non-blocking). If a channel listener
runs for an extended period of time, other pending listeners will be starved while the XNIO provider waits
for the listener to complete. If your design calls for long-running listeners, then the specific service,
or alternately the entire provider instance, should be configured with a java.util.concurrent.Executor
which
runs handlers in a thread pool. Such a configuration can simplify your design, at the cost of a slight
increase in latency.
Registering a channel listener involves accessing the setter for the corresponding listener type.
The setter can accept a listener, which will be stored internally, replacing any previous value, to be
invoked when the listener's condition is met. The new listener value will take effect immediately. Setting
a listener to null
will cause the corresponding event to be ignored. By default, unless explicitly
specified otherwise, all listeners for a channel will default to null
and have to be set in order
to receive the corresponding notification.
The ChannelListener
interface has a type parameter which specifies what channel type
the listener expects to receive. Every channel listener setter will accept a channel listener for
the channel type with which it is associated; however, they will additionally accept a channel listener
for any supertype of that channel type as well. This allows general-purpose listeners
to be applied to multiple channel types, while also allowing special-purpose listeners which take
advantage of the features of a more specific channel type.
There are several types of events for which a channel listener may be registered. Though the circumstances for each type may differ, the same interface is used for all of them. The types are:
Channel Readable - called when readable notifications are enabled and
a call to the channel's read
or receive
method is expected to yield useful information. This notification type is enabled by
invoking the resumeReads
method on a readable channel, and is implicitly
disabled once the corresponding channel listener, if any, is invoked. It can also be explicitly
disabled by invoking the suspendReads
method.
Channel Writable - called when writable notifications are enabled and
a call to the channel's write
or send
method is expected to accept at least some data; a call to the channel's flush
or
shutdownWrites
is expected to make progress or complete.
This notification type is enabled by
invoking the resumeWrites
method on a writable channel, and is implicitly
disabled once the corresponding channel listener, if any, is invoked. It can also be explicitly
disabled by invoking the suspendWrites
method.
Channel Closed - called when the channel is closed via the close
method.
Channel Bound - called when a channel was newly created and bound to a local address. The
new channel is passed in as the argument to the listener. Such channels will typically extend
the BoundChannel
interface.
Channel Opened - called when a channel was newly created and connected to,
or accepted from, a remote peer. The
new channel is passed in as the argument to the listener. Such channels will typically extend
the ConnectedChannel
interface.
Not all event types are relevant for all channels, however most channel types will support notification for channel close events, and most channel types will have a way to register a listener for channel binding or opening, though the mechanism may vary depending on whether the channel in question is a client or server, TCP or UDP, etc.
When using NIO, a channel may operate in a blocking or non-blocking fashion. Non-blocking I/O is achieved in NIO by way of selectors, which are essentially coordination points between multiple channels. However, this API is combersome and difficult to use; as such, XNIO does not use this facility, preferring instead the callback-based listener system.
An XNIO channel is always non-blocking; however,
blocking I/O may be simulated by way of the awaitReadable()
and awaitWritable()
methods, which will block until the channel is expected to be readable or writable without blocking,
or until the current thread is interrupted. The ChannelInputStream
and ChannelOutputStream
classes use
this facility by way of a wrapper around the stream channel types with a blocking InputStream
or OutputStream
for compatibility with APIs which rely on these types.
Because of this mechanism, blocking and non-blocking operations can be intermixed freely and easily. One common pattern, for example, revolves around using blocking operations for write and non-blocking operations for read. Another pattern is to use blocking I/O in both directions, but only for the duration of a request.
NIO supports so-called "zero-copy" I/O by way of two methods on the FileChannel
class:
transferTo
and transferFrom
. These methods accept either a readable or writable
channel as an argument. Since XNIO extends the NIO channels for its stream types, you can pass XNIO
stream channel types directly in to these methods.
However, most JDKs will, in reality, probe the
given channel type and perform special optimizations for certain implementations. Because of this,
passing in an XNIO channel type may yield poorer performance than the equivalent NIO channel would.
To address this issue, the XNIO stream channel types also have transferFrom
and
transferTo
methods which accept a FileChannel
as an argument, and in turn
can take advantage of the same optimizations.