1.通道的解释

通道表示到能够执行 I/O 操作的实体(如文件和套接字)的连接。通道 表示到实体(如硬件设备、文件、网络套接字或者可以执行一个或多个诸如读取或写入之类的不同 I/O 操作的程序组件)的开放连接。正如在 Channel 接口中所指定的,通道可以处于打开或关闭状态,并且它们既是可异步关闭的,又是可中断的。 通道并没有与之相关联的流,实际上它所拥有的read和write方法,都是通过调用buffer对象来实现的。

 

2.观察源代码

我们通过jdk 源代码来观察一下通道的实现

FileInputStream.java
public FileChannel getChannel() {
        synchronized (this) {
                if (channel == null) {
                    channel = FileChannelImpl.open(fd, true, false, this);
                    fd.incrementAndGetUseCount();
                }
                return channel;
         }
}

FileChannelImpl.java
// Required to prevent finalization of creating stream (immutable)
private Object parent;
这个字段的注释有意思:阻止流的建立,我们知道流的概念是存在java.io中的。

private FileChannelImpl(FileDescriptor fd, boolean readable,boolean writable, Object parent, boolean append) {
        this.fd = fd;
        this.readable = readable;
        this.writable = writable;
        this.parent = parent;
        this.appending = append;
}

//  Invoked by getChannel() methods
//  of java.io.File{Input,Output}Stream and RandomAccessFile
public static FileChannel open(FileDescriptor fd,boolean readable, boolean writable,Object parent){
         return new FileChannelImpl(fd, readable, writable, parent, false);

}

public static FileChannel open(FileDescriptor fd,boolean readable, boolean writable,Object parent, boolean append){
         return new FileChannelImpl(fd, readable, writable, parent, append);
}



观察得知,通道的建立从源代码的角度看起来好像真没有什么特别的,为了方便我们的理解,我们可以认为就是一个简单抽象,没什么特别的。

3.常见的两个通道
我们经常用到的通道有两个 文件通道 套接字通道。

public abstract class FileChannel
extends AbstractInterruptibleChannel
implements ByteChannel, GatheringByteChannel, ScatteringByteChannel
文件通道,用于读取、写入、映射和操作文件的通道。

public static void main(String[] args) throws Exception {
               String infile   = "C:\\TDDownload\\jdk-1_5_0_22-linux-i586.bin";
               String outfile = "C:\\TDDownload\\detectiong.log.bin";

               FileInputStream fin     = new FileInputStream(infile);
               FileOutputStream fout = new FileOutputStream(outfile);

               FileChannel fcin     = fin.getChannel();
               FileChannel fcout   = fout.getChannel();
               
               ByteBuffer buffer   = ByteBuffer.allocate(10240);
               while (true) {
                       buffer.clear();
                       int r = fcin.read(buffer);
                       if (r == -1) {
                            break;
                       }
                       buffer.flip();
                       fcout.write(buffer);
              }
}

public abstract class SocketChannel
extends AbstractSelectableChannel
implements ByteChannel, ScatteringByteChannel, GatheringByteChannel
套接字通道
public abstract class ServerSocketChannel
extends AbstractSelectableChannel 
服务器套接字通道

4.文件通道和套接字通道的不同点
文件通道和套接字通道唯一的不同的点是继承的抽象类不同。
文件通道            extends  AbstractInterruptibleChannel 可中断通道的基本实现类.
套接字通道         extends  AbstractSelectableChannel   extends  SelectableChannel(可通过Selector 实现多路复用的通道)  extends  AbstractInterruptibleChannel

5.文件通道和套接字通道的实例化
文件通道和套接字通道实例化也不相同
文件通道:直接通过 可从现有的 FileInputStream、FileOutputStream 或 RandomAccessFile  对象获得文件通道,非常简单。
套接字通道:此类的实例必须首先通过 register 方法进行注册。此方法返回一个表示该通道已向选择器注册的新 SelectionKey 对象。

6.多路复用的概念
这里我们又新增了两个新的概念
public abstract class Selector   SelectableChannel 对象的多路复用器。
我们来看一下维基百科的解释-多路复用
多路复用(Multiplexing,又称“多工”)是一个通信和计算机网络领域的专业术语,在没有歧义的情况下,“多路复用”也可被称为“复用”。多路复用通常表示在一个信道上传输多路信号或数据流的过程和技术。因为多路复用能够将多个低速信道整合到一个高速信道进行传输,从而有效地利用了高速信道。通过使用多路复用,通信运营商可以避免维护多条线路,从而有效地节约运营成本。
public abstract class SelectionKey 表示 SelectableChannel 在 Selector 中的注册的标记。
SelectionKey有以下4个标记
OP_ACCEPT   用于套接字接受操作的操作集位。
OP_CONNECT 用于套接字连接操作的操作集位。
OP_READ       用于读取操作的操作集位。
OP_WRITE     用于写入操作的操作集位。
我们可以这样理解,多路复用器是个硬件设备装入了通道这个虚拟设备,复用器对每个进入其中的通道都标记(这个标记就是我们的SelectionKey )了其当前的状态(自己的理解,不一定准确),比如某个通道处于读就绪的状态(是不是有点线程的味道),就表明可以开始读了,如何查看通道是否就绪就看我们的多路复用器了。
多路复用器的方法:select()选择一组键,其相应的通道已为 I/O 操作准备就绪。

7.socket通道简单实现例子

    服务端

public class SimpleServer {
	public static void main(String[] args) throws IOException {
		Selector selector = Selector.open();
		ByteBuffer buffer = ByteBuffer.allocate(1024);
		ServerSocketChannel listenerChannel = ServerSocketChannel.open();
		listenerChannel.socket().bind(new InetSocketAddress(80));
		listenerChannel.configureBlocking(false);
		listenerChannel.register(selector, SelectionKey.OP_ACCEPT);

		while (true) {
			if (selector.select() > 0) {
				for (SelectionKey key : selector.selectedKeys()) {
					if (key.isAcceptable()) {
						// 有客户端连接请求时
						SocketChannel clientChannel = ((ServerSocketChannel)key.channel()).accept();
					    clientChannel.configureBlocking(false);
					    clientChannel.register(key.selector(), SelectionKey.OP_READ);
					}
					if (key.isReadable()) {
						// 从客户端读取数据
					    SocketChannel clientChannel=(SocketChannel)key.channel();
					    buffer.clear();
					    long bytesRead = clientChannel.read(buffer);
					    
					    if(bytesRead==-1){
					      clientChannel.close();
					    } else{
					      buffer.flip();
					      String receivedString = Charset.forName("UTF-8").newDecoder().decode(buffer).toString();
					      System.out.println("接收到来自"+clientChannel.socket().getRemoteSocketAddress()+"的信息:"+receivedString);
					     
					      
					      // 设置为下一次读取或是写入做准备
					      key.interestOps(SelectionKey.OP_WRITE);
					    }
					}
					if (key.isWritable()) {
						// 客户端可写时
						buffer.clear();
						SocketChannel clientChannel=(SocketChannel)key.channel();
						String sendString="你好,客户端. @"+new Date().toString();
						buffer = ByteBuffer.wrap(sendString.getBytes("UTF-8"));
					   	clientChannel.write(buffer);
					   	key.interestOps(SelectionKey.OP_READ);
					}
					selector.selectedKeys().remove(key);
				}
			}
		}
	}
}


    客户端


public class SimpleClient {
	public static void main(String[] args) throws IOException{
		SocketChannel socketChannel=SocketChannel.open(new InetSocketAddress(80));
	    socketChannel.configureBlocking(false);
	    
	    Selector selector = Selector.open();
	    socketChannel.register(selector, SelectionKey.OP_WRITE);
	    
        while (selector.select() > 0) {
          for (SelectionKey sk : selector.selectedKeys()) {
	    	if (sk.isWritable()) {
	    		ByteBuffer writeBuffer = ByteBuffer.wrap("hello".getBytes("UTF-8"));
	    		socketChannel.write(writeBuffer);
	    		// 准备读操作
	    		sk.interestOps(SelectionKey.OP_READ);
	    	}
        	
            if (sk.isReadable()) {
            	SocketChannel sc = (SocketChannel) sk.channel();
            	ByteBuffer buffer = ByteBuffer.allocate(1024);
            	sc.read(buffer);
            	buffer.flip();
            	String receivedString= Charset.forName("UTF-8").newDecoder().decode(buffer).toString();
            	System.out.println("接收到来自服务器"+sc.socket().getRemoteSocketAddress()+"的信息:"+receivedString);
            	// 准备写操作
            	sk.interestOps(SelectionKey.OP_WRITE);
            }
            selector.selectedKeys().remove(sk);
          }
        }
	}
}


   结合上面的解释,你会发现理解nio中的通道一点都不费劲,很容易理解。