Netty数据传输

无意中发现了一个巨牛的人工智能教程,忍不住分享一下给大家。教程不仅是零基础,通俗易懂,而且非常风趣幽默,像看小说一样!觉得太牛了,所以分享给大家。点这里可以跳转到教程。

网络应用程序一个很重要的工作是传输数据。传输数据的过程不一样取决是使用哪种交通工具,但是传输的方式是一样的:都是以字节码传输。Java 开发网络程序传输数据的过程和方式是被抽象了的,我们不需要关注底层接口,只需要使用 Java API 或其他网络框架如 Netty 就能达到传输数据的目的。发送数据和接收数据都是字节码。Nothingmore,nothing less。

Netty传输API
传输 API 的核心是 Channel 接口,它用于所有出站的操作。Channel 接口的类层次结构如下:
这里写图片描述

如上图所示,每个 Channel 都会分配一个 ChannelPipeline 和 ChannelConfig。
ChannelConfig 负责设置并存储配置,并允许在运行期间更新它们。传输一般有特定的配置设置,只作用于传输,没有其他的实现。ChannelPipeline 容纳了使用的 ChannelHandler实例,这些 ChannelHandler 将处理通道传递的“入站”和“出站”数据。
ChannelHandler 的实现允许你改变数据状态和传输数据,ChannelHandler 是 Netty 的重点概念。

现在我们可以使用 ChannelHandler 做下面一些事情:

  • 传输数据时,将数据从一种格式转换到另一种格式
  • 异常通知
  • Channel 变为有效或无效时获得通知
  • Channel 被注册或从 EventLoop 中注销时获得通知
  • 通知用户特定事件

这些 ChannelHandler 实例添加到 ChannelPipeline 中,在 ChannelPipeline 中按顺序逐个执行。它类似于一个链条。
ChannelPipeline 实现了拦截过滤器模式, 这意味着我们连接不同的 ChannelHandler来拦截并处理经过 ChannelPipeline 的数据或事件。可以把 ChannelPipeline 想象成 UNIX管道,它允许不同的命令链(ChannelHandler 相当于命令)。你还可以在运行时根据需要添加 ChannelHandler 实例到 ChannelPipeline 或从 ChannelPipeline 中删除,这能帮助我们构建高度灵活的 Netty 程序。此外,访问指定的 ChannelPipeline 和 ChannelConfig,你能在 Channel 自身上进行操作。Channel 提供了很多方法,如下列表:

  • eventLoop(),返回分配给 Channel 的 EventLoop
  • pipeline(),返回分配给 Channel 的 ChannelPipeline
  • isActive(),返回 Channel 是否激活,已激活说明与远程连接对等
  • localAddress(),返回已绑定的本地 SocketAddress
  • remoteAddress(),返回已绑定的远程 SocketAddress
  • write(),写数据到远程客户端,数据通过 ChannelPipeline 传输过去

Netty 包含的传输实现

Netty 自带了一些传输协议的实现,虽然没有支持所有的传输协议,但是其自带的已足够我们来使用。Netty 应用程序的传输协议依赖于底层协议,本节我们将学习 Netty 中的传输协议。

Netty 中的传输方式有如下几种:

  • NIO,io.netty.channel.socket.nio,基于 java.nio.channels
    的工具包,使用选择器作为基础的方法。
  • OIO,io.netty.channel.socket.oio,基于 java.net 的工具包,使用阻塞流。
  • Local,io.netty.channel.local,用来在虚拟机之间本地通信。
  • Embedded,io.netty.channel.embedded,嵌入传输,它允许在没有真正网络的运输中使用 ChannelHandler,可以非常有用的来测试 ChannelHandler 的实现。

NIO - Nonblocking I/O

NIO 传输是目前最常用的方式, 它通过使用选择器提供了完全异步的方式操作所有的I/O,NIO 从 Java 1.4 才被提供。NIO 中,我们可以注册一个通道或获得某个通道的改变的状态,通道状态有下面几种改变:

  • 一个新的 Channel 被接受并已准备好
  • Channel 连接完成
  • Channel 中有数据并已准备好读取
  • Channel 发送数据出去

处理完改变的状态后需重新设置他们的状态,用一个线程来检查是否有已准备好的Channel,如果有则执行相关事件。在这里可能只同时一个注册的事件而忽略其他的。选择器所支持的操作在 SelectionKey 中定义,具体如下:

  • OP_ACCEPT,有新连接时得到通知
  • OP_CONNECT,连接完成后得到通知
  • OP_READ,准备好读取数据时得到通知
  • OP_WRITE,写入数据到通道时得到通知

Netty 中的 NIO 传输就是基于这样的模型来接收和发送数据,通过封装将自己的接口提供给用户使用,这完全隐藏了内部实现。如前面所说,Netty 隐藏内部的实现细节,将抽象出来的 API 暴露出来供使用,下面是处理流程图:
这里写图片描述
NIO 在处理过程也会有一定的延迟,若连接数不大的话,延迟一般在毫秒级,但是其吞吐量依然比 OIO 模式的要高。Netty 中的 NIO 传输是“zero-file-copy”,也就是零文件复制,这种机制可以让程序速度更快, 更高效的从文件系统中传输内容, 零复制就是我们的应用程序不会将发送的数据先复制到 JVM 堆栈在进行处理,而是直接从内核空间操作。接下来我
们将讨论 OIO 传输,它是阻塞的。

OIO - Old blocking I/O

OIO 就是 java 中提供的 Socket 接口,java 最开始只提供了阻塞的 Socket,阻塞会导致程序性能低。下面是 OIO的处理流程图

Local - In VM transport

Netty 包含了本地传输,这个传输实现使用相同的 API 用于虚拟机之间的通信,传输是完全异步的。 每个 Channel 使用唯一的 SocketAddress, 客户端通过使用 SocketAddress进行连接,在服务器会被注册为长期运行,一旦通道关闭,它会自动注销,客户端无法再使用它。 连接到本地传输服务器的行为与其他的传输实现几乎是相同的, 需要注意的一个重点是只能在本地的服务器和客户端上使用它们。Local 未绑定任何 Socket,值提供 JVM 进程之间的通信。

Embedded transport

Netty 还包括嵌入传输, 与之前讲述的其他传输实现比较, 它是不是一个真的传输呢?若不是一个真的传输,我们用它可以做什么呢?Embedded transport 允许更容易的使用不同的 ChannelHandler 之间的交互,这也更容易嵌入到其他的 ChannelHandler 实例并像一个辅助类一样使用它们。它一般用来测试特定的 ChannelHandler 实现,也可以在ChannelHandler 中重新使用一些 ChannelHandler 来进行扩展,为了实现这样的目的,它 自带了一个具体的 Channel 实现,即:EmbeddedChannel。

每种传输的使用时机

  • OIO,在低连接数、需要低延迟时、阻塞时使用
  • NIO,在高连接数时使用
  • Local,在同一个 JVM 内通信时使用
  • Embedded,测试 ChannelHandler 时使用
### 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
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值