pipimob 2019-07-01
这一节我们将确认Netty提供的核心功能是什么,以及它们怎么构成一个完整的网络应用开发堆栈。
Netty使用它自己的缓存API来表示字节序列而不是NIO的ByteBuffer。Netty的新缓存类——ChannelBuffer,彻底解决了ByteBuffer的问题,满足了网络应用开发者的日常需求。这里列举几个很酷的特点:
当在通信层传输数据时,数据经常需要被组合或者切分。比如一次装载被分成多个包,但是经常需要组合起来解码。
传统方式来说,多个包的数据通过复制到一个新的字节缓存而组合起来。
Netty支持零复制实现,通过让一个新的ChannelBuffer“指向”所有要求组合的缓存,从而避免了复制操作。
传统Java的I/O API对不同的传输类型提供不同的类和方法。举个例子,java.net.Socket和java.net.DatagramSocket没有公共父类因此它们在执行socket I/O的方式完全不一样。
这种不匹配的结果就是使得应用程序从一种传输方式移植成另一种变得十分困难。也就是说你需要支持额外的传输方式因为应用程序的网络层经常涉及到重写。逻辑上,许多协议可以在不只一种传输方式,比如TCP/IP、UDO/IP、SCTP和串行通信上运行。
更糟的是,Java的新I/O(NIO)API继承了老阻塞I/O(OIO)API的不兼容性,并且在下一个版本NIO.2(AIO)继续这样做。因为这些API在设计和表现特点上完全不同,你经常被迫在应用程序开始实现之前就需要确定好依赖那种API。
举个例子,你可能想用OIO方式开始因为你服务的客户数量很小且用OIO写一个socket服务比用NIO要简单得多。但是,当你的业务呈指数级上升然后你需要同时服务成千上万的客户时,麻烦就大了。你也可以用NIO方式开始,但是这可能会阻碍开发速度,毕竟NIO的选择器API很复杂。
Netty拥有统一的异步I/O接口——Channel,它抽象了点对点通信要求的所有操作。只要你用一种Netty传输写的应用程序,可以在其他Netty传输上运行。Nett通过统一的API提供了必要的传输方式:
要从一种传输方式切换到另一种,只需要改动几行代码,比如选择另一个ChannelFactory实现。
你甚至可以使用还没发布的新传输方式,比如串行通信传输,同样替换几行构造器调用的代码。再者,你也可以通过继承核心API实现自己的传输方式。
一个定义良好的并可扩展性高的事件模型是一个事件驱动应用程序的前提。Netty针对I/O有一个定义良好的事件模型。你可以在不破坏已有代码的情况下实现你自己的事件类,因为每一个事件类都根据一个严格的类层次体系区别于其他事件类。这是另一个和其他框架的不同点。很多NIO框架都没有或者很有限的事件模型概念。如果它们提供了扩展,就会经常在尝试增加自定义事件类时破坏已有代码。
一个ChannelEvent由一个ChannelPipeline里的一列ChannelHandler来处理。这个管道实现了一个过滤链模式的高级形式,让使用者完全控制一个事件如何被处理以及管道内的handler如何相互联系。举个例子,你可以规定数据从socket读取时做点什么:
public class MyReadHandler implements SimpleChannelHandler { public void messageReceived(ChannelHandlerContext ctx, MessageEvent evt) { Object message = evt.getMessage(); // Do something with the received message. ... // And forward the event to the next handler. ctx.sendUpstream(evt); } }
你也可以规定一个handler在接收一个写请求时做点什么:
public class MyWriteHandler implements SimpleChannelHandler { public void writeRequested(ChannelHandlerContext ctx, MessageEvent evt) { Object message = evt.getMessage(); // Do something with the message to be written. ... // And forward the event to the next handler. ctx.sendDownstream(evt); } }
之前“用POJO替代ChannelBuffer”那一节证实了,把协议编解码从业务逻辑中分离出来通常是一个好主意。但是从零开始实现有一些困难。你需要处理信息分片。有一些协议是多层的(建立在其他低级协议基础上)。还有一些协议在单机系统上实现特别困难。
因此,一个好的网络应用框架需要提供一个可扩展的、可重用的、可单元测试的和多层级的编解码器框架,产生可维护的用户编解码器。
Netty提供了一系列基础和高级的编解码器,可以解决你在写一个协议编解码器是遭遇的大多数问题,无论编解码器是否简单,是二进制的还是文本的。
不像阻塞I/O,在NIO支持SSL是个值得正视对任务。你不能简单得包装一个流对数据进行加密解密,你必须要用到javax.net.ssl.SSLEngine。SSLEngine是一个跟SSL本身一样复杂的状态机。你需要处理所有可能的状态,如密码套件、加密密钥协商、证书交换以及验证。此外,SSL甚至不是完全线程安全的。
在Netty,SslHandler负责对外屏蔽SSLEngine所有细枝末节,你只需要配置SslHandler然后添加到你的ChannelPipeline中。SslHandler同时让你可以很容易实现类似StartTLS的高级特性。
HTTP绝对是互联网最流行的协议。现在已经有一系列HTTP实现,比如Servlet容器。那么为什么Netty还要自己实现HTTP呢?
Netty的HTTP支持和现有的HTTP库很不一样。它让你可以完全控制HTTP在底层的消息交换。因为从根本上来说,它就是一个HTTP编解码器和HTTP消息类的结合,没有类似强制线程模型这样的限制。也就是说,你可以按照你希望的工作方式来实现HTTP客户端和服务端。你拥有所有HTTP规范内的控制权,包括线程模型、连接生命周期和分块编码。
基于它的高可定制性,你可以写一个高效的HTTP服务:
WebSocket在单TCP socket上实现一个双向全双工通信通道,用于web浏览器和web服务端端数据交互。
WebSocket协议由IETF标准化为RFC 6455。
Netty实现了RFC 6455和一系列旧版本规范。
Google Protocal Buffers是一个高效二进制协议的快速实现。凭借ProtobufEncoder和ProtobufDecoder,你可以将Goodgle Protocal Buffers编译程序生成的信息类转化为Netty编解码器。
这一节,我们从特点出发回顾了Netty的总体架构。Netty拥有一个简单且至今仍强大的架构。它由三个组件组成——buffer,channel和事件模型,所有高级特性都是在这三个组件的基础上建立的。只要你理解了这三个组件是如何一起工作的,你就不难理解这节简要提及的那些高级特性。