Netty中的流式传输

Netty中的流式传输

在网络编程中通常是使用流式传输来处理网络数据,比如对于Netty的解码处理器,如下图:
在这里插入图片描述
当从网络中读取数据的时候,并不是一次性的把所有的数据都读取到ByteBuf缓存中,虽然这样可能更方便。读取数据的时候我们是流式读取,即每次只从网络中读取一部分数据进行处理。那么采用网络传输数据的时候采用流式传输有什么样的好处呢

  • 提高出错时的传输效率。比如如果不是流式传输,而是一次性把网络中的所有数据都传输过来,会出现什么情况呢?比如你下载一个特别大的文件,下载到中间出错了,这个时候你还需要来进行重新下载,之前已经下载过的部分也需要进行重新下载,所以出错时的传输效率就很低。而如果使用流式传输的话,当中间下载出错的时候,不需要重新下载,我们只需要从出错的地方开始下载即可,已经下载过的部分无需重新下载。
  • 节省内存。如果一次性把网络中的所有数据都接收过来,那么需要很大的内存空间去处理,但是如果我们每次只接收一部分,接收完了去处理这部分数据,处理好了释放内存,这样就比较节省内存了。
  • 流式传输可以实现逐步读取数据和处理数据,这样既便数据还没有完全传输过来我们也可以使用。比如你在下载视频,如果是一次性的全部传输,那么只有等视频全部下载完了你才能去观看;但是如果是流式传输,我们一次只下载一部分视频,去读取处理这段视频,既便我们的视频还没有全部下载,我们也能观看已经下载好的小段视频。

Netty是如何支持网络中的流式传输数据的呢?
在Netty中应用程序中会有一个解码处理器,如下图:
在这里插入图片描述
每当应用程序需要从网络中读取数据的时候,第一个处理从网络中读取数据的处理器都是类似于ByteToMessageDecoder的解码处理器。但是网络冲刷数据到应用程序的解码处理器的时候,会把网络中的所有数据都一次性的冲刷到解码处理器的ByteBuf中吗?
答案是不会的。
比如现在有一个场景,客户端想要往服务端冲刷10个字节的数据,然后客户端会先把10个字节的数据冲刷到网络中,接着网络会把数据冲刷到服务端应用程序中,第一个接收网络中的数据的处理器就是解码处理器,但是因为Netty的设计考虑到了TCP网络中的流式数据传输,所以Netty也要能够支持流式数据传输,因此Netty就不会一次把网络中的数据全部接收,Netty可以选择一次只接收网络中的一部分数据。比如上图中的例子就是解码处理器中一次只接收网络中的4个字节的数据,服务端的解码处理器先接收到了网络中的4个字节的数据,然后处理这部分数据,当前处理器处理完之后再传给ChannelPipeline责任链中后续的ChannelHandler,继续处理网络中接受的这一小部分数据,最后服务端的入站处理器也可能往客户端冲刷数据,客户端可能拿到这部分数据后又会去处理,当然客户端从网络中拿数据的时候也是流式传输的,客户端也可以选择一次只读取一小部分数据,当然这些只是后话,我们重点要说的是当网络中的数据冲刷到应用程序的解码处理器之后,解码处理器一次只会处理一小部分数据,然后这一小部分数据都处理好了之后,解码处理器会继续读取网络中的数据,再继续处理新读取的这部分数据,然后再读取…
总之就是应用程序中的解码处理器一次不会读取网络中的全部数据处理,而是先读取一小部分数据,处理好了之后,再读取另外一小部分数据,再去处理…这就是Netty适应网络数据的流式传输所作的努力。

然后如果网络中的数据不足了,比如网络中只剩2个字节的数据,而我们应用程序的解码处理器一次会读取4个字节的数据,那么这个时候解码处理器会先保存这2个字节的数据,先不处理,等到后续网络中有了新的能传输的数据,解码处理器中能凑够4个字节了再做处理。

### Netty 数据传输流程详解 #### 1. 初始化阶段 在Netty应用程序启动过程中,会创建`Bootstrap`或`ServerBootstrap`实例,并配置各种参数,包括但不限于绑定端口、设置处理器链(Pipeline)。这些准备工作完成后,服务器进入监听状态等待客户端连接请求。 对于文件传输场景而言,如果涉及到大文件,则可以借助于`ChunkedWriteHandler`及其子类如`ChunkedFile`来完成文件的分割与逐块发送工作[^1]。这意味着当一个大型资源需要被传送出去的时候,不是一次性加载整个对象而是将其分解成更易于管理的小部分来进行流式的写出操作。 #### 2. 连接建立 一旦有新的客户端尝试接入服务端,NIO模型下的`NioEventLoop`线程就会负责监控对应的Selector上的注册事件。每当检测到一个新的连接到来时,便会触发相应的回调函数去接受这个新链接并初始化其专属的ChannelHandlerContext上下文环境[^5]。 #### 3. 数据接收/写入 随着通信双方成功建立了可靠的双向信道之后,任何一方都可以随时向对方发起数据交换活动。具体来说: - **读取**:当远程主机上有待读取的信息到达本地机器上时,操作系统内核空间里的套接口缓冲区内便会有可用字节存在;此时,关联着该socket channel的selector会被激活进而通知给eventloop线程使其能够调用适当的方法把外部输入流转移到应用层内存区域中供后续业务逻辑处理。 - **写入**:相反地,为了将某些内容传递至远端节点处,开发者只需简单地往channel pipeline末端追加目标buffer即可——当然在此之前可能还需要经历一系列编码转换工序以确保最终发出的消息格式符合预期标准。值得注意的是,在执行实际write动作前还可以附加一个`ChannelPromise`对象作为此次I/O任务完成与否的通知机制;而一旦flush指令被执行过后就无法再中途撤销此过程了[^2]。 #### 4. Flush 操作 在网络编程领域,“刷新”意味着立即将当前已缓存但尚未真正送出的数据包推送出去。对于基于Netty框架构建的应用程序来讲,这一行为是由`ChannelHandlerContext.flush()`方法所驱动的,它会促使内部维护的一系列outbound handler依次作用直至最底层的真实物理连接之上,从而实现了从用户态到核心态再到网络层面完整的数据流动路径[^3]。 #### 5. 断开连接 最后,无论是由于正常结束还是异常情况的发生而导致通讯终止的情况下,都应当妥善释放掉所有占用的系统资源,比如关闭不再使用的channels、清理残留的状态记录等。 ```java // 示例代码展示如何优雅地关闭一个Channel public void closeGracefully(Channel channel){ if (null != channel && channel.isActive()){ channel.close().addListener((ChannelFutureListener) future -> { System.out.println("Channel closed successfully."); }); } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Mr-X~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值