Netty源码分析第一章:Netty启动流程

 

第四节:注册多路复用

回顾下以上的小节, 我们知道了channel的的创建和初始化过程, 那么channel是如何注册到selector中的呢?我们继续分析

回到上一小节的代码:

final ChannelFuture initAndRegister() {
Channel channel = null;
try {
//创建channel
channel = channelFactory.newChannel();
//初始化channel
init(channel);
} catch (Throwable t) {
//忽略非关键代码
}
//注册channel
ChannelFuture regFuture = config().group().register(channel);
//忽略非关键代码
return regFuture;
}

我们讲完创建channel和初始化channel的关键步骤, 我们继续跟注册channel的步骤:

ChannelFuture regFuture = config().group().register(channel);

其中, 重点关注下register(channel)这个方法, 这个方法最终会调用到AbstractChannel中内部类AbstractUnsafe的register()方法, 具体如何调用到这个方法, 可以简单带大家捋一下

首先看下config()方法, 由于是ServerBootstrap调用的, 所以我们跟进去:

public final ServerBootstrapConfig config() {
return config;
}

返回的config是ServerBootrap的成员变量config:

private final ServerBootstrapConfig config = new ServerBootstrapConfig(this);

跟到ServerBootstrapConfig的构造方法:

ServerBootstrapConfig(ServerBootstrap bootstrap) {
super(bootstrap);
}

继续跟到其父类AbstractBootstrapConfig的构造方法:

protected AbstractBootstrapConfig(B bootstrap) {
this.bootstrap = ObjectUtil.checkNotNull(bootstrap, "bootstrap");
}

我们发现我们创建的ServerBootstrap作为参数初始化了其成员变量bootstrap

回到initAndRegister()方法:

config()返回的是ServerBootstrapConfig对象

再继续跟到其group()方法:

public final EventLoopGroup group() {
return bootstrap.group();
}

这里调用Bootstrap的group()方法:

public final EventLoopGroup group() {
return group;
}

这里返回了AbstractBootstrap的成员变量group, 我们回顾下第一小节, 还记得AbstractBootstrap的group(EventLoopGroup group)方法吗?

public B group(EventLoopGroup group) {
this.group = group;
return (B) this;
}

group(EventLoopGroup group)方法初始化了我们boss线程, 而group()返回了boss线程, 也就是说 config().group().register(channel) 中的register()方法是boss线程对象调用的, 由于我们当初初始化的是NioEventLoopGroup, 因此走的是NioEventLoopGroup的父类的MultithreadEventLoopGroup的register()方法

跟到MultithreadEventLoopGroup的register()方法:

public ChannelFuture register(Channel channel) {
return next().register(channel);
}

这里的代码看起来有点晕, 没关系, 以后会讲到, 现在可以大概做个了解, NioEventLoopGroup是个线程组, 而next()方法就是从线程组中选出一个线程, 也就是NioEventLoop线程, 所以这里的next()方法返回的是NioEventLoop对象, 其中register(channel)最终会调用NioEventLoop的父类SingleThreadEventLoop的register(channel)方法

跟到SingleThreadEventLoop的register(channel)方法:

public ChannelFuture register(Channel channel) {
return register(new DefaultChannelPromise(channel, this));
}

其中DefaultChannelPromise类我们之后也会讲到

我们先跟到register(new DefaultChannelPromise(channel, this)):

public ChannelFuture register(final ChannelPromise promise) {
ObjectUtil.checkNotNull(promise, "promise");
promise.channel().unsafe().register(this, promise);
return promise;
}

channel()会返回我们初始化的NioServerSocketChannel, unsafe()会返回我们创建channel的时候初始化的unsafe对象

跟进去看AbstractChannel的unsafe()的实现:

public Unsafe unsafe() {
return unsafe;
}

这里返回的unsafe, 就是我们初始化channel创建的unsafe

回顾下第二小节channel初始化的步骤:

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

我们看unsafe的初始化:unsafe=newUnsafe()

跟到newUnsafe()中, 我们之前讲过NioServerSokectChannel的父类是AbstractNioMessageChannel, 所以会调用到到AbstractNioMessageChannel类中的newUnsafe()

跟到AbstractNioMessageChannel类中的newUnsafe():

protected AbstractNioUnsafe newUnsafe() {
return new NioMessageUnsafe();
}

我们看到这里创建了NioMessageUnsafe()对象, 所以在 promise.channel().unsafe().register(this, promise) 代码中, unsafe()是返回的NioMessageUnsafe()对象, 最后调用其父类AbstractUnsafe(也就是AbstractChannel的内部类)的register()方法,

简单介绍下unsafe接口, unsafe顾名思义就是不安全的, 因为很多对channel的io方法都定义在unsafe中, 所以netty将其作为内部类进行封装, 防止被外部直接调用, unsafe接口是Channel接口的内部接口, unsafe的子类也分别封装在Channel的子类中, 比如我们现在剖析的register()方法, 就是封装在AbstractChannel类的内部类AbstractUnsafe中的方法, 有关Unsafe和Channel的继承关系如下:

1-4-1

以上内容如果不明白没有关系, 有关NioEventLoop相关会在后面的章节讲到, 目前我们只是了解是如何走到AbstractUnsafe类的register()即可

我们继续看看register()方法:

public final void register(EventLoop eventLoop, final ChannelPromise promise) {
//代码省略
//所有的复制操作, 都交给eventLoop处理(1)
AbstractChannel.this.eventLoop = eventLoop;
if (eventLoop.inEventLoop()) {
register0(promise);
} else {
try {
eventLoop.execute(new Runnable() {
@Override
public void run() {
//做实际主注册(2)
register0(promise);
}
});
} catch (Throwable t) {
//代码省略
}
}
}

我们跟着注释的步骤继续走, 第一步, 绑定eventLoop线程:

AbstractChannel.this.eventLoop = eventLoop;

eventLoop是AbstractChannel的成员变量, 有关eventLoop, 我们会在绪章节讲到, 这里我们只需要知道, 每个channel绑定唯一的eventLoop线程, eventLoop线程和channel的绑定关系就是在这里展现的

再看第二步, 做实际注册:

我们先看if判断, if(eventLoop.inEventLoop())

这里是判断是不是eventLoop线程, 显然我们现在是main()方法所在的线程, 所以走的else, eventLoop.execute()是开启一个eventLoop线程, 而register0(promise)就是再开启线程之后, 通过eventLoop线程执行的, 这里大家暂时作为了解

我们重点关注register0(promise), 跟进去:

private void register0(ChannelPromise promise) {
try {
//做实际的注册(1)
doRegister();
neverRegistered = false;
registered = true;
//触发事件(2)
pipeline.invokeHandlerAddedIfNeeded();
safeSetSuccess(promise);
//触发注册成功事件(3)
pipeline.fireChannelRegistered();
if (isActive()) {
if (firstRegistration) {
//传播active事件(4)
pipeline.fireChannelActive();
} else if (config().isAutoRead()) {
beginRead();
}
}
} catch (Throwable t) {
//省略代码
}
}

我们重点关注doRegister()这个方法

doRegister()最终会调用AbstractNioChannel的doRegister()方法:

protected void doRegister() throws Exception {
boolean selected = false;
for (;;) {
try {
//jdk底层的注册方法
//第一个参数为selector, 第二个参数表示不关心任何事件
selectionKey = javaChannel().register(eventLoop().selector, 0, this);
return;
} catch (CancelledKeyException e) {
//省略代码
}
}
}

我们终于看到和java底层相关的方法了

跟到javaChannel()的方法中:

protected SelectableChannel javaChannel() {
return ch;
}

这个ch, 就是本章第二小节创建NioServerSocketChannel中初始化的jdk底层ServerSocketChannel

这里register(eventLoop().selector, 0, this)方法中eventLoop().selector, 是获得每一个eventLoop绑定的唯一的selector, 0代表这次只是注册, 并不监听任何事件, this是代表将自身(NioEventLoopChannel)作为属性绑定在返回的selectionKey当中, 这个selectionKey就是与每个channel绑定的jdk底层的SelectionKey对象, 熟悉nio的小伙伴应该不会陌生, 这里不再赘述

回到register0(ChannelPromise promise)方法, 我们看后续步骤:

步骤(2)是触发handler的需要添加事件, 事件传递的内容我们将在后续课程详细介绍, 这里不必深究

步骤(3)是触发注册成功事件(3), 同上

步骤(4)是传播active事件(4), 这里简单强调一下, 这里的方法pipeline.fireChannelActive()第一个注册是执行不到的, 因为isActive()会返回false, 因为链路没完成

本小节梳理了有注册多路复用的相关逻辑, 同学们可以跟着代码自己走一遍以加深印象

上一节: 服务端Channel的初始化

下一节: 绑定端口

Netty源码分析第1章(Netty启动流程)---->第4节: 注册多路复用的更多相关文章

  1. Netty源码分析第3章(客户端接入流程)---->第4节: NioSocketChannel注册到selector

    Netty源码分析第三章: 客户端接入流程 第四节: NioSocketChannel注册到selector 我们回到最初的NioMessageUnsafe的read()方法: public void ...

  2. Netty源码分析第3章(客户端接入流程)---->第5节: 监听读事件

    Netty源码分析第三章: 客户端接入流程 第五节: 监听读事件 我们回到AbstractUnsafe的register0()方法: private void register0(ChannelPro ...

  3. Netty源码分析第3章(客户端接入流程)---->第1节: 初始化NioSockectChannelConfig

    Netty源码分析第三章: 客户端接入流程 概述: 之前的章节学习了server启动以及eventLoop相关的逻辑, eventLoop轮询到客户端接入事件之后是如何处理的?这一章我们循序渐进, 带 ...

  4. Netty源码分析第3章(客户端接入流程)---->第2节: 处理接入事件之handle的创建

    Netty源码分析第三章: 客户端接入流程 第二节: 处理接入事件之handle的创建 上一小节我们剖析完成了与channel绑定的ChannelConfig初始化相关的流程, 这一小节继续剖析客户端 ...

  5. Netty源码分析第3章(客户端接入流程)---->第3节: NioSocketChannel的创建

    Netty源码分析第三章: 客户端接入流程 第三节: NioSocketChannel的创建 回到上一小节的read()方法: public void read() { //必须是NioEventLo ...

  6. Netty源码分析第1章(Netty启动流程)---->第1节: 服务端初始化

    Netty源码分析第一章:  Server启动流程 概述: 本章主要讲解server启动的关键步骤, 读者只需要了解server启动的大概逻辑, 知道关键的步骤在哪个类执行即可, 并不需要了解每一步的 ...

  7. Netty源码分析第1章(Netty启动流程)---->第2节: NioServerSocketChannel的创建

    Netty源码分析第一章:  Server启动流程 第二节:NioServerSocketChannel的创建 我们如果熟悉Nio, 则对channel的概念则不会陌生, channel在相当于一个通 ...

  8. Netty源码分析第1章(Netty启动流程)---->第3节: 服务端channel初始化

    Netty源码分析第一章:Netty启动流程   第三节:服务端channel初始化 回顾上一小节的initAndRegister()方法: final ChannelFuture initAndRe ...

  9. Netty源码分析第1章(Netty启动流程)---->第5节: 绑定端口

    Netty源码分析第一章:Netty启动步骤 第五节:绑定端口 上一小节我们学习了channel注册在selector的步骤, 仅仅做了注册但并没有监听事件, 事件是如何监听的呢? 我们继续跟第一小节 ...

随机推荐

  1. 写了一个web使用向导的小插件

    运行效果: 引入插件: <link rel="stylesheet" href="ez-guide.css"> <script src=&qu ...

  2. 1015. [JSOI2008]星球大战【并查集】

    Description 很久以前,在一个遥远的星系,一个黑暗的帝国靠着它的超级武器统治者整个星系.某一天,凭着一个偶然的 机遇,一支反抗军摧毁了帝国的超级武器,并攻下了星系中几乎所有的星球.这些星球通 ...

  3. Day9 JSP

    JSP概述 JSP全称是Java Server Pages,它和servle技术一样,都是SUN公司定义的一种用于开发动态web资源的技术.JSP实际上就是Servlet. jsp = html + ...

  4. 《Java程序设计》第12周课堂实践总结

    <Java程序设计>第12周课堂实践总结 实践一 教材代码检查-p98 要求 修改教材P98 Score2.java, 让执行结果数组填充是自己的学号: 提交在IDEA或命令行中运行结查截 ...

  5. week6:Diagnosing Bias vs. Variance难点记录

    1.Bias vs. Variance是什么概念? 图形上的理解:https://www.zhihu.com/question/27068705          http://blog.csdn.n ...

  6. 设计一个分布式RPC框架

    0 前言 提前先祝大家春节快乐!好了,先简单聊聊. 我从事的是大数据开发相关的工作,主要负责的是大数据计算这块的内容.最近Hive集群跑任务总是会出现Thrift连接HS2相关问题,研究了解了下内部原 ...

  7. RHEL 7.6 安装 Oracle 18c RAC

    RHEL 7.6 安装 Oracle 18c RAC 第一部分 安装规划 虚拟环境 VirtualBox 6.0 OS 版本 Red Hat Enterprise Linux Server relea ...

  8. iOS字体相关

    1.使用自定义字体 (1)将字体文件导入项目 (2)在info.plist文件中添加 Fonts provided by application (3)获取字体在项目中的名称 for fontFami ...

  9. day 86 Vue学习之五DIY脚手架、webpack使用、vue-cli的使用、element-ui

      本节目录 一 vue获取原生DOM的方式 二 DIY脚手架 三 vue-cli脚手架的使用 四 webpack创建项目的玩法 五 element-ui的使用 六 xxx 七 xxx 八 xxx 一 ...

  10. hadoop环境搭建-伪分布模式

    Appache hadoop 版本:2.77   jdk:1.8  系统:centos7 注意不要在root下解压,要单独建一个用户安装hadoop及其组件. 一.先查看系统是否有自带j #dk: r ...