Introduction

When we introduced the channel, we mentioned that almost all the implementations in the channel are carried out through the channelPipeline. As a pipline, how does it work?

Let's take a look together.

ChannelPipeline

ChannelPipeline is an interface that inherits three interfaces, namely ChannelInboundInvoker, ChannelOutboundInvoker and Iterable:

public interface ChannelPipeline
        extends ChannelInboundInvoker, ChannelOutboundInvoker, Iterable<Entry<String, ChannelHandler>> 

Inherited from ChannelInboundInvoker, indicating that ChannelPipeline can trigger some events of channel inboud, such as:

ChannelInboundInvoker fireChannelRegistered();
ChannelInboundInvoker fireChannelUnregistered();
ChannelInboundInvoker fireChannelActive();
ChannelInboundInvoker fireChannelInactive();
ChannelInboundInvoker fireExceptionCaught(Throwable cause);
ChannelInboundInvoker fireUserEventTriggered(Object event);
ChannelInboundInvoker fireChannelRead(Object msg);
ChannelInboundInvoker fireChannelReadComplete();
ChannelInboundInvoker fireChannelWritabilityChanged();

Inherited from ChannelOutboundInvoker, indicating that ChannelPipeline can perform some active channel operations, such as bind, connect, disconnect, close, deregister, read, write, flush and other operations.

Inherited from Iterable, indicating that ChannelPipeline is traversable, why is ChannelPipeline traversable?

Because one or more ChannelHandlers can be added to ChannelPipeline, ChannelPipeline can be regarded as a collection of ChannelHandlers.

For example, ChannelPipeline provides a series of methods for adding ChannelHandler:

ChannelPipeline addFirst(String name, ChannelHandler handler);
ChannelPipeline addFirst(EventExecutorGroup group, String name, ChannelHandler handler);
ChannelPipeline addFirst(EventExecutorGroup group, ChannelHandler... handlers);
ChannelPipeline addFirst(ChannelHandler... handlers);

ChannelPipeline addLast(String name, ChannelHandler handler);
ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler);
ChannelPipeline addLast(ChannelHandler... handlers);
ChannelPipeline addLast(EventExecutorGroup group, ChannelHandler... handlers);

ChannelPipeline addBefore(String baseName, String name, ChannelHandler handler);
ChannelPipeline addBefore(EventExecutorGroup group, String baseName, String name, ChannelHandler handler);
ChannelPipeline addAfter(String baseName, String name, ChannelHandler handler);
ChannelPipeline addAfter(EventExecutorGroup group, String baseName, String name, ChannelHandler handler);

Handlers can be added from the front, from the back, or from a specific location.

It is also possible to remove specific channelHandlers from the pipeline, or move and replace handlers at specific locations:

ChannelPipeline remove(ChannelHandler handler);
ChannelHandler remove(String name);
ChannelHandler removeFirst();
ChannelHandler removeLast();
ChannelPipeline replace(ChannelHandler oldHandler, String newName, ChannelHandler newHandler);
ChannelHandler replace(String oldName, String newName, ChannelHandler newHandler);

Of course, the corresponding query operations are indispensable:

ChannelHandler first();
ChannelHandler last();
ChannelHandler get(String name);
List<String> names();

You can also obtain the ChannelHandlerContext corresponding to the handler according to the incoming ChannelHandler.

ChannelHandlerContext context(ChannelHandler handler);

There are also some channel-related events in the ChannelPipeline, such as:

    ChannelPipeline fireChannelRegistered();
    ChannelPipeline fireChannelUnregistered();
    ChannelPipeline fireChannelActive();
    ChannelPipeline fireChannelInactive();
    ChannelPipeline fireExceptionCaught(Throwable cause);
    ChannelPipeline fireUserEventTriggered(Object event);
    ChannelPipeline fireChannelRead(Object msg);
    ChannelPipeline fireChannelReadComplete();
    ChannelPipeline fireChannelWritabilityChanged();

event delivery

So some friends may ask, since there are many handlers in the ChannelPipeline, how are the events in the handlers transmitted?

In fact, these events are triggered by calling the corresponding methods in ChannelHandlerContext.

For inbound events, the following methods can be called to deliver events:

ChannelHandlerContext.fireChannelRegistered()
ChannelHandlerContext.fireChannelActive()
ChannelHandlerContext.fireChannelRead(Object)
ChannelHandlerContext.fireChannelReadComplete()
ChannelHandlerContext.fireExceptionCaught(Throwable)
ChannelHandlerContext.fireUserEventTriggered(Object)
ChannelHandlerContext.fireChannelWritabilityChanged()
ChannelHandlerContext.fireChannelInactive()
ChannelHandlerContext.fireChannelUnregistered()

For outbound events, the following methods can be called to deliver events:

ChannelHandlerContext.bind(SocketAddress, ChannelPromise)
ChannelHandlerContext.connect(SocketAddress, SocketAddress, ChannelPromise)
ChannelHandlerContext.write(Object, ChannelPromise)
ChannelHandlerContext.flush()
ChannelHandlerContext.read()
ChannelHandlerContext.disconnect(ChannelPromise)
ChannelHandlerContext.close(ChannelPromise)
ChannelHandlerContext.deregister(ChannelPromise)

Specifically, the corresponding method in ChannelHandlerContext is called in the handler:

   public class MyInboundHandler extends ChannelInboundHandlerAdapter {
        @Override
       public void channelActive(ChannelHandlerContext ctx) {
           System.out.println("Connected!");
           ctx.fireChannelActive();
       }
   }
  
   public class MyOutboundHandler extends ChannelOutboundHandlerAdapter {
        @Override
       public void close(ChannelHandlerContext ctx, ChannelPromise promise) {
           System.out.println("Closing ..");
           ctx.close(promise);
       }
   }
   

DefaultChannelPipeline

ChannelPipeline has an official implementation called DefaultChannelPipeline, because for the pipeline, the main function is to manage handlers and deliver events, which is relatively simple, but it also has some special implementations, such as it has two AbstractChannelHandlerContext Type of head and tail.

We know that ChannelPipeline is actually a collection of many handlers, so how are these collections stored? This stored data structure is the AbstractChannelHandlerContext. Each AbstractChannelHandlerContext has a next node and a prev node to form a doubly linked list.

Similarly, use head and tail in DefaultChannelPipeline to store the packaged handler.

Note that although the head and tail here are both AbstractChannelHandlerContext, the two are slightly different. First look at the definitions of head and tail:

    protected DefaultChannelPipeline(Channel channel) {
        this.channel = ObjectUtil.checkNotNull(channel, "channel");
        succeededFuture = new SucceededChannelFuture(channel, null);
        voidPromise =  new VoidChannelPromise(channel, true);

        tail = new TailContext(this);
        head = new HeadContext(this);

        head.next = tail;
        tail.prev = head;
    }

In the constructor of DefaultChannelPipeline, initialize tail and head, where tail is TailContext and head is HeadContext.

Where TailContext implements the ChannelInboundHandler interface:

final class TailContext extends AbstractChannelHandlerContext implements ChannelInboundHandler

And HeadContext implements the ChannelOutboundHandler and ChannelInboundHandler interfaces:

final class HeadContext extends AbstractChannelHandlerContext
            implements ChannelOutboundHandler, ChannelInboundHandler 

Let's take the addFirst method as an example to see how the handler is added to the pipline:

    public final ChannelPipeline addFirst(EventExecutorGroup group, String name, ChannelHandler handler) {
        final AbstractChannelHandlerContext newCtx;
        synchronized (this) {
            checkMultiplicity(handler);
            name = filterName(name, handler);

            newCtx = newContext(group, name, handler);

            addFirst0(newCtx);

            // If the registered is false it means that the channel was not registered on an eventLoop yet.
            // In this case we add the context to the pipeline and add a task that will call
            // ChannelHandler.handlerAdded(...) once the channel is registered.
            if (!registered) {
                newCtx.setAddPending();
                callHandlerCallbackLater(newCtx, true);
                return this;
            }

            EventExecutor executor = newCtx.executor();
            if (!executor.inEventLoop()) {
                callHandlerAddedInEventLoop(newCtx, executor);
                return this;
            }
        }
        callHandlerAdded0(newCtx);
        return this;
    }

Its working logic is to first build a new context based on the incoming handler, and then call the addFirst0 method to add the context to the doubly linked list composed of AbstractChannelHandlerContext:

    private void addFirst0(AbstractChannelHandlerContext newCtx) {
        AbstractChannelHandlerContext nextCtx = head.next;
        newCtx.prev = head;
        newCtx.next = nextCtx;
        head.next = newCtx;
        nextCtx.prev = newCtx;
    }

Then call the callHandlerAdded0 method to trigger the context's handlerAdded method.

Summarize

The channelPipeline is responsible for managing various handlers of the channel. In the DefaultChannelPipeline, the head and tail of the AbstractChannelHandlerContext are used to store multiple handlers, and this chain structure is used to manage various handlers, which is very convenient.

This article has been included in http://www.flydean.com/04-3-netty-channelpipeline/

The most popular interpretation, the most profound dry goods, the most concise tutorials, and many tricks you don't know are waiting for you to discover!

Welcome to pay attention to my official account: "Program those things", understand technology, understand you better!


flydean
890 声望437 粉丝

欢迎访问我的个人网站:www.flydean.com