【图解IO与Netty系列】Netty源码解析——服务端启动

Netty源码解析——服务端启动

  • Netty案例复习
  • Netty原理复习
  • Netty服务端启动源码解析
    • bind(int)
    • initAndRegister()
    • channelFactory.newChannel()
    • init(channel)
    • config().group().register(channel)
    • startThread()
    • run()
    • register0(ChannelPromise promise)
    • doBind0(...)

今天我们一起来学习Netty源码,对Netty有一个深入的认知,既能掌握其使用和原理,又能对它底层的设计有一个大概的认知

Netty案例复习

在阅读源码前,我们再看一下Netty服务端的启动代码

    public static void main(String[] args) throws Exception {EventLoopGroup bossGroup = new NioEventLoopGroup(1);EventLoopGroup workerGroup = new NioEventLoopGroup();try {ServerBootstrap b = new ServerBootstrap();b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class).option(ChannelOption.SO_BACKLOG, 100).handler(new LoggingHandler(LogLevel.INFO)).childHandler(new ChannelInitializer<SocketChannel>() {@Overridepublic void initChannel(SocketChannel ch) throws Exception {ChannelPipeline p = ch.pipeline();p.addLast(new EchoServerHandler());}});ChannelFuture f = b.bind(PORT).sync();f.channel().closeFuture().sync();} finally {bossGroup.shutdownGracefully();workerGroup.shutdownGracefully();}}
  1. 我们创建了两个EventLoopGroup已经,然后设置到ServerBootstrap上,bossGroup中的EventLoop处理accept事件,workerGroup中的EventLoop处理read事件时。
  2. 再通过ServerBootstrap设置好一些其他的参数,其中包括Netty服务端接收连接请求用的NioServerSocketChannel,NioServerSocketChannel会注册到bossGroup上,监听accept事件
  3. 然后设置好ChannelInitializer,服务端接收到客户端连接之后,会创建一个NioSocketChannel,ChannelInitializer就是用于初始化连接建立成功后的NioSocketChannel
  4. 最后就是通过ServerBootstrap的bind(int inetPort)绑定一个端口

在这里插入图片描述

Netty原理复习

我们再来复习一下Netty的原理,带着对Netty原理的认知去看源码,效率才会高,不至于走偏。

在这里插入图片描述

  1. 当调用了ServerBootstrap的bind(int inetPort)绑定端口后,我们注册的NioServerSocketChannel就会被注册到bossGroup上的唯一一个NioEventLoop上,然后NioEventLoop会把ServerSocketChannel注册到Selector上监听accept事件,并启动事件循环
  2. 当有客户端连接后,boosGroup中的EventLoop会获得一个SocketChannel然后将其包装成NioSocketChannel,然后NioServerSocketChannel对应的ChannelHandler会把它注册到workerGroup中
  3. workerGroup会将NioSocketChannel注册到其中一个NioEventLoop上,并使用我们配置的ChannelInitializer初始化NioSocketChannel
  4. workerGroup中的NioEventLoop的事件循环中监听到Channel有read事件时发生,就会调用Channel对应的ChannelPipeline进行处理
  5. ChannelPipeline通过责任链模式,逐一调用pipeline上的ChannelHandler处理事件

Netty服务端启动源码解析

接下来我们就开始读源码了,我们从ServerBootstrap的bind方法走起。

bind(int)

    /*** Create a new {@link Channel} and bind it.*/public ChannelFuture bind(int inetPort) {return bind(new InetSocketAddress(inetPort));}

bind方法的作用就是创建一个Channel并绑定端口,创建的Channel类型就是我们指定的NioServerSocketChannel。

bind方法会调用doBind方法。

    private ChannelFuture doBind(final SocketAddress localAddress) {final ChannelFuture regFuture = initAndRegister();...doBind0(regFuture, channel, localAddress, promise);...}

省略了非关键代码后,剩下的就是关键的两行:

  1. initAndRegister()方法做的事情就是创建并初始化Channel然后注册到NioEventLoop上。
  2. doBind0(…)方法给NioServerSocockChannel中的ServerSocketChannel绑定端口。

在这里插入图片描述

initAndRegister()

我们进入initAndRegister()方法,同样是只看核心代码,其他的代码不关心。

    final ChannelFuture initAndRegister() {...channel = channelFactory.newChannel();init(channel);...ChannelFuture regFuture = config().group().register(channel);...}
  1. channelFactory.newChannel():反射调用NioServerSocketChannel的构造方法进行实例化。
  2. init(channel):为NioServerSocketChannel的ChannelPipeline添加ChannelInitializer。
  3. config().group().register(channel):把NioServerSocketChannel中的ServerSocketChannel注册到workerGroup的NioEventLoop的Selector上。

在这里插入图片描述

channelFactory.newChannel()

channelFactory.newChannel()会进入到ReflectiveChannelFactory的newChannel()方法,里面就是调用Constructor构造器的newInstance()方法反射实例化一个Channel,这个构造器就是NioServerSocketChannel的构造器。是在我们调用ServerBootstrap的channel(NioServerSocketChannel.class)方法时设置进去的。

我们进入NioServerSocketChannel的构造方法看看。

    public NioServerSocketChannel() {this(newSocket(DEFAULT_SELECTOR_PROVIDER));}

newSocket(…)方法就是创建了一个NIO原生的ServerSocketChannel,然后进入重载的构造方法。

    public NioServerSocketChannel(ServerSocketChannel channel) {super(null, channel, SelectionKey.OP_ACCEPT);config = new NioServerSocketChannelConfig(this, javaChannel().socket());}

继续调用父类的构造方法,可以看到指定了事件类型是accept事件。然后进入到父类AbstractNioChannel的构造方法。

    protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) {super(parent);this.ch = ch;this.readInterestOp = readInterestOp;...ch.configureBlocking(false);...}

AbstractNioChannel的构造方法除了继续调用父类的构造方法以外,还保存了ServerSocketChannel和关注的事件类型OP_ACCEPT,然后设置ServerSocketChannel为非阻塞。

继续进入父类的构造方法。

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

创建了一个unsafe对象,和一个ChannelPipeline。unsafe其实是Netty内部自己使用的一个工具类,先不用管,后面会看到怎么用,这里看看如何创建ChannelPipeline。

    protected DefaultChannelPipeline(Channel channel) {...tail = new TailContext(this);head = new HeadContext(this);head.next = tail;tail.prev = head;}

我们知道ChannelPipeline中的ChannelHandler都是包在ChannelHandlerContext里面的,而这里先创建了一个头部Context和尾部Context。

也就是这样:

在这里插入图片描述

channelFactory.newChannel()的大体逻辑如下:
在这里插入图片描述

init(channel)

我们回到initAndRegister()方法,再来看init方法。

    @Overridevoid init(Channel channel) {...ChannelPipeline p = channel.pipeline();...p.addLast(new ChannelInitializer<Channel>() {@Overridepublic void initChannel(final Channel ch) {...ch.eventLoop().execute(new Runnable() {@Overridepublic void run() {pipeline.addLast(new ServerBootstrapAcceptor(ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));}});}});}

init方法就是拿到NioServerSocketChannel的ChannelPipeline,往里面添加了一个ChannelInitializer,ChannelInitializer会在ServerSocketChannel被注册到Selector后,异步的给ChannelPipeline添加一个ServerBootstrapAcceptor,这个ServerBootstrapAcceptor是一个专门处理连接事件的Handler。当然ChannelInitializer还会从ChannelPipeline中把自己删除。

在这里插入图片描述

config().group().register(channel)

我们回到initAndRegister()方法,再往后看一下config().group().register(channel)。

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

进入NioEventLoopGroup的父类MultithreadEventLoopGroup的register方法中。next()方法返回一个NioEventLoop,然后调用NioEventLoop的register(channel)方法。进入到NioEventLoop的父类SingleThreadEventLoop的register方法中。

    @Overridepublic ChannelFuture register(final ChannelPromise promise) {...promise.channel().unsafe().register(this, promise);...}

promise.channel().unsafe()获取到NioServerSocketChannel中的Unsafe对象,然后调用Unsafe对象的register(…)

        @Overridepublic final void register(EventLoop eventLoop, final ChannelPromise promise) {...if (eventLoop.inEventLoop()) {register0(promise);} else {...eventLoop.execute(new Runnable() {@Overridepublic void run() {register0(promise);}});...	}}

Unsafe的register方法中,判断当前线程是否是eventLoop的线程,如果是,直接调用register0方法注册Channel,否则调用eventLoop的execute方法开启一个任务异步注册Channel。

    private void execute(Runnable task, boolean immediate) {boolean inEventLoop = inEventLoop();addTask(task);if (!inEventLoop) {startThread();...}...}

execute方法首先调用addTask(task)方法,把这个任务放入taskQueue中。然后判断当选线程不是当前EventLoop的线程,因此调用startThread()开启EventLoop的线程,启动事件循环。

在这里插入图片描述

addTask方法就是把这个Runnable放入到NioEventLoop内部的队列当中taskQueue中,在NioEventLoop的事件循环的每一轮的最后,会处理taskQueue中的任务,我们就不细看了。我们接下来看一下startThread方法。

startThread()

    private void startThread() {if (state == ST_NOT_STARTED) {if (STATE_UPDATER.compareAndSet(this, ST_NOT_STARTED, ST_STARTED)) {...doStartThread();...}}}

startThread方法先修改NioEventLoop的状态state为开启,防止再次调用startThread方法的时候重复开启线程。然后调用doStartThread()方法。

    private void doStartThread() {...executor.execute(new Runnable() {@Overridepublic void run() {...SingleThreadEventExecutor.this.run();...}});}

doStartThread()方法里面调用了executor的execute方法,异步执行SingleThreadEventExecutor.this.run()方法,这个run方法就会进入到NioEventLoop的run方法中,里面就是事件循环。

这个executor其实是一个单线程的线程池,可以看做就是NioEventLoop的唯一一个线程,startThread方法并没有再创建一个线程,而是往NioEventLoop预先创建好的线程提交一个任务,这个任务会启动NioEventLoop的事件循环。

在这里插入图片描述

run()

接下来进入NioEventLoop的run方法看看,所谓的事件循环是什么。

    @Overrideprotected void run() {...for (;;) {...strategy = select(curDeadlineNanos);if (strategy > 0) {processSelectedKeys();}...runAllTasks();...}}

可以看到,就是先调用select方法,这个select方法自然是调用NioEventLoop的Selector的select方法,当然现在还没有任何Channel被注册,因此这里是不会select到东西的。然后processSelectedKeys()是处理所有就绪事件的方法,因为这里还没有Channel被注册,自然也没有就绪事件发生。因此最后只有runAllTasks()被执行,也就是获取taskQueue中刚刚放进去的任务,这个任务的执行就会触发ServerSocketChannel的注册。

在这里插入图片描述

runAlllTasks方法执行taskQueue中目前唯一一个的任务,也就是刚刚放进去的用于注册ServerSocketChannel的任务,这个任务会调用Unsafe的register0(ChannelPromise promise)方法。

register0(ChannelPromise promise)

        private void register0(ChannelPromise promise) {...doRegister();...}

register0调用doRegister()方法,进入AbstractNioChannel的doRegister()方法。

    @Overrideprotected void doRegister() throws Exception {...selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);...}

doRegister方法中把Channel注册到Selector的代码就是上面这一行,javaChannel()返回NIO原生的Channel类型,然后调用register方法,eventLoop().unwrappedSelector()返回的就是Selector,这个Selector作为register方法的参数,这样NIO的Channel就被注册到Selector中了,这里的Channel当然是ServerSocketChannel。

在这里插入图片描述

这里注册完ServerSocketChannel之后,就会调用ChannelInitializer初始化ChannelPipeline。

doBind0(…)

我们回调doBind方法,initAndRegister()方法就已经看完了,再看下面的doBind0方法。

    private static void doBind0(...,final Channel channel,...) {channel.eventLoop().execute(new Runnable() {@Overridepublic void run() {...channel.bind(localAddress, promise).addListener(ChannelFutureListener.CLOSE_ON_FAILURE);...}});}

dobind0()方法也是通过NioEventLoop执行异步任务的方式去绑定端口,channel.bind(localAddress, promise)方法最终会进入NioServerSocketChannel的doBind(SocketAddress localAddress)方法。

   protected void doBind(SocketAddress localAddress) throws Exception {if (PlatformDependent.javaVersion() >= 7) {javaChannel().bind(localAddress, config.getBacklog());} else {javaChannel().socket().bind(localAddress, config.getBacklog());}}

然后就是调用ServerSocketChannel的bind(SocketAddress local, int backlog)方法绑定指定端口。

在这里插入图片描述

至此,Netty服务端就启动起来,下面是Netty服务端启动的源码流程图。

在这里插入图片描述

服务端启动之后,就可以接收并处理客户端的请求了,后续的流程就放到下一次再作分析。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/28286.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ssm162基于SSM的药房药品采购集中管理系统的设计与实现+vue

药房药品采购集中管理系统的设计与实现 摘 要 互联网发展至今&#xff0c;无论是其理论还是技术都已经成熟&#xff0c;而且它广泛参与在社会中的方方面面。它让信息都可以通过网络传播&#xff0c;搭配信息管理工具可以很好地为人们提供服务。针对药房药品采购信息管理混乱&…

购物车店铺列表查询流程

购物车店铺列表查询流程 购物车结算流程图

【Git】基础操作

初识Git 版本控制的方式&#xff1a; 集中式版本控制工具&#xff1a;版本库是集中存放在中央服务器的&#xff0c;team里每个人work时从中央服务器下载代码&#xff0c;是必须联网才能工作&#xff0c;局域网或者互联网。个人修改之后要提交到中央版本库 例如&#xff1a;SVM和…

如何选择合适的大模型框架:LangChain、LlamaIndex、Haystack 还是 Hugging Face

节前&#xff0c;我们星球组织了一场算法岗技术&面试讨论会&#xff0c;邀请了一些互联网大厂朋友、参加社招和校招面试的同学。 针对算法岗技术趋势、大模型落地项目经验分享、新手如何入门算法岗、该如何准备、面试常考点分享等热门话题进行了深入的讨论。 合集&#x…

小北Chat GPT4o 文生图初体验~

前言 在人工智能领域中&#xff0c;生成图像和文本的技术一直在不断进步。OpenAI的Chat GPT-4结合DALL-E技术&#xff0c;为用户提供了一种全新的创作体验——通过文字生成图像。在这篇博客中&#xff0c;小北将分享几次与Chat GPT-4合作的创作过程&#xff0c;展示从文字描述到…

redis aof写入以及aof重写的源码分析

这里写目录标题 版本aof的面试问题aof正常写入流程aof重写流程 版本 redis&#xff1a;6.2.7 aof的面试问题 最近找工作&#xff0c;面试被问倒了&#xff0c;记录一下 比如redis的aof指令会不会丢失&#xff1f;比如在重写aof的什么新来的操作怎么办&#xff1f; 在重写的…

无限可能LangChain——概念指南之架构

本节包含对 LangChain 关键部分的介绍。 架构 LangChain 作为一个框架由多个包组成。 langchain-core 该包包含不同组件的基本抽象以及将它们组合在一起的方法。此处定义了LLM、向量存储、检索器等核心组件的接口。这里没有定义第三方集成。依赖项有目的地保持非常轻量级。…

LaTex入门教程

目录 1.说明 2.页面的分区 3.入门介绍 &#xff08;1&#xff09;命令 &#xff08;2&#xff09;环境 &#xff08;3&#xff09;声明 &#xff08;4&#xff09;注释 4.代码结构 &#xff08;1&#xff09;导言区 &#xff08;2&#xff09;支持中文 &#xff08;3…

【字符串】65. 有效数字

本文涉及知识点 字符串 LeetCode65. 有效数字 给定一个字符串 s &#xff0c;返回 s 是否是一个 有效数字。 例如&#xff0c;下面的都是有效数字&#xff1a;“2”, “0089”, “-0.1”, “3.14”, “4.”, “-.9”, “2e10”, “-90E3”, “3e7”, “6e-1”, “53.5e93”,…

药品销售管理系统带万字文档药店管理系统java项目药店商城网站

文章目录 药品销售管理系统一、项目演示二、项目介绍三、万字项目文档四、部分功能截图五、部分代码展示六、底部获取项目源码带万字文档&#xff08;9.9&#xffe5;带走&#xff09; 药品销售管理系统 一、项目演示 药品销售管理系统 二、项目介绍 系统角色&#xff1a;管理…

GPU的工作原理

location: Beijing 1. why is GPU CPU的存储单元和计算单元的互通过慢直接促进了GPU的发展 先介绍一个概念&#xff1a;FLOPS&#xff08;Floating Point Operations Per Second&#xff0c;浮点运算每秒&#xff09;是一个衡量其执行浮点运算的能力&#xff0c;可以作为计算…

【MySQL】事务的特性和隔离级别

创作不易&#xff0c;本篇文章如果帮助到了你&#xff0c;还请点赞 关注支持一下♡>&#x16966;<)!! 主页专栏有更多知识&#xff0c;如有疑问欢迎大家指正讨论&#xff0c;共同进步&#xff01; 给大家跳段街舞感谢支持&#xff01;ጿ ኈ ቼ ዽ ጿ ኈ ቼ ዽ ጿ ኈ ቼ …

vue 应用测试(一) --- 介绍

vue 应用测试&#xff08;一&#xff09; ---介绍 前端测试简介组件测试Jest 测试框架简介其他测试框架 第一个测试避免误报如何组织测试代码 组件挂载Vue2 组件挂载的方式Vue3 的挂载方式vue-test-utils挂载选项 如何调试测试用例参考小结 前端测试简介 软件测试&#xff1a;…

[AIGC] Python的Range函数

Python的range()函数是一个内置函数&#xff0c;常常用于编程中生成数列。这个函数可以生成一个整数序列&#xff0c;这个序列通常用在循环中。 文章目录 基本用法详细用法注意事项 基本用法 range()函数的基本形式为 range(stop) —— 这将生成一个从0开始&#xff0c;到stop…

Docker_1.0

1.初识Docker 1.1.什么是Docker 微服务虽然具备各种各样的优势&#xff0c;但服务的拆分通用给部署带来了很大的麻烦。 - 分布式系统中&#xff0c;依赖的组件非常多&#xff0c;不同组件之间部署时往往会产生一些冲突。 - 在数百上千台服务中重复部署&#xff0c;环境不一…

Content type ‘application/x-www-form-urlencoded;charset=UTF-8‘ not supported

Content type application/x-www-form-urlencoded;charsetUTF-8 not supported 问题背景新增页面代码改造 问题背景 这里有一个需求&#xff0c;前端页面需要往后端传参&#xff0c;参数包括主表数据字段以及子表数据字段&#xff0c;由于主表与子表为一对多关系&#xff0c;在…

基于单片机的多功能智能小车设计

第一章 绪论 1.1 课题背景和意义 随着计算机、微电子、信息技术的快速发展,智能化技术的发展速度越来越快,智能化与人们生活的联系也越来越紧密,智能化是未来社会发展的必然趋势。智能小车实际上就是一个可以自由移动的智能机器人,比较适合在人们无法工作的地方工作,也可…

C++ 12 之 指针引用

c12指针引用.cpp #include <iostream>using namespace std;struct students12 {int age; };int main() {students12 stu;students12* p &stu; // 结构体指针students12* &pp p; // 结构体指针起别名pp->age 20;// (*pp).age 22;cout << "…

【CTF Web】CTFShow 探针泄露 Writeup(PHP+探针泄露+信息收集)

探针泄露 10 对于测试用的探针&#xff0c;使用完毕后要及时删除&#xff0c;可能会造成信息泄露 解法 查看网页源代码。 view-source:https://11170dfe-84c7-4fde-b1ca-5d1ec3dd7570.challenge.ctf.show/没有找到有用的信息。 用 dirsearch 扫描。 dirsearch -u https://1…

打造私密的通信工具,极空间搭建免费开源的电子邮件管理程序『Cypht』

打造私密的通信工具&#xff0c;极空间搭建免费开源的电子邮件管理程序『Cypht』 哈喽小伙伴门好&#xff0c;我是Stark-C~ 说起电子邮件大家都不陌生&#xff0c;哪怕是在当前微信或者QQ已经非常普遍的今天&#xff0c;电子邮件在我们很多人的工作中都充当了重要的通信工具。…