JBoss.orgCommunity Documentation
The first step when writing any XNIO application is usually to create a class which implements the
ChannelListener
interface. This is true regardless of protocol or whether it is a client
or a server. In a TCP server, the ChannelListener
instance is invoked every time a client connection
is accepted. In the client, it is invoked only once when the connection to the server is established.
Either way, the parameter to the listener is the newly created channel.
This primordial channel listener will generally do some connection setup work (such as
checking the remote IP address against a blacklist or whitelist, and registering additional
channel listeners for read and/or write events), but it should generally not
perform any operation that can block for an extended period of time unless an Executor
is in use (either configured when the client or server is set up [see Section 4.2, “The XNIO Provider”] or explicitly used by
the handler implementation).
For most simple usages, there are two options for what to do next: send some data, or await the
reception of some data. The simplest way to send is to use the Channels.writeBlocking()
methods, which performs a blocking write. Since this operation may block, it should be done in an
executor task.
Receiving data is generally best accomplished by way of registering a channel-readable
listener via the channel.getReadSetter().set()
method sequence, and then calling the
channel.resumeReads()
method (usually from handleOpened()
and implementing
the handleReadable()
method. Once data is available on the channel, your read handler
method will be invoked with the channel as the argument.
The read handler, like any channel listener method, should not indulge in any
blocking or long-running operations as this can starve other consumers. If such an operation is
required, then it should be spun off to another thread using an Executor
. The general
form for this method is something like the following code snippet:
public void handleEvent(final StreamChannel channel) { boolean ok = false; final ByteBuffer buffer = ByteBuffer.allocate(400); try { int c; while ((c = channel.read(buffer) != 0) { if (c == -1) { // Channel end-of-file log.info("Remote side closed the channel."); IoUtils.safeClose(channel); return; } else if (c == 0) { // Channel has no data available; indicate our further interest and return channel.resumeReads(); ok = true; return; } buffer.flip(); // XXX process buffer here // now clear the buffer for the next data buffer.clear(); } } catch (IOException e) { log.error("I/O exception on read: %s", e); return; } finally { if (! ok) IoUtils.safeClose(channel); } }
Following this general form is important - it is resilient against sporadic notifications which are possible on some platforms; it ensures that data is read in sequence, which is very important for stream channels; and it ensures that if the read fails, the channel is closed in an orderly fashion rather than just "hanging".