跳至主要内容

Netty(2): Handler

Netty(2): Handler

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.

enter image description here

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 Channeland 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

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 ChannelHandlers 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.

评论

此博客中的热门博文

Spring Boot: Customize Environment

Spring Boot: Customize Environment Environment variable is a very commonly used feature in daily programming: used in init script used in startup configuration used by logging etc In Spring Boot, all environment variables are a part of properties in Spring context and managed by Environment abstraction. Because Spring Boot can handle the parse of configuration files, when we want to implement a project which uses yml file as a separate config file, we choose the Spring Boot. The following is the problems we met when we implementing the parse of yml file and it is recorded for future reader. Bind to Class Property values can be injected directly into your beans using the @Value annotation, accessed via Spring’s Environment abstraction or bound to structured objects via @ConfigurationProperties. As the document says, there exists three ways to access properties in *.properties or *.yml : @Value : access single value Environment : can access multi

Elasticsearch: Join and SubQuery

Elasticsearch: Join and SubQuery Tony was bothered by the recent change of search engine requirement: they want the functionality of SQL-like join in Elasticsearch! “They are crazy! How can they think like that. Didn’t they understand that Elasticsearch is kind-of NoSQL 1 in which every index should be independent and self-contained? In this way, every index can work independently and scale as they like without considering other indexes, so the performance can boost. Following this design principle, Elasticsearch has little related supports.” Tony thought, after listening their requirements. Leader notice tony’s unwillingness and said, “Maybe it is hard to do, but the requirement is reasonable. We need to search person by his friends, didn’t we? What’s more, the harder to implement, the more you can learn from it, right?” Tony thought leader’s word does make sense so he set out to do the related implementations Application-Side Join “The first implementation

Implement isdigit

It is seems very easy to implement c library function isdigit , but for a library code, performance is very important. So we will try to implement it and make it faster. Function So, first we make it right. int isdigit ( char c) { return c >= '0' && c <= '9' ; } Improvements One – Macro When it comes to performance for c code, macro can always be tried. #define isdigit (c) c >= '0' && c <= '9' Two – Table Upper version use two comparison and one logical operation, but we can do better with more space: # define isdigit(c) table[c] This works and faster, but somewhat wasteful. We need only one bit to represent true or false, but we use a int. So what to do? There are many similar functions like isalpha(), isupper ... in c header file, so we can combine them into one int and get result by table[c]&SOME_BIT , which is what source do. Source code of ctype.h : # define _ISbit(bit) (1 << (