传统的I/O:

传统的服务器的I/O是阻塞的,即当服务器端开启后,它会监听端口,直到有客户端发来请求,否则一直将是堵塞的,

NIO(非阻塞I/O):

基础概念:

通道:用于建立客户端和服务端的连接,可以将输入/输出流写入到通道,通道将缓冲区的数据块移入或移出到各种I/O源,如文件,socket,数据报。对于网络编程,只有三类通道类:SocketChannel,ServerSocketChannel,DatagramChannel.

缓冲区:在新的I/O中,不再是从输入输出流读取数据,而是从缓冲区中读写数据,流和缓冲区的区别就是流是基于字节的,通道是基于块的,即传送缓冲区中的数据块,一次读写一个缓冲区中的数据;Java中所有的基本数据类型都有特定的Buffer子类,ByteBuffer,CharBuffer,ShortBuffer,IntBuffer,LongBuffer,FloatBuffer,DoubleBuffer,在网络中,几乎只会使用ByteBuffer.

选择器:用来管理通道,可以将不同的通道注册到一个Selector对象,每个通道分配一个SelectionKey,SelectionKey类相当于通道的指针,可以保存一个对象附件,一般会存储这个通道上的连接的状态。

具体实现:

通道:网络编程的主要类有:

⑴SocketChannel:类可以读写TCP socket,数据必须编码到ByteBuffer对象中来完成读/写,每个SocketChannel都有一个对等端Socket对象。

⑵ServerSocketChannel:接受入站连接,你无法读取,写入或者连接ServerSocketChannel,只接受一个新的入站连接方法,accept()方法。

package com.dong.NIO;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
/**
 * Channel类的方法
 * @author liuD
 */
public class testChannel {
	public static void main(String[] args) throws IOException {
		//不立即建立来连接,创建一个初始未连接的socket,必须使用connect()方法进行连接;
		SocketChannel schannel = SocketChannel.open();
		SocketAddress address = new InetSocketAddress("www.xxx.com",80);
		schannel.connect(address);
		
		//建立一个非阻塞的连接
		SocketChannel schannel2 = SocketChannel.open(address);
		
		//配置非阻塞通道,注意使用非阻塞通道,connect()会立即返回,然后程序就去做别的事情;必须调用finishConnect(); 对于阻塞的通道,finishConnect()方法将立即返回true;
		schannel.configureBlocking(false);
		
		//返回boolean值,用来判断连接是否建立;
		schannel.isConnected();
		
		ByteBuffer  buffer = ByteBuffer.allocate(100); 
		ByteBuffer [] buffers = null;
		//读取通道中缓冲区的数据,首先将数据读取的数据放入到缓冲区,直到缓冲区放满,然后返回放入的字节数,如果到达流的末尾,则下一次调用read(),返回-1;
		schannel.read(buffer);
		//从一个源填充多个缓冲区,read()接受一个ByteBuffer对象数组作为参数,按顺序填充数组中的各个ByteBuffer;
		schannel.read(buffers);
		//同上,从第0个缓冲区开始,填充100个缓冲区;
		schannel.read(buffers, 0, 100);
		
		//像通道中缓冲区写入数据,填充一个ByteBuffer,然后穿个某个写入方法,其原理和输入相同
		schannel.write(buffer);
		schannel.write(buffers);
		schannel.write(buffers, 0, 100);
		
		//关闭连接
		schannel.close();                                   
		
		////ServerScoketChannel
		//只有一个目的,就是接受入站连接,open方法并不是打开一个新的socket,而是只创建这个对象;
		ServerSocketChannel server = ServerSocketChannel.open();
		SocketAddress saddress = new InetSocketAddress(80);
		//绑定监听端口
		server.bind(saddress);
		//监听端口入站连接,阻塞情况下,accept()方法,等待连接,并返回连接到远程客户端的一个SocketChannel对象,阻塞模式是默认的;
		//在非阻塞模式下,如果没有入站连接,accept()方法将返回null,
		server.accept();
		
		
	}
}

缓冲区:缓冲区是NIO连接的基本单元;

package com.dong.NIO;

import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
/**
 * ByteBuffer类的方法
 * @author liuD
 */
public class testBuffer {
	public static void main(String[] args) throws UnsupportedEncodingException {
		//创建一个指定容量的缓冲区,游标位于缓冲区的开始位置,
		ByteBuffer buffer = ByteBuffer.allocate(100);
		//对于已经有的数据数组,一般要用缓冲区进行包***yte[] data = "xxx".getBytes("UTF-8");
		ByteBuffer buffer1 = ByteBuffer.wrap(data); 
		
		//位置:缓冲区中用于读取或写入的下一个位置,这个位置从0开始,最大值等于缓冲区;
		buffer1.position();
		//容量:缓冲区中用来保存数据的大小,一旦在创建缓冲区时设置,此后就不能改变;
		buffer1.capacity();
		//限度:缓冲区可访问数据的末尾位置,即读取的位置,只能在限度之内,即使容量大于限度,,也不可以访问限度以外的;
		buffer1.limit();
		//标记:客户端中指定的索引,mark()可以将标记设置为当前位置;reset()可以将当前位置设置为所标记的位置;
		buffer1.mark();
		
		//注意读取缓冲区实际上不会以任何方式改变缓冲区中的数据,只可能向前或者向后设置位置,从而可以从特定位置开始读取;
		
		//将位置设置为0,限度设置为容量,缓冲区的数据没有删除,只是改动位置,
		buffer1.clear();
		//将位置设置为0,但不改变限度
		buffer1.rewind();
		//将限度设置为当前位置
		buffer1.flip();
		//缓冲区中当前位置与限度之间的元素数
		buffer1.remaining();
		//判断当前位置和限度之间是否还有元素,有的话返回true
		buffer1.hasRemaining();
		
		//填充缓冲区:至多填充到其容量的大小;超出抛异常; put(index) 指定位置填充
		buffer1.put((byte) 12);
		//获得数据,get()会将位置前移一个元素,到达限度时,返回false; get(index) 获取指定位置
		buffer1.get();
		
		//复制缓冲区,返回值并不是克隆,复制的缓冲区共享相同的数据,修改一个缓冲区数据会反应到另一个缓冲区中;
		buffer1.duplicate();
		
		
	}
}

选择器:Selector,用来管理通道

package com.dong.NIO;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Set;
/**
 * 
 * Selector选择器
 * @author liuD
 */
public class testSelector {
	public static void main(String[] args) throws IOException {
		//创建一个选择器,用来管理连接的通道
		Selector selector = Selector.open();
		//向选择器中增加通道,selector类中没有增加通道的方法,可以通过register()方法通道类中声明,注意,FileChannel不可选择,
		ServerSocketChannel server = ServerSocketChannel.open();
		//将选择器传递给通道的注册方法,就可以向选择器注册这个通道;
		server.register(selector, SelectionKey.OP_ACCEPT);
		//SelectionKey类中的一个命名常量,标识通道注册的操作;
		//SelectionKey.OP_ACCEPT;SelectionKey.OP_CONNECT;SelectionKey.OP_READ;SelectionKey.OP_WRITE;
		
		//查询选择器,找出哪些通道已经准备好可以进行处理,
		//选择就绪的通道:
		//完成非阻塞选择,如果当前没有准备好要处理的连接,则立即返回
		selector.selectNow();
		//等待,直到至少有一个注册的通道准备好可以进行处理才返回
		selector.select();
		//获取准备就绪的通道,返回一个集合,依次处理各个SelectionKey,也可以从迭代器中删除键;
		Set<SelectionKey> selectionKeys = selector.selectedKeys();
		//不在使用selector选择器时,应该关闭它
		selector.close();
		
		//SelectionKey类,相当于通道的指针,可以保存一个对象附件,一般会存储通道的连接状态
		
		for(SelectionKey key : selectionKeys) {
			//测试键的操作,即SelectionKey
			 key.isAcceptable();
			//使用Channel()方法可以获取准备就绪的通道;
			 ServerSocketChannel channel = (ServerSocketChannel) key.channel();
			 
			 //如果SelectionKey存储了一个对象,则可以使用attachment()方法来获取该对象
			 ByteBuffer buffer  = (ByteBuffer) key.attachment();
		}	
	}
}

完整的NIO客户端和服务端例子: 

NIO客户端:

package com.dong.NIO;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;

/**
 * NIO客户端
 * @author liuD
 *
 */
public class NIOClient {
	//通道管理器
	private Selector selector;
	
	/**
	 * 获得一个Socket通道,并对该通道做一些工作
	 * @throws IOException 
	 */
	
	public void initClient(String ip,int port) throws IOException {
		//获得一个Socket通道
		SocketChannel channel = SocketChannel.open();
		//设置为非阻塞
		channel.configureBlocking(false);
		//h获得一个通道管理器
		this.selector =selector.open();
		//客户端连接服务器,其方法执行并没有实现连接,需要listen()方法中调用Channel.finshConection();才算完成连接
		channel.connect(new InetSocketAddress(ip, port));
		//将通道管理器和该通道绑定,并为该通道注册SelectionKey.OP_CONNECT事件。
		channel.register(selector, SelectionKey.OP_CONNECT);
	}
	
	/**
	 * 
	 * 采用轮询的方式监听selector上是否有需要处理的对象,如果有,则进行处理
	 * @throws IOException 
	 */
	
	public void connect() throws IOException {
		//轮询访问Selector
		while(true) {
			//选择一组可以进行I/O的操作的事件,放在selector中,客户端的该方法不会阻塞
			selector.select();
			//获得selector中选中的项的迭代器 
			Iterator<SelectionKey> ite  = this.selector.selectedKeys().iterator();
			while(ite.hasNext()) {
				SelectionKey key = ite.next();
				//删除已经选择key,防止重复
				ite.remove();
				//连接事件的发生
				if(key.isConnectable()) {
					SocketChannel  channel = (SocketChannel) key.channel();
					//如果正在连接,则完成连接
					if(channel.isConnectionPending()) {
						channel.finishConnect();
					}
					//设置成非阻塞
					channel.configureBlocking(false);
					//给服务端发送信息;
					channel.write(ByteBuffer.wrap(new String("1111").getBytes("utf-8")));
					//和服务器端连接成功后,为了可以接受到服务端的信息,需要给通道设置读的权限
					channel.register(this.selector, SelectionKey.OP_READ);
				}else if(key.isReadable()) {
					read(key);
				}
			}
		}
	}
	
	
	/**
	 * 处理读取服务端发来的信息 的事件  
	 * @throws IOException 
	 */
	public void read(SelectionKey key ) throws IOException {
		//和服务端的read方法一样  

				// 服务器可读取消息:得到事件发生的Socket通道

				SocketChannel channel = (SocketChannel) key.channel();

				// 创建读取的缓冲区

				ByteBuffer buffer = ByteBuffer.allocate(512);

				channel.read(buffer);

				byte[] data = buffer.array();

				String msg = new String(data).trim();

				System.out.println("客户端收到信息:" + msg);

				ByteBuffer outBuffer = ByteBuffer.wrap(msg.getBytes("utf-8"));

				channel.write(outBuffer);// 将消息回送给客户端
	}
	/**
	 * 
	 * 启动测试
	 * @throws IOException 
	 */
	
	public static void main(String args[]) throws IOException {
		NIOClient client =new NIOClient();
		client.initClient("localhost", 8000);
		client.connect();
	}
}

NIO服务端:

package com.dong.NIO;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;

/**
 * NIO客户端
 * @author liuD
 *
 */
public class NIOClient {
	//通道管理器
	private Selector selector;
	
	/**
	 * 获得一个Socket通道,并对该通道做一些工作
	 * @throws IOException 
	 */
	
	public void initClient(String ip,int port) throws IOException {
		//获得一个Socket通道
		SocketChannel channel = SocketChannel.open();
		//设置为非阻塞
		channel.configureBlocking(false);
		//h获得一个通道管理器
		this.selector =selector.open();
		//客户端连接服务器,其方法执行并没有实现连接,需要listen()方法中调用Channel.finshConection();才算完成连接
		channel.connect(new InetSocketAddress(ip, port));
		//将通道管理器和该通道绑定,并为该通道注册SelectionKey.OP_CONNECT事件。
		channel.register(selector, SelectionKey.OP_CONNECT);
	}
	
	/**
	 * 
	 * 采用轮询的方式监听selector上是否有需要处理的对象,如果有,则进行处理
	 * @throws IOException 
	 */
	
	public void connect() throws IOException {
		//轮询访问Selector
		while(true) {
			//选择一组可以进行I/O的操作的事件,放在selector中,客户端的该方法不会阻塞
			selector.select();
			//获得selector中选中的项的迭代器 
			Iterator<SelectionKey> ite  = this.selector.selectedKeys().iterator();
			while(ite.hasNext()) {
				SelectionKey key = ite.next();
				//删除已经选择key,防止重复
				ite.remove();
				//连接事件的发生
				if(key.isConnectable()) {
					SocketChannel  channel = (SocketChannel) key.channel();
					//如果正在连接,则完成连接
					if(channel.isConnectionPending()) {
						channel.finishConnect();
					}
					//设置成非阻塞
					channel.configureBlocking(false);
					//给服务端发送信息;
					channel.write(ByteBuffer.wrap(new String("1111").getBytes("utf-8")));
					//和服务器端连接成功后,为了可以接受到服务端的信息,需要给通道设置读的权限
					channel.register(this.selector, SelectionKey.OP_READ);
				}else if(key.isReadable()) {
					read(key);
				}
			}
		}
	}
	
	
	/**
	 * 处理读取服务端发来的信息 的事件  
	 * @throws IOException 
	 */
	public void read(SelectionKey key ) throws IOException {
		//和服务端的read方法一样  

				// 服务器可读取消息:得到事件发生的Socket通道

				SocketChannel channel = (SocketChannel) key.channel();

				// 创建读取的缓冲区

				ByteBuffer buffer = ByteBuffer.allocate(512);

				channel.read(buffer);

				byte[] data = buffer.array();

				String msg = new String(data).trim();

				System.out.println("客户端收到信息:" + msg);

				ByteBuffer outBuffer = ByteBuffer.wrap(msg.getBytes("utf-8"));

				channel.write(outBuffer);// 将消息回送给客户端
	}
	/**
	 * 
	 * 启动测试
	 * @throws IOException 
	 */
	
	public static void main(String args[]) throws IOException {
		NIOClient client =new NIOClient();
		client.initClient("localhost", 8000);
		client.connect();
	}
}

内容参考:《Java网络编程》   作者 Elliotte Rusty Harold  译 李帅, 荆涛,,由衷感谢书籍作者提供的内容