传输API
传输 API 的核心是 Channel 接口,它被用于所有的 I/O 操作。Channel 实现是线程安全的,即使你在多个线程中使用它向远程节点写数据。 Channel 类的层次结构如下:
每个 Channel 都会被分配给一个 ChannelPipeline 和 ChannelConfig 。ChannelConfig 包含了该 Channel 的所有配置,并支持热更新。由于特定的传输可能具有独特的设置,所以它可能会实现一个 ChannelConfig 的子类型。
ChannelPipeline 持有所有将应用于入站和出站数据以及事件的 ChannelHandler 实例,这些 ChannelHandler 实现了应用程序用于处理状态变化以及数据处理的逻辑。
ChannelHandler 的典型用途包括:
- 将数据从一种格式转换为另一种格式。
- 提供异常的通知。
- 提供 Channel 变为活动的或者非活动的通知。
- 提供当 Channel 注册到 EventLoop 或者从 EventLoop 注销时的通知。
- 提供有关用户自定义事件的通知。
ChannelPipeline 实现了一种常见的设计模式拦截过滤器(Intercepting Filter)。UNIX 管道是另外一个熟悉的例子:多个命令被链接在一起,其中一个命令的输出端将连接到命令行中下一个命令的输入端。
你也可以根据需要通过或者移除 ChannelHandler 实例来修改 ChannelPipeline 。通过利用 Netty 的这项能力可以构建出高度灵活的应用程序。例如:每当 STARTTLS 协议被请求时,你可以简单地通过向 ChannelPipeline 添加一个适当的 ChannelHandler (SslHandler)来按需地支持 STARTTLS 协议。
Channel 除了访问所分配的 ChannelPipeline 和 ChannelConfig 之外,还有其他一些常用方法:
eventLoop
:返回分配给 Channel 的 EventLoop 。pipeline
:返回分配给 Channel 的 ChannelPipeline 。isActive
:如果 Channel 是活动的,则返回 true 。活动的意义可能依赖于底层的传输。例如,一个 Socket 传输一旦连接到了远程节点便是活动的,而一个 Datagram 传输一旦被打开便是活动的。localAddress
:返回本地的 SocketAddress 。remoteAddress
:返回远程的 SocketAddress 。write
:将数据写到远程节点。这个数据将被传递给 ChannelPipeline ,并且排队直到它被冲刷。flush
:将之前已写的数据冲刷到底层传输,如一个 Socket 。writeAndFlush
:一个简便的方法,如同调用 write() 并接着调用 flush() 。
内置的传输
Netty 内置了一些可以开箱即用的传输。因为并不是它们所有的传输都支持每一种协议,所以你必须选择一个和你的应用程序所使用的协议相容的传输。
NIO(io.netty.channel.socket.nio)
:使用 java.nio.channels 包作为基础,基于选择器的方式。Epoll(io.netty.channel.epoll)
:由 JNI 驱动的 epoll() 和非阻塞 IO 。这个传输支持只有在 Linux 上可用的多种特性,如 SO_REUSEPORT ,比 NIO 传输更快,而且是完全非阻塞的。OIO(io.netty.channel.socket.oio)
:使用 java.net 包作为基础,使用阻塞流。Local(io.netty.channel.local)
:可用在 VM 内部通过管道进行通信的本地传输。Embedded(io.netty.channel.embedded)
:Embedded 传输,允许使用 ChannelHandler 而又不需要一个真正的基于网络的传输。这在测试你的 ChannelHandler 实现时非常有用。
NIO - 非阻塞 I/O
NIO 提供了一个所有 I/O 操作的全异步的实现。它利用了自 NIO 子系统被引入 JDK 1.4 时便可用的基于选择器的 API 。
选择器背后的基本概念是充当一个注册表,在那里你将可用请求在 Channel 的状态发生变化时得到通知。可能的状态变化有:
- 新的 Channel 已被接受并且就绪。
- Channel 连接已经完成。
- Channel 有已经就绪的可供读取的数据。
- Channel 可用于写数据。
选择器运行在一个检查状态变化并对其做出响应的线程上,在应用程序对状态的改变做出响应之后,选择器将会被重置,并将重复这个过程。
java.nio.channels.SelectionKey
类定义了一些位模式,可以组合起来定义一组应用程序正在请求通知的状态变化集。
OP_ACCEPT
:请求在接受新连接并创建 Channel 时获得通知。OP_CONNECT
:请求在建立一个连接时获得通知。OP_READ
:请求当数据已经就绪,可以从 Channel 中读取时获得通知。OP_WRITE
:请求当可以向 Channel 中写更多的数据时获得通知。这处理了套接字缓冲区被完全填满时的情况,这种情况通常发生在数据的发送速度比远程节点可处理的速度更快的时候。
零拷贝
零拷贝(zero-copy)是一种目前只有在使用 NIO 和 Epoll 传输时才可以使用的特性。它使你可以快速高效地将数据从文件系统移动到网络接口,而不需要将其从内核空间复制到用户空间,其在像 FTP 或者 HTTP 这样的协议中可以显著地提升性能。但是,并不是所有的操作系统都支持这一特性。特别地,它对于实现了数据加密或压缩的文件系统是不可用的,只能传输文件的原始内容。反过来说,传输已被加密的文件则不是问题。
Epoll - 用于 Linux 的本地非阻塞传输
Netty 为 Linux 提供了一组 NIO API ,其以一种和它本身的设计更加一致的方式使用 epoll ,并且以一种更加轻量的方式使用中断。如果你的应用程序旨在运行于 Linux 系统,那么请考虑利用这个版本的传输。在高负载下它的性能要优于 JDK 的 NIO 实现。
如果想使用 epoll 替代 NIO ,只需要在引导服务器的时候,将 NioEventLoopGroup
替换为 EpollEventLoopGroup
,并且将 NioServerSocketChannel.class
替换为 EpollServerSocketChannel.class
。参考下面(示例原地址: http://www.shareapi.cn/2496.html)的示例:
public class EchoServer {
private final int port;
public EchoServer(int port) {
this.port = port;
}
public void start() throws Exception {
final EchoServerHandler serverHandler = new EchoServerHandler();
EventLoopGroup group = new EpollEventLoopGroup();
try {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(group)
// 指定所使用的 Epoll 传输 Channel
.channel(EpollServerSocketChannel.class)
// 使用指定的端口设置套接字地址
.localAddress(new InetSocketAddress(port))
// 添加一个 EchoServerHandler 到子 Channel 的 ChannelPipeline
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
// EchoServerHandler 被标注为 @Sharable,所以我们可以总是使用同样的实例
ch.pipeline().addLast(serverHandler);
}
});
// 异步地绑定服务器,调用 sync() 方法阻塞等待直到绑定完成
ChannelFuture future = bootstrap.bind().sync();
// 获取 Channel 的 CloseFuture,并且阻塞当前线程直到它完成
future.channel().closeFuture().sync();
} finally {
// 关闭 EventLoopGroup ,释放所有的资源
group.shutdownGracefully().sync();
}
}
public static void main(String[] args) throws Exception {
new EchoServer(8888).start();
}
}
用于 JVM 内部通信的 Local 传输
Local 传输用于在同一个 JVM 中运行的客户端和服务器程序之间的异步通信。 这个传输中,和服务器 Channel 相关联的 SocketAddress
并没有绑定物理网络地址。所以这个传输并不接受真正的网络流量,并且不能喝其他传输实现进行互操作,客户端希望连接到(在同一 JVM 中)使用了这个传输的服务器端时也必须使用它。
Embedded 传输
Embedded 传输可以将一组 ChannelHandler 作为帮助器类嵌入到其他的 ChannelHandler 内部,实现扩展一个 ChannelHandler 的功能,而不需要修改其内部代码。