In last blog, we skim the basic abstraction of Netty
network model: events cause the state change, user get involved in via Pipeline & Handler
. In this blog, we will dive into more design details.
Overall Review
In Netty
, the source of data is not the abstraction in Java, like Stream
, but Event
. When an event (from remove peer or user defined) is received by Channel
, it will use Buffer
to hold the data, and give it to Pipeline
, which is a serial of Handler
.
Then, the thread in EventLoop
will run the code of Handler
(Callback
job, Listener
) to react to it.
Today, we focus on core of user code – Pipeline & Handler
.
Handler Detail
Inbound & Outbound
As the core of user interaction with Netty
, we need to dive into ChannelHandler
. It can be categorized as two broad kinds according to the event types: ChannelInboundHandler
& ChannelOutboundHandler
.
The ChannelInboundHandler
will respond to network related events like: Registered
, Active
, Read
, ReadComplete
, exceptionCaught
, or more customized userEvent
. (If you are confused about the differences between Registered
, Active
, you may like to refer to here)
public interface ChannelInboundHandler extends ChannelHandler {
void channelRegistered(ChannelHandlerContext ctx) throws Exception;
void channelUnregistered(ChannelHandlerContext ctx) throws Exception;
void channelActive(ChannelHandlerContext ctx) throws Exception;
void channelInactive(ChannelHandlerContext ctx) throws Exception;
void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception;
/**
* Invoked when the last message read by the current read operation has been consumed by
* {@link #channelRead(ChannelHandlerContext, Object)}. If {@link ChannelOption#AUTO_READ} is off, no further
* attempt to read an inbound data from the current {@link Channel} will be made until
* {@link ChannelHandlerContext#read()} is called.
*/
void channelReadComplete(ChannelHandlerContext ctx) throws Exception;
/**
* Gets called if an user event was triggered.
*/
void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception;
/**
* Gets called once the writable state of a {@link Channel} changed. You can check the state with
* {@link Channel#isWritable()}.
*/
void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception;
void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception;
}
If we take InboundHandler
as passive reaction to other’s event, OutboundHandler
is more like interceptors/filters for our own actions:
public interface ChannelOutboundHandler extends ChannelHandler {
void bind(ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise) throws Exception;
void connect(
ChannelHandlerContext ctx, SocketAddress remoteAddress,
SocketAddress localAddress, ChannelPromise promise) throws Exception;
void disconnect(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception;text ctx, ChannelPromise promise) throws Exception;
/**
* Called once a deregister operation is made from the current registered {@link EventLoop}.
*/
void deregister(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception;
void read(ChannelHandlerContext ctx) throws Exception;
/**
* Called once a write operation is made. The write operation will write the messages through the
* {@link ChannelPipeline}. Those are then ready to be flushed to the actual {@link Channel} once
* {@link Channel#flush()} is called
*/
void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception;
/**
* Called once a flush operation is made. The flush operation will try to flush out all previous written messages
* that are pending.
*/
void flush(ChannelHandlerContext ctx) throws Exception;
}
What should be noticed is deregister
: it deregister the Channel
from EventLoop
and let user get the control of Channel
from Netty
. We may wonder what’s the usage of this feature, the documentation of Netty
gives the following scenario:
you can take advantage of high-level non-blocking I/O Netty provides to deal with complex protocols, and then later deregister the
Channel
and switch to blocking mode to transfer a file at possible maximum throughput.
Parent & Child
If we look at the example of Netty
closely, we will notice that ServerBootstrap
has two method: handler
and childHandler
. It can be seen as a different classifying method according to their application scenario:
- In
ServerBootstrap
:- There exists a parent channel (acceptor for receiving connection) and many child channel (real connection for communication);
handler
registers a channel handler for the parent channel;childHandler
registers a channel handler for child channels;
- In
Bootstrap
:- Only
handler
for communication
- Only
This design split abstraction by responsibility: Channel
for listening connection and handling connection. Via this design, it make much clean code than original Java IO
way.
Handler vs Servlet
Servlet
API, just like Netty
's design, split where code where accepting new connection and later real handling. But Servlet
API hide the parent handler using application server, making getting involved in it much harder. Whereas, Netty
's design is more friendly: parent Handler
& parent EventLoop
.
More than that, Handler
implements an advanced form of the Intercepting Filter pattern to give a user full control over how an event is handled and how the
ChannelHandler
s in a pipeline interact with each other.
- Fine-grained control:
Encoder/Decoder
- Control event handle: Not only data flow, but also control flow by firing different event;
- Combine filter & servlet into one abstraction: handler
Ref
Written with StackEdit.
评论
发表评论