首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

Netty:io请求处理过程解析

文接上一篇。上篇讲到netty暴露一个端口出来,acceptor, handler, pipeline, eventloop 都已准备好。但是并没体现其如何处理接入新的网络请求,今天我们就一起来看看吧。

1. eventloop主循环

上篇讲到,netty启动起来之后,就会有很多个eventloop线程会一直在循环工作(server通用特性),比如进行select或者执行task. 我们再来回顾 NioEventLoop 的实现方式吧!

我们先看看下 NioEventLoop 的类图吧:

看起来非常复杂,不管它。它核心方法自然是 run();

大体来说就是:eventloop是一个一直在运行的线程,它会不停地检测是否发生了网络事件或者被提交上来了新任务,如果有那么就会去执行这些任务。

在处理io事件和task时,为防止调度的饥饿问题,它设置了一个ioRatio来避免发生。即如果io事件占用了ioTime时间,那么task也应该占用相应剩下比例的时间,以保持公平性。

在实现上,发现网络io事件是通过 selector.select()的,而发现task任务是通过 hasTasks()来实现检测的。每检测一次,一般不超过1s的休眠时间,以免在特殊情况下发生意外而导致系统假死。

2. acceptor 运行io操作

io操作主要就是监控一些网络事件,比如新连接请求,请请求,写请求,关闭请求等。它是一个网络应用的非常核心的功能之一。从eventloop的核心循环中,我们看到其 processSelectedKeys() 就做网络io事件处理的。

以上是处理一条io事件的大体流程:

1. 调用 AdaptiveRecvByteBufAllocator 分配一个新的 ByteBuf, 用于接收新数据;

2. 调用 doReadMessages() 转到 accept() 接收socket进来, 存入 ByteBuf 备用;

3. 对接入的socket, 调用pipeline.fireChannelRead(), 处理读过程;

4. 调用pipeline.fireChannelReadComplete() 方法,触发read完成事件;

5. 异常处理;

注意,当前运行的线程是在bossGroup中,它的pipeline是相对固定的,即只有head -> acceptor -> tail, 而我们的handler是在childGroup中的,所以我们只能再等等咯。

下面我们就来细分解下这几个步骤!

2.1 acceptor 接入socket

在调用AdaptiveRecvByteBufAllocator, 分配一个新的 allocHandle 之后,就进行socket的接入,实际上就是调用 serverSocketChannel.accept() 方法, 初步读取数据。来看下!

将新接入的socket封装成 NioSocketChannel 后, 添加到 readBuf 中, 进入下一步.

2.2 read 事件传播

socket 接入完成后, 会依次读取数据. (所以, 前面会同时接入多个 socket ??) pipeline 机制正式上场. 此时pipeline中有head,acceptor,tail, 但只有acceptor会真正处理数据.

acceptor 最主要的工作就是将socket提交到 childGroup 中. 而childGroup的注册过程, 与bossGroup的注册过程是一致的, 它们的最大差异在于关注的事件不一致. acceptor 关注 OP_ACCEPT, 而childGroup 关注 OP_READ.

2.3 readComplete 事件的传播

实际上,在bossGroup中, readComplete() 事件基本是会不被关注的, 但我们也可以通过它来了解下 readComplete 的传播方式吧! 总体和 read() 事件的传播是一致的.

总结下 pipeline 的传播方式:

1. 以 pipeline.fireChannelReadComplete() 等方式触发事件传播;

2. 调用 invokeChannelReadComplete, 传入 head或者tail作为传播的起点;

3. 判断是否在 eventloop 中,如果是则直接调用 next.invokeChannelReadComplete();

4. 调用 handler.channelReadComplete(this) 触发具体的事件;

5. 具体handler处理事务,如果想向下一节点传播,则调用 ctx.fireChannelReadComplete(), 否则停止传播;

以上是以 fireChannelReadComplete 来讲解的pipeline过程,实际上也是几乎所有的事件传播的方式。

3. childGroup 运行io操作

上一节讲到的是acceptor接入了socket, 他会提交到childGroup中进行处理, 然后自己就返回了。那么 childGroup 又是如何处理事务的呢?

实际上,它与bossGroup是完全一样的处理方式,差别在于它们各自的pipeline是不一样的,线程数是不一样的,从而实现处理不同业务。而它处理是的读写事件,而acceptor则是处理的OP_ACCEPT事件。它的OP_READ事件是在创建NioSocketChannel的时候注册好的。我们先看看下:

ok, 说回childGroup处理事件流中。因大家都是 NioEventLoopGroup, 所以创建的eventloop自然都是一样的。即都会处理io事件和task运行。回顾下上节的processSelectedKey()操作:

以上,就是 childGroup 处理 io 事件的基本过程了。总体和acceptor的差不多,这也是netty抽象得比较合理的地方,所有地方都可以套用同一个模式。

1. 准备环境,获取pipeline,配置config分配内存;

2. doReadBytes() 读取数据buffer, 最大读取1024字节;

3. 读取完成后记录并触发pipeline下游处理本次的channelRead()事件,保证各handler都有机会处理该部分数据;

4. 只要数据没读取完,且没有超过最大数据量限制,循环处理2/3步骤;

5. 总体触发一次 channelReadComplete 事件,并同理在pipeline中传播;

6. 异常处理,close处理;

pipeline 的传播方式, 前面我们已经见识过了,范式就是:read() 作为入站事件, 从head开始传播,依次调用各handler的channelRead()方法,直到链尾。

接下来我们就其中几个关键的步骤看下,netty都是如何实现的。

3.1 doReadBytes 读取socket数据

以上就是socket数据的读取过程了,总体可以描述为内核内存到java堆内存的拷贝过程(当然具体实现方式是另一回事)。

数据读取完成后(可能是部分),就会交pipeline处理这部分数据,head -> handler... -> tail 的过程。我们还是一个具体的 netty提供的一个解码的实现:

3.2 netty解码实现1 byteToMsg

就是一个 channelRead 处理过程 。

总结下对数据的解码过程:

1. 接收外部读取的byteBuf;

2. 判断数据是否足够进行解码,如果解码成功将其添加到out中;

3. 将out的数据传入到pipeline下游,进行业务处理;

4. 释放已读取的buffer数据,进入下一次数据读取准备;

对于短连接请求,每次都会有新的encoder, decoder, 但对于长连接而言, 则会复用之前的handler, 从而也需要处理好各数据的分界问题,即自定义协议时得够严谨以避免误读。

4. write 数据的实现

write 数据是向对端进行数据输出的过程,一般有 write, 和 flush 过程, write 仅向应用缓冲中写入数据,在合适的时候flush到对端。而writeAndFlush则表示立即输出数据到对端。有 DefaultChannelHandlerContext 的实现:

4.1 netty write 的事件如何处理

write 含义明确,写数据到xxx。那这是如何实现的呢?(仅从应用层分析,咱们就不讨论底层TCP协议了)

实际上,它就是write事件的传播过程,最终由 head 节点处理。

即write只向 outboundBuffer中写入数据,应该是比较快速的。但它也是经历了 pipeline 的事件流的层层处理,如果想在这其中做点什么,也是比较方便的。

4.2 flush 事件流处理

上面一步写入数据到 outboundBuffer 中,并未向对端响应数据,需要进行 flush 对端才能感知到。

如上,写数据的过程理论都是通用的,都会先向应用缓冲中写入数据,然后再进行flush. netty 使用 DirectByteBuffer 进行写入优化,使用eventloop保证写入的完整性和及时性。

本文通过netty 对网络事件的处理过程,以对通用网络io处理实现方式的理解必然有所加深。

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20201231A01I2H00?refer=cp_1026
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券