NIO(阻塞IO),不论是磁盘IO仍是网络IO,数据在写入OutputStream或从InputStream读取的时候均可能发生阻塞,当发生阻塞,线程会失去CPU的使用权,这个在当前访问量大和有性能要求的状况下是不被容许的。若果咱们采用一个客户端对应一个线程的方式来解决这个问题,可是当须要大量的HTTP长链接的时候就会出现问题,好比淘宝的Web旺旺。为了解决上面的问题,咱们来介绍NIO。
IO NIO java
面向流 面向缓冲 算法
阻塞IO 非阻塞IO 服务器
无 选择器
网络
首先引用iteye一个大神的回复关于NIO的总结:并发
Channel 通道 Buffer 缓冲区 Selector是NIO的三个核心部分。 dom
选择器其中Channel对应之前的流,Buffer不是什么新东西,Selector是由于nio可使用异步的非堵塞模式才加入的东西。 之前的流老是堵塞的,一个线程只要对它进行操做,其它操做就会被堵塞,也就至关于水管没有阀门,你伸手接水的时候,无论水到了没有,你就都只能耗在接水(流)上。异步
nio的Channel的加入,至关于增长了水龙头(有阀门),虽然一个时刻也只能接一个水管的水,但依赖轮换策略,在水量不大的时候,各个水管里流出来的水,均可以获得妥善接纳,这个关键之处就是增长了一个接水工,也就Selector,他负责协调,也就是看哪根水管有水了的话,在当前水管的水接到必定程度的时候,就切换一下:临时关上当前水龙头,试着打开另外一个水龙头(看看有没有水)。socket
当其余人须要用水的时候,不是直接去接水,而是事前提了一个水桶给接水工,这个水桶就是Buffer。也就是,其余人虽然也可能要等,但不会在现场等,而是回家等,能够作其它事去,水接满了,接水工会通知他们。性能
其实也是很是接近当前社会分工细化的现实,也是统分利用现有资源达到并发效果的一种很经济的手段,而不是动不动就来个串行处理,虽然那样是最简单的,但也是最浪费资源的方式。测试
NIO 有一个主要的类Selector,这个相似一个观察者,只要咱们把须要探知的socketchannel告诉Selector,咱们接着作别的事情,当有事件发生时,他会通知咱们,传回一组SelectionKey,咱们读取这些Key,就会得到咱们刚刚注册过的socketchannel,而后,咱们从这个Channel中读取数据,放心,包准可以读到,接着咱们能够处理这些数据。 Selector内部原理实际是在作一个对所注册的channel的轮询访问,不断的轮询(目前就这一个算法),一旦轮询到一个channel有所注册的事情发生,好比数据来了,他就会站起来报告,交出一把钥匙,让咱们经过这把钥匙来读取这个channel的内容。
RandomAccessFile aFile = new RandomAccessFile("/Users/wjk/myproject/test/IO/src/main/resources/io.txt", "rw"); //获取接水工 FileChannel channel = aFile.getChannel(); //设置水桶的大小 ByteBuffer buffer = ByteBuffer.allocate(43); //节水工用水桶接水 int bytesRead = channel.read(buffer); if (bytesRead != -1) { System.out.println("read: " + bytesRead); //接水工准备倒出水桶里的水(能够倒出刚才接的水) buffer.flip(); //水桶是否还有水 while (buffer.hasRemaining()) { System.out.println((char) buffer.get()); } //倒掉剩余的水 //compact(将剩余的水留在水桶中) buffer.clear(); bytesRead = channel.read(buffer); } aFile.close();
/** * 服务器端 **/ public class NIOServer { //通道管理器 private Selector selector; /** * 得到一个ServerSocket通道,并对该通道作一些初始化的工做 * * @param port 绑定的端口号 * @throws IOException */ public void initServer(int port) throws IOException { // 得到一个ServerSocket通道 ServerSocketChannel serverChannel = ServerSocketChannel.open(); // 设置通道为非阻塞 serverChannel.configureBlocking(false); // 将该通道对应的ServerSocket绑定到port端口 serverChannel.socket().bind(new InetSocketAddress(port)); // 得到一个通道管理器 this.selector = Selector.open(); //将通道管理器和该通道绑定,并为该通道注册SelectionKey.OP_ACCEPT事件,注册该事件后, //当该事件到达时,selector.select()会返回,若是该事件没到达selector.select()会一直阻塞。 serverChannel.register(selector, SelectionKey.OP_ACCEPT); } /** * 采用轮询的方式监听selector上是否有须要处理的事件,若是有,则进行处理 * * @throws IOException */ @SuppressWarnings("unchecked") public void listen() throws IOException { System.out.println("服务端启动成功!"); // 轮询访问selector while (true) { //当注册的事件到达时,方法返回;不然,该方法会一直阻塞 selector.select(); // 得到selector中选中的项的迭代器,选中的项为注册的事件 Iterator ite = this.selector.selectedKeys().iterator(); while (ite.hasNext()) { SelectionKey key = (SelectionKey) ite.next(); // 删除已选的key,以防重复处理 ite.remove(); // 客户端请求链接事件 if (key.isAcceptable()) { ServerSocketChannel server = (ServerSocketChannel) key .channel(); // 得到和客户端链接的通道 SocketChannel channel = server.accept(); // 设置成非阻塞 channel.configureBlocking(false); //在这里能够给客户端发送信息哦 channel.write(ByteBuffer.wrap(new String("向客户端发送了一条信息").getBytes())); //在和客户端链接成功以后,为了能够接收到客户端的信息,须要给通道设置读的权限。 channel.register(this.selector, SelectionKey.OP_READ); // 得到了可读的事件 } else if (key.isReadable()) { read(key); } } } } /** * 处理读取客户端发来的信息 的事件 * * @param key * @throws IOException */ public void read(SelectionKey key) throws IOException { // 服务器可读取消息:获得事件发生的Socket通道 SocketChannel channel = (SocketChannel) key.channel(); // 建立读取的缓冲区 ByteBuffer buffer = ByteBuffer.allocate(1024); channel.read(buffer); byte[] data = buffer.array(); String msg = new String(data).trim(); System.out.println("服务端收到信息:" + msg); ByteBuffer outBuffer = ByteBuffer.wrap(msg.getBytes()); channel.write(outBuffer);// 将消息回送给客户端 } /** * 启动服务端测试 * * @throws IOException */ public static void main(String[] args) throws IOException { NIOServer server = new NIOServer(); server.initServer(8001); server.listen(); } } /** * 客户端 **/ public class NIOClient { //通道管理器 private Selector selector; /** * 得到一个Socket通道,并对该通道作一些初始化的工做 * @param ip 链接的服务器的ip * @param port 链接的服务器的端口号 * @throws IOException */ public void initClient(String ip,int port) throws IOException { // 得到一个Socket通道 SocketChannel channel = SocketChannel.open(); // 设置通道为非阻塞 channel.configureBlocking(false); // 得到一个通道管理器 this.selector = Selector.open(); // 客户端链接服务器,其实方法执行并无实现链接,须要在listen()方法中调 //用channel.finishConnect();才能完成链接 channel.connect(new InetSocketAddress(ip,port)); //将通道管理器和该通道绑定,并为该通道注册SelectionKey.OP_CONNECT事件。 channel.register(selector, SelectionKey.OP_CONNECT); } /** * 采用轮询的方式监听selector上是否有须要处理的事件,若是有,则进行处理 * @throws IOException */ @SuppressWarnings("unchecked") public void listen() throws IOException { // 轮询访问selector while (true) { selector.select(); // 得到selector中选中的项的迭代器 Iterator ite = this.selector.selectedKeys().iterator(); while (ite.hasNext()) { SelectionKey key = (SelectionKey) 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("向服务端发送了一条信息").getBytes())); //在和服务端链接成功以后,为了能够接收到服务端的信息,须要给通道设置读的权限。 channel.register(this.selector, SelectionKey.OP_READ); // 得到了可读的事件 } else if (key.isReadable()) { read(key); } } } } /** * 处理读取服务端发来的信息 的事件 * @param key * @throws IOException */ public void read(SelectionKey key) throws IOException{ //和服务端的read方法同样 } /** * 启动客户端测试 * @throws IOException */ public static void main(String[] args) throws IOException { NIOClient client = new NIOClient(); client.initClient("localhost",8001); client.listen(); } }