4.3 Included transports
Netty天生绑定了一些传输方式供开发者使用,因为这些传输方式并不能支持所有的应用和操作系统,你应该选择一个正确的传输服务与你的应用相匹配,在这个小节中,我们将向你介绍这些协议与服务的对应关系
表4.2列出了Netty支持的一些传输服务
名字 | 所处的包 | 描述 |
NIO | io.netty.channel.socket.nio | 使用java.nio.channels包将使用以基于selector为基础的方法 |
Epoll | io.netty.channel.epoll | 使用JNI的epoll和非阻塞I/O,这种传输服务支持的一些特性在Linux上才能有效,例如SO_REUSEPORT 并且比NIO和完全非阻塞都要更加快捷 |
OIO | io.netty.channel.socket.oio | 底层使用java.net包下的子类作为实现基础,使用块状流传输 |
Local | io.netty.channel.local | 通过管道,本地传输服务可以被用在虚拟机上的通信 |
Embedded | io.netty.channel.embedded | 一个嵌入式传输服务,可以使用ChannelHandler在没有真实网络传输的基础上,这可以测试你channelHandler的可用性呢 |
我们将在下一个小节中详细的讲解这些传输服务
4.3.1 NIO—non-blocking I/O
NIO在所有I/O操作上提供了完全的异步实现,它基于selector的在jdk1.4后提供了NIO的API
关于selector的理论是将selector当做一个注册器,当channel中的状态改变的时候,这个注册器通知你,这些可能的状态改变有:
1)一个新的channel被接收且已经就绪准备
2)一个channel的连接已经被建立
3)在一个channel中有些数据已经准备就绪读
4)一个channel已经准备好写入数据
当你的应用程序已经对这些状态改变做出了应对处理,selector将会被重置然后重复前面的动作,以一个线程的方式对检查状态的改变并相应的做出响应
表4.3向你展示了java.nio.channels.SelectionKey中定义的一些状态模式的常量,这些常量与应用状态的改变需要通知的类型一一对应
Table4.3
名称 | 描述 |
OP_ACCEPT | 当一个channel被创建和一个连接被接收的时候需要通知 |
OP_CONNECT | 当一个连接建立的时候需要通知 |
OP_READ | 当有数据需要从channel中被读取的时候需要被通知 |
OP_WRITE | 当需要有数据写入到channel的时候需要被通知,当socket的缓存区被完全填充的时候或者当数据传输的速度比远程端处理的速度更加快捷的时候会发生 |
图4.2展示了使用Netty用户级的API的时候隐藏的一些NIO的细节
TIPS:Zero-copy
Zero-copy是一个特殊属性,当前只能在NIO和Epoll传输服务中可使用,它允许你可以快速高效地移动你的数据从文件系统到网络中而不需要从你的内存空间复制到你的用户空,这对于你提高协议中的传输性能是一个非常重要的特性,例如FTP和HTTP协议,但是这种特性并不是被所有的操作系统所支持的,具体来说,如果数据被加密或者压缩过就不能正常使用了,只有一些简单的原生的文件内容可以被传输
4.3.2 Epoll—native non-blocking transport for Linux
我们之前解释过,Netty的NIO传输服务是基于java提供的异步或者非阻塞网络通信的普通抽象去实现的,尽管这可以保证Netty的非阻塞的API可以在任何的平台都是可用的,但是这依旧会有一些限制,因为JDK为了能在所有的系统上有同样的可用性做了一些折中
因为Linux的网络的高性能促使了很多一些特性的产生,包括epoll,一个高扩展性的IO事件驱动的新特性,这个APIs在从2012年的2.5.44版本的Linux的内核开始供大家使用,比poll和旧版的POSIX select有更高的传输性能,现在de factor是linux平台上的非阻塞传播的标准
Netty为Linux提供的NIO的API是使用的epoll的,通过这个方法我们可以与使用的linux保持一致,而不需要浪费一些性能为了适用Linux做了一些适配拦截,思考一下如果你的系统是linux,你的应用可以利用这个特性,你会发现在高负载的情况下这比JDK的NIO的实现更加高效
关于概念图与图4.2展示的是一样的,并且它的使用更加方便,举例来说,对于代码清单4.4中来说,如果用epoll替换NIO,只需要将NioEventLoopGroup用EpollEventLoopGroup替换,NioServerSocketChannel用EpollServerSocketChannel替换就可以了
4.3.3 OIO—old blocking I/O
Netty的OIO传输实现代表了一种折中,通过普通的传输API也可以实现,这是因为它是构建于java.net的包的基础上的,不是异步的,但却适合用于一些特定的场合
举例来说,你也许需要一些普通的代码来实现阻塞调用例如JDBC,如果你将其转化成非阻塞的也许并不是那么的实用,短期内你可以直接使用Netty的OIO传输服务,如果有需要你可以在未来的时间内将其转化成其他的任意一种异步传输,我们还是先看看阻塞通信是如何工作的吧
在java.net包下的API,经常有一个线程对接收新的连接到serverSocket的请求,一个新的socket即将被创建来与远程服务端进行交互,然后需要一个新的线程分配来处理后继的数据交互,多开一个新的线程是有必要的,因为在一个具体的socket上的任何IO操作都可能随时被阻断,如果用一个线程处理多个sockets很容易导致一个阻塞的操作也会影响其他的操作
正是因为如此,你可能会有疑问为什么可以使用与非阻塞一样的API来支持OIO呢?这个问题的答案是Netty使用了SO_TIMEOUT这个socket的参数标识,它规定了一个I/O操作完成的最长等待的毫秒数,如果在内部规定的时间内操作并没有完成,那么一个SocketTimeOutException将会被抛出,Netty将会捕获这个异常,然后继续处理循环,在下一个EcentLoop运行的时候,它会再次尝试,这是唯一的一个方式异步通信框架Netty可以支持OIO,图4.3说明了这个逻辑
4.3.4 Local transport for communication within a JVM
Netty为运行在同一个虚拟机上的服务端和客户端提供了本地传输的异步通信,同样,对于Netty而言对此传输服务依旧提供了相同的实现
这种传输方式,与服务端channel相连的SocketAddress并没有绑定物理地址,而是,只要服务器一运行就在注册器上存储注册,只要channel一关闭,就从注册器上解除注册,因为这种传输服务并没有真实的网络传输产生,它不能与其他的传输服务相互操作,因此,在同一个JVM上的客户端想要连接到服务器端的话,必须使用这种传输方式,除了这种限制,它就与其他的传输服务一样了
4.3.5 Embedded transport
Netty还提供了一种额外的传输方式允许你将一些ChannelHandler作为一种辅助工具类嵌入到其他的channelHandler中,在这种方式下,我们可以在不修改内部代码的基础上扩展ChannelHandler的功能
这种实质嵌入式的传输服务的实现被叫做EmbededChannel,在第九章,我们会去讲解这种类如何为你的channelHandler去创建单元测试