A list of
ChannelHandlers which handles or intercepts
ChannelEvents of a
Channel.
ChannelPipelineimplements 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 the pipeline interact with each other.
Creation of a pipeline
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:
import static org.jboss.netty.channel.
Channels.*;
ChannelPipeline pipeline = pipeline(); // same with Channels.pipeline()
How an event flows in a pipeline
The following diagram describes how
ChannelEvents are processed by
ChannelHandlers in a
ChannelPipeline typically.
A
ChannelEvent can be handled by either a
ChannelUpstreamHandleror a
ChannelDownstreamHandler and be forwarded to the closest
handler by calling
ChannelHandlerContext#sendUpstream(ChannelEvent)or
ChannelHandlerContext#sendDownstream(ChannelEvent). The meaning
of the event is interpreted somewhat differently depending on whether it is
going upstream or going downstream. Please refer to
ChannelEvent for
more information.
I/O Request
via
Channel or
ChannelHandlerContext|
+----------------------------------------+---------------+
| ChannelPipeline | |
| \|/ |
| +----------------------+ +-----------+------------+ |
| | Upstream Handler N | | Downstream Handler 1 | |
| +----------+-----------+ +-----------+------------+ |
| /|\ | |
| | \|/ |
| +----------+-----------+ +-----------+------------+ |
| | Upstream Handler N-1 | | Downstream Handler 2 | |
| +----------+-----------+ +-----------+------------+ |
| /|\ . |
| . . |
| [ sendUpstream() ] [ sendDownstream() ] |
| [ + INBOUND data ] [ + OUTBOUND data ] |
| . . |
| . \|/ |
| +----------+-----------+ +-----------+------------+ |
| | Upstream Handler 2 | | Downstream Handler M-1 | |
| +----------+-----------+ +-----------+------------+ |
| /|\ | |
| | \|/ |
| +----------+-----------+ +-----------+------------+ |
| | Upstream Handler 1 | | Downstream Handler M | |
| +----------+-----------+ +-----------+------------+ |
| /|\ | |
+-------------+--------------------------+---------------+
| \|/
+-------------+--------------------------+---------------+
| | | |
| [ Socket.read() ] [ Socket.write() ] |
| |
| Netty Internal I/O Threads (Transport Implementation) |
+--------------------------------------------------------+
An upstream event is handled by the upstream handlers in the bottom-up
direction as shown on the left side of the diagram. An upstream 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
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 as shown on the right side of the diagram. 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[]).
For example, let us assume that we created the following pipeline:
ChannelPipeline p =
Channels.pipeline();
p.addLast("1", new UpstreamHandlerA());
p.addLast("2", new UpstreamHandlerB());
p.addLast("3", new DownstreamHandlerA());
p.addLast("4", new DownstreamHandlerB());
p.addLast("5", new UpstreamHandlerX());
In the example above, the class whose name starts with
Upstream means
it is an upstream handler. The class whose name starts with
Downstream means it is a downstream handler.
In the given example configuration, the handler evaluation order is 1, 2, 3,
4, 5 when an event goes upstream. When an event goes downstream, 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
ChannelUpstreamHandler, and therefore the
actual evaluation order of an upstream event will be: 1, 2, and 5.
- 1, 2, and 5 don't implement
ChannelDownstreamHandler, and
therefore the actual evaluation order of a downstream event will be:
4 and 3.
- If 5 extended
SimpleChannelHandler which implements both
ChannelUpstreamHandler and
ChannelDownstreamHandler, the
evaluation order of an upstream and a downstream event could be 125 and
543 respectively.
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.
ChannelBuffer)
into a Java object.
- Protocol Encoder - translates a Java object into binary data.
-
ExecutionHandler - applies a thread model.
- Business Logic Handler - performs the actual business logic
(e.g. database access).
and it could be represented as shown in the following example:
ChannelPipeline pipeline =
Channels#pipeline();
pipeline.addLast("decoder", new MyProtocolDecoder());
pipeline.addLast("encoder", new MyProtocolEncoder());
pipeline.addLast("executor", new
ExecutionHandler(
new
OrderedMemoryAwareThreadPoolExecutor(16, 1048576, 1048576)));
pipeline.addLast("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 a
SslHandler when sensitive information is about to be exchanged,
and remove it after the exchange.
Pitfall
Due to the internal implementation detail of the current default
ChannelPipeline, the following code does not work as expected if
FirstHandler is the last handler in the pipeline:
public class FirstHandler extends
SimpleChannelUpstreamHandler {
@Overridepublic void messageReceived(
ChannelHandlerContext ctx,
MessageEvent e) {
// Remove this handler from the pipeline,
ctx.getPipeline().remove(this);
// And let SecondHandler handle the current event.
ctx.getPipeline().addLast("2nd", new SecondHandler());
ctx.sendUpstream(e);
}
}
To implement the expected behavior, you have to add SecondHandler
before the removal or make sure there is at least one more handler between
FirstHandler and SecondHandler.