Chapter 8. Bootstrapping

This chapter covers

  • Bootstrapping clients and servers
  • Bootstrapping clients from within a Channel
  • Adding ChannelHandlers
  • Using ChannelOptions and attributes

Having studied ChannelPipelines, ChannelHandlers, and codec classes in depth, your next question is probably, “How do all these pieces add up to a working application?”

The answer? “Bootstrapping.” Up to now, we’ve used the term somewhat vaguely, and the time has come to define it more precisely. Simply stated, bootstrapping an application is the process of configuring it to run—though the details of the process may not be as simple as its definition, especially in network applications.

Consistent with its approach to application architecture, Netty handles bootstrapping in a way that insulates your application, whether client or server, from the network layer. As you’ll see, all of the framework components are connected and enabled in the background. Bootstrapping is the missing piece of the puzzle we’ve been assembling; when you put it in place, your Netty application will be complete.

8.1. Bootstrap classes

The bootstrapping class hierarchy consists of an abstract parent class and two concrete bootstrap subclasses, as shown in figure 8.1.

Figure 8.1. Bootstrapping class hierarchy

Rather than thinking of the concrete classes as server and client bootstraps, it’s helpful to keep in mind the distinct application functions they’re intended to support. Namely, a server devotes a parent channel to accepting connections from clients and creating child channels for conversing with them, whereas a client will most likely require only a single, non-parent channel for all network interactions. (As we’ll see, this applies also to connectionless transports such as UDP, because they don’t require a channel for each connection.)

Several of the Netty components we’ve studied in previous chapters participate in the bootstrapping process, and some of these are used in both clients and servers. The bootstrapping steps common to both application types are handled by AbstractBootstrap, whereas those that are specific to clients or servers are handled by Bootstrap or ServerBootstrap, respectively.

In the rest of this chapter we’ll explore these two classes in detail, beginning with the less complex, Bootstrap.

Why are the bootstrap classes Cloneable?

You’ll sometimes need to create multiple channels that have similar or identical settings. To support this pattern without requiring a new bootstrap instance to be created and configured for each channel, AbstractBootstrap has been marked Cloneable.[1] Calling clone() on an already configured bootstrap will return another bootstrap instance that’s immediately usable.

1

Java Platform, Standard Edition 8 API Specification, java.lang, Interface Cloneable, http://docs.oracle.com/javase/8/docs/api/java/lang/Cloneable.html.

Note that this creates only a shallow copy of the bootstrap’s EventLoopGroup, so the latter will be shared among all of the cloned channels. This is acceptable, as the cloned channels are often short-lived, a typical case being a channel created to make an HTTP request.

The full declaration of AbstractBootstrap is

public abstract class AbstractBootstrap
    <B extends AbstractBootstrap<B,C>,C extends Channel>

In this signature, the subclass B is a type parameter to the superclass, so that a reference to the runtime instance can be returned to support method chaining (so-called fluent syntax).

The subclasses are declared as follows:

public class Bootstrap
    extends AbstractBootstrap<Bootstrap,Channel>

and

public class ServerBootstrap
    extends AbstractBootstrap<ServerBootstrap,ServerChannel>

8.2. Bootstrapping clients and connectionless protocols

Bootstrap is used in clients or in applications that use a connectionless protocol. Table 8.1 gives an overview of the class, many of whose methods are inherited from AbstractBootstrap.

Table 8.1. The Bootstrap API

Name

Description

Bootstrap group(EventLoopGroup) Sets the EventLoopGroup that will handle all events for the Channel.
Bootstrap channel(
Class<? extends C>)
Bootstrap channelFactory(
ChannelFactory<? extends C>)
channel() specifies the Channel implementation class. If the class doesn’t provide a default constructor, you can call channelFactory() to specify a factory class to be called by bind().
Bootstrap localAddress(
SocketAddress)
Specifies the local address to which the Channel should be bound. If not provided, a random one will be created by the OS. Alternatively, you can specify the localAddress with bind() or connect().
<T> Bootstrap option(
ChannelOption<T> option,
T value)
Sets a ChannelOption to apply to the ChannelConfig of a newly created Channel. These options will be set on the Channel by bind() or connect(), whichever is called first. This method has no effect after Channel creation. The ChannelOptions supported depend on the Channel type used. Refer to section 8.6 and to the API docs of the ChannelConfig for the Channel type used.
<T> Bootstrap attr(
Attribute<T> key, T value)
Specifies an attribute of a newly created Channel. These are set on the Channel by bind() or connect(), depending on which is called first. This method has no effect after Channel creation. Please refer to section 8.6.
Bootstrap handler(ChannelHandler) Sets the ChannelHandler that’s added to the ChannelPipeline to receive event notification.
Bootstrap clone() Creates a clone of the current Bootstrap with the same settings as the original.
Bootstrap remoteAddress(
SocketAddress)
Sets the remote address. Alternatively, you can specify it with connect().
ChannelFuture connect() Connects to the remote peer and returns a ChannelFuture, which is notified once the connection operation is complete.
ChannelFuture bind() Binds the Channel and returns a ChannelFuture, which is notified once the bind operation is complete, after which Channel.connect() must be called to establish the connection.

The next section presents a step-by-step explanation of client bootstrapping. We’ll also discuss the matter of maintaining compatibility when choosing among the available component implementations.

8.2.1. Bootstrapping a client

The Bootstrap class is responsible for creating channels for clients and for applications that utilize connectionless protocols, as illustrated in figure 8.2.

Figure 8.2. Bootstrapping process

The code in the following listing bootstraps a client that uses the NIO TCP transport.

Listing 8.1. Bootstrapping a client

This example uses the fluent syntax mentioned earlier; the methods (except connect()) are chained by the reference to the Bootstrap instance that each one returns.

8.2.2. Channel and EventLoopGroup compatibility

The following directory listing is from the package io.netty.channel. You can see from the package names and the matching class-name prefixes that there are related EventLoopGroup and Channel implementations for both the NIO and OIO transports.

Listing 8.2. Compatible EventLoopGroups and Channels
channel
├───nio
│       NioEventLoopGroup
├───oio
│       OioEventLoopGroup
└───socket
    ├───nio

    │       NioDatagramChannel
    │       NioServerSocketChannel
    │       NioSocketChannel
    └───oio
            OioDatagramChannel
            OioServerSocketChannel
            OioSocketChannel

This compatibility must be maintained; you can’t mix components having different prefixes, such as NioEventLoopGroup and OioSocketChannel. The following listing shows an attempt to do just that.

Listing 8.3. Incompatible Channel and EventLoopGroup

This code will cause an IllegalStateException because it mixes incompatible transports:

Exception in thread "main" java.lang.IllegalStateException:
incompatible event loop type: io.netty.channel.nio.NioEventLoop at
io.netty.channel.AbstractChannel$AbstractUnsafe.register(
AbstractChannel.java:571)
More on IllegalStateException

When bootstrapping, before you call bind() or connect() you must call the following methods to set up the required components.

  • group()
  • channel() or channnelFactory()
  • handler()

Failure to do so will cause an IllegalStateException. The handler() call is particularly important because it’s needed to configure the ChannelPipeline.

8.3. Bootstrapping servers

We’ll begin our overview of server bootstrapping with an outline of the ServerBootstrap API. We’ll then examine the steps involved in bootstrapping servers, and several related topics, including the special case of bootstrapping a client from a server channel.

8.3.1. The ServerBootstrap class

Table 8.2 lists the methods of ServerBootstrap.

Table 8.2. Methods of the ServerBootstrap class

Name

Description

group Sets the EventLoopGroup to be used by the ServerBootstrap. This EventLoopGroup serves the I/O of the ServerChannel and accepted Channels.
channel Sets the class of the ServerChannel to be instantiated.
channelFactory If the Channel can’t be created via a default constructor, you can provide a ChannelFactory.
localAddress Specifies the local address the ServerChannel should be bound to. If not specified, a random one will be used by the OS. Alternatively, you can specify the localAddress with bind() or connect().
option Specifies a ChannelOption to apply to the ChannelConfig of a newly created ServerChannel. Those options will be set on the Channel by bind() or connect(), depending on which is called first. Setting or changing a ChannelOption after those methods have been called has no effect. Which ChannelOptions are supported depends on the channel type used. Refer to the API docs for the ChannelConfig you’re using.
childOption Specifies a ChannelOption to apply to a Channel’s ChannelConfig when the channel has been accepted. Which ChannelOptions are supported depends on the channel type used. Please refer to the API docs for the ChannelConfig you’re using.
attr Specifies an attribute on the ServerChannel. Attributes will be set on the channel by bind(). Changing them after calling bind() has no effect.
childAttr Applies an attribute to accepted Channels. Subsequent calls have no effect.
handler Sets the ChannelHandler that’s added to the ChannelPipeline of the ServerChannel. See childHandler() for a more frequently used approach.
childHandler Sets the ChannelHandler that’s added to the ChannelPipeline of accepted Channels. The difference between handler() and childHandler() is that the former adds a handler that’s processed by the accepting ServerChannel, whereas childHandler() adds a handler that’s processed by an accepted Channel, which represents a socket bound to a remote peer.
clone Clones the ServerBootstrap for connecting to a different remote peer with settings identical to those of the original ServerBootstrap.
bind Binds the ServerChannel and returns a ChannelFuture, which is notified once the connection operation is complete (with the success or error result).

The next section explains the details of server bootstrapping.

8.3.2. Bootstrapping a server

You may have noticed that table 8.2 lists several methods not present in table 8.1: childHandler(), childAttr(), and childOption(). These calls support operations that are typical of server applications. Specifically, ServerChannel implementations are responsible for creating child Channels, which represent accepted connections. Thus ServerBootstrap, which bootstraps ServerChannels, provides these methods to simplify the task of applying settings to the ChannelConfig member of an accepted Channel.

Figure 8.3 shows a ServerBootstrap creating a ServerChannel on bind(), and the ServerChannel managing a number of child Channels.

Figure 8.3. ServerBootstrap and ServerChannel

The code in this listing implements the server bootstrapping shown in figure 8.3.

Listing 8.4. Bootstrapping a server

8.4. Bootstrapping clients from a Channel

Suppose your server is processing a client request that requires it to act as a client to a third system. This can happen when an application, such as a proxy server, has to integrate with an organization’s existing systems, such as web services or databases. In such cases you’ll need to bootstrap a client Channel from a ServerChannel.

You could create a new Bootstrap as described in section 8.2.1, but this is not the most efficient solution, as it would require you to define another EventLoop for the new client Channel. This would produce additional threads, necessitating context switching when exchanging data between the accepted Channel and the client Channel.

A better solution is to share the EventLoop of the accepted Channel by passing it to the group() method of the Bootstrap. Because all Channels assigned to an EventLoop use the same thread, this avoids the extra thread creation and related context-switching mentioned previously. This sharing solution is illustrated in figure 8.4.

Figure 8.4. EventLoop shared between channels

Implementing EventLoop sharing involves setting the EventLoop by calling the group() method, as shown in the following listing.

Listing 8.5. Bootstrapping a server

The topic we’ve discussed in this section and the solution presented reflect a general guideline in coding Netty applications: reuse EventLoops wherever possible to reduce the cost of thread creation.

8.5. Adding multiple ChannelHandlers during a bootstrap

In all of the code examples we’ve shown, we’ve called handler() or childHandler() during the bootstrap process to add a single ChannelHandler. This may be sufficient for simple applications, but it won’t meet the needs of more complex ones. For example, an application that has to support multiple protocols will have many ChannelHandlers, the alternative being a large and unwieldy class.

As you’ve seen repeatedly, you can deploy as many ChannelHandlers as you require by chaining them together in a ChannelPipeline. But how can you do this if you can set only one ChannelHandler during the bootstrapping process?

For exactly this use case, Netty supplies a special subclass of ChannelInboundHandlerAdapter,

public abstract class ChannelInitializer<C extends Channel>
    extends ChannelInboundHandlerAdapter

which defines the following method:

protected abstract void initChannel(C ch) throws Exception;

This method provides an easy way to add multiple ChannelHandlers to a ChannelPipeline. You simply provide your implementation of ChannelInitializer to the bootstrap, and once the Channel is registered with its EventLoop your version of initChannel() is called. After the method returns, the ChannelInitializer instance removes itself from the ChannelPipeline.

The following listing defines the class ChannelInitializerImpl and registers it using the bootstrap’s childHandler(). You can see that this apparently complex operation is quite straightforward.

Listing 8.6. Bootstrapping and using ChannelInitializer

If your application makes use of numerous ChannelHandlers, define your own ChannelInitializer to install them in the pipeline.

8.6. Using Netty ChannelOptions and attributes

Manually configuring every channel when it’s created could become quite tedious. Fortunately, you don’t have to. Instead, you can use option() to apply ChannelOptions to a bootstrap. The values you provide will be applied automatically to all Channels created in the bootstrap. The ChannelOptions available include low-level connection details such as keep-alive or timeout properties and buffer settings.

Netty applications are often integrated with an organization’s proprietary software, and components such as Channel may even be utilized outside the normal Netty lifecycle. In the event that some of the usual properties and data aren’t available, Netty offers the AttributeMap abstraction, a collection provided by the channel and bootstrap classes, and AttributeKey<T>, a generic class for inserting and retrieving attribute values. With these tools, you can safely associate any kind of data item with both client and server Channels.

Consider, for example, a server application that tracks the relationship between users and Channels. This can be accomplished by storing the user’s ID as an attribute of a Channel. A similar technique could be used to route messages to users based on their ID or to shut down a channel if there is low activity.

The next listing shows how you can use ChannelOptions to configure a Channel and an attribute to store an integer value.

Listing 8.7. Using attributes

8.7. Bootstrapping DatagramChannels

The previous bootstrap code examples used a SocketChannel, which is TCP-based, but a Bootstrap can be used for connectionless protocols as well. Netty provides various DatagramChannel implementations for this purpose. The only difference is that you don’t call connect() but only bind(), as shown next.

Listing 8.8. Using Bootstrap with DatagramChannel

8.8. Shutdown

Bootstrapping gets your application up and running, but sooner or later you’ll need to shut it down gracefully. You could, of course, just let the JVM handle everything on exiting, but this wouldn’t meet the definition of graceful, which refers to releasing resources cleanly. There isn’t much magic needed to shut down a Netty application, but there are a few things to keep in mind.

Above all, you need to shut down the EventLoopGroup, which will handle any pending events and tasks and subsequently release all active threads. This is a matter of calling EventLoopGroup.shutdownGracefully(). This call will return a Future, which is notified when the shutdown completes. Note that shutdownGracefully() is also an asynchronous operation, so you’ll need to either block until it completes or register a listener with the returned Future to be notified of completion.

The following listing meets the definition of a graceful shutdown.

Listing 8.9. Graceful shutdown

Alternatively, you can call Channel.close() explicitly on all active channels before calling EventLoopGroup.shutdownGracefully(). But in all cases, remember to shut down the EventLoopGroup itself.

8.9. Summary

In this chapter you learned how to bootstrap Netty server and client applications, including those that use connectionless protocols. We covered a number of special cases, including bootstrapping client channels in server applications and using a ChannelInitializer to handle the installation of multiple ChannelHandlers during bootstrapping. You saw how to specify configuration options on channels and how to attach information to a channel using attributes. Finally, you learned how to shut down an application gracefully to release all resources in an orderly fashion.

In the next chapter we’ll examine the tools Netty provides to help you test your ChannelHandler implementations.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset