Netty in action ——— 传输协议

本文是Netty文集中“Netty in action”系列的文章。主要是对Norman Maurer and Marvin Allen Wolfthal 的 《Netty in action》一书简要翻译,同时对重要点加上一些自己补充和扩展。

概要

  • OIO —— 阻塞传输
  • NIO —— 异步传输
  • Local transport —— JVM内部的异步通讯
  • Embedded transport —— 测试你的ChannelHandlers

数据流经一个网络时总是有一样的类型:字节。 使用JAVA提供OIO API 和 NIO API 有着很大的不同。 Netty使用了一个公共的API层,该API涵盖了所以的传输实现

在Netty中使用OIO 和 NIO

通过Netty实现阻塞网络(OIO)

通过Netty实现异步网络(NIO)

传输协议API

传输API的关键是 Channel 接口,Channel接口被用于所有的I/O操作。

一个Channel会被分配有一个ChannelPipeline和一个ChannelConfig。 ChannelConfig持有所有设置Channel的配置并支持热修改。因为一个指定的传输可能有它独特的设置,它可以实现一个ChannelConfig的子类。 因为Channel都是独一无二的,所以声明Channel为java.lang.Comparable的子类用意是为了保证排序。因此,AbstractChannel对compareTo方法实现:当两个不同的channel实例返回了相同的hashCode将抛出一个Error异常。 ChannelPipeline持有所以的ChannelHandler实例,这些ChannelHandler实例将被应用到入站和出站数据和事件上。这些ChannelHandlers实现了用于处理状态改变和数据处理的应用逻辑。

典型的ChannelHandlers的使用包括:

  • 转换数据格式从一种到另外一种
  • 提供异常的通知
  • 提供一个Channel活跃( active )或不活跃( inactive )的通知
  • 提供当一个Channel注册( registered )到EventLoop或从EventLoop注销( deregistered )的通知
  • 提供关于用户定义事件的通知

Intercepting Filter :ChannelPipeline实现了一个常见的设计模式,拦截过滤器。UNIX 的管道是另一个常见的例子:指令被链接到一起,通过一个指令的输出连接到下一个行的输入。( 也就是将当前指令的输出作为下一条指令的输入内容,以此方式将指令给链接到一起 )

你可以通过需要添加或删除ChannelHandler来即时修改ChannelPipeline。Netty的这个能力能被利用与构建一个高灵活性的应用。

Netty的Channel实现是线程安全的,所以你能够存有一个Channel的引用,并在你需要的任何时候使用它去写数据到远端,甚至可以多个线程同时使用这个引用。

在多个线程中使用同个Channel

注意:消息将被保证按顺序发送!

包含的传输协议

Netty提供的传输协议

NIO —— 非阻塞 I/O

NIO提供所有I/O操作的完全异步实现。它使用了基于selector的API。 selector的一个基本概念是作为一个注册表,你请求收到一个通知当Channel的状态改变时。 可能的状态改变有:

  • OP_ACCEPT :个新Channel被接收并准备好 ( 服务端 )
  • OP_CONNECT :一个Channel连接已经完成 ( 客户端 )
  • OP_READ :一个Channel的数据已经准备好被读取
  • OP_WRITE :一个Channel的写数据有效。 OP_WRITE需要特别注意。该事件表示的是:请求收到通知,当Channel能够写入更多的数据时。这是当socket缓存已经完全满的处理情况( 即,当socket缓存已经满了,但还有数据未写完时,需要注册该事件为希望得到通知的事件 ),这经常发生在当数据的传输速度远快于远端处理数据的速度时。

在应用对状态的改变作出反应后,selector将被重置,并且重复该过程。 这些模式被合并到一个指定的集合中,应用请求得到一个通知当该集合中包含的状态改变时。

这些NIO的内部实现被用户级API所隐藏,该API是Netty所有传输的共同实现。

零拷贝是目前仅适用于NIO和Epoll传输的功能。它允许你 快速且高效的移动数据从一个文件系统到网络,而无需从内核空间拷贝数据到用户空间,这能够显著提升如FTP 或 HTTP协议的性能。零拷贝功能并不是所有的操作系统都支持的。需要指明的零拷贝不能用于实现文件系统的数据加密或压缩,它只能够传输未加工的文件内容。相反的,传输一个已经被加密过的文件不是问题。 也就是说,有些文件系统不是单纯的操作一个数据的传输,还要对文件进行一些加密和压缩的操作,而这些需要将数据拷贝到用户空间并对数据进行修改操作。所以像这样的文件操作是不支持零拷贝的。

Epoll —— Linux的本地非阻塞传输

正如我们前面说展示的,Netty的NIO传输是基于java提供的异步/非阻塞网络的通用抽象。尽管这确保了Netty的NIO能在任何平台上使用;但它也有限制,因为JDK必须妥协才能让所有的系统都具有相同的功能。 Linux作为日渐重要的高性能网络平台,这导致了许多先进功能的开发,包括epoll,一个高可扩展的I/O事件通知功能。 Netty为Linux提供了一个使用epoll的NIO API,通过该方式与你的设计更加一致并且使中断的使用成本更低。在大负载的性能上,Linux NIO 实现优于JDK NIO 的实现。

OIO —— 老的阻塞 I/O

Netty OIO传输实现代表着一种妥协:它通过通用的传输API来访问,但因为他构建在java.net的阻塞实现上,它是非异步的。它非常适用于某些情况。

鉴于此,你可能担心Netty如何提供一个NIO通过一样的API用于异步的传输。这个答案是Netty使用 SO_TIMEOUT Socket 标志,该标志指定了等待I/O操作完成的最大毫秒数。如果一个操作在指定期间内没有完成,那么将抛出一个SocketTimeoutException异常。Netty捕获这个异常并继续处理循环。在下一次EventLoop运行时,将再尝试一次前面的逻辑。这是一个像Netty的异步框架能够支持OIO的唯一方式。

我们通过OioSocketChannel的读操作来了解下关于上面描述的源码实现:

?这个读操操作如果抛出超时异常,则会返回读到的字节数为0。这里大家可以关注另外一点,在当socket关闭是,返回时可读字节数为-1。这个是和NIO的模式相一致的,在NIO中如果read返回的可读字节数为-1时,也就表示当远端连接已经关闭了。

用于JVM内部通讯的本地传输

Netty提供了一个本地传输用于客户端和服务端在相同JVM的异步通讯。 在该传输中,一个同服务端Channel关联的SocketAddress不会绑定到一个物理网络地址;当然,它会被保存到一个注册表在服务端运行的期间,并在Channel ( 这里指服务端的channel )关闭时被注销。所以传输没有通过真实的网络传输,所以它不能通过其他传输的实现来进行交互 ( 也就是不能同其他传输,如NIO transport 进行数据的传输交互 )。所以客户端希望连接一个在同一JVM的使用了该传输方式的服务端,那么客户端也需要使用该传输方式。除了这个限制,它与其他传输方式并无不同。

内嵌的传输协议

Netty提供了一个附加的传输方式,该传输方式允许你一个ChannelHandler作为辅助类嵌入到其他ChannelHandler中。照这样,你能在不修改内部代码的情况下够扩展一个ChannelHandler的功能。

EmbeddedChannel 允许你一个ChannelHandler作为辅助类嵌入到其他ChannelHandler中的方式类似如下:

这样就可以传入辅助channelHandler和原channelHandler,得到一个嵌套的channelHandler

传输协议使用场景

并不是所有的传输方式都支持所有的传输协议。

这里是你可能会遇到的使用场景:

  • 非阻塞代码库 —— 如果你不要一个阻塞调用在你的代码库中,或者你能够限制它们,在Linux上使用NIO或epoll经常是个好主意。当NIO/epoll 用于处理许多并发的连接,它也能通过更少的线程来更好的工作,尤其是在连接间共享线程的方式。
  • 阻塞代码库 —— 正如我们已经说到的,如果你的代码库严重依赖于阻塞I/O,并且你的应用有对应于此的设计。如果你直接转为Netty的NIO传输方式,你可能会遇到阻塞操作问题。对比与重写你的代码去完成这些,考虑一个阶段性的迁移:从OIO开始,然后转移到NIO(或epoll如果你在Linux上)当你改进你的代码后。
  • 相同JVM的内部通讯 —— 在相同JVM的内部通讯不需要暴露一个服务在网络表现层,在相同JVM的内部通讯为本地传输的完美使用情况。这将消除真实网络操作的所有开销,同时仍然使用你的Netty代码库。如果需要暴露一个服务在网络上,你只需要简单的修改传输方式为NIO或OIO。
  • 测试你的ChannelHandler的实现 —— 如果你想要写单元测试用于你的ChannelHandler实现,考虑使用内嵌的传输方式。这将使测试你的代码变得简单,而不需创建许多的mock对象。你的类将仍然遵循通用API的事件流,保证ChannelHandler将在真实传输中正确工作。

后记

本文主要对Netty的支持的传输协议进行了介绍。即便是不同的传输协议,Netty也为我们提供了一致的API接口,它将大量复杂的处理逻辑封装在了源码实现中,为用户提供了简易且方便的API接口,这也是Netty设计一致性的例子之一。 若文章有任何错误,望大家不吝指教:)

参考

《Netty in action》

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏阿杜的世界

应用GC长时间停顿分析

早上被报警叫醒,使用gceasy.io分析了服务器的gc日志,报告见:2017-05-28 gc.log报告

563
来自专栏大内老A

《WCF技术剖析》博文系列汇总[持续更新中]

近半年以来,一直忙于我的第一本WCF专著《WCF技术剖析(卷1)》的写作,一直无暇管理自己的Blog。在《WCF技术剖析(卷1)》写作期间,对WCF又有了新的感...

1638
来自专栏IT技术精选文摘

从构建分布式秒杀系统聊聊分布式锁

最近懒成一坨屎,学不动系列一波接一波,大多还都是底层原理相关的。上周末抽时间重读了周志明大湿的 JVM 高效并发部分,每读一遍都有不同的感悟。路漫漫,借此,把前...

773
来自专栏企鹅号快讯

Spring编程式事务处理不当引起的连接泄露事件

某一日正在孜孜不倦的研究代码,忽然测试童鞋说系统服务挂了,完全不可用。 程序大量抛出如下异常: 对于程序员来说,系统宕机就是军令,更可况是难得一见的连接池泄露问...

1966
来自专栏大宽宽的碎碎念

聊聊BIO,NIO和AIO (2)磁盘IO磁盘IO的优化AIO反思AIO

5379
来自专栏熊二哥

快速入门系列--WCF--04元数据和异常处理

本章节将进行元数据和异常处理的介绍,这部分内容概念型比较强,可以快速浏览一下就好。 ? 客户端和服务器借助于终结点进行通信,服务的提供者通过一个或者多个终结点...

1758
来自专栏人人都是极客

聊聊Linux IO(中)

结合这个图,想想Linux系统编程里用到的Buffered IO、mmap(2)、Direct IO,这些机制怎么和Linux IO栈联系起来呢?上面的图有点复...

812
来自专栏Java技术

【面试题】2018年最全Java面试通关秘籍第二套!

注:本文是从众多面试者的面试经验中整理而来,其中不少是本人出的一些题目,网络资源众多,如有雷同,纯属巧合!禁止一切形式的碰瓷行为!未经允许禁止一切形式的转载和复...

661
来自专栏Golang语言社区

论获取缓存值的正确姿势

论获取缓存值的正确姿势 cache 时至今日,大家对缓存想必不在陌生。我们身边各种系统中或多或少的都存在缓存,自从有个缓存,我们可以减少很多计算压力,提高应用程...

3568
来自专栏微信公众号:Java团长

Java多线程的应用场景和应用目的举例

多线程用于堆积处理,就像一个大土堆,一个推土机很慢,那么10个推土机一起来处理,当然速度就快了,不过由于位置的限制,如果20个推土机,那么推土机之间会产生相互的...

671

扫码关注云+社区