A list of
ChannelHandlers which handles or intercepts inbound events and outbound operations of a
Channel.
ChannelPipeline implements an advanced form of the
Intercepting Filter pattern
to give a user full control over how an event is handled and how the
ChannelHandlers in a pipeline
interact with each other.
Creation of a pipeline
Each channel has its own pipeline and it is created automatically when a new channel is created.
How an event flows in a pipeline
The following diagram describes how I/O events are processed by
ChannelHandlers in a
ChannelPipelinetypically. An I/O event is handled by either a
ChannelInboundHandler or a
ChannelOutboundHandlerand be forwarded to its closest handler by calling the event propagation methods defined in
ChannelHandlerContext, such as
ChannelHandlerContext#fireChannelRead(Object) and
ChannelHandlerContext#write(Object).
I/O Request
via
Channel or
ChannelHandlerContext|
+---------------------------------------------------+---------------+
| ChannelPipeline | |
| \|/ |
| +---------------------+ +-----------+----------+ |
| | Inbound Handler N | | Outbound Handler 1 | |
| +----------+----------+ +-----------+----------+ |
| /|\ | |
| | \|/ |
| +----------+----------+ +-----------+----------+ |
| | Inbound Handler N-1 | | Outbound Handler 2 | |
| +----------+----------+ +-----------+----------+ |
| /|\ . |
| . . |
| ChannelHandlerContext.fireIN_EVT() ChannelHandlerContext.OUT_EVT()|
| [ method call] [method call] |
| . . |
| . \|/ |
| +----------+----------+ +-----------+----------+ |
| | Inbound Handler 2 | | Outbound Handler M-1 | |
| +----------+----------+ +-----------+----------+ |
| /|\ | |
| | \|/ |
| +----------+----------+ +-----------+----------+ |
| | Inbound Handler 1 | | Outbound Handler M | |
| +----------+----------+ +-----------+----------+ |
| /|\ | |
+---------------+-----------------------------------+---------------+
| \|/
+---------------+-----------------------------------+---------------+
| | | |
| [ Socket.read() ] [ Socket.write() ] |
| |
| Netty Internal I/O Threads (Transport Implementation) |
+-------------------------------------------------------------------+
An inbound event is handled by the inbound handlers in the bottom-up direction as shown on the left side of the
diagram. An inbound handler usually handles the inbound data generated by the I/O thread on the bottom of the
diagram. The inbound data is often read from a remote peer via the actual input operation such as
SocketChannel#read(ByteBuffer). If an inbound event goes beyond the top inbound handler, it is discarded
silently, or logged if it needs your attention.
An outbound event is handled by the outbound handler in the top-down direction as shown on the right side of the
diagram. An outbound handler usually generates or transforms the outbound traffic such as write requests.
If an outbound event goes beyond the bottom outbound 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
SocketChannel#write(ByteBuffer).
For example, let us assume that we created the following pipeline:
ChannelPipeline p = ...;
p.addLast("1", new InboundHandlerA());
p.addLast("2", new InboundHandlerB());
p.addLast("3", new OutboundHandlerA());
p.addLast("4", new OutboundHandlerB());
p.addLast("5", new InboundOutboundHandlerX());
In the example above, the class whose name starts with
Inbound means it is an inbound handler.
The class whose name starts with
Outbound means it is a outbound handler.
In the given example configuration, the handler evaluation order is 1, 2, 3, 4, 5 when an event goes inbound.
When an event goes outbound, the order is 5, 4, 3, 2, 1. On top of this principle,
ChannelPipeline skips
the evaluation of certain handlers to shorten the stack depth:
- 3 and 4 don't implement
ChannelInboundHandler, and therefore the actual evaluation order of an inbound
event will be: 1, 2, and 5.
- 1 and 2 don't implement
ChannelOutboundHandler, and therefore the actual evaluation order of a
outbound event will be: 5, 4, and 3.
- If 5 implements both
ChannelInboundHandler and
ChannelOutboundHandler, the evaluation order of
an inbound and a outbound event could be 125 and 543 respectively.
Forwarding an event to the next handler
As you might noticed in the diagram shows, a handler has to invoke the event propagation methods in
ChannelHandlerContext to forward an event to its next handler. Those methods include:
- Inbound event propagation methods:
-
ChannelHandlerContext#fireChannelRegistered()
-
ChannelHandlerContext#fireChannelActive()
-
ChannelHandlerContext#fireChannelRead(Object)
-
ChannelHandlerContext#fireChannelReadComplete()
-
ChannelHandlerContext#fireExceptionCaught(Throwable)
-
ChannelHandlerContext#fireUserEventTriggered(Object)
-
ChannelHandlerContext#fireChannelWritabilityChanged()
-
ChannelHandlerContext#fireChannelInactive()
-
ChannelHandlerContext#fireChannelUnregistered()
- Outbound event propagation methods:
-
ChannelHandlerContext#bind(SocketAddress,ChannelPromise)
-
ChannelHandlerContext#connect(SocketAddress,SocketAddress,ChannelPromise)
-
ChannelHandlerContext#write(Object,ChannelPromise)
-
ChannelHandlerContext#flush()
-
ChannelHandlerContext#read()
-
ChannelHandlerContext#disconnect(ChannelPromise)
-
ChannelHandlerContext#close(ChannelPromise)
-
ChannelHandlerContext#deregister(ChannelPromise)
and the following example shows how the event propagation is usually done:
public class MyInboundHandler extends
ChannelInboundHandlerAdapter {
@Overridepublic void channelActive(
ChannelHandlerContext ctx) {
System.out.println("Connected!");
ctx.fireChannelActive();
}
}
public class MyOutboundHandler extends
ChannelOutboundHandlerAdapter {
@Overridepublic void close(
ChannelHandlerContext ctx,
ChannelPromise promise) {
System.out.println("Closing ..");
ctx.close(promise);
}
}
Building a pipeline
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 your mileage may vary depending on the complexity and characteristics of the
protocol and business logic:
- Protocol Decoder - translates binary data (e.g.
ByteBuf) into a Java object.
- Protocol Encoder - translates a Java object into binary data.
- Business Logic Handler - performs the actual business logic (e.g. database access).
and it could be represented as shown in the following example:
static final
EventExecutorGroup group = new
DefaultEventExecutorGroup(16);
...
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast("decoder", new MyProtocolDecoder());
pipeline.addLast("encoder", new MyProtocolEncoder());
// Tell the pipeline to run MyBusinessLogicHandler's event handler methods
// in a different thread than an I/O thread so that the I/O thread is not blocked by
// a time-consuming task.
// If your business logic is fully asynchronous or finished very quickly, you don't
// need to specify a group.
pipeline.addLast(group, "handler", new MyBusinessLogicHandler());
Thread safety
A
ChannelHandler can be added or removed at any time because a
ChannelPipeline is thread safe.
For example, you can insert an encryption handler when sensitive information is about to be exchanged, and remove it
after the exchange.