JBoss.orgCommunity Documentation

Chapter 1. Quick Start

1.1. TCP Client and Server

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".