一文讲透Netty核心源码

4 篇文章 0 订阅
深入分析 Netty Reactor 线程模型的实现原理
以生活中场景为例,类比说明 Netty 各核心组件的原理、交互流程
详细分析 Netty 核心源码流程

1. Netty是什么

1.1 这个没营养的定义好长...

Netty是 一个异步的,事件驱动的网络应用程序框架,基于Java Nio的Api实现 ...

定义较长,看完估计懂的人感觉没营养,不懂的人依然茫然;不妨换个角度理解下。

1.2 我的理解

大部分同学对 Spring Mvc、Mybatis 框架很熟悉,对其所实现功能、提供哪些 Api 都了然于大脑。

Netty 框架与 Java Nio 之间的关系, 类似于 Spring Mvc 框架与 Java Servlet 、Mybatis 框架与 Jdbc 之间的关系, 框架会让对应的功能开发更简洁、高效。

基于 Spring Mvc 框架开发 web 服务端应用,通常只需开发出 Controller、通用拦截器、参数/返回值解析器(框架有已提供多个选项)。

基于 Netty 框架开发 Nio 服务端应用,通常也只需开发出请求处理器、通用过滤器、编/解码器(框架有已提供多个选项)。

Netty 与 Spring Mvc 工作层次和原理很不一样,以上只是从框架对功能封装角度进行类比。

2. 换个方式理解Netty Nio通讯

2.1借助一个生活中的例子

假设你是老板,需要开家电话客服公司,提供的服务很简单:接电话并与用户沟通。

你为了更高效地服务大量用户,准备设置两个团队A和B,职责分派如下:

  • A团队:守着呼叫总台,接听并转交给B团队某一个人的电话

  • B团队:守着自己的工位电话,记录转交来的用户电话号码并与用户进行沟通

A团队活少,理论上可多个人干,你公司初期接听业务量不大,一个人即可;

B团队活多,理论上一个人干也行,通常会多个人一起干(按公司业务量分配)。

在开工前你准备好办公场所,为了节省人力成本,你分别告诉A和B职位的中介,等有活时在招聘对应人过来,一旦人招聘来之后,就按对应工位的工作职责夜以继日地干活。

等一切就绪之后,你给A团队工位1扔去第一个任务(开机)并告诉中介A找人干活,然后就坐到一边休息去了。

A1员工开启呼叫台后,就能接到广大用户的咨询电话。当来电话后,A1员工将对应呼叫信息包装成任务【和老板的沟通方式一致】扔给B团队中的某一个工位任务表,若工位没人则告诉中介B找人干活。

【故事 End】

故事核心角色有 老板、A团队员工、A团队人力中介、B团队员工、B团队人力中介,重点要理解各角色的职责、交互方式(若已阅读过源码应该能对应到netty的各个组件)。

AB团队的员工在处理大多数任务时,会按照固定的流程顺序倒序执行,下文用 Netty 组件描述,暂不用故事描述了(否则题主讲的难受,读者看的更难受)。

2.2 Netty交互流程

BossGroup(A团队)主要处理连接事件,各组件间关系,核心交互流程如下:

  • ThreadPreTaskExecutor:每次执行任务会创建新线程执行任务,NioEventLoopGroup 持有该线程池属性;

  • NioEventLoopGroup:是线程池组或线程池集群,启动 Netty server 的线程(幕后老板)提交任务到 bossGroup,bossGroup 轮询选择一个 NioEventLoop 并提交任务到任务队列中;

  • NioEventLoop:仅有一个线程的线程池,当往线程池第一次提交任务时,通过 ThreadPreTaskExecutor 创建线程并赋值给 NioEventLoop.thread 属性,并且持有 nio selector;

  • 连接 op_accept 事件 select 出之后,会交给 NioServerSocketChannel 的内部 Unsafe 对象进行 read(op_accept事件读取的是连接),之后会通过 pipeline 方法传播 channelRead 事件;

  • Channel:Netty server 程序中有 NioServerSocketChannel (端口监听 channel ),NioSocketChannel (与 client 通讯 channel );

  • 各自封装了 jdk nio channel,扩展了 bind、connect、register、close、read、write 等 io 事件;其内部类 UnSafe 主要用于和 jdk nio 中的类进行交互;

  • ChannelPipeline:与 channel 对象相互引用 ,内部包装了 ChannelHandler 链,类似于过滤器中的 FilterChain。

workGroup 读取 client 消息与 bossGroup 读取 client 连接流程基本一致,内容不一样而已。

业务 handler 处理完请求后通常会调用 netty Channel 的 writeAndFlush 方法,再经过 ChannelPipeline 中的 ChannelHandler 链传播到内部 Unsafe 对象,最终调用 javachannel 的 write 方法写出响应给 client。

read 与 write 对比

  1. read 消息,消息到网卡,经过 nio selector 后传播到应用程序的 ChannelHandler,从外向内传播(inbound 事件),对应用程序来说被动接收

jdkChannel(read到了消息) → unsafe → pipeline → head → businessHandler → tail

  1. write 返回值,应用程序调用 channel.write 方法后传播到 Javachannel.write 写出到网卡并发送,从内向外传播(outbound 事件),是应用程序主动发起

nettychannel → pipeline → tail → businessHandler → head → unsafe → jdkChannel

3. Netty组件详解

3.1 inbound 和 outbound 的区别

inbound、outbound 唯一区别是事件传播方向不一样

  • 从 Java nio 层向应用程序层(Netty代码和用户代码)方法传播 是 inbound 事件

  • 从应用程序层方法向 Java nio 层传播是 outbound 事件。

3.2 inbound 事件如何发起

inbound 事件不一定都是由 client 发起,也可能是 Netty server 自身发起的。

如:channel 注册功能,从 Netty 框架代码经过 pipeline 传播到 Java nio 层属于 outbound 事件。当注册完成之后,Netty 框架会反方向(从Java nio层向应用程序层方法)传播 channelRegistered (inbound事件)。

  • ChannelOutboundInvoker :用于传播outbound事件(如:writeAndFlush)

  • ChannelInboundInvoker :用于传播Inbound事件 (如:fireChannelRead)

  • ChannelOutboundHandler :处理outbound事件业务逻辑(如:bind,write,connect)

  • ChannelInboundHandler :处理inbound事件逻辑 (如:channelActive,channelRead)

ChannelPipeline、ChannelHandlerContext 组件都实现了 ChannelOutboundInvoker 与 ChannelInboundInvoker,两者都可传递 inbound 和 outbound 事件。

Channel 组件只实现了 ChannelOutboundInvoker,若阅读到对应代码,可能会疑问:

为什么只实现了 ChannelOutboundInvoker?难道不能传播 inbound 事件?

通过上面流程得知,所有的 io 事件都会经过 Channel, 题主认为不实现 ChannelInboundInvoker 是因为用不到,原因如下:

  1. inbound 事件是从 jdk nio 层向应用程序层传播,通过 javachannel 可以引用获取到 nettychannel 进而能引用到 unsafe 对象,直接调用 unsafe 或 pipeline 进行传播事件即可,netty 内部也是这么操作的。

  1. outbound 事件是从应用程序层向 jdk nio 层传播,需要提供对应的 write、bind、close 等方法给调用方使用。

ChannelPipeline 中的 ChannelHandler 在被添加时会包装成 ChannelHandlerContext,具体的某一个 ChannelHandler 可只处理 inbound 或 outbound 事件。

但每个 ChannelHandlerContext 对象都具有传播 inbound 和 outbound 事件的能力,才能传播所有事件。

3.3 HeadContext 特殊性

  1. 实现了 inbound 与 outbound 接口但强制设置 inbound 为 false(题主对这里理解还不成熟、暂不表述)。

  1. head 比 tail 多了个 unsafe 属性,从调用层次来看 head 紧挨着 unsafe,事件传播时会相互调用。

finalclass HeadContext extends AbstractChannelHandlerContext
            implements ChannelOutboundHandler, ChannelInboundHandler {

        /**
         * HeadContext比TailContext多了个 unsafe属性
         */
        privatefinal Unsafe unsafe; 

        HeadContext(DefaultChannelPipeline pipeline) {
            super(pipeline, null, HEAD_NAME, false(inbound), true(outbound));
            unsafe = pipeline.channel().unsafe();
            setAddComplete();
        }
        。。。
   }

4. 走读Netty Nio代码

4.1 Nnetty server启动四大步骤

  1. 创建 NioServerSocketChannel

  1. 初始化 NioServerSocketChannel 参数

  1. 注册 channel 到 selector

  1. 绑定端口

4.2 解读

  • 端口绑定后,可接受客户端连接,并收发消息

  • 注意 Main 线程、boss io 线程、worker io 线程之间的交互

  • 代码流程较长、建议在电脑端对着源码阅读

5. 作者团队介绍

三翼鸟数字化技术平台-智能运营平台团队主要以用户行为数据为基础,利用推荐引擎为用户提供“千人千面”的个性化推荐服务,改善用户体验,持续提升核心业务指标。通过构建高效、智能的线上运营系统,全面整合数据资产,实现数据分析-人群圈选-用户触达-后效分析-策略优化的运营闭环,并提供可视化报表,一站式操作提升数字化运营效率。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值