NIO Channel 管道
Java NIO的通道类似流,但又有些不同:
- 既可以从通道中读取数据,又可以写数据到通道。但流的读写通常是单向的。
- 通道可以异步地读写。
- 通道中的数据总是要先读到一个Buffer,或者总是要从一个Buffer中写入。
正如上面所说,从通道读取数据到缓冲区,从缓冲区写入数据到通道。
Channel的实现
这些是Java NIO中最重要的通道的实现:
- FileChannel
- DatagramChannel
- SocketChannel
- ServerSocketChannel
FileChannel 从文件中读写数据。
DatagramChannel 能通过UDP读写网络中的数据。
SocketChannel 能通过TCP读写网络中的数据。
ServerSocketChannel可以监听新进来的TCP连接,像Web服务器那样。对每一个新进来的连接都会创建一个SocketChannel。
多数情况下,通道与操作系统的文件描述符(File Descriptor)和文件句柄(File Handle)有着一对一的关系(通道比文件描述符更广义)。
Channel接口:
public interface Channel { public boolean isOpen( ); public void close( ) throws IOException; }
与缓冲区不同,通道API主要由接口指定。不同的操作系统上通道实现(Channel Implementation)会有根本性的差异,所以通道API仅仅描述了可以做什么。因此很自然地,通道实现经常使用操作系统的本地代码。通道接口允许您以一种受控且可移植的方式来访问底层的I/O服务。
InterruptibleChannel接口:
InterruptibleChannel是一个标记接口,当被通道使用时可以标示该通道是可以中断的(Interruptible)。如果连接可中断通道的线程被中断,那么该通道会以特别的方式工作。
两个对应的核心抽象类:
这两个类是AbstractInterruptibleChannel和AbstractSelectableChannel,它们分别为可中断的(interruptible)和可选择的(selectable)的通道实现提供所需的常用方法。尽管描述通道行为的接口都是在java.nio.channels包中定义的,不过具体的通道实现却都是从java.nio.channels.spi中的类引申来的。这使得他们可以访问受保护的方法,而这些方法普通的通道用户永远都不会调用。
获取管道的集中常见操作:
ServerSocketChannel ssc = ServerSocketChannel.open( );
ssc.socket( ).bind (new InetSocketAddress (8080));
SocketChannel sc = SocketChannel.open( );
sc.connect (new InetSocketAddress("localhost", 8080));
DatagramChannel dc = DatagramChannel.open( );
RandomAccessFile raf = new RandomAccessFile ("E:\\JcloudMyProject\\blogcode\\src\\main\\java\\heapStark\\blogCode\\nio\\Main.java", "r");
FileChannel fc = raf.getChannel( );
//Socket 无法产生Channel
Socket socket = new Socket();
socket.connect( new InetSocketAddress("localhost",8080));
assert (socket.getChannel()==null);
Socket类也有新的getChannel( )方法。这些方法虽然能返回一个相应的socket通道对象,但它们却并非新通道的来源,RandomAccessFile.getChannel( )方法才是。只有在已经有通道存在的时候,Socket类的getChannel( )方法才返回与一个socket关联的通道否则返回空。
关闭通道:
与缓冲区不同,通道不能被重复使用。一个打开的通道即代表与一个特定I/O服务的特定连接并封装该连接的状态。当通道关闭时,那个连接会丢失,然后通道将不再连接任何东西。调用通道的close( )方法时,可能会导致在通道关闭底层I/O服务的过程中线程暂时阻塞,哪怕该通道处于非阻塞模式。通道关闭时的阻塞行为(如果有的话)是高度取决于操作系统或者文件系统的。在一个通道上多次调用close( )方法是没有坏处的,但是如果第一个线程在close( )方法中阻塞,那么在它完成关闭通道之前,任何其他调用close( )方法都会阻塞。后续在该已关闭的通道上调用close( )不会产生任何操作,只会立即返回。尝试进行任何需要通道处于开放状态作为前提的操作,如读、写等都会导致ClosedChannelException异常。
ByteChannel继承关系:
ByteChannel接口本身并不定义新的API方法,它是一种用来聚集它自己以一个新名称继承的多个接口的便捷接口。根据定义,实现ByteChannel接口的通道会同时实现ReadableByteChannel 和WritableByteChannel两个接口,所以此类通道是双向的。这是简化类定义的语法糖(syntactic sugar)。
阻塞(blocking)或非阻塞(nonblocking):
通道可以以阻塞(blocking)或非阻塞(nonblocking)模式运行。非阻塞模式的通道永远不会让调用的线程休眠。请求的操作要么立即完成,要么返回一个结果表明未进行任何操作。只有面向流的(stream-oriented)的通道,如sockets和pipes才能使用非阻塞模式。SocketChannel通道类从SelectableChannel引申而来。从SelectableChannel引申而来的类可以和支持有条件的选择(readiness selectio)的选择器(Selectors)一起使用。将非阻塞I/O和选择器组合起来可以使您的程序利用多路复用I/O(multiplexed I/O)。
- InterruptibleChannel接口,标记接口
如果一个线程在一个InterruptibleChannel通道上被阻塞并且同时被中断(由调用该被阻塞线程的interrupt( )方法的另一个线程中断),那么该通道将被关闭,
该被阻塞线程也会产生一个ClosedByInterruptException异常。此外,假如一个线程的interrupt status被设置并且该线程试图访问一个通道,
那么这个通道将立即被关闭,同时将抛出相同的ClosedByInterruptException异常。线程的interrupt status在线程的interrupt( )方法被调用时会被设置。
我们可以使用isInterrupted( )来测试某个线程当前的interrupt status。当前线程的interrupt status可以通过调用静态的Thread.interrupted( )方法清除。
仅仅因为休眠在其上的线程被中断就关闭通道,这看起来似乎过于苛刻了。不过这却是NIO架构师们所做出的明确的设计决定。经验表明,想要在所有的操作系统上一致而可靠地处理被中断的I/O操作是不可能的。“在全部平台上提供确定的通道行为”这一需求导致了“当I/O操作被中断时总是关闭通道”这一设计选择。这个选择被认为是可接受的,因为大部分时候一个线程被中断就是希望以此来关闭通道。java.nio包中强制使用此行为来避免因操作系统独特性而导致的困境,因为该困境对I/O区域而言是极其危险的。这也是为增强健壮性(robustness)而采用的一种经典的权衡。可中断的通道也是可以异步关闭的。实现InterruptibleChannel接口的通道可以在任何时候被关闭,即使有另一个被阻塞的线程在等待该通道上的一个I/O操作完成。当一个通道被关闭时,休眠在该通道上的所有线程都将被唤醒并接收到一个AsynchronousCloseException异常。接着通道就被关闭并将不再可用。