You are on page 1of 7

ServerBootstrap: A helper class which creates a new server-side Channel and accepts incoming

connections. This bootstrap is for connection oriented transports only such as TCP/IP and local
transport.

A parent channel is a channel which is supposed to accept incoming connections. It is created by this
bootstrap’s ChannelFactory via bind () and bind (SocketAddress).

Once successfully bound, the parent channel starts to accept incoming connections, and the accepted
connections become the children of the parent channel.

Every channel has its own ChannelPipeline and you can configure it in two ways. The recommended
approach is to specify a ChannelPipelineFactory by calling Bootstrap.setPipelineFactory
(ChannelPipelineFactory).

ServerBootstrap is just a helper class. It neither allocates nor manages any resources. What manages the
resources is the ChannelFactory implementation you specified in the constructor of ServerBootstrap.

ChannelFactory: The main interface to a transport that creates a Channel associated with a certain
communication entity such as a network socket. For example, the NioServerSocketChannelFactory
creates a channel which has a NIO-based server socket as its underlying communication entity.

Once a new Channel is created, the ChannelPipeline which was specified as a parameter in the
newChannel(ChannelPipeline) is attached to the new Channel, and starts to handle all associated
ChannelEvents.

Graceful shutdown
To shut down a network application service which is managed by a factory follow the following steps:
1. close all channels created by the factory and their child channels usually using ChannelGroup.close()
2. call releaseExternalResources().

NioServerSocketChannelFactory: A ServerSocketChannelFactory which creates a server-side


NIO-based ServerSocketChannel. It utilizes the non-blocking I/O mode which was introduced with NIO to
serve many number of concurrent connections efficiently.

There are two types of threads in a NioServerSocketChannelFactory -boss thread and worker thread.
Boss threads:
Each bound ServerSocketChannel has its own boss thread. For example, if you opened two server ports
such as 80 and 443, you will have two boss threads. A boss thread accepts incoming connections until
the port is unbound. Once a connection is accepted successfully, the boss thread passes the accepted
Channel to one of the worker threads that the NioServerSocketChannelFactory manages.

Worker threads:
One NioServerSocketChannelFactory can have one or more worker threads. A worker thread performs
non-blocking read and write for one or more Channels in a non-blocking mode.
All threads are acquired from the Executors which were specified when a
NioServerSocketChannelFactory was created. Boss threads are acquired from the bossExecutor, and
worker threads are acquired from the workerExecutor. Therefore, you should make sure the specified
Executors are able to lend the sufficient number of threads. It is the best bet to specify a cached thread
pool.

Both boss and worker threads are acquired lazily, and then released when there's nothing left to
process. All the related resources such as Selector are also released when the boss and worker threads
are released. Therefore, to shut down a service gracefully, you should do the following:
1. unbind all channels created by the factory,
2. close all child channels accepted by the unbound channels, and (these two steps so far is usually
done using ChannelGroup.close())
3. call releaseExternalResources().

Please make sure not to shut down the executor until all channels are closed. Otherwise, you will end up
with a RejectedExecutionException and the related resources might not be released properly.

ChannelGroup: A thread-safe Set that contains open Channels and provides various bulk operations
on them. Using ChannelGroup, you can categorize Channels into a meaningful group (e.g. on a per-
service or per-state basis.) A closed Channel is automatically removed from the collection, so that you
don't need to worry about the life cycle of the added Channel. A Channel can belong to more than one
ChannelGroup.

Broadcast a message to multiple Channels


If you need to broadcast a message to more than one Channel, you can add the Channels associated
with the recipients and call write(Object):
ChannelGroup recipients = new DefaultChannelGroup();
recipients.add(channelA);
recipients.add(channelB);
..
recipients.write(ChannelBuffers.copiedBuffer(
"Service will shut down for maintenance in 5 minutes.",
CharsetUtil.UTF_8));

All I/O operations in ChannelGroup are asynchronous. It means any I/O calls will return immediately
with no guarantee that the requested I/O operations have been completed at the end of the call.
Instead, you will be returned with a ChannelGroupFuture instance which tells you when the requested
I/O operations have succeeded, failed, or cancelled.
ChannelGroupFuture is composed of ChannelFutures which represent the outcome of the individual I/O
operations that affect the Channels in the ChannelGroup.

Various methods are provided to let you check if the I/O operations has been completed, wait for the
completion, and retrieve the result of the I/O operation. It also allows you to add more than one
ChannelGroupFutureListener so you can get notified when the I/O operation have been completed.
It is recommended to prefer addListener(ChannelGroupFutureListener) to await() wherever possible to
get notified when I/O operations are done and to do any follow-up tasks.
addListener(ChannelGroupFutureListener) is non-blocking. It simply adds the specified
ChannelGroupFutureListener to the ChannelGroupFuture, and I/O thread will notify the listeners when
the I/O operations associated with the future is done. ChannelGroupFutureListener yields the best
performance and resource utilization because it does not block at all, but it could be tricky to implement
a sequential logic.

By contrast, await() is a blocking operation. Once called, the caller thread blocks until all I/O operations
are done. It is easier to implement a sequential logic with await(), but the caller thread blocks
unnecessarily until all I/O operations are done and there's relatively expensive cost of inter-thread
notification. Moreover, there's a chance of dead lock in a particular circumstance

Channel: A nexus to a network socket or a component which is capable of I/O operations such as
read, write, connect, and bind.
A channel provides a user:
* the current state of the channel (e.g. is it open? is it connected?),
* the configuration parameters of the channel (e.g. receive buffer size),
* the I/O operations that the channel supports (e.g. read, write, connect, and bind), and
* the ChannelPipeline which handles all I/O events and requests associated with the channel.

All I/O operations in Netty are asynchronous. It means any I/O calls will return immediately with no
guarantee that the requested I/O operation has been completed at the end of the call. Instead, it
returns with a ChannelFuture instance which will notify once the requested I/O operation has
succeeded, failed, or cancelled.

A Channel can have a parent depending on how it was created. For instance, a SocketChannel, that was
accepted by ServerSocketChannel, will return the ServerSocketChannel as its parent on getParent().
Some transports exposes additional operations that is specific to the transport. Down-cast the Channel
to sub-type to invoke such operations.

A Channel has a property called interestOps which is similar to that of NIO SelectionKey. It is
represented as a bit field which is composed of the two flags([OP_READ] , [OP_WRITE] ,
[OP_READ_WRITE] - only write requests are suspended,[ OP_NONE] -only read operation is suspended).

ChannelPipeline: For each new channel, a new pipeline must be created and attached to the
channel. Once attached, the coupling between the channel and the pipeline is permanent; the channel
cannot attach another pipeline to it nor detach the current pipeline from it.

The recommended way to create a new pipeline is to use the helper methods in Channels rather than
calling an individual implementation's constructor.

Channel Events are processed by ChannelHandlers in a ChannelPipeline typically. A ChannelEvent can


be handled by either a ChannelUpstreamHandler or a ChannelDownstreamHandler and be forwarded to
the closest handler by calling ChannelHandlerContext.sendUpstream (ChannelEvent) or
ChannelHandlerContext.sendDownstream (ChannelEvent).
An upstream event is handled by the upstream handlers in the bottom-up direction. An upstream
handler usually handles the inbound data generated by the I/O thread. The inbound data is often read
from a remote peer via the actual input operation such as InputStream.read (byte []). If an upstream
event goes beyond the top upstream handler, it is discarded silently.

A downstream event is handled by the downstream handler in the top-down direction. A downstream
handler usually generates or transforms the outbound traffic such as write requests. If a downstream
event goes beyond the bottom downstream handler, it is handled by an I/O thread associated with the
Channel. The I/O thread often performs the actual output operation such as OutputStream.write (byte
[]).

A user is supposed to have one or more ChannelHandlers in a pipeline to receive I/O events (e.g. read)
and to request I/O operations (e.g. write and close). For example, a typical server will have the following
handlers in each channel's pipeline, but it may vary depending on the complexity and characteristics of
the protocol and business logic:
1. Protocol Decoder - translates binary data (e.g. ChannelBuffer) into a Java object.
2. Protocol Encoder - translates a Java object into binary data.
3. ExecutionHandler - applies a thread model.
4. Business Logic Handler - performs the actual business logic (e.g. database access).

And these handlers can be attached to the ChannelPipeline as following:


ChannelPipeline pipeline = Channels.pipeline ();
pipeline.addLast ("decoder", new MyProtocolDecoder ());

A ChannelHandler can be added or removed at any time because a ChannelPipeline is thread safe.

ChannelPipelineFactory: Creates a new ChannelPipeline for a new Channel. When a server-side


channel accepts a new incoming connection, a new child channel is created for each newly accepted
connection. A new child channel uses a new ChannelPipeline, which is created by the
ChannelPipelineFactory specified in the server-side channel's "pipelineFactory" option.

Channels: A helper class which provides various convenience methods related with Channel,
ChannelHandler, and ChannelPipeline.

ChannelEvent: An I/O event or I/O request associated with a Channel. A ChannelEvent is handled by
a series of ChannelHandlers in a ChannelPipeline.

Every event is either an upstream event or a downstream event. If an event flows forward from the first
handler to the last handler in a ChannelPipeline, we call it an upstream event and say "an event goes
upstream." If an event flows backward from the last handler to the first handler in a ChannelPipeline, we
call it a downstream event and say "an event goes downstream”.
When the server receives a message from a client, the event associated with the received message is an
upstream event. When the server sends a message or reply to the client, the event associated with the
write request is a downstream event.

Upstream events are often the result of inbound operations such as InputStream.read(byte[]), and
downstream events are the request for outbound operations such as OutputStream.write(byte[]),
Socket.connect(SocketAddress), and Socket.close().

ChannelHandler: Handles or intercepts a ChannelEvent, and sends a ChannelEvent to the next


handler in a ChannelPipeline.

A ChannelHandler is provided with a ChannelHandlerContext object. A ChannelHandler is supposed to


interact with the ChannelPipeline it belongs to via a context object. Using the context object, the
ChannelHandler can pass events upstream or downstream, modify the pipeline dynamically, or store the
information (attachment) which is specific to the handler.

State management:
A ChannelHandler often needs to store some stateful information. The simplest and recommended
approach is to use member variables:

public class DataServerHandler extends SimpleChannelHandler {

private boolean loggedIn;

@Override
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) {
Channel ch = e.getChannel();
Object o = e.getMessage();
if (o instanceof LoginMessage) {
authenticate((LoginMessage) o);
loggedIn = true;
} else (o instanceof GetDataMessage) {
if (loggedIn) {
ch.write(fetchSecret((GetDataMessage) o));
} else {
fail();
}
}
}
...
}

Because the handler instance has a state variable which is dedicated to one connection, you have
to create a new handler instance for each new channel to avoid a race condition where a
unauthenticated client can get the confidential information:
// Create a new handler instance per channel.
// See Bootstrap.setPipelineFactory(ChannelPipelineFactory).
public class DataServerPipelineFactory implements ChannelPipelineFactory {
public ChannelPipeline getPipeline() {
return Channels.pipeline(new DataServerHandler());
}
}

Using an attachment

Although it's recommended to use member variables to store the state of a handler, for some
reason you might not want to create many handler instances. In such a case, you can use an
attachment which is provided by ChannelHandlerContext:
@Sharable
public class DataServerHandler extends SimpleChannelHandler {

@Override
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) {
Channel ch = e.getChannel();
Object o = e.getMessage();
if (o instanceof LoginMessage) {
authenticate((LoginMessage) o);
ctx.setAttachment(true);
} else (o instanceof GetDataMessage) {
if (Boolean.TRUE.equals(ctx.getAttachment())) {
ch.write(fetchSecret((GetDataMessage) o));
} else {
fail();
}
}
}
...
}

Now that the state of the handler is stored as an attachment, you can add the same handler
instance to different pipelines:
public class DataServerPipelineFactory implements ChannelPipelineFactory {

private static final DataServerHandler SHARED = new DataServerHandler();

public ChannelPipeline getPipeline() {


return Channels.pipeline(SHARED);
}
}

Using a ChannelLocal

If you have a state variable which needs to be accessed either from other handlers or outside handlers,
you can use ChannelLocal.

If a ChannelHandler is annotated with the @Sharable annotation, it means you can create an instance
of the handler just once and add it to one or more ChannelPipelines multiple times without a race
condition.
If this annotation is not specified, you have to create a new handler instance every time you add it to a
pipeline because it has unshared state such as member variables.
ChannelHandlerContext : Enables a ChannelHandler to interact with its ChannelPipeline and
other handlers. A handler can send a ChannelEvent upstream or downstream, modify the
ChannelPipeline it belongs to dynamically.

You can send or forward a ChannelEvent to the closest handler in the same ChannelPipeline by calling
sendUpstream(ChannelEvent) or sendDownstream(ChannelEvent).

You can get the ChannelPipeline your handler belongs to by calling getPipeline(). A non-trivial
application could insert, remove, or replace handlers in the pipeline dynamically in runtime.

Storing stateful information:


setAttachment(Object) and getAttachment() allow you to store and access stateful information that is
related with a handler and its context.

A handler can have more than one context. A ChannelHandler instance can be added to more than one
ChannelPipeline. It means a single ChannelHandler instance can have more than one
ChannelHandlerContext and therefore the single instance can be invoked with different
ChannelHandlerContexts if it is added to one or more ChannelPipelines more than once.

You might also like