ChannelHandler

1. Channel life cycle

The lifecycle state of a Channel is as follows:

state describe
ChannelUnregistered A Channel has been created, but not yet registered with EventLoop
ChannelRegistered The Channel is already registered with the EventLoop
ChannelActive A Channel is active (already connected to its remote node) and can receive and send data
ChannelInactive The Channel is not connected to the remote node

The life cycle of a Channel is shown from top to bottom in the table above, and events are generated when the state changes. These events are forwarded to the ChannelHandler in the ChannelPipeline, which can then respond

2. ChannelHandler Life cycle

The following table lists the ChannelHandler defined lifecycle operations that are invoked when ChannelHandler is added to or removed from ChannelPipeline. Each of these methods takes a ChannelHandlerContext parameter

type describe
handlerAdded Called when a ChannelHandler is added to the ChannelPipeline
handlerRemoved Called when a ChannelHandler is removed from the ChannelPipeline
exceptionCaught Called when an error occurs in the ChannelPipeline during processing

3. ChannelInboundHandler interface

ChannelInboundHandler is a subinterface of the ChannelHandler interface that handles inbound data and various state changes. The following table lists the ChannelInboundHandler lifecycle methods that will be called when data is received or when the corresponding Channel state changes

type describe
channelRegistered Called when a Channel has registered with its EventLoop and is able to handle IO
channelUnregistered Called when a Channel unlogs from its EventLoop and is unable to process any IO
channelActive Called when a Channel is active, connected/bound and ready
channelInactive Called when a Channel is out of the active state and no longer connects to its remote node
channelReadComplete Called when a read operation on a Channel completes
channelRead Called when reading data from a Channel
ChannelWritabilityChanged Called when a Channel’s writable state changes
useEventTriggered When ChannelInboundHandler. FireUserEventTriggered () method is invoked is called, because a POJO flows through ChannelPipeline

When an implementation of a ChannelInboundHandler overrides the channelRead() method, it is responsible for explicitly freeing memory associated with pooled ByteBuf instances. ReferenceCountUtil Netty provides a practical method. The release ()

@Sharable
public class DiscardHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) { ReferenceCountUtil.release(msg); }}Copy the code

This way might be very troublesome, and the other a more simple way is to use SimpleChannelInboundHandler

@Sharable
public class SimpleDiscardHandler extends SimpleChannelInboundHandlerAdapter<Object> {
    @Override
    public void channelRead0(ChannelHandlerContext ctx, Object msg) {
        // No explicit resource release is required}}Copy the code

Because SimpleChannelInboundHandler will automatically release resources, so you shouldn’t store any reference to the message, because these references will be invalid

4. ChannelOutboundHandler interface

Outbound operations and data are handled by the ChannelOutboundHandler, whose methods are called by the Channel, ChannelPipeline, and ChannelHandlerContext

A powerful feature of ChannelOutboundHandler is that it can defer operations or events on demand, making it possible to handle requests in complex ways. For example, if a write to a remote node is suspended, you can postpone the flush operation and continue at a later date

The subscript shows all the methods defined by the ChannelOutboundHandler itself

type describe
bind(ChannelHandlerContext, SocketAddress, ChannelPromise) Called when a request binds a Channel to a local address
connect(ChannelHandlerContext, SocketAddress, ChannelPromise) Called when a request is made to connect a Channel to a remote node
disconnect(ChannelHandlerContext, ChannelPromise) Called when a request disconnects a Channel from a remote node
close(ChannelHandlerContext, ChannelPromise) Called when a request is made to close a Channel
deregister(ChannelHandlerContext, ChannelPromise) Called when the request unlogs a Channel from its EventLoop
read(ChannelHandlerContext) Called when a request reads more data from a Channel
flush(ChannelHandlerContext) Called when a request flushes enqueued data to a remote node via a Channel
write(ChannelHandlerContext, Object, ChannelPromise) Called when a request writes data to a remote node via a Channel

Most methods in ChannelOutboundHandler require a ChannelPromise parameter to be notified when the operation completes. CHannelPromise is a subclass of ChannelFuture that defines writable methods

5. ChannelHandler Adapter

Can use ChannelInboundHandlerAdapter and ChannelOutboundHandlerAdapter class as a starting point to the Channel, and These two adapters provide basic implementations of ChannelInboundHandler and CHannelOutboundHandler, respectively, and extend the abstract ChannelHandlerAdapter class

ChannelHandlerAdapter also provides the utility method isSharable(), which returns true if its corresponding implementation is marked Sharable, indicating that it can be added to multiple ChannelPipelines

If you want to use these adapter classes in your own ChannelHandler, you simply extend them and rewrite your custom methods

ChannelPipeline interface

A Channel pipeline can be thought of as a chain of ChannelHandler instances that intercept inbound and outbound events that flow through a Channel. Each newly created Channel will be assigned a new ChannelPipeline. A Channel cannot be attached to another Channel pipeline or detached from the current one. Depending on the origin of the event, the event will be handled by ChannelInboundHandler or ChannelOutboundHandler, and then implemented by calling ChannelHandlerContext, It will be forwarded to the next ChannelHandler of the same supertype

A typical ChannelPipeline layout with both inbound and outbound channelhandlers is shown here

As ChannelPipeline propagates an event, it tests whether the type of the next ChannelHandler in ChannelPipeline matches the direction of the event’s movement. If not, The ChannelPipeline skips the ChannelHandler and proceeds to the next one until it finds a match to the expected direction of the event

1. Modify the ChannelPipeline

ChannelHandler can add, remove, or replace other channelhandlers, including itself, by calling relevant methods on the ChannelPipeline

The following table lists methods for modifying ChannelPipeline by ChannelHandler

The name of the describe
addFirst

addBefore

addAfter

addLast
Add a ChannelHandler to the ChannelPipeline
remove Remove a ChannelHandler from the ChannelPipeline
replace Replace one ChannelHandler in the ChannelPipeline with another

ChannelPipeline operation to access ChannelHandler

The name of the describe
get Return ChannelHandler by type or name
context Returns the ChannelHandlerContext bound to ChannelHandler
names Returns the names of all channelhandlers in the ChannelPipeline

2. Trigger events

The ChannelPipeline API exposes additional methods for invoking inbound and outbound operations

The inbound operations of ChannelPipeline are shown in the table

Method names describe
fireChannelRegistered Call the channelRegistered(ChannelHandlerContext) method of the next ChannelInboundHandler in the ChannelPipeline
fireChannelUnregistered Call the channelUnregistered(ChannelHandlerContext) method of the next ChannelInboundHandler in ChannelPipeline
fireChannelActive Call the channelActive(ChannelHandlerContext) method of the next ChannelInboundHandler in ChannelPipeline
fireChannelInactive Call the channelInactive(ChannelHandlerContext) method of the next ChannelInboundHandler in the ChannelPipeline
fireExceptionCaught Call exceptionCaught(ChannelHandlerContext, Throwable) of the next ChannelInboundHandler in ChannelPipeline
fireUserEventTriggered Call the userEventTriggered(ChannelHandlerContext, Object) method of the next ChannelInboundHandler in ChannelPipeline
fireChannelRead Call the channelRead(ChannelHandlerContext) method of the next ChannelInboundHandler in ChannelPipeline
fireChannelReadComplete Call the channelReadComplete(ChannelHandlerContext) method of the next ChannelInboundHandler in ChannelPipeline
fireChannelWritabilityChanged The next ChannelInboundHandler call ChannelPipeline channelWritabilityChanged (ChannelHandlerContext) method

The outbound operations of ChannelPipeline are shown in the table

Method names describe
bind Bind a Channel to a local address, This calls the bind(ChannelHandlerContext, SocketAddress, ChannelPromise) method of the next ChannelOutboundHandler in the ChannelPipeline
connect Bind a Channel to a remote address, This calls the Connect (ChannelHandlerContext, SocketAddress, ChannelPromise) method of the next ChannelOutboundHandler in the ChannelPipeline
disconnect Disconnect the Channel, which calls the Disconnect (ChannelHandlerContext, ChannelPromise) method of the next ChannelOutboundHandler in the ChannelPipeline
close Closing the Channel calls the close(ChannelHandlerContext, ChannelPromise) method of the next ChannelOutboundHandler in the ChannelPipeline
deregister Unregister a Channel from its previously assigned EventExecutor (that is, EventLoop), This calls the deregister(ChannelHandlerContext, ChannelPromise) method of the next ChannelOutboundHandler in the ChannelPipeline
flush Flush all pending writes to a Channel, which calls the Flush (ChannelHandlerContext) method of the next ChannelOutboundHandler in the ChannelPipeline
write Write a message to a Channel, This calls the write(ChannelHandlerContext, Object, ChannelPromise) method of the next ChannelOutboundHandler in the ChannelPipeline
writeAndFlush This is a convenient method to call write() followed by Flush ()
read The request reads more data from the Channel, which calls the read(ChannelHandlerContext) method of the next ChannelOutboundHandler in the ChannelPipeline

To sum up:

  • The ChannelPipeline holds the ChannelHandler associated with a Channel
  • A ChannelPipeline can dynamically modify its ChannelHandler as needed
  • ChannelPipeline has a rich API to respond to inbound and outbound events

ChannelContext interface

ChannelHandlerContext represents the association between ChannelHandler and ChannelPipeline. Whenever a ChannelHandler is added to a ChannelPipeline, Creates a ChannelHandlerContext. The main function of the ChannelHandlerContext is to manage the interaction between the ChannelHandler it is associated with and other ChannelHandlers in the same ChannelPipeline

ChannelHandlerContext has many methods, some of which also exist on Channel and ChannelPipeline. The difference is that if you call these methods on Channel or ChannelHandlerPipeline, They propagate along the entire ChannelPipeline. Calling the same method on ChannelHandlerContext will start with the currently associated ChannelHandler and will only propagate to the next ChannelHandler in the ChannelPipeline capable of handling the event

1. Use ChannelHandlerContext

The relationship between Channel, ChannelPipeline, ChannelHandler, and ChannelHandlerContext is shown in the figure

Through ChannelHandler or ChannelPipeline operations, events propagate the entire ChannelPipeline, but at the ChannelHandler level, The movement of events from the previous ChannelHandler to the next ChannelHandler is done by a call to the ChannelHandlerContext

Why would you want to propagate events from a specific point in the ChannelPipeline?

  • Reduce the overhead of channelhandlers that are not interested in passing events through
  • Avoid passing the event to channelhandlers that might be interested in it

To call a process that starts with a particular ChannelHandler, you must get the ChannelHandlerContext associated with the ChannelHandler before that ChannelHandler, This ChannelHandlerContext will call the ChannelHandler that follows its associated ChannelHandler

We can also get a reference to ChannelPipeline by calling the Pipeline () method on the ChannelHandlerContext, which allows us to operate ChannelHandler at run time

Exception handling

1. Handle inbound exceptions

If an exception is thrown during the processing of an inbound event, it will flow through the ChannelPipeline from the point at which it was raised in the ChannelInboundHandler. To handle this type of inbound exception, you need to override the following methods in your ChannelInboundHandler implementation, otherwise the exception will be forwarded to the next ChannelHandler by default:

public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception
Copy the code

Because the exception continues to flow in the inbound direction, the ChannelInboundHandler that implements the logic to handle the exception is usually at the end of the ChannelPipeline, ensuring that the exception is always handled

2. Handle the outbound exception

Options for handling normal completion of outbound operations as well as exceptions are based on the following notification mechanism:

  • Each outbound operation will return a ChannelFuture, and the ChannelFutureListener registered with ChannelFuture will notify the result when the operation completes
  • Almost all methods on ChannelOutboundHandler pass in an instance of ChannelPromise, which, as a subclass of ChannelFuture, can also be assigned as listeners for asynchronous notifications

Adding ChannelFutureListener simply calls the addListener(ChannelFutureListener) method on the ChannelFuture instance, and there are two ways to do this, The first is to call the addListener() method on ChannelFuture returned by an outbound operation (such as the write method). The second is a ChannelFutureListener added to the ChannelPromise of the method ChannelOutboundHandler passed as a parameter

So, if your ChannelOutboundHandler throws an exception, Netty itself will notify any registrars that have registered with the corresponding ChannelPromise