Netty core source code analysis (3) The key to business request execution - ChannelPipeline, ChannelHandler, ChannelHandlerContext source code analysis

Series Article Directory

Netty core source code analysis (1), Netty’s server-side startup process source code analysis
Netty core source code analysis (2), Netty’s server-side receiving request process source code analysis
Netty core source code analysis (3) The key to business request execution - ChannelPipeline, ChannelHandler, ChannelHandlerContext source code analysis
Netty core source code analysis (4) heartbeat detection source code analysis
Netty core source code analysis (5) core component EventLoop source code analysis

一、ChannelPipeline、ChannelHandler、ChannelHandlerContext

1. The relationship between the three

  1. Whenever ServerSocket creates a new connection, a Socket is created corresponding to the target client.
    2) Each newly created Socket will be assigned a new ChannelPipeline (hereinafter referred to as pipeline)
  2. Each ChannelPipeline contains multiple ChannelHandlerContexts (hereinafter referred to as Context) 4) Together they form a doubly linked list, and these Contexts are used to wrap the ChannelHandler (hereinafter referred to as handler)
    added when we call the addLast method. In the above figure : ChannelSocket and ChannelPipeline are a One-to-one association, and multiple Contexts inside the pipeline form a linked list, and Context is just an encapsulation of Handler.

    insert image description here

When a request comes in, it will enter the pipeline corresponding to the Socket and go through all the handlers in the pipeline, which is the filter mode in the design mode.

Two, ChannelPipeline source code analysis

1. ChannelPipeline interface design

insert image description here
insert image description here
We can see that ChannelPipeline inherits the ChannelInboundInvoker, ChannelOutboundInvoker, and Iterable interfaces, which means that it can call data outbound and inbound methods, and supports iterative traversal.

The internal methods of ChannelPipeline are basically aimed at adding, deleting, modifying and checking the handler linked list.

2. ChannelPipeline handles events

A picture is provided on the ChannelPipeline interface:
insert image description here
this is a list of handlers, handlers are used to process or intercept inbound events and outbound events, pipelines implement advanced forms of filters, so that users can control how events are processed and handlers are in pipelines How to interact.
The figure above describes how a typical handler handles IO events in the pipeline. IO events are processed by inboundHandler or outBoundHandler and forwarded to its nearest handler by calling the ChannelHandlerContext.fireChannelRead method.

// 在ChannelInboundHandlerAdapter中的channelRead方法中有着对该方法的默认实现,默认就是调用了ChannelHandlerContext的fireChannelRead方法:
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
    
    
    ctx.fireChannelRead(msg);
}
// io.netty.channel.AbstractChannelHandlerContext#fireChannelRead
@Override
public ChannelHandlerContext fireChannelRead(final Object msg) {
    
    
    invokeChannelRead(findContextInbound(), msg);
    return this;
}

Among them, the findContextInbound method finds the next inboundHandler by looping:

// io.netty.channel.AbstractChannelHandlerContext#findContextInbound
private AbstractChannelHandlerContext findContextInbound() {
    
    
    AbstractChannelHandlerContext ctx = this;
    do {
    
    
        ctx = ctx.next;
    } while (!ctx.inbound);
    return ctx;
}

invokeChannelRead is used to execute the channelRead method of the next Handler found, which is equivalent to an executor chain.

// io.netty.channel.AbstractChannelHandlerContext#invokeChannelRead(io.netty.channel.AbstractChannelHandlerContext, java.lang.Object)
static void invokeChannelRead(final AbstractChannelHandlerContext next, Object msg) {
    
    
    final Object m = next.pipeline.touch(ObjectUtil.checkNotNull(msg, "msg"), next);
    EventExecutor executor = next.executor();
    if (executor.inEventLoop()) {
    
    
        next.invokeChannelRead(m);
    } else {
    
    
        executor.execute(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                next.invokeChannelRead(m);
            }
        });
    }
}
// io.netty.channel.AbstractChannelHandlerContext#invokeChannelRead(java.lang.Object)
private void invokeChannelRead(Object msg) {
    
    
    if (invokeHandler()) {
    
    
        try {
    
    
            ((ChannelInboundHandler) handler()).channelRead(this, msg);
        } catch (Throwable t) {
    
    
            notifyHandlerException(t);
        }
    } else {
    
    
        fireChannelRead(msg);
    }
}

The same is true for outBound, except that it traverses from the end to the front.

And these Handlers are stored in the Pipeline:

insert image description here
Usually a pipeline has multiple handlers. For example, a typical server will have the following handlers in the pipeline of each channel: codec + business handler.

Business programs cannot block threads, which will affect the speed of IO, which in turn will affect the performance of the entire Netty program. If your business program is fast, it can be placed in the IO thread, otherwise, it needs to be executed asynchronously (using MQ). Or add a thread pool when adding a Handler, for example:

// 下面这个任务执行的时候,将不会阻塞IO线程,执行的线程来自group线程池
pipeline.addLast(group, "handler", new MyHandler());

Three, ChannelHandler source code analysis

1. ChannelHandler interface

public interface ChannelHandler {
    
    
	// 当把ChannelHandler添加到pipeline时调用
    void handlerAdded(ChannelHandlerContext ctx) throws Exception;

	// 当从pipeline中移除时调用
    void handlerRemoved(ChannelHandlerContext ctx) throws Exception;

	// 当处理过程在pipeline发生异常时调用
    @Deprecated
    void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception;


    @Inherited
    @Documented
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @interface Sharable {
    
    
        // no value
    }
}

The @Sharable annotation in Netty is used to identify whether a ChannelHandler can be shared between multiple ChannelPipelines, that is, whether the ChannelHandler is stateless. A ChannelHandler marked with the @Sharable annotation can be shared between multiple ChannelPipelines, thereby reducing resource consumption. In Netty, every time the constructor of ChannelHandler is called or added to ChannelPipeline, a new instance is created. Therefore, for a stateful ChannelHandler, it will not be possible to mark it with @Sharable.

It should be noted that when reusing ChannelHandler, thread safety issues are taken into consideration. For the ChannelHandler class annotated with @Sharable, thread safety needs to be guaranteed when sharing among multiple ChannelPipelines. If the ChannelHandler class contains non-thread-safe member variables, it needs to be synchronized on the member variables to prevent thread safety problems. Therefore, this is also a point that needs to be carefully considered when using the @Sharable annotation.

The role of ChannelHandler is to process IO events or intercept IO events and forward them to the next handler ChannelHandler. When Handler processes events, it is divided into inbound and outbound, and the operations in both directions are different. Therefore, Netty defines two sub-interfaces to inherit ChannelHandler, namely ChannelInboundHandler and ChannelOutboundHandler.

2. ChannelInboundHandler inbound interface

public interface ChannelInboundHandler extends ChannelHandler {
    
    

    // channel注册时调用
    void channelRegistered(ChannelHandlerContext ctx) throws Exception;

    // channel取消注册时调用
    void channelUnregistered(ChannelHandlerContext ctx) throws Exception;

    // channel处于活动状态时调用
    void channelActive(ChannelHandlerContext ctx) throws Exception;

    // channel非活动状态时调用
    void channelInactive(ChannelHandlerContext ctx) throws Exception;

    // 从channel读取数据时被调用
    void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception;

    // 数据读取完成时被调用
    void channelReadComplete(ChannelHandlerContext ctx) throws Exception;

    // 用户事件触发
    void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception;

    // 通道可写状态被触发时调用
    void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception;

    // 发生异常时调用
    @Override
    @SuppressWarnings("deprecation")
    void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception;
}

We need to rewrite some of these methods to pay attention to certain events we need, and implement our own logic in the rewritten methods. When a Netty event occurs, Netty will call back the corresponding rewritten method.

3. ChannelOutboundHandler outbound interface

public interface ChannelOutboundHandler extends ChannelHandler {
    
    
    // 当请求将channel绑定到本地端口时调用
    void bind(ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise) throws Exception;

    // 发生连接时调用
    void connect(
            ChannelHandlerContext ctx, SocketAddress remoteAddress,
            SocketAddress localAddress, ChannelPromise promise) throws Exception;

    // 断开连接时调用
    void disconnect(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception;

    // 当请求关闭channel时调用
    void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception;

    // 取消注册时调用
    void deregister(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception;

    
    void read(ChannelHandlerContext ctx) throws Exception;

    // 在进行写操作时调用。写操作将通过ChannelPipeline写入消息。一旦调用Channel.flush(),它们就准备好被刷新到实际的Channel
    void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception;

    // 在执行刷新操作时调用。flush操作将尝试flush所有以前写的挂起的消息
    void flush(ChannelHandlerContext ctx) throws Exception;
}

Outbound operations are methods that connect to or write out data.

4. ChannelDuplexHandler handles outbound and inbound events

ChannelDuplexHandler inherits the ChannelInboundHandlerAdapter class, implements the ChannelOutboundHandler interface, and can handle all inbound and outbound events.

We try to avoid using ChannelDuplexHandler to handle events. Inbound and outbound events are best handled by independent classes, otherwise it is easy to confuse.

Four, ChannelHandlerContext source code analysis

1. ChannelHandlerContext interface

public interface ChannelHandlerContext extends AttributeMap, ChannelInboundInvoker, ChannelOutboundInvoker

insert image description here
The ChannelHandlerContext interface inherits the AttributeMap, ChannelInboundInvoker, and ChannelOutboundInvoker interfaces.
insert image description here
ChannelHandlerContext not only inherits the methods of ChannelInboundInvoker and ChannelOutboundInvoker interfaces, but also defines some of its own methods. These methods can obtain the corresponding channels, executors, handlers, pipelines, memory allocators, and whether the associated handlers in the context context are deleted. .

Context is to wrap all the classes related to the handler, so that the context can easily operate the handler in the pipeline.

2. ChannelInboundInvoker and ChannelOutboundInvoker interfaces

insert image description here
insert image description here
These two invokers are for the inbound or outbound method, which is to wrap a layer on the outer layer of the inbound or outbound handler to achieve the purpose of intercepting and doing some specific operations before and after the method.

5. Source code analysis of the creation process

1. SocketChannel creation process creates pipeline

In the previous article, we analyzed that the doReadMessages method will create a NioSocketChannel:
Analysis of Netty core source code (2), source code analysis of Netty's server receiving request process

// io.netty.channel.socket.nio.NioServerSocketChannel#doReadMessages
@Override
protected int doReadMessages(List<Object> buf) throws Exception {
    
    
	// 实际上调用NIO的的accept方法,获取SocketChannel
    SocketChannel ch = SocketUtils.accept(javaChannel());

    try {
    
    
        if (ch != null) {
    
    
        	// 将NIO的SocketChannel包装成NioSocketChannel
            buf.add(new NioSocketChannel(this, ch));
            return 1;
        }
    } catch (Throwable t) {
    
    
        logger.warn("Failed to create a new channel from an accepted socket.", t);

        try {
    
    
            ch.close();
        } catch (Throwable t2) {
    
    
            logger.warn("Failed to close a socket.", t2);
        }
    }

    return 0;
}

In the process of new NioSocketChannel(), keep looking for the parent class, and you will find the construction method of AbstractChannel:

protected AbstractChannel(Channel parent) {
    
    
    this.parent = parent;
    id = newId();
    unsafe = newUnsafe();
    pipeline = newChannelPipeline();
}

protected DefaultChannelPipeline newChannelPipeline() {
    
    
    return new DefaultChannelPipeline(this);
}

At this time, a DefaultChannelPipeline will be created, and the Pipeline will be initialized in the constructor of DefaultChannelPipeline.

protected DefaultChannelPipeline(Channel channel) {
    
    
	// 将channel赋值给channel字段
    this.channel = ObjectUtil.checkNotNull(channel, "channel");
    // 创建future和promise,用于异步回调使用
    succeededFuture = new SucceededChannelFuture(channel, null);
    voidPromise =  new VoidChannelPromise(channel, true);
	// 创建inbound的TailContext
    tail = new TailContext(this);
    // 创建outbound的HeadContext(实际上实现了inbound和outbound两者)
    head = new HeadContext(this);
	// 形成双向链表
    head.next = tail;
    tail.prev = head;
}

2. The addLast method of the pipeline

The source code when adding a Handler through the pipeline's addLiast method on the server side:

// io.netty.channel.DefaultChannelPipeline#addLast(io.netty.channel.ChannelHandler...)
@Override
public final ChannelPipeline addLast(ChannelHandler... handlers) {
    
    
    return addLast(null, handlers);
}
// io.netty.channel.DefaultChannelPipeline#addLast(io.netty.util.concurrent.EventExecutorGroup, io.netty.channel.ChannelHandler...)
@Override
public final ChannelPipeline addLast(EventExecutorGroup executor, ChannelHandler... handlers) {
    
    
    if (handlers == null) {
    
    
        throw new NullPointerException("handlers");
    }

    for (ChannelHandler h: handlers) {
    
    
        if (h == null) {
    
    
            break;
        }
        addLast(executor, null, h);
    }

    return this;
}
// io.netty.channel.DefaultChannelPipeline#addLast(io.netty.util.concurrent.EventExecutorGroup, java.lang.String, io.netty.channel.ChannelHandler)
@Override
public final ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler) {
    
    
    final AbstractChannelHandlerContext newCtx;
    synchronized (this) {
    
     // 多线程安全问题
    	// 检查这个handler实例是否是共享的,如果不是,并且已经被别的pipeline使用了,则抛出异常
        checkMultiplicity(handler);
		// 创建一个Context,我们可以看出,每添加一个Handler都会关联一个Context
        newCtx = newContext(group, filterName(name, handler), handler);
		// 将Context追加到链表中
        addLast0(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.
        /*
			如果这个通道还没有注册到selector上,就将这个Context添加到这个pipeline的待办任务中。
			当注册好了以后,就会调用callHandlerAdded0方法(默认是什么都不做,用户可以实现这个方法)
		*/
        if (!registered) {
    
    
            newCtx.setAddPending();
            callHandlerCallbackLater(newCtx, true);
            return this;
        }

        EventExecutor executor = newCtx.executor();
        if (!executor.inEventLoop()) {
    
    
            newCtx.setAddPending();
            executor.execute(new Runnable() {
    
    
                @Override
                public void run() {
    
    
                    callHandlerAdded0(newCtx);
                }
            });
            return this;
        }
    }
    callHandlerAdded0(newCtx);
    return this;
}
// io.netty.channel.DefaultChannelPipeline#addLast0
// 实际上是插在了尾的前一个,而不是尾,因为尾部的Handler用于框架做最后的处理用
private void addLast0(AbstractChannelHandlerContext newCtx) {
    
    
    AbstractChannelHandlerContext prev = tail.prev;
    newCtx.prev = prev;
    newCtx.next = tail;
    prev.next = newCtx;
    tail.prev = newCtx;
}

3. Summary

At this point, the creation process for the three objects is over. Whenever a ChannelSocket is created, a bound pipeline is created, a one-to-one relationship. When a pipeline is created, a tail node and a head node are also created to form an initial linked list .

tail is a handler of inbound type, and head is a handler of both inbound and outbound types.

When the addLast method of the pipeline is called, a Context will be created according to the given handler, and then this Context will be inserted into the end of the linked list (before the tail) and it will be OK.

Context wraps the handler, and multiple Contexts form a doubly linked list in the pipeline.

The inbound method is called inbound, starting from the head node. The outbound method is called outbound, starting from the tail node.

Six, ChannelPipeline scheduling Handler source code analysis

When a request comes in, ChannelPipeline will be the first to call the relevant methods of the pipeline. If it is an inbound event, these methods start with fire, which means to start the flow of the pipeline. Let the subsequent handler continue processing.

As we have analyzed above, ChannelPipeline creates a DefaultChannelPipeline.

We also analyzed above that the DefaultChannelPipeline contains a series of event methods. After an event is triggered, its related methods will be executed.

1、inbound——fireChannelRead

Let's take the fireChannelRead method as an example and trace the source code.

When the client business data is sent, it will trigger the fireChannelRead method of DefaultChannelPipeline, indicating that there is data to read.

// io.netty.channel.DefaultChannelPipeline#fireChannelRead
@Override
public final ChannelPipeline fireChannelRead(Object msg) {
    
    
	// 将head传入
    AbstractChannelHandlerContext.invokeChannelRead(head, msg);
    return this;
}

// io.netty.channel.AbstractChannelHandlerContext#invokeChannelRead(io.netty.channel.AbstractChannelHandlerContext, java.lang.Object)
static void invokeChannelRead(final AbstractChannelHandlerContext next, Object msg) {
    
    
    final Object m = next.pipeline.touch(ObjectUtil.checkNotNull(msg, "msg"), next);
    EventExecutor executor = next.executor(); // 获取下一个执行器
    if (executor.inEventLoop()) {
    
    
        next.invokeChannelRead(m); // 执行器调度ChannelRead事件
    } else {
    
    
        executor.execute(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                next.invokeChannelRead(m);
            }
        });
    }
}

At this point, the handler() method returns this, which is the current ChannelHandler, and calls the channelRead method of the Handler.

//  io.netty.channel.AbstractChannelHandlerContext#invokeChannelRead(java.lang.Object)
private void invokeChannelRead(Object msg) {
    
    
    if (invokeHandler()) {
    
    
        try {
    
    
            ((ChannelInboundHandler) handler()).channelRead(this, msg);
        } catch (Throwable t) {
    
    
            notifyHandlerException(t);
        }
    } else {
    
    
        fireChannelRead(msg);
    }
}

We analyzed above that the first Handler of the pipeline is the HeadContext created by the system, and the channelRead method of the HeadContext will be called at this time (the method is implemented in its parent class DefaultChannelPipeline):

// io.netty.channel.DefaultChannelPipeline.HeadContext#channelRead
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
    
    
    ctx.fireChannelRead(msg);
}
// io.netty.channel.AbstractChannelHandlerContext#fireChannelRead
@Override
public ChannelHandlerContext fireChannelRead(final Object msg) {
    
    
    invokeChannelRead(findContextInbound(), msg);
    return this;
}
// io.netty.channel.AbstractChannelHandlerContext#findContextInbound
private AbstractChannelHandlerContext findContextInbound() {
    
    
    AbstractChannelHandlerContext ctx = this;
    do {
    
    
        ctx = ctx.next;
    } while (!ctx.inbound);
    return ctx;
}
// io.netty.channel.AbstractChannelHandlerContext#invokeChannelRead(io.netty.channel.AbstractChannelHandlerContext, java.lang.Object)
static void invokeChannelRead(final AbstractChannelHandlerContext next, Object msg) {
    
    
    final Object m = next.pipeline.touch(ObjectUtil.checkNotNull(msg, "msg"), next);
    EventExecutor executor = next.executor();
    if (executor.inEventLoop()) {
    
    
        next.invokeChannelRead(m);
    } else {
    
    
        executor.execute(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                next.invokeChannelRead(m);
            }
        });
    }
}

We found that there is also a fireChannelRead method in AbstractChannelHandlerContext, which calls the findContextInbound method to find the next Handler of the pipeline, and then calls the channelRead method of the next Handler, and so on.

That is to say, in the channelRead method of Handler, calling the fireChannelRead method of ChannelHandlerContext will pass the message to the next Handler.

2、outbound——connect

We use the connect method to analyze the outbound source code:

// io.netty.channel.DefaultChannelPipeline#connect(java.net.SocketAddress, java.net.SocketAddress, io.netty.channel.ChannelPromise)
@Override
public final ChannelFuture connect(
        SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) {
    
    
        // 从tail开始调用connect事件
    return tail.connect(remoteAddress, localAddress, promise);
}
// io.netty.channel.AbstractChannelHandlerContext#connect(java.net.SocketAddress, java.net.SocketAddress, io.netty.channel.ChannelPromise)
@Override
public ChannelFuture connect(
        final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise promise) {
    
    

    if (remoteAddress == null) {
    
    
        throw new NullPointerException("remoteAddress");
    }
    if (isNotValidPromise(promise, false)) {
    
    
        // cancelled
        return promise;
    }

    final AbstractChannelHandlerContext next = findContextOutbound();
    EventExecutor executor = next.executor();
    if (executor.inEventLoop()) {
    
    
        next.invokeConnect(remoteAddress, localAddress, promise);
    } else {
    
    
        safeExecute(executor, new Runnable() {
    
    
            @Override
            public void run() {
    
    
                next.invokeConnect(remoteAddress, localAddress, promise);
            }
        }, promise, null);
    }
    return promise;
}
// io.netty.channel.AbstractChannelHandlerContext#invokeConnect
private void invokeConnect(SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) {
    
    
    if (invokeHandler()) {
    
    
        try {
    
    
            ((ChannelOutboundHandler) handler()).connect(this, remoteAddress, localAddress, promise);
        } catch (Throwable t) {
    
    
            notifyOutboundHandlerException(t, promise);
        }
    } else {
    
    
        connect(remoteAddress, localAddress, promise);
    }
}

We found that the connect method of ChannelOutboundHandler is also called in a chain, which is exactly the same as the inbound.

3. Summary

insert image description here
Context wraps the handler, and multiple contexts form a two-way linked list in the pipeline. The inbound direction is called inbound, starting from the head node, and the outbound method is called outbound, starting from the tail node.

The transmission among the nodes is through the fire series methods inside the AbstractChannelHandlerContext, and the next node of the current node is found for continuous transmission, and the scheduling of the handler is completed in a chain.

At this point, our Netty source code of the whole process from request establishment, request reception, and request processing has been completely analyzed
insert image description here

Guess you like

Origin blog.csdn.net/A_art_xiang/article/details/130326161