Netty 源码分析

Netty 中的Channel

在这里插入图片描述

    public B channel(Class<? extends C> channelClass) {
        if (channelClass == null) {
            throw new NullPointerException("channelClass");
        } else {
            return this.channelFactory((io.netty.channel.ChannelFactory)(new ReflectiveChannelFactory(channelClass)));
        }
    }

    public ReflectiveChannelFactory(Class<? extends T> clazz) {
        ObjectUtil.checkNotNull(clazz, "clazz");

        try {
            this.constructor = clazz.getConstructor();
        } catch (NoSuchMethodException var3) {
            throw new IllegalArgumentException("Class " + StringUtil.simpleClassName(clazz) + " does not have a public non-arg constructor", var3);
        }
    }

//在connect方法或者bind方法执行时调用
    public T newChannel() {
        try {
            return (Channel)this.constructor.newInstance();
        } catch (Throwable var2) {
            throw new ChannelException("Unable to create Channel from class " + this.constructor.getDeclaringClass(), var2);
        }
    }

在这里插入图片描述

调用connect或者bind方法时
NioSocketChannel对应开启一个JDK NIO包下的SocketChannel,设置了非阻塞
在这里插入图片描述
NioServerSocketChannel对应开启一个JDK NIO包下的ServerSocketChannel,设置了非阻塞模式,然后设置服务端关心的SelectionKey.OP_ACCEPT事件
在这里插入图片描述

Netty 中的异步编程: Future 和 Promise

在这里插入图片描述

J.U.C的Future

首先看看Java.util.Concurrent包下的Future接口,其有以下声明方法:
在这里插入图片描述
一般情况下我们为了获得异步结果,会去使用FutureTask。
而FutureTask实现了RunableFuture接口,RunableFuture又同时继承了Runnable和Future接口,所以FutureTask也是Future接口的实现类。
在这里插入图片描述在这里插入图片描述
对于FutureTask来说,它的get()和cancel()方法是通过AQS来实现的。
调用这两个方法的阻塞情况和FutureTask的三个状态相关,如下图所示。
在这里插入图片描述

Netty中的Future

Netty中的Future接口(同名)继承了JDK中的Future接口,然后添加了一些方法

package io.netty.util.concurrent;

import java.util.concurrent.TimeUnit;

public interface Future<V> extends java.util.concurrent.Future<V> {
    boolean isSuccess();

    boolean isCancellable();
    
    // 如果任务执行失败,这个方法返回异常信息
    Throwable cause();
	
	//添加Listener方法来进行回调
    Future<V> addListener(GenericFutureListener<? extends Future<? super V>> var1);
    Future<V> addListeners(GenericFutureListener... var1);

    Future<V> removeListener(GenericFutureListener<? extends Future<? super V>> var1);
    Future<V> removeListeners(GenericFutureListener... var1);

	//阻塞等待任务结束,如果任务失败,则抛出“导致任务失败的异常”
    Future<V> sync() throws InterruptedException;
	//不响应中断的sync()
    Future<V> syncUninterruptibly();
	//阻塞等待任务结束,和sync()功能是一样的,不过如果任务失败,不会抛出执行过程中的异常
    Future<V> await() throws InterruptedException;

    Future<V> awaitUninterruptibly();

    boolean await(long var1, TimeUnit var3) throws InterruptedException;

    boolean await(long var1) throws InterruptedException;

    boolean awaitUninterruptibly(long var1, TimeUnit var3);

    boolean awaitUninterruptibly(long var1);
	//获取执行结果,不阻塞。相比之下,j.u.c包下的Future类的方法get()是阻塞的
    V getNow();

    boolean cancel(boolean var1);
}

从上面可以看出来Netty中的Future接口相比JDK中的Future接口添加了sync()和await()方法用于阻塞等待、addListener()等方法用于回调,这样就不用主动调用isDone()来获取状态,或通过get()阻塞方法获取值。所以Netty中的Future有两种使用范式
顺便说下 sync() 和 await() 的区别:sync() 内部会先调用 await() 方法,等 await() 方法返回后,会检查下这个任务是否失败,如果失败,重新将导致失败的异常抛出来。也就是说,如果使用 await(),任务抛出异常后,await() 方法会返回,但是不会抛出异常,而 sync() 方法返回的同时会抛出异常。
在这里插入图片描述

Future 接口没有和IO操作关联在一起。

ChannelFuture

ChannelFuture在Netty中比较常用,实现了Netty中的Future接口,并添加了isVoid()方法和channel()方法,它将和IO操作中的Channel关联在一起,用于异步处理Channel中的事件。
在这里插入图片描述

Promise

和ChannelFuture一样,Promise也继承了Netty的Future接口
在这里插入图片描述
Promise 实例内部是一个任务,任务的执行往往是异步的,通常是一个线程池来处理任务。Promise 提供的 setSuccess(V result) 或 setFailure(Throwable t) 将来会被某个执行任务的线程在执行完成以后调用,同时那个线程在调用 setSuccess(result) 或 setFailure(t) 后会回调 listeners 的回调函数(当然,回调的具体内容不一定要由执行任务的线程自己来执行,它可以创建新的线程来执行,也可以将回调任务提交到某个线程池来执行)。而且,一旦 setSuccess(…) 或 setFailure(…) 后,那些 await() 或 sync() 的线程就会从等待中返回。
在这里插入图片描述

ChannelPromise

ChannelPromise接口同时继承了ChannelFuture和Promise接口
在这里插入图片描述

图片来源于https://www.javadoop.com/post/netty-part-3
图片来源于https://www.javadoop.com/post/netty-part-3

下面,我们来介绍下 DefaultPromise 这个实现类,这个类很常用,它的源码也不短,我们先介绍几个关键的内容,然后介绍一个示例使用。

首先,我们看下它有哪些属性:

public class DefaultPromise<V> extends AbstractFuture<V> implements Promise<V> {
      // 保存执行结果
    private volatile Object result;
    // 执行任务的线程池,promise 持有 executor 的引用,这个其实有点奇怪了
    // 因为“任务”其实没必要知道自己在哪里被执行的
    private final EventExecutor executor;
      // 监听者,回调函数,任务结束后(正常或异常结束)执行
    private Object listeners;

    // 等待这个 promise 的线程数(调用sync()/await()进行等待的线程数量)
    private short waiters;

    // 是否正在唤醒等待线程,用于防止重复执行唤醒,不然会重复执行 listeners 的回调方法
    private boolean notifyingListeners;
    ......
}

可以看出,此类实现了 Promise,但是没有实现 ChannelFuture,所以它和 Channel 联系不起来。

别急,我们后面会碰到另一个类 DefaultChannelPromise 的使用,这个类是综合了 ChannelFuture 和 Promise 的,但是它的实现其实大部分都是继承自这里的 DefaultPromise 类的。

说完上面的属性以后,大家可以看下 setSuccess(V result) 、trySuccess(V result) 和 setFailure(Throwable cause) 、 tryFailure(Throwable cause) 这几个方法:

在这里插入图片描述

看出 setSuccess(result) 和 trySuccess(result) 的区别了吗?

上面几个方法都非常简单,先设置好值,然后执行监听者们的回调方法。notifyListeners() 方法感兴趣的读者也可以看一看,不过它还涉及到 Netty 线程池的一些内容,我们还没有介绍到线程池,这里就不展开了。上面的代码,在 setSuccess0 或 setFailure0 方法中都会唤醒阻塞在 sync() 或 await() 的线程。

现在重新分析启动器的代码(以客户端为例):
在这里插入图片描述
b.connect()方法会返回一个ChannelFuture,connect()方法是一个异步方法,ChannelFuture执行sync()方法阻塞等待,直到有线程成功连上服务端后标记future成功然后唤醒阻塞的sync()方法,并返回ChannelFuture引用;如果connect方法失败,则sync()会抛出异常。
channel.closeFuture() 也会返回一个 ChannelFuture,然后调用了 sync() 方法,这个 sync() 方法返回的条件是:有其他的线程关闭了 NioSocketChannel,往往是因为需要停掉服务了,然后那个线程会设置 future 的状态( setSuccess(result) 或 setFailure(cause) ),这个 sync() 方法才会返回。

Netty中的ChannelPipeline

ChannelPipeline,和 Inbound、Outbound

我想很多读者应该或多或少都有 Netty 中 pipeline 的概念。前面我们说了,使用 Netty 的时候,我们通常就只要写一些自定义的 handler 就可以了,我们定义的这些 handler 会组成一个 pipeline,用于处理 IO 事件,这个和我们平时接触的 Filter 或 Interceptor 表达的差不多是一个意思。

每个 Channel 内部都有一个 pipeline,pipeline 由多个 handler 组成,handler 之间的顺序是很重要的,因为 IO 事件将按照顺序顺次经过 pipeline 上的 handler,这样每个 handler 可以专注于做一点点小事,由多个 handler 组合来完成一些复杂的逻辑。

11

从图中,我们知道这是一个双向链表。

首先,我们看两个重要的概念:InboundOutbound。在 Netty 中,IO 事件被分为 Inbound 事件和 Outbound 事件。

Outboundout 指的是 出去,有哪些 IO 事件属于此类呢?比如 connect、write、flush 这些 IO 操作是往外部方向进行的,它们就属于 Outbound 事件。

其他的,诸如 accept、read 这种就属于 Inbound 事件。

比如客户端在发起请求的时候,需要 1️⃣connect 到服务器,然后 2️⃣write 数据传到服务器,再然后 3️⃣read 服务器返回的数据,前面的 connect 和 write 就是 out 事件,后面的 read 就是 in 事件。

比如很多初学者看不懂下面的这段代码,这段代码用于服务端的 childHandler 中:

1. pipeline.addLast(new StringDecoder());
2. pipeline.addLast(new StringEncoder());
3. pipeline.addLast(new BizHandler());

初学者肯定都纳闷,以为这个顺序写错了,应该是先 decode 客户端过来的数据,然后用 BizHandler 处理业务逻辑,最后再 encode 数据然后返回给客户端,所以添加的顺序应该是 1 -> 3 -> 2 才对。

其实这里的三个 handler 是分组的,分为 Inbound(1 和 3) 和 Outbound(2):

1. pipeline.addLast(new StringDecoder());
2. pipeline.addLast(new StringEncoder());
3. pipeline.addLast(new BizHandler());
  • 客户端连接进来的时候,读取(read)客户端请求数据的操作是 Inbound 的,所以会先使用 1,然后是 3 对处理进行处理;
  • 处理完数据后,返回给客户端数据的 write 操作是 Outbound 的,此时使用的是 2。

所以虽然添加顺序有点怪,但是执行顺序其实是按照 1 -> 3 -> 2 进行的。

如果我们在上面的基础上,加上下面的第四行,这是一个 OutboundHandler:

4. pipeline.addLast(new OutboundHandlerA());

那么执行顺序是不是就是 1 -> 3 -> 2 -> 4 呢?答案是:不是的。

对于 Inbound 操作,按照添加顺序执行每个 Inbound 类型的 handler;而对于 Outbound 操作,是反着来的,从后往前,顺次执行 Outbound 类型的 handler。

所以,上面的顺序应该是先 1 后 3,它们是 Inbound 的,然后是 4,最后才是 2,它们两个是 Outbound 的。说实话,我真不喜欢这种组织方式。

到这里,我想大家应该都知道 Inbound 和 Outbound 了吧?下面我们来介绍它们的接口使用。

9

定义处理 Inbound 事件的 handler 需要实现 ChannelInboundHandler,定义处理 Outbound 事件的 handler 需要实现 ChannelOutboundHandler。最下面的三个类,是 Netty 提供的适配器,特别的,如果我们希望定义一个 handler 能同时处理 Inbound 和 Outbound 事件,可以通过继承中间的 ChannelDuplexHandler 的方式。

有了 Inbound 和 Outbound 的概念以后,我们来开始介绍 Pipeline 的源码。

我们说过,一个 Channel 关联一个 pipeline,NioSocketChannel 和 NioServerSocketChannel 在执行构造方法的时候,都会走到它们的父类 AbstractChannel 的构造方法中:

protected AbstractChannel(Channel parent) {
    this.parent = parent;
    // 给每个 channel 分配一个唯一 id
    id = newId();
    // 每个 channel 内部需要一个 Unsafe 的实例
    unsafe = newUnsafe();
    // 每个 channel 内部都会创建一个 pipeline
    pipeline = newChannelPipeline();
}

上面的三行代码中,id 比较不重要,Netty 中的 Unsafe 实例其实挺重要的,这里简单介绍一下。

在 JDK 的源码中,sun.misc.Unsafe 类提供了一些底层操作的能力,它设计出来是给 JDK 中的源码使用的,比如 AQS、ConcurrentHashMap 等,我们在之前的并发包的源码分析中也看到了很多它们使用 Unsafe 的场景,这个 Unsafe 类不是给我们的代码使用的,是给 JDK 源码使用的(需要的话,我们也是可以获取它的实例的)。

Unsafe 类的构造方法是 private 的,但是它提供了 getUnsafe() 这个静态方法:

Unsafe unsafe = Unsafe.getUnsafe();

大家可以试一下,上面这行代码编译没有问题,但是执行的时候会抛 java.lang.SecurityException 异常,因为它就不是给我们的代码用的。

但是如果你就是想获取 Unsafe 的实例,可以通过下面这个代码获取到:

Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
Unsafe unsafe = (Unsafe) f.get(null);

Netty 中的 Unsafe 也是同样的意思,它封装了 Netty 中会使用到的 JDK 提供的 NIO 接口,比如将 channel 注册到 selector 上,比如 bind 操作,比如 connect 操作等,这些操作都是稍微偏底层一些。Netty 同样也是不希望我们的业务代码使用 Unsafe 的实例,它是提供给 Netty 中的源码使用的。

不过,对于我们源码分析来说,我们还是会有很多时候需要分析 Unsafe 中的源码的

关于 Unsafe,我们后面用到了再说,这里只要知道,它封装了大部分需要访问 JDK 的 NIO 接口的操作就好了。这里我们继续将焦点放在实例化 pipeline 上:

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

这里开始调用 DefaultChannelPipeline 的构造方法,并把当前 channel 的引用传入:

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

这里实例化了 tail 和 head 这两个 handler。tail 实现了 ChannelInboundHandler 接口,而 head 实现了 ChannelOutboundHandler 和 ChannelInboundHandler 两个接口,并且最后两行代码将 tail 和 head 连接起来:

12

注意,在不同的版本中,源码也略有差异,head 不一定是 in + out,大家知道这点就好了。

还有,从上面的 head 和 tail 我们也可以看到,其实 pipeline 中的每个元素是 ChannelHandlerContext 的实例,而不是 ChannelHandler 的实例,context 包装了一下 handler,但是,后面我们都会用 handler 来描述一个 pipeline 上的节点,而不是使用 context,希望读者知道这一点。

这里只是构造了 pipeline,并且添加了两个固定的 handler 到其中(head + tail),还不涉及到自定义的 handler 代码执行。我们回过头来看下面这段代码:

13

我们说过 childHandler 中指定的 handler 不是给 NioServerSocketChannel 使用的,是给 NioSocketChannel 使用的,所以这里我们不看它。

这里调用 handler(…) 方法指定了一个 LoggingHandler 的实例,然后我们再进去下面的 bind(…) 方法中看看这个 LoggingHandler 实例是怎么进入到我们之前构造的 pipeline 内的。

顺着 bind() 一直往前走,bind() -> doBind() -> initAndRegister():

final ChannelFuture initAndRegister() {
    Channel channel = null;
    try {
        // 1. 构造 channel 实例,同时会构造 pipeline 实例,
        // 现在 pipeline 中有 head 和 tail 两个 handler 了
        channel = channelFactory.newChannel();
        // 2. 看这里
        init(channel);
    } catch (Throwable t) {
    ......
}

上面的两行代码,第一行实现了构造 channel 和 channel 内部的 pipeline,我们来看第二行 init 代码:

// ServerBootstrap:

@Override
void init(Channel channel) throws Exception {
    ......
    // 拿到刚刚创建的 channel 内部的 pipeline 实例
    ChannelPipeline p = channel.pipeline();
    ...
    // 开始往 pipeline 中添加一个 handler,这个 handler 是 ChannelInitializer 的实例
    p.addLast(new ChannelInitializer<Channel>() {
        // 我们以后会看到,下面这个 initChannel 方法何时会被调用
        @Override
        public void initChannel(final Channel ch) throws Exception {
            final ChannelPipeline pipeline = ch.pipeline();
            // 这个方法返回我们最开始指定的 LoggingHandler 实例
            ChannelHandler handler = config.handler();
            if (handler != null) {
                // 添加 LoggingHandler
                pipeline.addLast(handler);
            }
            // 先不用管这里的 eventLoop
            ch.eventLoop().execute(new Runnable() {
                @Override
                public void run() {
                    // 添加一个 handler 到 pipeline 中:ServerBootstrapAcceptor
                    // 从名字可以看到,这个 handler 的目的是用于接收客户端请求
                    pipeline.addLast(new ServerBootstrapAcceptor(
                            ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
                }
            });
        }
    });
}

这里涉及到 pipeline 中的辅助类 ChannelInitializer,我们看到,它本身是一个 handler(Inbound 类型),但是它的作用和普通 handler 有点不一样,它纯碎是用来辅助将其他的 handler 加入到 pipeline 中的。

大家可以稍微看一下 ChannelInitializer 的 initChannel 方法,有个简单的认识就好,此时的 pipeline 应该是这样的:

14

ChannelInitializer 的 initChannel(channel) 方法被调用的时候,会往 pipeline 中添加我们最开始指定的 LoggingHandler 和添加一个 ServerBootstrapAcceptor。但是我们现在还不知道这个 initChannel 方法何时会被调用。

上面我们说的是作为服务端的 NioServerSocketChannel 的 pipeline,NioSocketChannel 也是差不多的,我们可以看一下 Bootstrap 类的 init(channel) 方法:

void init(Channel channel) throws Exception {
    ChannelPipeline p = channel.pipeline();
    p.addLast(config.handler());
    ...
}

23

它和服务端 ServerBootstrap 要添加 ServerBootstrapAcceptor 不一样,它只需要将 EchoClient 类中的 ChannelInitializer 实例加进来就可以了,它的 ChannelInitializer 中添加了两个 handler,LoggingHandler 和 EchoClientHandler:

16

很显然,我们需要的是像 LoggingHandler 和 EchoClientHandler 这样的 handler,但是,它们现在还不在 pipeline 中,那么它们什么时候会真正进入到 pipeline 中呢?以后我们再揭晓。

还有,为什么 Server 端我们指定的是一个 handler 实例,而 Client 指定的是一个 ChannelInitializer 实例?其实它们是可以随意搭配使用的,你甚至可以在 ChannelInitializer 实例中添加 ChannelInitializer 的实例。

非常抱歉,这里又要断了,下面要先介绍线程池了,大家要记住 pipeline 现在的样子,head + channelInitializer + tail

本节没有介绍 handler 的向后传播,就是一个 handler 处理完了以后,怎么传递给下一个 handler 来处理?比如我们熟悉的 JavaEE 中的 Filter 是采用在一个 Filter 实例中调用 chain.doFilter(request, response) 来传递给下一个 Filter 这种方式的。

我们用下面这张图结束本节。下图展示了传播的方法,但我其实是更想让大家看一下,哪些事件是 Inbound 类型的,哪些是 Outbound 类型的:

19

Outbound 类型的几个事件大家应该比较好认,注意 bind 也是 Outbound 类型的。

Netty中的线程池

https://www.javadoop.com/post/netty-part-5

Netty的concurrent包创建在JDK的J.U.C包上,用来提供线程执行器;
io.netty.channel包中的类,为了与Channel的事件进行交互,扩展了这些接口/类。

异步传输实现------一个EventLoop对多个channel
阻塞传输-----一个EventLoop对一个Channel

Channel 的 register 操作

https://www.javadoop.com/post/netty-part-6
register操作前

实例化了 JDK 底层的 Channel,设置了非阻塞,实例化了 Unsafe,实例化了 Pipeline,同时往 pipeline 中添加了 head、tail 以及一个 ChannelInitializer 实例。

在register操作中

将Channel实例注册到NioEventLoopGroup 实例中的某个 NioEventLoop 实例,那么后续该 Channel 的所有操作,都是由该 NioEventLoop 实例来完成的。

NioEventLoop 工作流程

https://www.javadoop.com/post/netty-part-7
NioEventLoop的线程启动以后,会执行 NioEventLoop 中的 run() 方法,这是一个非常重要的方法,这个方法肯定是没那么容易结束的,必然是像 JDK 线程池的 Worker 那样,不断地循环获取新的任务的。它需要不断地做 select 操作和轮询 taskQueue 这个队列。

我们先来简单地看一下它的源码,这里先不做深入地介绍:

@Override
protected void run() {
    // 代码嵌套在 for 循环中
    for (;;) {
        try {
            // selectStrategy 终于要派上用场了
            // 它有两个值,一个是 CONTINUE 一个是 SELECT
            // 针对这块代码,我们分析一下。
            // 1. 如果 taskQueue 不为空,也就是 hasTasks() 返回 true,
            //         那么执行一次 selectNow(),该方法不会阻塞
            // 2. 如果 hasTasks() 返回 false,那么执行 SelectStrategy.SELECT 分支,
            //    进行 select(...),这块是带阻塞的
            // 这个很好理解,就是按照是否有任务在排队来决定是否可以进行阻塞
            switch (selectStrategy.calculateStrategy(selectNowSupplier, hasTasks())) {
                case SelectStrategy.CONTINUE:
                    continue;
                case SelectStrategy.SELECT:
                    // 如果 !hasTasks(),那么进到这个 select 分支,这里 select 带阻塞的
                    select(wakenUp.getAndSet(false));
                    if (wakenUp.get()) {
                        selector.wakeup();
                    }
                default:
            }


            cancelledKeys = 0;
            needsToSelectAgain = false;
            // 默认地,ioRatio 的值是 50
            final int ioRatio = this.ioRatio;

            if (ioRatio == 100) {
                // 如果 ioRatio 设置为 100,那么先执行 IO 操作,然后在 finally 块中执行 taskQueue 中的任务
                try {
                    // 1. 执行 IO 操作。因为前面 select 以后,可能有些 channel 是需要处理的。
                    processSelectedKeys();
                } finally {
                    // 2. 执行非 IO 任务,也就是 taskQueue 中的任务
                    runAllTasks();
                }
            } else {
                // 如果 ioRatio 不是 100,那么根据 IO 操作耗时,限制非 IO 操作耗时
                final long ioStartTime = System.nanoTime();
                try {
                    // 执行 IO 操作
                    processSelectedKeys();
                } finally {
                    // 根据 IO 操作消耗的时间,计算执行非 IO 操作(runAllTasks)可以用多少时间.
                    final long ioTime = System.nanoTime() - ioStartTime;
                    runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
                }
            }
        } catch (Throwable t) {
            handleLoopException(t);
        }
        // Always handle shutdown even if the loop processing threw an exception.
        try {
            if (isShuttingDown()) {
                closeAll();
                if (confirmShutdown()) {
                    return;
                }
            }
        } catch (Throwable t) {
            handleLoopException(t);
        }
    }
}

上面这段代码是 NioEventLoop 的核心,这里介绍两点:

  1. 首先,会根据 hasTasks() 的结果来决定是执行 selectNow() 还是 select(oldWakenUp),这个应该好理解。如果有任务正在等待,那么应该使用无阻塞的 selectNow(),如果没有任务在等待,那么就可以使用带阻塞的 select 操作。
  2. ioRatio 控制 IO 操作所占的时间比重:
    如果设置为 100%,那么先执行 IO 操作,然后再执行任务队列中的任务。
    如果不是 100%,那么先执行 IO 操作,然后执行 taskQueue 中的任务,但是需要控制执行任务的总时间。也就是说,非 IO 操作可以占用的时间,通过 ioRatio 以及这次 IO 操作耗时计算得出。
    我们这里先不要去关心 select(oldWakenUp)、processSelectedKeys() 方法和 runAllTasks(…) 方法的细节,只要先理解它们分别做什么事情就可以了。

回过神来,我们前面在 register 的时候提交了 register 任务给 NioEventLoop,这是 NioEventLoop 接收到的第一个任务,所以这里会实例化 Thread 并且启动,然后进入到 NioEventLoop 中的 run 方法。

当然了,实际情况可能是,Channel 实例被 register 到一个已经启动线程的 NioEventLoop 实例中。

回到 Channel 的 register 操作

https://www.javadoop.com/post/netty-part-8

connect 过程和 bind 过程分析

https://www.javadoop.com/post/netty-part-9
上一节介绍的 register 操作中,channel 已经 register 到了 selector 上,只不过将 interestOps 设置为了 0,也就是什么都不监听。
不同于register的inbound类型操作,
bind操作和connect操作一样,都是Outbound类型的,都是从tail开始。
在这里插入图片描述
在这里插入图片描述

Connect()方法
从 tail 开始往前找 out 类型的 handlers,每经过一个 handler,都执行里面的 connect() 方法,最后会到 head 中,因为 head 也是 Outbound 类型的,我们需要的 connect 操作就在 head 中,它会负责调用 unsafe 中提供的 connect 方法:这里面主要会做 JDK 底层的 SocketChannel connect,然后设置 interestOps 为 SelectionKey.OP_CONNECT

参考:https://www.javadoop.com/post/netty-part-1

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值