04 传输

点击查看 《Netty in Action》笔记目录

本文是对《Netty in Action》第4章内容的笔记和翻译,主要内容包括:
* OIO:阻塞传输
* NIO:异步传输
* 本地传输:和 JVM 异步交互
* 测试你的 ChannelHandler

案例学习:传输迁移

不通过 Netty 使用 OIO 和 NIO

我们将要展示基于 JDK API 的阻塞(OIO)和异步(NIO)应用版本。下面展示了阻塞实现版本。

下面展示了非阻塞版本。

通过 Netty 使用 OIO 和 NIO

非阻塞 Netty 版本

因为 Netty 对每一种传输实现都暴露了相同的 API,所以无论你采用哪一种传输,你的代码几乎不用修改。

传输 API

传输 API 的核心是 Channel 接口,所有的 I/O 操作都会使用这个接口。Channel 类的继承关系如图4.1所示。

Channel 中组合了 ChannelPipelineChannelConfigChannelConfig 保存了 Channel 所有的配置,并且支持热更新。

由于 Channel 是独一无二的,所以 Channel 继承了 java.lang.Comparable 接口,从而保证对象的有序。因此,如果两个 Channel 实例返回相同的 hash 值,那 AbstractChannelcompareTo() 的实现将会抛出 Error

图4.1 Channel接口继承关系

ChannelHandler 常用于:
* 转换数据格式。
* 提供异常通知。
* 提供 Channel 被激活或者关闭的通知。
* 提供 ChannelEventLoop 中注册和取消注册的通知。
* 提供用户定义事件的通知。

拦截过滤器
ChannelPipeline 实现了一个常用设计模式:拦截过滤器。UNIX 管道是这个模式的另一个典型例子:命令被链式编排,每个命令的输出会作为接下来一个命令的输入。

你同样可以在运行时按照你的需求为 ChannelPipeline 添加或删除 ChannelHandler

除了可以使用 ChannelPipelineChannelConfig 之外,你还可以使用 Channel 的方法,下表展示了一些常用的方法。

方法名称描述
eventLoop返回与 Channel 绑定的 EventLoop
pipeline返回与 Channel 绑定的 ChannelPipeline
isActive如果 Channel 是激活(active)的,返回 true。 Active 的含义取决于下层的传输协议。例如, Socket 中的 active 是指与远端建立连接;Datagram 中的 active 是指 Datagram 打开。
localAddress返回本地的 SocketAddress.
remoteAddress返回远端的 SocketAddress.
write将数据写到远端。数据会经过 ChannelPipeline,并将会进行排队,直到刷新时才会发送。
flush将之前写的数据刷新到底层的传输协议中,例如:Socket
writeAndFlush先调用 write() ,然后调用 flush()

下图展示了如何利用 Channel.writeAndFlush() 实现最常见的发送数据到远端的任务。

Netty 的 Channel 实现是线程安全的,所以你可以存储 Channel 的一个引用,并且在你需要发送数据的时候使用它,即使在多线程环境中也没有关系。

包含的传输

下表展示了 Netty 中提供的传输。

名称所在的包描述
NIOio.netty.channel.socket.niojava.nio.channels 包为基础,是一个基于 selector 的实现方案。
Epollio.netty.channel.epoll通过 JNI 使用 epoll() 进入非阻塞 IO。这个传输的特性只适用于 Linux,如:SO_REUSEPORT。它比 NIO 传输要快,是完全非阻塞。
OIOio.netty.channel.socket.oio使用 java.net 包作为基础,使用阻塞流。
Localio.netty.channel.local一个本地传输,可以被用来通过管道与 VM 交互。
Embeddedio.netty.channel.embedded一个嵌入式传输协议。通过它,你可以在虚拟的传输协议上使用 ChannelHandler。这在测试 ChannelHandler 实现时很有用。

NIO — 非阻塞 I/O

Selector 背后基本的原理是:提供一个注册器,通过注册感兴趣的事件,当 Channel 状态改变的时候你可以得到通知。可能的状态改变包括:
* 新的 Channel 被接收到并且已经准备好。
* Channel 连接已经完成。
* Channel 已经准备好为读操作提供数据。
* Channel 已经准备好,可以写入数据了。

下表展示了 java.nio.channels.SelectionKey 类定义的一些模式。

方法名称描述
OP_ACCEPT当一个新的请求被接受并且 Channel 被创建的时候,会进行通知。
OP_CONNECT当连接建立的时候,会进行通知。
OP_READ当可以从 Channel 中读取数据的时候,会进行通知。
OP_WRITE当可以往 Channel 中写入更多数据的时候,会进行通知。这个事件在 socket 缓存被填满的时候发生,通常意味着数据发送的速率操过了远端的处理能力。

NIO 内部实现的细节,在所有的 Netty 用户级别的 API 中都被隐藏了。图4.2 展示了处理的数据流。

图4.2 选择和处理状态改变

零拷贝
零拷贝这个特性目前只在 NIO 和 Epoll 传输中被支持。通过它可以使你快速并高效地从一个文件系统转移数据到网络中,不需要从内核空间拷贝到用户空间,这大大提高了 FTP、HTTP 等协议的传输效率。这个特性并不是被所有的操作系统支持。确切地说,它并不适用于采用加密或压缩的文件系统,只有文件的原始数据可以被转移。但对于已经加密好的文件是可以被传输的。

Epoll — 针对 Linux 的原生非阻塞传输

Netty 为 Linux 系统提供了一个使用 epoll 的 NIO API。在某种程度上,这与 Netty 本身的设计更加一致,并且比中断的实现方式开销更少。如果你的应用准备在 Linux 上使用,那么可以考虑这个版本,你会发现在高负载的情况下,它的性能表现会好于 JDK 本生的 NIO 实现。

这个传输和图4.2中展示的传输语义相同,并且它的使用是很简单的,可以参考 list 4.4。为了使用 epoll 版的 NIO,可以将 NioEventLoopGroup 替换为 EpollEventLoopGroup,将 NioServerSocketChannel.class 替换为 EpollServerSocketChannel.class

OIO — 阻塞型 I/O

图4.3 OIO 处理逻辑]

你可能会对此感到好奇:Netty 是如何通过相同的 API 来支持异步传输的 NIO?答案是 Netty 使用了 SO_TIMEOUT Socket 标志。 这个标志表明等待 I/O 操作完成的最大毫秒数。如果操作在指定的时间间隔内没有完成,那么将会抛出 SocketTimeoutException。Netty 会捕捉这个异常,并继续循环处理。

Local — 通过本地传输与 JVM 交互

Netty 为同一个 JVM 上的客户端和服务端的异步交互提供了一个本地传输。

因为本地传输并没有真正的网络传输,所以它不能和其它传输协议一起协作。因此,一个客户端如果想通过这个传输来连接服务端的话(在同一个 JVM 上),服务端也必须使用这个传输。除了这个限制,它的使用和其它传输协议是一样的。

嵌入传输

Netty 还提供了一个传输,可以允许你嵌入在 ChannelHandler 中嵌入一个帮助类 ChannelHandler。在这种模式下,你可以扩展 ChannelHandler 的功能,而不用内部的代码。

传输使用例子

下表展示了当前版本中传输支持的协议。

TransportTCPUDPSCTPUDT
NIO支持支持支持支持
Linux 上的 Epoll支持支持--
OIO支持支持支持

在 Linux 上开启 SCTP
SCTP 需要内核支持并且需要安装一些用户库。例如,在 Ubuntu 上可以使用下面的命令:

# sudo apt-get install libsctp1

在 Fedora 上,你需要使用 yum:

# sudo yum install kernel-modules-extra.x86_64 lksctp-tools.x86_64

请阅读你的 Linux 发布版本的手册,了解如何开启 SCTP。

下面是你可能会遇到的一些用例。
* 基于非阻塞的代码:如果你在你的代码中不阻塞调用,或者你要限制阻塞的调用,推荐使用 NIO(在 Linux 上使用 epoll)。NIO/epoll 主要是用于处理很多并发的连接,在数量较少的时候变现的也不错,尤其是在多个连接共享线程的情况下。
* 基于阻塞的代码:正如我们之前提过,如果你的代码很依赖阻塞 I/O,并且你应用具有相应的设计,那么你直接从阻塞 I/O 切换到 Netty 的 NIO 传输,可能会遇到麻烦。最好不要直接重写代码来适应新的 I/O, 可以考虑一个阶段性的迁移:从 OIO 开始,迁移到 NIO(或者你使用 Linux 的话就是 epoll)。
* 在同一个 JVM 上进行交互:如果要在同一个 JVM 上进行交互不需要使用网络,使用本地传输再恰当不过了。这可以在使用 Netty 代码的同时消除真实网络的开销。如果你需要在真实网络上使用该服务,你只需将传输替换为 NIO 或者 OIO。
* 测试你的 ChannelHandler 实现:如果你想为你的 ChannelHandler 实现写单元测试,可以考虑使用嵌入传输。这可以使得你在测试实现的时候不用创建很多的模拟对象。你写的类还是会和通用的 API 事件流保持一致,保证 ChannelHandler 在真实传输上表现正确。

下表总结了我们提到过的用例。

应用需求推荐使用的协议
非阻塞或者普通的尝试NIO(或者在 Linux 上使用 epoll)
阻塞OIO
在同一个 JVM 上交互Local
测试你的 ChannelHandler 实现Embedded
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值