Chapter 6 Netty In Action Chinese edition: ChannelHandler,
Note: This article is from Netty In Action;
NOTE: If your original translation is reprinted, please indicate the source!
This chapter introduces
- ChannelPipeline
- ChannelHandlerContext
- ChannelHandler
- Inbound vs outbound (Inbound and outbound)
Accepting connections or creating them is only part of your application. Although these are important, trademanager is a more complex network application and requires more code writing, such as processing incoming and outgoing data. Netty provides a powerful function to handle these tasks, allowing you to customize the implementation of ChannelHandler to process data. What makes ChannelHandler more powerful is that it can connect to each ChannelHandler to implement the task, which helps to clean and reuse the code. However, data processing is only one of what ChannelHandler does. It can also suppress I/O operations, such as write requests. All of these can be implemented dynamically.
6.1 ChannelPipeline
ChannelPipeline is a list of ChannelHandler instances used to process or intercept data received and sent by the channel. ChannelPipeline provides an advanced interception filter mode, allowing you to completely control an event in ChannelPipeline and how to handle the interaction between ChannelHandler and ChannelPipeline. For each new channel, a new ChannelPipeline is created and appended to the channel. Once connected, the coupling between the Channel and ChannelPipeline is permanent. Channel cannot be appended with other ChannelPipeline or separated from ChannelPipeline. Describes the I/O processing of ChannelHandler in ChannelPipeline. an I/O operation can be processed by ChannelInboundHandler or ChannelOutboundHandler, call ChannelInboundHandler to process inbound IO or use ChannelOutboundHandler to process outbound IO.
As shown in, ChannelPipeline is a list of ChannelHandler. If an inbound I/O event is triggered, the event will first pass ChannelHandler in ChannelPipeline; for an inbound I/O event, the ChannelHandler in ChannelPipeline is used from the last one. ChannelHandler can process the event and check the type. If a ChannelHandler cannot process the event, it will be skipped and the event will be passed to the next ChannelHandler. ChannelPipeline can be dynamically added, deleted, and replaced with ChannelHandler, which improves flexibility.
Method for modifying ChannelPipeline:
- AddFirst (...), add ChannelHandler in the first position of ChannelPipeline
- AddBefore (...), add ChannelHandler before the ChannelHandler name specified in ChannelPipeline
- AddAfter (...), add ChannelHandler after the ChannelHandler name specified in ChannelPipeline
- AddLast (ChannelHandler...), add ChannelHandler at the end of ChannelPipeline
- Remove (...), delete the ChannelHandler specified in ChannelPipeline
- Replace (...), replace the ChannelHandler specified in ChannelPipeline
ChannelPipeline pipeline = ch.pipeline();FirstHandler firstHandler = new FirstHandler();pipeline.addLast("handler1", firstHandler);pipeline.addFirst("handler2", new SecondHandler());pipeline.addLast("handler3", new ThirdHandler());pipeline.remove("handler3");pipeline.remove(firstHandler);pipeline.replace("handler2", "handler4", new FourthHandler());
The ChannelHandler added to ChannelPipeline processes events through IO-Thread, which means that there must be no other IO-Thread blocking to affect the overall processing of IO; sometimes it may need to be blocked, for example, JDBC. Therefore, Netty allows you to use an EventExecutorGroup to process each ChannelPipeline. add * method. Custom events are processed by EventExecutor contained in EventExecutorGroup. The default implementation is DefaultEventExecutorGroup. In addition to some modification methods, ChannelPipeline also has many other methods. For details about the methods and usage, see the API documentation or source code. 6.2 ChannelHandlerContext
After each ChannelHandler is added to ChannelPipeline, A ChannelHandlerContext is created and associated with the created ChannelHandler. ChannelHandlerContext allows ChannelHandler to interact with other ChannelHandler implementations, which are part of the same ChannelPipeline. ChannelHandlerContext does not change the ChannelHandler added to it, so it is safe. 6.2.1 notify the next ChannelHandler
In the same ChannelPipeline, notify the nearest handler by calling one of the methods in ChannelInboundHandler and ChannelOutboundHandler. The start of the notification depends on how you set it. Shows the relationships among ChannelHandlerContext, ChannelHandler, and ChannelPipeline:
If you want to use ChannelPipeline for all event streams, there are two different methods:
- Call the Channel Method
- Call ChannelPipeline
Both methods allow all event streams to pass through ChannelPipeline. Whether it starts from the header or tail, because it mainly depends on the nature of the event. For an "inbound" event, it starts at the header; for an "outbound" event, it starts at the end. The following code shows how a write event starts from the tail using ChannelPipeline:
@Overrideprotected void initChannel(SocketChannel ch) throws Exception {ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {@Overridepublic void channelActive(ChannelHandlerContext ctx) throws Exception {//Event via ChannelChannel channel = ctx.channel();channel.write(Unpooled.copiedBuffer("netty in action", CharsetUtil.UTF_8));//Event via ChannelPipelineChannelPipeline pipeline = ctx.pipeline();pipeline.write(Unpooled.copiedBuffer("netty in action", CharsetUtil.UTF_8));}});}
Notifications Through Channel or ChannelPipeline:
You may want to start from the specified location of ChannelPipeline and do not want to flow through the entire ChannelPipeline, as shown below:
- In order to save the cost, the channel handler that is not interested won't pass through
- Exclude some ChannelHandler
In this case, you can use ChannelHandlerContext's ChannelHandler to notify the start point. It uses ChannelHandlerContext to execute the next ChannelHandler. The following code directly uses ChannelHandlerContext:
// Get reference of ChannelHandlerContextChannelHandlerContext ctx = ..;// Write buffer via ChannelHandlerContextctx.write(Unpooled.copiedBuffer("Netty in Action", CharsetUtil.UTF_8));
The message flows through ChannelPipeline to the next ChannelHandler. In this case, use ChannelHandlerContext to start the next ChannelHandler. The event stream is displayed:
As shown in, all the preceding ChannelHandler is skipped from the specified ChannelHandlerContext. The ChannelHandlerContext operation is a common mode. The most common mode is to call the operation from ChannelHanlder or use ChannelHandlerContext externally, this is thread-safe.
6.2.2 modify ChannelPipeline
You can call the pipeline () method of ChannelHandlerContext to access ChannelPipeline, and dynamically add, delete, and replace ChannelHandler in ChannelPipeline at runtime. The ChannelHandlerContext can be maintained for future use. For example, the external Handler method triggers an event, or even from a different thread. The following code stores ChannelHandlerContext for later use or other threads:
public class WriteHandler extends ChannelHandlerAdapter {private ChannelHandlerContext ctx;@Overridepublic void handlerAdded(ChannelHandlerContext ctx) throws Exception {this.ctx = ctx;}public void send(String msg){ctx.write(msg);}}
Note that a ChannelHandler instance with the @ Sharable annotation can be added to multiple ChannelPipeline instances. That is to say, a single ChannelHandler instance can have multiple ChannelHandlerContext, so you can call different ChannelHandlerContext to obtain the same ChannelHandler. If a ChannelHandler instance without the @ Sharable annotation is added to multiple ChannelPipeline instances, an exception is thrown. ChannelHandler after the @ Sharable annotation is used securely on different threads and channels. How is it insecure? See the following code:
@Sharablepublic class NotSharableHandler extends ChannelInboundHandlerAdapter {private int count;@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {count++;System.out.println("channelRead(...) called the " + count + " time");ctx.fireChannelRead(msg);}}
The above is a Handler with the @ Sharable annotation. When it is used by multiple threads, the count in it is insecure, leading to a count value error.
Why share ChannelHandler? Using the @ Sharable annotation to share a ChannelHandler still plays a good role in some requirements, such as using a ChannelHandler to count the number of connections or to process some global data. 6.3 State Model
Netty has a simple but powerful state model and perfectly maps to various ChannelInboundHandler methods. The following are four different statuses of the Channel lifecycle:
- ChannelUnregistered
- ChannelRegistered
- ChannelActive
- ChannelInactive
The status of a Channel changes in its lifecycle, because the status change needs to be triggered and shows the Channel status change:
You can also see the additional status changes, because the user can cancel the Channel from EventLoop to pause the event execution, and then re-register. In this case, you will see multiple channelRegistered and channelUnregistered status changes, and there will always be only one channelActive and channelInactive status, because one channel can only be connected once within its lifecycle, then it will be recycled; reconnect will create a new channel. Shows the status changes after the Channel is deregistered from EventLoop and then re-registered:
6.4 ChannelHandler and its subclass
Netty has three classes that implement the ChannelHandler interface, two of which are interfaces and the other is abstract classes. For example:
6.4.1 methods in ChannelHandler
Netty defines a good type hierarchy to indicate different handler types. The parent class of all types is ChannelHandler. ChannelHandler provides methods to add or delete ChannelPipeline within its lifecycle.
- HandlerAdded, ChannelHandler added to the actual next section to prepare for event processing
- HandlerRemoved: Delete the ChannelHandler from the actual context and no longer process the event.
- ExceptionCaught to handle thrown exceptions
The ChannelHandlerContext parameter must be passed in the preceding three methods. When each ChannelHandler is added to ChannelPipeline, ChannelHandlerContext is automatically created. ChannelHandlerContext allows secure storage and retrieval of values through local channels. Netty also provides an abstract class that implements ChannelHandler: ChannelHandlerAdapter. ChannelHandlerAdapter implements all the methods of the parent class, basically passing the event to the next ChannelHandler in ChannelPipeline until the end.
6.4.2 ChannelInboundHandler
ChannelInboundHandler provides some methods to receive data or be called when the Channel status changes. The following are some ChannelInboundHandler methods:
- ChannelRegistered: the Channel of ChannelHandlerContext is registered to EventLoop;
- ChannelUnregistered: the Channel of ChannelHandlerContext is deregistered from EventLoop.
- ChannelActive, Channel of ChannelHandlerContext is activated
- ChannelInactive: end the Channel lifecycle of ChannelHanderContxt
- ChannelRead: reads messages from the peer end of the current Channel.
- ChannelReadComplete, which is executed after the message is read.
- UserEventTriggered: A User event is penalized.
- ChannelWritabilityChanged: Change the write status of the Channel. You can use Channel. isWritable () to check
- ExceptionCaught: rewrite the ChannelHandler method of the parent class to handle exceptions.
Netty provides a class that implements the ChannelInboundHandler interface and inherits the ChannelHandlerAdapter: ChannelInboundHandlerAdapter. ChannelInboundHandlerAdapter implements all ChannelInboundHandler methods to process messages and forward them to the next ChannelHandler in ChannelPipeline. The ChannelInboundHandlerAdapter's channelRead method does not automatically release the message after processing the message. to automatically release the received message, you can use SimpleChannelInboundHandler <I>. See the following code:
/*** The Handler that implements the ChannelInboundHandlerAdapter and does not automatically release the received message object * @ author c. k **/public class DiscardHandler extends ChannelInboundHandlerAdapter {@ Overridepublic void channelRead (ChannelHandlerContext ctx, Object msg) throws Exception {// manually release the message ReferenceCountUtil. release (msg );}}
/*** Inherits SimpleChannelInboundHandler and automatically releases the message object * @ author c. k **/public class SimpleDiscardHandler extends SimpleChannelInboundHandler <Object >{@ Overrideprotected void channelRead0 (ChannelHandlerContext ctx, Object msg) throws Exception {// Manual release is not required }}
If you need notifications of other status changes, you can override other Handler methods. Generally, custom message types are used to decode bytes. You can implement ChannelInboundHandler or ChannelInboundHandlerAdapter. There is a better solution, and the use of the encoding/decoder framework can be very compatible. Which one of ChannelInboundHandler, ChannelInboundHandlerAdapter, and SimpleChannelInboundhandler is used to process the received message depends on the requirement. In most cases, SimpleChannelInboundHandler is used to process the message, use ChannelInboundHandlerAdapter to handle other "inbound" events or status changes.
ChannelInitializer is used to initialize ChannelHandler and add custom ChannelHandler to ChannelPipeline.
6.4.3 ChannelOutboundHandler
ChannelOutboundHandler is used to process "outbound" data messages. ChannelOutboundHandler provides the following methods:
- Bind: bind the Channel to the local address.
- Connect and Channel connection operations
- Disconnect and Channel disconnected
- Close, close the Channel
- Deregister: log out of the Channel.
- Read: reads messages. Actually, it intercepts ChannelHandlerContext. read ()
- Write, write operation, actually write messages through ChannelPipeline, Channel. flush () attribute to the actual Channel
- Flush, refresh the message to the Channel
ChannelOutboundHandler is a subclass of ChannelHandler and implements all ChannelHandler methods. All the most important Methods Adopt ChannelPromise. Therefore, once the request stops forwarding parameters from ChannelPipeline, it must be notified. Netty provides ChannelOutboundHandler implementation: ChannelOutboundHandlerAdapter. ChannelOutboundHandlerAdapter implements all methods of the parent class and can rewrite the methods of interest as needed. By default, all these methods are implemented by calling the ChannelHandlerContext method to forward the event to the next ChannelHandler in ChannelPipeline. See the following code:
public class DiscardOutboundHandler extends ChannelOutboundHandlerAdapter {@Overridepublic void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {ReferenceCountUtil.release(msg);promise.setSuccess();}}
It is important to remember to release Zhiyuan and pass through ChannelPromise. If ChannelPromise is not notified, one of the ChannelFutureListener may not be notified to process a message.
If a message is consumed and is not passed to the next ChannelOutboundHandler in ChannelPipeline, you need to call ReferenceCountUtil. release (message) to release the message resource. Once a message is transmitted to the actual channel, it is automatically written to the message or released when the channel is closed.