JavaEE 企业级分布式高级架构师(十九)异步事件驱动框架Netty(3)

Netty源码篇

简述NIO

  • 由于 Netty 的底层是 NIO,所以在读 Netty 源码之前,首先了解一下 NIO 网络编程相关知识。

NIO简介

  • NIO:New IO、Non-blocking IO,是 JDK1.4 中引入的一种新的 IO 标准,是一种同步非阻塞 IO。NIO 是以块(Buffer)为单位进行数据处理的,当然 Block 的大小是程序员自己指定的。其相对于 BIO 的以字节/字符为单位所进行的阻塞式处理方式,大大提高了读写效率与并发度。
    • BIO:Blocking IO,同步阻塞IO
    • NIO:Non-blocking IO,同步非阻塞IO JDK1.4
    • AIO:异步非阻塞IO,也称NIO2.0 JDK1.7

NIO通信

  • 这里使用 NIO 实现一个简单的 C/S 通信:Client 向 Server 发送一个数据,显示在 Server端控制台。
  • 定义服务端:
public class NioServer {
    public static void main(String[] args) throws Exception {
        // 创建一个服务端Channel
        ServerSocketChannel serverChannel = ServerSocketChannel.open();
        // 指定channel采用的是非阻塞模式
        serverChannel.configureBlocking(false);
        // 指定要监听的端口
        serverChannel.bind(new InetSocketAddress(8888));
        // 创建一个多路复用器Selector
        Selector selector = Selector.open();
        // 将channel注册到selector,并告诉selector让其监听“接收Client连接事件”
        serverChannel.register(selector, SelectionKey.OP_ACCEPT);
        while (true) {
            // select()是一个阻塞方法,这里设置会阻塞1秒
            // 若阻塞时间到了,或在阻塞期间有channel就绪,都会打破阻塞
            if (selector.select(1000) == 0) {
                System.out.println("当前没有就绪的channel");
                continue;
            }
            // 获取所有就绪channel的key
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            // 遍历所有就绪的key
            for (SelectionKey key : selectionKeys) {
                // 若当前的key是OP_ACCEPT,则说明当前channel是可以接收客户端连接的
                if (key.isAcceptable()) {
                    System.out.println("接收到Client的连接");
                    // 获取连接到Server的客户端channel,其是客户端channel在Server端的代表
                    SocketChannel clientChannel = serverChannel.accept();
                    clientChannel.configureBlocking(false);
                    // 将客户端channel注册到selector,并告诉selector让其监听这个channel中是否发送读事件
                    clientChannel.register(selector, SelectionKey.OP_READ);
                }
                // 若当前的key是OP_READ,则说明当前channel中有客户端发送来的数据
                if (key.isReadable()) {
                    // 创建buffer
                    ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                    // 根据key获取其对应的channel
                    SocketChannel clientChannel = (SocketChannel) key.channel();
                    // 把channel中的数据读取到buffer
                    clientChannel.read(byteBuffer);
                }
                // 删除当前处理过的key,以免重复处理
                selectionKeys.remove(key);
            } // end-for
        }
    }
}
  • 定义客户端:
public class NioClient {
    public static void main(String[] args) throws Exception {
        // 创建客户端channel
        SocketChannel clientChannel = SocketChannel.open();
        // 指定channel使用非阻塞模式
        clientChannel.configureBlocking(false);
        // 指定要连接的Server地址
        InetSocketAddress serverAddr = new InetSocketAddress("localhost", 8888);
        // 连接Server
        if (!clientChannel.connect(serverAddr)) { // 首次连接
            while (!clientChannel.finishConnect()) { // 完成重连
                continue;
            }
        }
        if (clientChannel.isConnected()) {
            // 将消息写入到channel
            clientChannel.write(ByteBuffer.wrap("hello".getBytes()));
            System.out.println("Client 消息已发送");
        }
        System.in.read();
    }
}

NIO实现群聊功能

  • 该工程实现的功能是:只要有 Client 启动、发送消息,及下线,都会广播给所有其它 Client 通知。
  • 定义服务端启动类:
public class NioChatServerStarter {
    public static void main(String[] args) throws Exception {
        // 创建一个服务端Channel
        ServerSocketChannel serverChannel = ServerSocketChannel.open();
        // 指定channel采用的是非阻塞模式
        serverChannel.configureBlocking(false);
        // 指定要监听的端口
        serverChannel.bind(new InetSocketAddress(8888));
        // 创建一个多路复用器Selector
        Selector selector = Selector.open();
        // 将channel注册到selector
        serverChannel.register(selector, SelectionKey.OP_ACCEPT);

        // 创建支持群聊的NIO Server
        NioChatServer chatServer = new NioChatServer();
        chatServer.start(serverChannel, selector);
    }
}
  • 定义服务端Server类:
public class NioChatServer {
    /**
     * 开启Server的群聊功能
     */
    public void start(ServerSocketChannel serverChannel, Selector selector) throws Exception {
        System.out.println("Chat Server Started.");
        do {
            if (selector.select(1000) == 0) {
                System.out.println("等待连接...");
                continue;
            }
            Iterator<SelectionKey> keyIterator = selector.selectedKeys().iterator();
            while (keyIterator.hasNext()) {
                SelectionKey key = keyIterator.next();
                // 处理客户端上线
                if (key.isAcceptable()) {
                    SocketChannel clientChannel = serverChannel.accept();
                    clientChannel.configureBlocking(false);
                    clientChannel.register(selector, SelectionKey.OP_READ);
                    String msg = clientChannel.getRemoteAddress() + "-上线了";
                    // 将上线通知广播给所有在线的其它client
                    sendToOtherOnlineClient(selector, clientChannel, msg);
                }
                // 处理客户端发送消息
                if (key.isReadable()) {
                    SocketChannel clientChannel = (SocketChannel) key.channel();
                    ByteBuffer buffer = ByteBuffer.allocate(1024);
                    clientChannel.read(buffer);
                    String msgFromClient = new String(buffer.array()).trim();
                    if (msgFromClient.length() > 0) {
                        // 获取到客户端地址
                        SocketAddress clientAddr = clientChannel.getRemoteAddress();
                        String msgToSend = clientAddr + " say:" + msgFromClient;
                        if ("88".equals(msgFromClient)) {
                            msgToSend = clientAddr + "下线";
                            // 取消当前key,即放弃其所对应的channel,将其对应的channel从selector中去掉
                            key.cancel();
                        }
                        // 将client消息广播给所有在线的其它client
                        sendToOtherOnlineClient(selector, clientChannel, msgToSend);
                    }
                }
                // 一般不用下面这两个事件,因为其不是外部条件,是自己的主动行为
//                if (key.isWritable()){}
//                if (key.isConnectable()){}

                // 删除当key,防止重复处理
                keyIterator.remove();
            }
        } while (true);
    }

    private void sendToOtherOnlineClient(Selector selector, SocketChannel self, String msg) throws IOException {
        // 遍历所有注册到selector的channel,即所有在线的client
        for (SelectionKey key : selector.keys()) {
            SelectableChannel channel = key.channel();
            // 将消息发送给所有其它client
            if (channel instanceof SocketChannel && channel != self) {
                ((SocketChannel) channel).write(ByteBuffer.wrap(msg.trim().getBytes()));
            }
        }
    }
}
  • 定义客户端启动类:
public class NioChatClientStarter {
    public static void main(String[] args) throws Exception {
        // 创建客户端channel
        SocketChannel clientChannel = SocketChannel.open();
        // 指定channel使用非阻塞模式
        clientChannel.configureBlocking(false);
        // 指定要连接的Server地址
        InetSocketAddress serverAddr = new InetSocketAddress("localhost", 8888);
        // 连接Server
        if (!clientChannel.connect(serverAddr)) {   // 首次连接
            while (!clientChannel.finishConnect()) {   // 完成重连
                System.out.println("连接不上server,正在尝试连接中。。。");
                continue;
            }
        }
        // 创建群聊客户端,启动聊天功能
        NioChatClient chatClient = new NioChatClient();
        chatClient.start(clientChannel);
    }
}
  • 定义客户端Client类:
public class NioChatClient {
    /**
     * 开启Client的群聊功能
     */
    public void start(SocketChannel clientChannel) throws Exception {
        // 获取client自己的地址
        SocketAddress selfAddr = clientChannel.getLocalAddress();
        System.out.println(selfAddr + ",你已经成功上线了");

        // 创建一个线程用于不间断地接收来自于Server的消息
        new Thread(() -> {
            do {
                try {
                    if (!clientChannel.isConnected()) {
                        return;
                    }
                    receiveMsgFromServer(clientChannel);
                    TimeUnit.SECONDS.sleep(1);
                } catch (Exception e) {
                    System.out.println(e.getMessage());
                }
            } while (true);
        }).start();
        /**
         * 注意,该方法不能写到前面的创建线程之前,这样会导致无法接收到来自于Server的消息,
         * 因为该方法中的Scanner是阻塞的向server发送消息
         */
        sendMsgToServer(clientChannel);
    }

    private void receiveMsgFromServer(SocketChannel clientChannel) throws Exception {
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        clientChannel.read(buffer);
        String msg = new String(buffer.array()).trim();
        if (msg.length() > 0) {
            System.out.println(msg);
        }
    }

    private void sendMsgToServer(SocketChannel clientChannel) throws Exception {
        // 接收来自于键盘的输入
        Scanner scanner = new Scanner(System.in);
        while (scanner.hasNextLine()) {
            String msg = scanner.nextLine();
            // 将消息写入到channel,其中有可能是下线请求消息88
            clientChannel.write(ByteBuffer.wrap(msg.trim().getBytes()));
            // 若消息为88,则表示当前client要下线,则将该channel关闭
            if ("88".equals(msg.trim())) {
                // 关闭客户端
                clientChannel.close();
                return;
            }
        }
    }
}

Reactor/Proactor模型

  • 在解析 Netty 源码之前,我们首先要搞清楚 Reactor 模型。因为现在的网络通信框架,大多数都是基于 Reactor 模型进行设计和开发的,Netty 也不例外。

Reactor 单线程模型

在这里插入图片描述

  • Reactor 单线程模型,指的是当前的 Sever 会为每一个通信客户端形成一个 Channel,而所有这些 Channel 都会与一个线程相绑定,该线程用于完成它们间的所有通信处理。
  • 该线程需要完成的操作有:
    • 若当前为 Server,则该线程需要接收并处理 Client 的连接请求
    • 若当前为 Client,则该线程需要向 Server 发起连接
    • 读取通信对端的消息
    • 向通信对端发送消息

Reactor 线程池模型

在这里插入图片描述

  • Reactor 单线程模型中使用一个线程处理所有通信对端的所有请求,在高并发场景中会严重影响系统性能。所以,就将单线程模型中的这一个线程替换为了一个线程池。大大提高了系统性能。

Reactor 多线程池模型

在这里插入图片描述

  • 若请求连接的并发量是数以百万计的,且 IO 操作还比较耗时,此时的 Server 即使采用的是 Reactor 线程池模型,系统性能也会急剧下降。此时,可以将连接操作与 IO 操作分开处理,形成 Reactor 的多线程模型。
  • 当客户端通过处理连接请求的 Channel 连接上 Server 后,系统会为该客户端再生成一个子 Channel 专门用于处理该客户端的 IO 请求。这两类不同的 Channel 连接着两类不同的线程池。而线程池中的线程数量,可以根据需求分别设置。提高了系统性能。

Netty-Server 的 Reactor 模型

在这里插入图片描述

  • Netty-Server 采用了多线程模型。不过线程池是由 EventLoopGroup 充当。EventLoopGroup中的每一个 EventLoop 都绑定着一个线程,用于处理该 Channel 与当前 Server 间的操作。一个 Channel 只能与一个 EventLoop 绑定,但一个 EventLoop 可以绑定多个 Channel。即 Channel 与 EventLoop 间的关系是 n:1。

Netty-Client 的 Reactor 模型

在这里插入图片描述

  • Netty-Client 采用的是线程池模型。因为其只需要与 Server 连接一次即可,无需区分连接请求与 IO 请求。

Proactor 模型

  • 在高性能的网络通信设计中,有两个比较著名的网络通信模型 Reactor 和 Proactor 模式,其中 Reactor 模式属于同步非阻塞 I/O 的网络通信模型,而 Proactor 运属于异步非阻塞 I/O的网络通信模型。
IO模型
概念
  • 用户空间
  • 内核空间
  • IO中的同步/异步
  • IO中的阻塞/非阻塞
    在这里插入图片描述
四种IO模型
  • 同步非阻塞IO模型:两个任务由同一个线程完成,IO调用逻辑与IO执行逻辑由同一个线程完成,它们的执行时串行的,即同步的。此时线程一直在做while(true)查看user buffer中的数据是否就绪。若就绪,则执行IO调用后的语句;若没有就绪,则继续while(true)。整个过程线程一直处于运行状态,没有发生阻塞。
  • 同步阻塞IO模型:两个任务由同一个线程完成,IO调用逻辑与IO执行逻辑由同一个线程完成,它们的执行时串行的,即同步的。此时该线程首先向user buffer注册监听,监听其数据是否就绪,然后哦阻塞自己。若数据就绪,则唤醒阻塞的线程,继续执行IO调用后的语句。
  • 异步非阻塞IO模型:两个任务由两个不同的线程完成,在IO执行线程执行期间,IO调用后的语句也是同时执行。只不过IO调用线程会为IO执行线程添加一个异步监听,监听IO操作是否结束。若结束,则再处理“IO调用后的语句”与Future回调语句的优先级问题:是执行完毕IO调用语句再执行Future回调,还是先把IO调用后语句挂起,然后马上执行Future回调,回调执行完毕后再继续执行IO调用后语句。
  • 异步阻塞IO模型:两个任务由两个不同的线程完成,在IO执调用线程调用了IO执行线程后,其首先向这个IO执行线程的Future添加一个监听,监听其IO是否执行完毕,然后阻塞自己。若执行完毕,则唤醒阻塞的IO调用线程。
Reactor 模型的 IO
  • 对于 Reactor 模型,其 IO 属于同步非阻塞 IO。下面仍以 channel 发起读操作请求为例来分析整个执行过程。
  • 当 channel 的执行线程发起了 read()调用后,其会向 selector 注册了 OPS_READ 事件,然后该线程会不停的查看该事件是否就绪
  • 当 selector 接收到这个注册后,其就会不停的查看该 channel 所关联的网卡缓存中是否具有了数据。当 selector 轮询到该 channel 的网卡缓存中具有了数据后,该读操作就绪。
  • 此时该线程就查看到了就绪,会发起 system call,将网卡缓存中的数据读取到 user buffer 中。这些操作完成后,会再执行 read()后面的逻辑。整个执行过程,该线程未发生阻塞。所以 Reactor 模型是“同步非阻塞 IO”模型。
Proactor 模型的 IO
  • 对于 Proactor 模型,其 IO 属于异步非阻塞 IO。下面仍以 channel 发起读操作请求为例来分析整个执行过程。
  • 当 channel 的执行线程调用了异步的 read()操作后,其会继续执行 read()后的逻辑。而该 read()会将本次操作注册到一个 Proactor 实例中,注册本次操作关注的事件为 Read Complete
  • 一个 channel 的所有 IO 操作共享一个 Proactor 实例。或者说,一个 Proactor 实例处理同一 channel 中所有的 IO 请求。这个 Proactor 实例是在 channel 创建时完成的初始化。每个Proactor 实例具有一个绑定的线程,用于执行相关 IO 操作。
  • 当 Proactor 实例接收了 read()操作的注册后,其会为网络缓存注册一个监听。若网卡缓存中有了数据,则马上通过 DMA 控制将数据写入到 user buffer 中。一旦数据写入 user buffer 完成,则该 IO 操作完毕,产生 Read Complete 事件,此时会将该事件写入到 Proactor 所维的一个队列。Proactor 实例会将队列中的事件发送给各个 IO 调用者线程,以使他们触发相应的回调。
  • 当前 read()操作的调用线程无需阻塞等待 read()操作的完成,而是直接执行后面的逻辑。由于 read()操作本身是由另外一个线程来执行,所以 Proactor 模型是“异步非阻塞 IO”模型。
  • Reactor 中的 selector 是一种“事件分离器”的实现。在 Proactor 中不存在事件分离器,但存在一个 Proactor 实例。该实例会根据不同的 IO 操作,监听不同的内容。例如,本例为网卡缓存注册了监听。
Proactor 优缺点
  • Proactor 在处理高耗时 IO 时的性能要高于 Reactor,但对于低耗时 IO 的执行效率提升并不明显。因为 Reactor 的同步性(IO 也是调用线程来处理)导致其处理高耗时 IO 时性能会极低(IO 操作的后续逻辑无法执行)。但对于低耗时 IO,异步也不会比同步快太多。同时异步还需要线程切换等操作,会使异步的优越性无法体现。
  • Proactor 的异步性使其并发处理能力要强于 Reactor。
  • Proactor 的实现逻辑复杂,编码成本较 Reactor 要高很多。
  • Proactor 的异步依赖于操作系统对于异步的支持。若操作系统对异步的支持不好,Proactor 的性能还不如 Reactor。
Netty 与 Proactor
  • Netty4 是 NIO 的,其网络通信模型采用的是 Reactor。
  • Netty5 是 AIO 的,其网络通信模型采用的是 Proactor。但该版本已经被不再维护。主要原因还是 Linux 目前对于异步的支持不完善,导致其执行效率很低。
Proactor 与 Epoll

Proactor 与 Epoll 是没有可比性的。

  • Epoll:是“事件分离器”对就绪事件的发现方式,有 select、poll 与 epoll 三种方式。epoll采用的是回调方式,而不是轮询方式。Reactor 模型中具有“事件分离器”。
  • Proactor:是一种网络通信模型,该模型中就不存在“事件分离器”。

Netty 中的 Epoll 多路复用器

以下内容来自于从 Google 查询到的国外的一篇文章,由于没有对 Netty 中的 epoll 模型进行深入研究,所以仅将看到的内容告诉大家,不做进一步讨论。感兴趣的可以自己研究。
epoll 的效率在如下场景中并不一定比 poll 的高。

  • 当出现大批量的读/写事件切换时,epoll 的效率会远远低于 poll。因为 epoll 需要进行大量的用户空间到内核空间的切换,而 poll 仅需要在用户空间做简单的位运算即可完成。
  • 若Client与Server端有大量的仅用于传递少量数据的短连接,则epoll的效率要低于poll。因为 epoll 下的每个 socket 连接都需要发生两次用户空间与内核空间的转换,而 poll 不需要。
  • epoll 完全属于 Linux,虽然其它系统平台也有 epoll 的支持,但并不完全相同。
  • 高性能处理的代码编写逻辑 epoll 要比 poll 更复杂,更难调试。特别是边缘触发。如果错过额外的读/写操作,很容易导致死锁。

导入Netty框架源码

Netty 框架中导入外部工程

  • Netty 框架源码是直接以 maven 项目的形式导入到 Idea 中的。导入后,在该项目中以model 的形式导入我们自己定义的工程。
  • 然后,将自己工程中的 pom 文件删除,替换为 netty-all 模块中的 pom。当然,若原来的 pom 中还有其它依赖,需要将其实依赖导入到当前这个新的 pom 中。然后,修改 artifactId。

添加插件

  • 在 netty-common 模板的 pom 文件中添加如下插件配置。
<plugin>
  <artifactId>maven-checkstyle-plugin</artifactId>
  <configuration>
    <skip>true</skip>
  </configuration>
</plugin>

重新编译

  • 进入到 netty 框架的 netty-common 模板根目录中执行 mvn compile 命令,对该模块进行重新编译。注意,要跳过风格检测。
mvn compile -Dcheckstyle.skip=true

在这里插入图片描述

预备知识

Server 端启动的总体分析

  • 服务端的启动总体由三部分构成:
    • EventLoopGroup 的创建与初始化
    • ServerBootstrap 的创建与初始化
    • 任务的执行

NioEventLoop是什么

在这里插入图片描述

  • 是一个 EventExecutor
  • 是一个 Executor:这个Executor中的execute方法,是使用新建线程这种方式去执行command任务的。那么这个线程是谁创建的,在哪里创建的?
  • 封装着一个 Executor:
public abstract class SingleThreadEventExecutor extends AbstractScheduledEventExecutor implements OrderedEventExecutor {
	// ...
	// 当前NioEventLoop所封装的executor
    private final Executor executor;
	// ...
	protected SingleThreadEventExecutor(EventExecutorGroup parent, Executor executor,
                                        boolean addTaskWakesUp, int maxPendingTasks,
                                        RejectedExecutionHandler rejectedHandler) {
        super(parent);
        this.addTaskWakesUp = addTaskWakesUp;
        this.maxPendingTasks = Math.max(16, maxPendingTasks);
        // 为了好区分,我们暂且称这个executor为 子executor
        // apply()方法中的executor为 总executor
        this.executor = ThreadExecutorMap.apply(executor, this);
        // 任务队列
        taskQueue = newTaskQueue(this.maxPendingTasks);
        rejectedExecutionHandler = ObjectUtil.checkNotNull(rejectedHandler, "rejectedHandler");
    }
    // ...
}
  • 拥有两个 execute()方法:一个是EventLoop的execute()方法,另一个是内部封装的Executor的execute()方法

在这里插入图片描述

NioEventLoopGroup是什么

在这里插入图片描述

  • 是一个 ExecutorService 线程池
  • 是一个 Executor:这个Executor中的execute方法,是使用线程池中的线程去执行command任务的。
  • 封装着一个线程 Executor:

在这里插入图片描述

  • 拥有两个 execute()方法:

在这里插入图片描述

  • 继续跟一下总Executor的execute方法

在这里插入图片描述

到这里我们可以小结一下:

  • ThreadPerTaskExecutor#execute:总的Exector使用DefaultThreadFactory创建了一个线程,并启动线程。
  • ThreadExecutorMap#apply:而第①步运行的线程是这里apply的Runnable任务,而这个任务的run方法调用了我们真正任务的run方法。并且这里使用 ThreadLocal 做了线程隔离。

EventLoopGroup的创建与初始化

分析构造器

在这里插入图片描述

  • 跟到这里我们知道了,在创建NioEventLoopGroup时,若不指定线程数量,则默认会创建当前机器的逻辑内核数量的2倍。
  • 接着继续往下跟踪:我们就找到了创建 NioEventLoopGroup 的核心构造方法

在这里插入图片描述

  • 到这里,构造方法首先创建了一个Executor(总executor)。先整体分析这个构造器方法,后面再详细分析。

在这里插入图片描述

线程工厂的创建newDefaultThreadFactory()

在这里插入图片描述

  • 至此,默认的线程工厂对象创建好了,而且知道了将来创建的线程的名称格式是什么样的了。

创建NioEventLoop

在这里插入图片描述

  • 里面的细节再跟下,创建了一个selector的二元组实例SelectorTuple:

在这里插入图片描述

  • ThreadExecutorMap.apply创建子的executor:

在这里插入图片描述

创建EventExecutorChooser

在这里插入图片描述

ServerBootstrap的创建与初始化

  • 使用:

在这里插入图片描述

  • 类图:

在这里插入图片描述

  • group() 方法、channel() 方法、options()方法等

在这里插入图片描述

任务的执行

  • 分析入口:
ChannelFuture future = bootstrap.bind(8888).sync();

整体分析

在这里插入图片描述

初始化并注册initAndRegister()

  • 该方法整体完成三件事:① 创建parentChannel;② 初始化parentChannel;③ 注册parentChannel。

在这里插入图片描述

创建parentChannel

在这里插入图片描述

  • 继续往下分析代码:

在这里插入图片描述

  • 生成channel的id的构成是怎样的?

在这里插入图片描述

  • 至此,我们就创建好了一个NioServerSocketChannel。该Channel是对NIO原生Channel的封装,里面有一个config(NioServerSocketChannelConfig)可以对原生Channel进行配置,并且里面还有 id、底层操作对象unsafe、pipeline等。
初始化parentChannel

在这里插入图片描述

  • 这里有个疑问,根据代码的逻辑,应该是在注册parentChannel时,才会进行当前channel与eventLoop的绑定,那么这里ch.eventLoop()存在吗?后面会进行分析。
  • 将options属性初始化到channel中:

在这里插入图片描述

  • 再跟踪下ServerBootstrapAcceptpor:

在这里插入图片描述

  • 这样我们就初始化完成parentChannel了,并将childChannel注册到了selector。
注册parentChannel

在这里插入图片描述

  • 选择一个eventLoop:chooser在前面已经跟踪过了

在这里插入图片描述

  • 继续跟踪注册方法 register0():

在这里插入图片描述

  • 到这里我们就将parentChannel注册到了selector。那么有个问题:这里的selector与前面childChannel注册的selector是不是同一个呢?

答案是否定的,原因是 parentChannel 绑定的selector是从parentGroup中的eventLoop来的,而childChannel绑定的selector是从childGroup中的eventLoop来的,eventLoop对象不是同一个,那么其对于的selector也不是同一个。

// parentGroup中的eventLoop将来是用于与处理客户端连接请求的parentChannel进行绑定的
EventLoopGroup parentGroup = new NioEventLoopGroup();
// childGroup中的eventLoop将来是用于与处理客户端读写请求的childChannel进行绑定的
EventLoopGroup childGroup = new NioEventLoopGroup();

eventLoop绑定线程的创建与执行

  • 回过来我们再跟踪下 eventLoop.execute 这个方法的执行:

在这里插入图片描述
至此,我们可以梳理一下execute的执行:

  • eventLoop调用自己的execute()方法,去创建并启动线程,其会调用**自己所包含的executor(子executor)**的execute()方法。

  • 子executor 其实是ThreadExecutorMap中用匿名内部类创建的,其调用execute()方法就会调用**eventLoopGroup内部的executor(总executor)**的execute()方法。

  • 总executor 其实是ThreadPerTaskExecutor,其调用execute()方法就会创建并启动新的线程,执行start()方法,即会调用command的run()方法。

  • 这个command就是ThreadExecutorMap.apply()方法中通过线程隔离处理过的command,该command的运行就会最终触发eventLoop调用一个无限循环的for【SingleThreadEventExecutor.this.run()】,去执行我们任务队列里面的任务。

  • 源码阅读github地址:https://github.com/shouwangyw/netty/tree/main/netty-netty-4.1.36.Final

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
开课吧-javaEE企业级分布式高级架构师是一门专注于培养企业级应用开发高级技术课程。该课程旨在帮助学员全面掌握Java EE企业级开发的技能和知识,培养他们成为具备分布式应用系统设计和架构能力的高级架构师。 在这门课程中,学员将学习Java EE的核心概念和技术,包括Servlet、JSP、JDBC、EJB、JNDI等。同时,学员还将深入学习分布式应用开发的相关技术,如Web服务、消息队列、分布式缓存、负载均衡等。除此之外,课程还将涉及如何使用流行的Java EE开发框架(如Spring、Hibernate等)进行企业应用开发,并介绍分布式系统的设计原则和最佳实践。 通过学习这门课程,学员将能够了解分布式应用架构的基本原理,并具备设计和构建分布式应用系统的能力。他们将熟练掌握Java EE平台的各种技术和工具,能够灵活运用它们开发高性能、可扩展性强的企业级应用系统。此外,通过课程中的实战项目,学员还将锻炼解决实际问题和项目管理的能力。 作为一门高级架构师的课程,它将帮助学员进一步提升自己的职业发展。毕业后,学员可以在企业中担任分布式应用的架构师、系统设计师、技术经理等角色,负责企业级应用系统的设计和开发。此外,他们还可以选择独立开发,提供技术咨询和解决方案。 总之,开课吧-javaEE企业级分布式高级架构师是一门非常有价值的课程,它将帮助学员掌握Java EE企业级开发的核心技术和分布式应用架构的设计原理,培养他们成为具备高级架构师能力的软件开发专业人士。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

讲文明的喜羊羊拒绝pua

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值