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》

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,378评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,356评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,702评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,259评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,263评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,036评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,349评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,979评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,469评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,938评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,059评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,703评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,257评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,262评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,485评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,501评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,792评论 2 345

推荐阅读更多精彩内容