[Netty] Netty ChannelHandler (4)

Article directory

  • foreword
  • One, ChannelHandler
  • Two, ChannelInboundHandler
  • Three, ChannelOutboundHandler
  • Four, ChannelDuplexHandler
  • Summarize

Foreword

In the first two articles, we have had a simple understanding of Netty and an analysis of its architectural design principles.
Links to related articles are as follows:

  • Netty overview (1)
  • Netty architecture design (2)
  • Overview of Netty Channel (3)

Let’s briefly understand the concepts of ChannelPipeline and ChannelHandler.

Imagine an assembly line workshop. When the components enter from the head of the assembly line and pass through the assembly line, the workers on the assembly line process the components in sequence, and the product assembly is completed when they reach the end of the assembly line. ChannelPipeline can be regarded as a pipeline, and ChannelHandler can be regarded as a pipeline worker. The source components are regarded as events, such as read, write and so on.

In this article, let’s talk about the relevant knowledge of ChannelHandler first, let’s enter the text below.

1. ChannelHandler

  • ChannelHandler does not handle events, but is handled by its subclasses: ChannelInboundHandler intercepts and processes inbound events, and ChannelOutboundHandler intercepts and processes outbound events.
  • ChannelHandler and ChannelHandlerContext are associated and used in pairs through combination or inheritance. The event actively calls methods such as fireXXX() and write(msg) through ChannelHandlerContext to propagate the event to the next processor.

Note: Inbound events propagate forward from head to tail in the ChannelPipeline doubly linked list, while outbound events propagate in the opposite direction.

As the top-level interface, ChanneleHandler does not handle inbound and outbound events, so the interface only contains the most basic methods:

 // Called when the Handler itself is added to the ChannelPipeline
 void handlerAdded(ChannelHandlerContext var1) throws Exception;
  // Called when the Handler itself is removed from the ChannelPipeline
 void handlerRemoved(ChannelHandlerContext var1) throws Exception;
 ?
 // called when an exception occurs
 @Deprecated
 void exceptionCaught(ChannelHandlerContext var1, Throwable var2) throws Exception;

Sharable annotation:

When the client connects to the server, Netty creates a new ChannelPipeline to process the events, and a ChannelPipeline contains several ChannelHandlers. If each client connection creates a new ChannelHandler instance, when there are a large number of clients, the server will save a large number of ChannelHandler instances. To this end, Netty provides the Sharable annotation. If a ChannelHandler has nothing to do with its state, it can be marked as Sharable. In this way, the server only needs to save one instance to handle all client events.

 @Inherited
 @Documented
 @Target({<!-- -->ElementType. TYPE})
 @Retention(RetentionPolicy. RUNTIME)
 public @interface Sharable {<!-- -->
 }

As the default implementation of ChannelHandler, ChannelHandlerAdapter has an important method isSharable(), the code is as follows:

public boolean isSharable() {<!-- -->
     Class<?> clazz = this. getClass();
     // one buffer per thread
     Map<Class<?>, Boolean> cache = InternalThreadLocalMap.get().handlerSharableCache();
     Boolean sharable = (Boolean)cache. get(clazz);
     if (sharable == null) {<!-- -->
         // Does the Handler have Sharable annotations?
         sharable = clazz.isAnnotationPresent(Sharable.class);
         cache. put(clazz, sharable);
     }
 ?
     return sharable;
 }

The optimized thread local variable InternalThreadLocalMap is introduced here, that is, each thread has a cache of whether the ChannelHandler is Sharable or not. This reduces competition between threads and improves performance.

2. ChannelInboundHandler

ChannelInboundHandler handles inbound data and various state changes. When the Channel state changes, some life cycle methods in ChannelInboundHandler will be called. These methods are closely related to the life of Channel.

Inbound data is the data that enters the socket. The following shows some life cycle APIs of this interface:

< /table>

ChannelInboundHandlerAdapter, as the implementation of ChannelInboundHandler, automatically propagates inbound events to the next inbound handler by default. The codes are highly consistent, as follows:

 public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {<!-- -->
     ctx.fireChannelRead(msg);
 }

3. ChannelOutboundHandler

Outbound operations and data will be handled by ChannelOutboundHandler. Its methods will be called by Channel, ChannelPipeline and ChannelHandlerContext. A powerful feature of ChannelOutboundHandler is the ability to defer operations or events on demand, which allows complex methods to handle requests.

For example, if a write to a remote node is suspended, you can defer the flush operation and resume it later.

Similarly, ChannelOutboundHandlerAdapter, as an event of ChannelOutboundHandler, propagates the outbound event to the next outbound handler by default:

@Override
 public void read(ChannelHandlerContext ctx) throws Exception {<!-- -->
     ctx. read();
 }

4. ChannelDuplexHandler

ChannelDuplexHandler implements both ChannelInboundHandler and ChannelOutboundHandler interfaces. If a desired ChannelHandler handles both inbound and outbound events, it is recommended to inherit from this class.

Summary

The above is the analysis of ChannelHandler. I believe you have a certain understanding of ChannelHandler. In the next issue, we will analyze the source code of ChannelPipeline.

syntaxbug.com © 2021 All Rights Reserved.
Type Description
channelRegistered Called when a Channel has registered to its EventLoop and is able to handle I/O
channelUnregistered Called when a Channel unregisters from its EventLoop and cannot handle any I/O td>
channelActive Called when the Channel is active; the Channel is already connected/bound and Ready
channelInactive When the Channel leaves the active state and is no longer connected to its remote Called when a node
channelReadComplete When a read operation on the Channel is completed Call
channelRead Called when reading data from Channel
ChannelWritabilityChanged Called when the writable status of the Channel changes. The user can ensure that the write operation does not complete too quickly (to avoid an OutOfMemoryError) or can resume writing when the Channel becomes writable again. A Channel’s writability can be detected by calling the Channel’s isWritable() method. Thresholds related to writability can be set via the Channel.config().setWriteHighWaterMark() and Channel.config().setWriteLowWaterMark() methods
userEventTriggered Called when the ChannelnboundHandler.fireUserEventTriggered() method is called because a POJO is passed through the ChannelPipeline