NIO-Channel相干接口详解

NIO-Channel相关接口详解

 

一.Channel相关接口(概述):

NIO中核心的API,它被底层操作封装,提供了数据进行NIO操作的"途径",可以配合Selector实现多路IO复用/非阻塞操作.Channel(通道)表示实体(例如硬件设备,文件,网络socket等)的开放链接.Channel与其实体关联,并具有相同的状态,例如关闭或者开启.Channels可以是异步的(noblocking),可中断的(inerruptable).

Channel接口是最*的接口.其子接口列表如下:

  1. ReadableByteChannel:可以将内容读入缓冲区,此接口具有一个read(ByteBuffer bb)方法.
  2. ScatteringByteChannel:继承自ReadableByteChannel,可以讲内容依次读入一个或多个缓存区,此接口有一个方法read(ByteBuffer[] dts).
  3. WritableByteChannel:可以将缓冲区内如写入通道,此接口有一个方法write(ByteBuffer bb).
  4. GatheringByteChannel:继承自WritableByteChannel,可以讲多个缓冲区内容依次写入到通道中.此接口有一个方法write(ByteBuffer[] dts).
  5. ByteChannel:一个综合接口,"字节操作渠道",此接口继承ReadableByteChannel/WritableByteChannel.此接口本身无定义方法.

Channels是一个辅助类,提供了多个基于channel操作的util方法.

FileChannel:文件操作的通道,支持向文件进行读取/写入操作FileLock:对文件或者文件区域进行锁定.

MappedByteBuffer:直接映射缓冲区.FileChannel支持连接到文件通道中读取/写入数据,以及查询文件中指定区域(region)的数据.已经在这个文件或者region上锁定的功能(mapping),获取一个FileLock实例.

而且还提供了将文件数据直接映射到内存的方法,即MappedByteBuffer.目前,FileInputStream/FileOutputStream/RandomAccessFile类添加了getChannel方法.这个方法可以获取底层和文档操作绑定的channel.

 

二.多路复用/非阻塞IO通道(概述):

SelectableChannel: 可被选择的通道,能够被Selector支持,是NIO + selector支持的通道."可被选择通道"继承自InterruputableChannel.即为可中断通道.它也是基于网络NIO channel的父接口.我们熟悉的SocketChannel,ServerSocketChannel,DatagramChannel也是扩展自它.

Pipe.SinkChannel,Pipe.SourceChannel这两个基于"管道模式"的通道,也是SelectableChannel.但是Pipe更加特殊.

Selector即为"选择器",是提供多路复用的"操作事件控制"类.

SelectionKey即为"选择键",是channel与selector建立关联的"凭证"或者说是"channel就绪事件"的持有者.

Pipe:一种特殊的channel,形成单向管道的的两个通道,SourceChannel和SinkChannel.

Selector + SelectableChannel + SelectionKey,三者各司其职,并有效配合,最终支撑了NIO数据通讯...

一个channel需要register到指定的一个Selector,此后就可以在此selector上获得和当前channel有关的就绪事件(即SelectionKey).如果某个通道已准备就绪,则将注册是所返回的key添加到选择器的已选择键集合中(selectedKeySet).为了确定哪个通道已准备好执行哪些操作,那么可以通过检查该键集合和其中的key(通常我们采用遍历的方式).selectionKey持有channel信息.

SelectionKey只是channel就绪状态,它本身并不负责对channel的实际操作,因此需要手动去处理这些事件(忽略或者执行).

三.Channel接口

Channel接口是*接口,用于控制I/O操作连接,目前已知的文件IO/网络IO都可以获取.

Channel表示到实体(网络设备,文件,Socket等有IO操作)的程序组件的开放连接.我们认为Channel创建成功后,本身持有IO操作的Input和Output.

Channel接口仅仅实现了Closable接口,即任何通道需要具备"开启"状态,以及需要应用手动关闭.

Channel本身底层仍然基于普通的IO,因此channel在多线程环境中是线程安全的.

注意,在Socket操作中,在多个线程中,同时进行进行数据read/write时,数据的操作顺序可能将不能得到保证.所以如果想确保数据发送/读取数据的顺序性,要么对数据进行队列化,要么对操作队列化.(主要原因是底层InputStream/OutputStream非同步操作的).

 

四.WritableByteChannel:可写字节Channel,此接口提供了向通道写入字节序列的能力.在任意给定时刻,一个可写入通道上只能进行一个写入操作。如果某个线程在通道上发起写入操作,那么在第一个操作完成之前,将阻塞其他所有试图发起另一个写入操作的线程。其他种类的 I/O 操作是否继续与写入操作并发执行则取决于该通道的类型。这一点有别于Socket.WritableByteChannel只有一个方法:

  • public int write(ByteBuffer src) throws Exception:write操作,在NIO中只支持ByteBuffer.此方法将制定的字节序列,写入到channel中.注意在非阻塞模式下,write操作并不保证将src中所有的内容都写入后才返回,此方法返回写入成功的字节数,有可能为0,但总是不大于byteBuffer.remaining().在阻塞模式下,write将所有的字节写入完成后,返回.参见Channels.WritableByteChannelImpl

五.ReadableByteChannel:可读字节Channel,同WritableByteChannel对称.任何时刻,只能有一个read操作在此通道上.如果某个线程在通道上发起读取操作,在此操作完成之前,其他线程对此通道的read操作将会被阻塞.参见Channels.ReadableByteChannelImpl;此接口只有一个方法:

  • public int read(ByteBuffer dst) throws Exceoption:从通道中读取数据到buffer中.返回读取到的字节数,可能为0;读取数据长度最大为buffer.capacity().如果此时通道已经到达流的结尾,则返回-1.在非阻塞模式下,read方法可能读取0个字节也会返回.在阻塞模式下,read方法在则阻塞,在至少读取一个字节后可以返回.

六.ByteChannel:一个相对高层的接口,此接口是一个标记接口,没有定义API方法.此接口继承了WritableByteChannel和ReadableByteChannel.常用的FileChannel和SocketChannel都直接实现了此接口.

七.ScatteringByteChannel:"分散"读取操作,是一个高级接口,支持一个通道中一次read操作的结果"分散"给多个byteBuffer.此接口继承自ReadableByteChannel;此接口增加一个特殊的方法:

  • public long read(ByteBuffer[] dsts) throws IOException:将字节序列依次读入指定的缓冲区数组.此接口终于提供给我一种解决方案:一次read的数据分散到过个buffer,然后在多线程使用.或者将一个相对较大的buffer,以多个较小buffer的方式操作.

八.GatheringByteChannel:"集中"写入操作,是一个高级接口,支持一个通道中一次write操作中,将指定的多个ByteBuffer数据依次写入通道.此接口继承自WritableByteChannel.

此接口新增一个特殊的方法:

  • public long write(ByteBuffer[] srcs) throws Exception:返回写入成功的字节个数,可能为0.在非阻塞模式下,有可能返回0或者其他任意不大于srcs的remaning()之和的数字..在阻塞模式下,只有当所有字节都写入,尚可返回.buffer数组将会被依次写入.

九.InterruptibleChannel:"可中断"通道,即此通道可以被异步关闭和中断.

实现此接口的通道是可被异步关闭的,如果某个线程阻塞于InterruptibleChannel的I/O操作中,另一个线程可以调用该通道的close方法.此时被阻塞的线程将会受到AsynchronousCloseException.实现此接口的通道是可被中断的,如果某个线程阻塞于InterruptibleChannel的I/O操作中,则另一个线程可调用该阻塞线程的interrupt方法.这将导致channel被关闭,已阻塞线程将收到ClosedByInterruptException,并且设置阻塞线程的状态为中断.如果已设置某个线程的中断状态,此后它在通道上调用某个阻塞的I/O操作,则该通道将会被关闭且该线程将立即受到ClosedByInterruptException.此接口没有特殊的方法,只是重新定义了close()方法.

此处描述一下"可中断"通道是如何实现异步关闭和中断响应的:参见AbstractInterruptibleChannel

  1. 对于任何通过channel方式read和write操作(参见Channels.WritableByteChannelImpl,Channels.ReadableByteChannelImpl),都将被同步,在read()方法中,需要对一个Object readLock进行同步,在write()方法中需要对一个Object writeLock进行同步.因此对于一个channel在多线程中read(或者write)将会被序列化.
  2. read或者write操作中,在实际的IO操作之前,都会执行AbstractInterruptibleChannel.begin()方法:此方法做的一件事情,就是向Thread.currentThread()注册一个支持异步回调的interruptor对象,即赋值给Thread中Interruptible blocker属性.实际IO操作完毕之后(例如:outputStream.write()...),read或者write方法中将会调用AbstractInterruptibleChannel.end(boolean)方法:此方法将会首先解除Thread中的blocker(即赋值为null),然后检测channel是否被"中断"(每个channel实例,都有一个interrupted属性),如果是,将抛出ClosedByInterruptException.然后检测,当前Channel是否被关闭(channel中有open属性,默认为true),如果是,抛出AsynchronousCloseException.
  3. 执行channel.close()方法时,将会导致其open属性置为false.因此在AbstractInterruptibleChannel.end()方法中检测open属性时,将会抛出相应的异常.难道这就是异步关闭??????好吧,我们简单的认为,当线程"close"了通道,只有其他线程在read或者write时,将会在此后的某个时间被告知"已关闭".根据"异步"的概念,这也可以被认为"异步"吧.
  4. Thread.interrupt()方法中,会对channel注册的interruptor对象进行检测,如果发现此对象不为null,则触发interruptor执行回调方法.回调方法,主要是讲此channel的interrupt属性改为true,open改为false.此后再次channel上调用read或者write时将会触发对interrupt和open属性的校验,以抛出异常.
  5. begin()方法中,仍然会对Thread.currentThead().isInterrupted()校验,如果当前线程已经被"中断",则直接抛出异常..
  6. 经上所述,一个channel的中断和关闭,与Thread控制,形成了密切的回环控制.
  7. 支持noblocking的NIO Channel都实现了此接口.例如SocketChannel,ServerSocketChannel,DatagramChannel,Pipe.SinkChannel,Pipe.SourceChannel等.