JBoss.orgCommunity Documentation

Chapter 3. XNIO Channels

3.1. Channels Overview
3.2. Channel Types
3.3. Channels: Reading and Writing
3.4. Channels: Cleaning Up and Shutting Down
3.5. Channel Listeners
3.6. Channel Blocking Modes
3.7. Zero-Copy I/O

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:

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.