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