Netty核心源码分析(三)业务请求执行关键——ChannelPipeline、ChannelHandler、ChannelHandlerContext源码分析

系列文章目录

Netty核心源码分析(一),Netty的Server端启动过程源码分析
Netty核心源码分析(二),Netty的Server端接收请求过程源码分析
Netty核心源码分析(三)业务请求执行关键——ChannelPipeline、ChannelHandler、ChannelHandlerContext源码分析
Netty核心源码分析(四)心跳检测源码分析
Netty核心源码分析(五)核心组件EventLoop源码分析

一、ChannelPipeline、ChannelHandler、ChannelHandlerContext

1、三者关系

  1. 每当 ServerSocket 创建一个新的连接,就会创建一个 Socket,对应的就是目标客户端。
    2)每一个新创建的 Socket 都将会分配一个全新的 ChannelPipeline (以下简称 pipeline)
  2. 每一个 ChannelPipeline 内部都含有多个 ChannelHandlerContext (以下简称 Context)
    4)他们一起组成了双向链表,这些 Context 用于包装我们调用 addLast 方法时添加的 ChannelHandler (以下简称
    handler)
    在这里插入图片描述
    上图中: ChannelSocket 和 ChannelPipeline 是一对一的关联关系,而 pipeline 内部的多个 Context 形成了链表,Context 只是对 Handler 的封装。

当一个请求进来的时候,会进入 Socket 对应的 pipeline,并经过 pipeline 所有的 handler,就是设计模式中的过滤器模式。

二、ChannelPipeline源码分析

1、ChannelPipeline接口设计

在这里插入图片描述
在这里插入图片描述
我们可以看到,ChannelPipeline继承了ChannelInboundInvoker、ChannelOutboundInvoker、Iterable接口,表示可以调用数据出站、入站的方法,同时支持迭代遍历。

ChannelPipeline内部的方法基本都是针对handler链表的增删改查。

2、ChannelPipeline处理事件

ChannelPipeline接口上提供了一张图:
在这里插入图片描述
这是一个handler的list,handler用于处理或拦截入站事件和出站事件,pipeline实现了过滤器的高级形式,以便用户控制事件如何处理以及handler在pipeline中如何交互。
上图描述了一个典型的handler在pipeline中处理IO事件的方式,IO事件由inboundHandler或者outBoundHandler处理,并通过调用ChannelHandlerContext.fireChannelRead方法转发给其最近的处理程序。

// 在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;
}

其中,findContextInbound方法,通过循环的方式查找下一个inboundHandler:

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

invokeChannelRead用于执行查找出的下一个Handler的channelRead方法,相当于一个执行器链。

// 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);
    }
}

outBound处理同理,只不过是从尾往前进行遍历。

而这些Handler,正是保存在Pipeline中的:

在这里插入图片描述
通常一个pipeline有多个handler,例如,一个典型的服务器在每个通道的pipeline都会有以下处理程序:编解码器+业务处理程序。

业务程序不能将线程阻塞,会影响IO的速度,进而影响整个Netty程序的性能。如果你的业务程序很快,就可以放在IO线程中,反之,需要异步执行(使用MQ)。或者在添加Handler的时候添加一个线程池,例如:

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

三、ChannelHandler源码分析

1、ChannelHandler接口

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

Netty中的@Sharable注解用于标识某个ChannelHandler是否可以在多个ChannelPipeline之间共享使用,也即表示该ChannelHandler是否是无状态的。使用@Sharable注解标记的ChannelHandler可以在多个ChannelPipeline之间共享使用,从而降低资源的消耗。在Netty中,每次调用ChannelHandler的构造函数或添加到ChannelPipeline时,都会创建一个新的实例。因此,对于有状态的ChannelHandler,将不能使用@Sharable进行标记。

需要注意的是,在进行ChannelHandler的复用时,考虑到线程安全问题。对于使用@Sharable注解的ChannelHandler类,在多个ChannelPipeline之间共享时需要保证线程安全性。假如ChannelHandler类中包含非线程安全的成员变量,需要在成员变量上进行同步,以防止出现线程安全问题。因此,这也是使用@Sharable注解时需要仔细考虑的一点。

ChannelHandler的作用就是处理IO事件或拦截IO事件,并将其转发给下一个处理程序ChannelHandler。Handler处理事件时分入站和出站的,两个方向的操作都是不同的,因此,Netty定义了两个子接口继承ChannelHandler,分别是ChannelInboundHandler和ChannelOutboundHandler。

2、ChannelInboundHandler入站接口

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;
}

我们需要重写其中的一些方法,用于关注某些我们需要的事件,在重写的方法中实现我们自己的逻辑。当Netty事件发生时,Netty会回调对应重写的方法。

3、ChannelOutboundHandler出站接口

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;
}

出站操作都是一些连接或者写出数据的方法。

4、ChannelDuplexHandler处理出站和入站事件

ChannelDuplexHandler继承了ChannelInboundHandlerAdapter类,实现了ChannelOutboundHandler接口,可以处理入站和出站所有的事件。

我们尽量避免使用ChannelDuplexHandler处理事件,入站出站事件最好由独立的类处理,否则容易混淆。

四、ChannelHandlerContext源码分析

1、ChannelHandlerContext接口

public interface ChannelHandlerContext extends AttributeMap, ChannelInboundInvoker, ChannelOutboundInvoker

在这里插入图片描述
ChannelHandlerContext接口继承了AttributeMap, ChannelInboundInvoker, ChannelOutboundInvoker接口。
在这里插入图片描述
ChannelHandlerContext不仅仅继承了ChannelInboundInvoker和ChannelOutboundInvoker接口的方法,同时也定义了一些自己的方法,这些方法能够获取context上下文环境中对应的比如channel、executor、handler、pipeline、内存分配器、关联的handler是否被删除。

Context就是包装了handler相关的一切类,以便Context可以在pipeline方便地操作handler。

2、ChannelInboundInvoker和ChannelOutboundInvoker接口

在这里插入图片描述
在这里插入图片描述
这两个invoker就是针对入站或出站方法来的,就是在入站或出站handler的外层再包装一层,达到在方法前后拦截并做一些特定操作的目的。

五、创建过程源码分析

1、SocketChannel创建过程创建pipeline

在上一篇文章我们分析到,doReadMessages方法会创建NioSocketChannel:
Netty核心源码分析(二),Netty的Server端接收请求过程源码分析

// 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;
}

在new NioSocketChannel()的过程中,一直往父类找,会找到AbstractChannel的构造方法:

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

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

此时会创建一个DefaultChannelPipeline,在DefaultChannelPipeline的构造方法中会做Pipeline初始化。

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、pipeline的addLast方法

在服务端通过pipeline的addLiast方法添加Handler时的源码:

// 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、小总结

到这里,针对三个对象创建过程就结束了,每当创建 ChannelSocket 的时候都会创建个绑定的 pipeline,一对一的关系,创建 pipeline 的时候也会创建 tail 节点和 head 节点,形成最初的链表。

tail是入站 inbound 类型的 handler, head 既是 inbound 也是 outbound 类型的 handler。

在调用 pipeline 的 addLast方法的时候,会根据给定的 handler 创建一个 Context,然后,将这个 Context 插入到链表的尾端(tail 前面)到此就 OK 了。

Context包装handler,多个Context在pipeline中形成了双向链表。

入站方法叫inbound,由head节点开始。出站方法叫outbound,由tail节点开始。

六、ChannelPipeline调度Handler源码分析

当一个请求进来的时候,ChannelPipeline会第一个调用pipeline的相关方法,如果是入站事件,这些方法由fire开头,表示开始管道的流动。让后面的handler继续处理。

上面我们分析过了,ChannelPipeline创建的是一个DefaultChannelPipeline。

上面我们也分析了,DefaultChannelPipeline中包含着一系列的事件方法,触发某一个事件之后,会执行其相关的方法。

1、inbound——fireChannelRead

我们以fireChannelRead方法为例,追一下源代码。

当client业务数据发送过来之后,会触发DefaultChannelPipeline的fireChannelRead方法,表示有数据可读。

// 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);
            }
        });
    }
}

此时,handler()方法返回this,也就是当前的ChannelHandler,并且调用该Handler的channelRead方法。

//  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);
    }
}

上面我们分析到,pipeline的第一个Handler是系统创建的HeadContext,此时会调用HeadContext的channelRead方法(方法实现在其父类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);
            }
        });
    }
}

我们发现,在AbstractChannelHandlerContext中也有个fireChannelRead方法,该方法调用了findContextInbound方法查找了pipeline的next下一个Handler,然后调用下一个Handler的channelRead方法,如此重复。

也就是说,在Handler的channelRead方法中,调用ChannelHandlerContext的fireChannelRead方法,就会将消息传递给下一个Handler。

2、outbound——connect

我们以connect方法来分析出站源码:

// 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);
    }
}

我们发现,也是链式的调用ChannelOutboundHandler的connect方法,与入站如出一辙。

3、总结

在这里插入图片描述
Context包装handler,多个Context在pipeline中形成了双向链表,入站方向叫inbound,由head节点开始,出站方法叫outbound,由tail节点开始。

节点中间的传递通过AbstractChannelHandlerContext内部的fire系列方法,找到当前节点的下一个节点不断地进行传播,以链式完成对handler的调度。

到此时,我们Netty从请求建立、请求接收、请求处理的整个过程源码已经全部分析完毕
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/A_art_xiang/article/details/130326161