来源:逐码

我们知道Netty是一个基于JDK的nio实现的网络编程框架,那Netty的服务端是怎么启动的呢,包括他是何时register 的,何时 bind 端口的,以及何时开始读取网络中的数据的?

让我们带着这个疑问,通过一个官方的例子来深入探究Netty服务端的启动过程。

PS:本文基于netty源码的4.1分支进行分析。

首先我们拿一个最简单的EchoServer的例子来举例说明,具体的代码如下:

EventLoopGroup bossGroup = new NioEventLoopGroup(1);  // 1
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {ServerBootstrap b = new ServerBootstrap();b.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) // 2.option(ChannelOption.SO_BACKLOG, 100).handler(new LoggingHandler(LogLevel.INFO)) // 3.childHandler(new ChannelInitializer<SocketChannel>() {  // 4@Overridepublic void initChannel(SocketChannel ch) throws Exception {ChannelPipeline p = ch.pipeline();if (sslCtx != null) {p.addLast(sslCtx.newHandler(ch.alloc()));}p.addLast(new EchoServerHandler());}});// Start the server.ChannelFuture f = b.bind(PORT).sync(); // 5// Wait until the server socket is closed.f.channel().closeFuture().sync();
} finally {// Shut down all event loops to terminate all threads.bossGroup.shutdownGracefully();workerGroup.shutdownGracefully();
}

从上面的代码来看,在启动的过程中共有5处地方需要我们关注,不过最重要的启动服务端的代码,还是在最后第5步的时候。

为了更加清晰的描述整个启动的过程,也便于我们更好的理解和记忆,我将使用多图形少代码的形式来表达。

首先我把启动过程的一个大致流程画成如下的图:

其中有以下几个核心的方法:

  • channel()

  • handler()

  • childHandler()

  • doBind()

除此之外,还有一个初始化EventLoopGroup类的方法:

  • NioEventLoopGroup()

一、初始化EventLoopGroup

我们从最初的初始化 EventLoopGroup 类开始吧,从源码中可以看到是一层一层的构造方法的调用,然后再super到了父类中,最终会调用到 AbstractEventExecutor 类,具体的调用流程如下图所示:

这个过程中创建了几个重要的实例,我用淡蓝色标记出来了。

首先我们需要知道的是,在Netty中有几个比较重要的类:

  • EventLoop

  • EventLoopGroup

  • EventExecutor

  • EventExecutorGroup

他们之间的关系图如下所示:

EventLoop和EventExecutor说到底都是一种Executor。

然后通过调用ServerBootstrap的group()方法,我们将创建的EventLoopGroup对象分别赋值给了ServerBootstrap的 groupchildGroup 属性。

二、执行channel()方法

初始化完了EventLoopGroup之后,接着就开始执行 channel() 方法了,这个方法很简单,就是通过 ReflectiveChannelFactory 类创建了一个 channelFactory ,这个 channelFactory 后面会很有用,都是通过它来创建需要的Channel实例的。这里我就不贴具体的代码了,具体的执行过程可以用下面的图来表示:

通过调用该方法,ServerBootstrap类的 channelFactory 属性就被赋予了值,并且该ChannelFactory的实现类是通过反射来创建Channel的。

后面在需要创建Channel的时候,会调用该channelFactory的 newChannel() 方法,执行该方法之后,会创建三种非常有用的对象:

  • channel

  • pipeline

  • unsafe

三、执行handler()方法

该方法没有创建其他的对象,只是把用户提供的方法参数中所表示的ChannelHandler对象通过该方法来赋值给ServerBootstrap的 handler 属性。

PS:这里创建的handler在后面的初始化时会使用到

四、执行childHandler()方法

该方法没有创建其他的对象,只是把用户提供的方法参数中所表示的ChannelHandler对象通过该方法来赋值给ServerBootstrap的 childHandler 属性。

PS:这里创建的childHandler在后面的初始化时会使用到

五、执行doBind()方法

Netty启动过程中最复杂,步骤最多的就是这个方法了,不过不用担心,我已经把该方法核心的执行过程整理好了,如下图所示:

这里我推荐大家在读源码的时候,可以拿一张纸,一支笔,用画图的形式把方法的调用过程,以及创建了哪些属性等等这些都记下来,一开始可以不用知道那些方法和属性具体是干什么的。先把整个调用流程理清楚,然后再一点一点细化,由点到面的扩展开来,最终把你那张图丰富成一个完整的调用图。

从图中可以看的出来,doBind方法拆分成了两个核心的方法:

  • initAndRegister()

  • doBind0()

第一个 initAndRegister 方法,从方法名字上就可以看得出来,它主要是执行某个init的过程,然后又执行了某个register的过程。

第二个 doBind0 方法,主要是执行了端口的绑定,然后创建了eventLoop不断的执行JDK中的Selector.select()方法,从注册到selector中的channel中选择符合条件的channel。另外创建了一个task,用来从选中的channel中读取数据,然后把读取到的数据给到childHandler进行处理。

下面让我们来深入到这两个方法的执行过程中去,看看到底发生了什么。

5.1 执行initAndRegister方法

initAndRegister方法的执行过程如下图所示:

initAndRegister方法做的事有两件:init和register。在这之前首先通过channelFactory创建了一个channel。该方法是在初始化EventLoopGroup的时候出现的,可以回头看一下,初始化的过程一共创建了三种对象:channel、unsafe、pipeline。

从该方法中慢慢的往下看,就可以看到,通过channelFactory创建了一个channel对象后,然后又拆分成了两个部分,分别对channel进行了初始化,和对channel进行了register。其中register方法,最终会调用到JDK中最原始的register方法,即把一个channel注册到一个selector中去。

  • init

初始化的过程主要是把用户先前创建的handler和childHandler添加到pipeline中去。

  • register

注册的过程主要是把该channel注册到selector中去,这里的channel就是用来接受客户端连接的。

5.2 执行doBind0方法

doBind0方法的执行过程如下图所示:

doBind0做的事也很明确:bind、select以及runTask。

bind的过程最终是调用到JDK中原生的bind方法,其中在unsafe中执行bind的过程时,除了执行了具体的bind之外,还在NioEventLoop中启动了一个线程,用来不断的执行JDK中selector的select方法。然后读取选中的channel中的数据,最后把读取到的数据丢给childHandler去处理。

JDK的epoll空轮询bug

我们知道JDK中的Selector会出现epoll空轮询的bug,若Selector的轮询结果为空,也没有wakeup或新消息处理,则发生空轮询,此时CPU使用率将达到100%。

Netty是通过重建Selector的方式修复该bug的,具体的做法是:

  • 对Selector的select操作周期进行统计,每完成一次空的select操作进行一次计数,

  • 若在某个周期内连续发生n(SELECTORAUTOREBUILD_THRESHOLD)次空轮询,则触发了epoll死循环bug。

  • 重建Selector,判断是否是其他线程发起的重建请求,若不是则将原SocketChannel从旧的Selector上解除注册,重新注册到新的Selector上,并将原来的Selector关闭。

具体的代码是在NioEventLoop中的select方法中执行的,代码如下:

private void select(boolean oldWakenUp) throws IOException {Selector selector = this.selector;try {int selectCnt = 0;long currentTimeNanos = System.nanoTime();long selectDeadLineNanos = currentTimeNanos + delayNanos(currentTimeNanos);for (;;) {long timeoutMillis = (selectDeadLineNanos - currentTimeNanos + 500000L) / 1000000L;if (timeoutMillis <= 0) {if (selectCnt == 0) {selector.selectNow();selectCnt = 1;}break;}if (hasTasks() && wakenUp.compareAndSet(false, true)) {selector.selectNow();selectCnt = 1;break;}int selectedKeys = selector.select(timeoutMillis);selectCnt ++;if (selectedKeys != 0 || oldWakenUp || wakenUp.get() || hasTasks() || hasScheduledTasks()) {break;}if (Thread.interrupted()) {selectCnt = 1;break;}long time = System.nanoTime();if (time - TimeUnit.MILLISECONDS.toNanos(timeoutMillis) >= currentTimeNanos) {// timeoutMillis elapsed without anything selected.selectCnt = 1;// 当发生的select次数大于指定的阈值时,重建Selector    } else if (SELECTOR_AUTO_REBUILD_THRESHOLD > 0 &&selectCnt >= SELECTOR_AUTO_REBUILD_THRESHOLD) {// 重建Selector,以解决JDK中的epoll的bugrebuildSelector();selector = this.selector;// Select again to populate selectedKeys.selector.selectNow();selectCnt = 1;break;}currentTimeNanos = time;}} catch (CancelledKeyException e) {if (logger.isDebugEnabled()) {logger.debug(CancelledKeyException.class.getSimpleName() + " raised by a Selector {} - JDK bug?",selector, e);}// Harmless exception - log anyway}
}

完整的启动过程

通过上面的分析,我们最后来总结一下,Netty服务端在启动的时候做了以下的事情:

  • 1.创建了EventLoopGroup、NioEventLoop的实例,并且创建了一个selector

  • 2.创建了一个channelHandler用来在未来实例化Channel

    • 创建Channel的过程中会一并创建pipeline和unsafe

  • 3.设置了ServerBootstrap的handler和childHandler属性,用以在接收到数据后进行业务逻辑的处理

  • 4.通过channelFactory创建了channel实例,并对其进行了初始化和注册到selector上

  • 5.通过Unsafe调用JDK的bind方法将服务绑定到了端口上,并通过EventLoop创建了一个线程来循环执行以下任务

    • 5.1.执行selector的select方法,并通过计数的方式,满足一定条件的情况下对selector进行重建,以解决JDK的epoll空轮询的bug

    • 5.2.对选中的channel执行读操作,并将读取到的数据丢给childHandler进行处理

一个完整的Netty服务端启动过程如下图所示:

END

Java面试题专栏

【41期】盘点那些必问的数据结构算法题之链表

【42期】盘点那些必问的数据结构算法题之二叉堆

【43期】盘点那些必问的数据结构算法题之二叉树基础

【44期】盘点那些必问的数据结构算法题之二分查找算法

【45期】盘点那些必问的数据结构算法题之基础排序

【46期】盘点那些必问的数据结构算法题之快速排序

【47期】六大类二叉树面试题汇总解答

【48期】盘点Netty面试常问考点:什么是 Netty 的零拷贝?

【49期】面试官:SpringMVC的控制器是单例的吗?

【50期】基础考察:ClassNotFoundException 和 NoClassDefFoundError 有什么区别

我知道你 “在看”

图说Netty服务端启动过程相关推荐

  1. zookeeper源码分析之一服务端启动过程

    zookeeper简介 zookeeper是为分布式应用提供分布式协作服务的开源软件.它提供了一组简单的原子操作,分布式应用可以基于这些原子操作来实现更高层次的同步服务,配置维护,组管理和命名.zoo ...

  2. 服务端_说说Netty服务端启动流程

    点击上方☝SpringForAll社区 轻松关注!及时获取有趣有料的技术文章 本文来源:http://yeming.me/2016/03/12/netty1/ netty服务端代码分析 服务端启动配置 ...

  3. androidpn的学习研究(二)androidpn-server服务端启动过程的理解分析

    在Androidpn的底层主要采用的mina和openfire两大框架,其中mina主要为底层数据传输的Socket框架.下面简单说明mina的框架. Apache Mina Server 是一个网络 ...

  4. Spring Cloud Eureka 源码分析(一) 服务端启动过程

    2019独角兽企业重金招聘Python工程师标准>>> 一. 前言 我们在使用Spring Cloud Eureka服务发现功能的时候,简单的引入maven依赖,且在项目入口类根据服 ...

  5. 原理剖析-Netty之服务端启动工作原理分析(上)

    一.大致介绍 1.Netty这个词,对于熟悉并发的童鞋一点都不陌生,它是一个异步事件驱动型的网络通信框架: 2.使用Netty不需要我们关注过多NIO的API操作,简简单单的使用即可,非常方便,开发门 ...

  6. Netty实战 IM即时通讯系统(四)服务端启动流程

    ## Netty实战 IM即时通讯系统(四)服务端启动流程 零. 目录 IM系统简介 Netty 简介 Netty 环境配置 服务端启动流程 实战: 客户端和服务端双向通信 数据传输载体ByteBuf ...

  7. 《netty入门与实战》笔记-02:服务端启动流程

    为什么80%的码农都做不了架构师?>>>    1.服务端启动流程 这一小节,我们来学习一下如何使用 Netty 来启动一个服务端应用程序,以下是服务端启动的一个非常精简的 Demo ...

  8. netty 5 alph1源码分析(服务端创建过程)

    研究了netty的服务端创建过程.至于netty的优势,可以参照网络其他文章.<Netty系列之Netty 服务端创建>是 李林锋撰写的netty源码分析的一篇好文,绝对是技术干货.但抛开 ...

  9. Netty学习笔记(二)Netty服务端流程启动分析

    先贴下在NIO和Netty里启动服务端的代码 public class NioServer { /*** 指定端口号启动服务* */public boolean startServer(int por ...

最新文章

  1. Hibernate 乐观锁和悲观锁
  2. 新发现:高速下载Eclipse
  3. Oracle创建表管理表
  4. php memcache 基础操作
  5. 【机器学习】周志华 读书笔记 第一章 绪论
  6. Linux(CentOS6.4)Solr4.8.1中文分词配置(IK分词)
  7. js中四种创建对象的方式
  8. CAS单点登录详细流程
  9. 深入研究Java中一个对象的初始化过程
  10. xcode 怎么调用midi开发录音_音频应用专业录音声卡:雅马哈UR242声卡教程
  11. opencv-api erode
  12. mui 页面滚动解决方案
  13. VC6.0工程设置说明
  14. 一张网络路由器与能源路由器对照表(2015年)
  15. 了解一下,Android 10中的ART虚拟机(2)
  16. WordPress Contact Form插件‘cntctfrm_contact_emai’参数跨站脚本漏洞
  17. 再论iPhone Push Notification
  18. Excel - 斜线表头制作
  19. 《王道2023》P360 T3 计数排序
  20. 安凯AK3918E加载mtk7601驱动不能ifconfig wlan0 down

热门文章

  1. 三星Galaxy Note10前脸照曝光:下巴比iPhone还要窄
  2. 高通侧目!联发科发布面向高端手机的5G芯片
  3. vivo X27发布日期官宣: 3月19日 三亚见!
  4. Python+Selenium自动化测试:Page Object模式
  5. 支付宝web支付,mobileclientgw-
  6. java从入门到秃头,小白的秃头之路
  7. onvif学习笔记9:OSD命令学习
  8. vue-cli目录结构介绍
  9. jQuery中append()、prepend()与after()、before()的区别
  10. ORA-12505,TNS:listener does not currently know of SID given in connect descriptor(不知道的SID)