java中IO的主要来源是本地和网络传输。
在了解三种处理方式之前,先了解,同步异步,阻塞非阻塞:
1、同步: 用户进程触发IO操作并等待或者轮询的去查看IO操作是否就绪; 例如自己亲自去 餐厅吃饭
2、异步: 用户触发IO操作以后,可以干别的事,IO操作完成以后再通知当前线程;例如自己通过美团外卖订购了,送餐上面;自己在等餐到来时间可以干别的事情;
3、阻塞: 当试图进读写文件的时候,发现不可读取或没东西读,则进入等待状态知道可读;例如去楼下排队等餐;
4、非阻塞:用户进程访问数据时,会马上返回一个状态值(可读不可读);例如在餐厅吃饭,先取个号,然后坐着等待服务员报号才能入座吃饭(你可以不停的询问服务员还有多久)。(使用非阻塞IO时,如果不能读写Java调用会马上返回,当IO事件分发器会通知可读写时再继续进行读写,不断循环直到读写完成)。
一、同步阻塞BIO
在JDK1.4出来之前,我们建立网络连接的时候采用BIO模式,需要先在服务端启动一个ServerSocket,然后在客户端启动Socket来对服务端进行通信,默认情况下服务端需要对每个请求建立一堆线程等待请求,而客户端发送请求后,先咨询服务端是否有线程相应,如果没有则会一直等待或者遭到拒绝请求,如果有的话,客户端会线程会等待请求结束后才继续执行。
该模型最大的问题就是缺乏弹性伸缩能力,当客户端并发访问量增加后,服务端的线程个数和客户端并发访问数呈1:1的正比关系,Java中的线程也是比较宝贵的系统资源,线程数量快速膨胀后,系统的性能将急剧下降,随着访问量的继续增大,系统最终就死掉了。不适合高并发和高性能方面。
二、同步非阻塞的NIO
NIO详细文档: http://ifeve.com/java-nio-all/
JDK 1.4中的java.nio.*包中引入新的Java I/O库,其目的是提高速度。实际上,“旧”的I/O包已经使用NIO重新实现过,即使我们不显式的使用NIO编程,也能从中受益。
NIO我们一般认为是New I/O(也是官方的叫法),因为它是相对于老的I/O类库新增的(其实在JDK 1.4中就已经被引入了,但这个名词还会继续用很久,即使它们在现在看来已经是“旧”的了,所以也提示我们在命名时,需要好好考虑),做了很大的改变。但民间跟多人称之为Non-block I/O,即非阻塞I/O,因为这样叫,更能体现它的特点。而下文中的NIO,不是指整个新的I/O库,而是非阻塞I/O。
NIO提供了与传统BIO模型中的Socket和ServerSocket相对应的SocketChannel和ServerSocketChannel两种不同的套接字通道实现。
新增的着两种通道都支持阻塞和非阻塞两种模式。
阻塞模式使用就像传统中的支持一样,比较简单,但是性能和可靠性都不好;非阻塞模式正好与之相反。
对于低负载、低并发的应用程序,可以使用同步阻塞I/O来提升开发速率和更好的维护性;对于高负载、高并发的(网络)应用,应使用NIO的非阻塞模式来开发。
服务端流程图:
客户端流程图:
客户端代码:
package com.example.demo.aio; 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; import java.util.Set; /** * Created with IntelliJ IDEA. * User: 简德群 * Date: 2018/3/12 * Time: 18:15 * To change this template use File | Settings | File Templates. */ public class NioClient implements Runnable{ private String ip; private int port; private Selector selector; private SocketChannel socketChannel; private volatile boolean started; public NioClient(String ip,int port){ this.ip=ip; this.port=port; try { //创建选择器 selector =Selector.open(); //打开监听端口 socketChannel = SocketChannel.open(new InetSocketAddress(ip,port)); socketChannel.configureBlocking(false);//设置非阻塞模式 started = true; } catch (IOException e) { e.printStackTrace(); System.exit(1); } } public void stop(){ this.started = false; } @Override public void run() { //连接服务器 try { doConnect(); } catch (IOException e) { e.printStackTrace(); System.exit(1); } //循环遍历selector while (started) { try { //无论是否有读写事件发生,selector每隔1s被唤醒一次 selector.select(1000); //阻塞,只有当至少一个注册的事件发生的时候才会继续. // selector.select(); Setkeys = selector.selectedKeys(); Iterator it = keys.iterator(); SelectionKey key = null; while (it.hasNext()) { key = it.next(); it.remove(); try { handleInput(key); } catch (Exception e) { if (key != null) { key.cancel(); if (key.channel() != null) { key.channel().close(); } } } } } catch (Exception e) { e.printStackTrace(); System.exit(1); } } //selector关闭后会自动释放里面管理的资源 if (selector != null) try { selector.close(); } catch (Exception e) { e.printStackTrace(); } } private void handleInput(SelectionKey key) throws IOException { if(key.isValid()){ SocketChannel sc = (SocketChannel) key.channel(); if(key.isConnectable()){ if(sc.finishConnect()); else System.exit(1); } //读消息 if(key.isReadable()){ //创建ByteBuffer,并开辟一个1M的缓冲区 ByteBuffer buffer = ByteBuffer.allocate(1024); //读取请求码流,返回读取到的字节数 int readBytes = sc.read(buffer); //读取到字节,对字节进行编解码 if(readBytes>0){ //将缓冲区当前的limit设置为position=0,用于后续对缓冲区的读取操作 buffer.flip(); //根据缓冲区可读字节数创建字节数组 byte[] bytes = new byte[buffer.remaining()]; //将缓冲区可读字节数组复制到新建的数组中 buffer.get(bytes); String result = new String(bytes,"UTF-8"); System.out.println("客户端收到消息:" + result); } //没有读取到字节 忽略 // else if(readBytes==0); //链路已经关闭,释放资源 else if(readBytes<0){ key.cancel(); sc.close(); } } } } private void doConnect() throws IOException { if(socketChannel.connect(new InetSocketAddress(this.ip,this.port))); else socketChannel.register(selector, SelectionKey.OP_CONNECT); } public void sendMsg(String message) throws IOException { socketChannel.register(selector,SelectionKey.OP_READ); doWrite(socketChannel,message); } //异步发送消息 private void doWrite(SocketChannel channel,String request) throws IOException{ //将消息编码为字节数组 byte[] bytes = request.getBytes(); //根据数组容量创建ByteBuffer ByteBuffer writeBuffer = ByteBuffer.allocate(bytes.length); //将字节数组复制到缓冲区 writeBuffer.put(bytes); //flip操作 writeBuffer.flip(); //发送缓冲区的字节数组 channel.write(writeBuffer); //****此处不含处理“写半包”的代码 } }
package com.example.demo.aio; /** * Created with IntelliJ IDEA. * User: 简德群 * Date: 2018/3/13 * Time: 9:13 * To change this template use File | Settings | File Templates. */ public class RunClient { private final static int DEFUALT_PORT=12345; private final static String SERVER_IP="127.0.0.1"; private static NioClient nioClient; public static void start(){ start(DEFUALT_PORT,SERVER_IP); } public synchronized static void start(int port,String ip){ if(nioClient!=null) nioClient.stop(); nioClient = new NioClient(ip,port); new Thread(nioClient,"client").start(); } //向服务器发送消息 public static boolean sendMsg(String msg) throws Exception{ if(msg.equals("q")) return false; nioClient.sendMsg(msg); return true; } public static void main(String[] args){ start(); } }
服务端代码:
package com.example.demo.aio; 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.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.util.Iterator; import java.util.Set; /** * Created with IntelliJ IDEA. * User: 简德群 * Date: 2018/3/12 * Time: 17:42 * To change this template use File | Settings | File Templates. */ public class NioServer implements Runnable { private Selector selector; private ServerSocketChannel serverSocketChannel; private volatile boolean started; public NioServer(int port) throws IOException { //指定选择器 selector = Selector.open(); //打开监听通道 serverSocketChannel = ServerSocketChannel.open(); //设置监听通道是否为阻塞(true)/非阻塞(false) serverSocketChannel.configureBlocking(false); //开启非阻塞模式 //绑定端口, backlog设为1024 serverSocketChannel.socket().bind(new InetSocketAddress(port),1024); //监听客户端连接请求 serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); //标记服务器开启 started = true; System.out.println("AioServer.AioServer已开启端口:"+port); } public void stop(){ started =false; } @Override public void run() { //如果服务器开启 while(started){ //循环变量selector try { //无论是否有读写事件发生,selector每隔1s被唤醒一次 selector.select(1000); //阻塞,只有当至少一个注册的事件发生的时候才会继续. // selector.select(); Setset = selector.selectedKeys(); Iterator iterator = set.iterator(); SelectionKey key = null; while(iterator.hasNext()){ key = iterator.next(); iterator.remove();; handleInputSelectionKey(key); } } catch (IOException e) { e.printStackTrace(); } } } public void handleInputSelectionKey(SelectionKey key) throws IOException { if(key.isValid()){ //处理新接入的请求信息 if(key.isAcceptable()){ ServerSocketChannel ssc = (ServerSocketChannel) key.channel(); //通过ServerSocketChannel的accept创建SocketChannel实例 //完成该操作意味着完成TCP三次握手,TCP物理链路正式建立 SocketChannel sc = ssc.accept(); sc.configureBlocking(false);//设置为非阻塞 //注册为读 sc.register(selector, SelectionKey.OP_READ); } //读消息 if(key.isReadable()){ SocketChannel sc = (SocketChannel) key.channel(); //创建ByteBuffer,并开辟一个1M的缓冲区 ByteBuffer byteBuffer = ByteBuffer.allocate(1024); ////读取请求码流,返回读取到的字节数 int readbytes = sc.read(byteBuffer); //读取到字节,对字节进行编解码 if(readbytes >0){ //将缓冲区当前的limit设置为position=0,用于后续对缓冲区的读取操作 byteBuffer.flip(); //根据缓冲区可读字节数创建字节数组 byte[] bytes = new byte[byteBuffer.remaining()]; //将缓冲区可读字节数组复制到新建的数组中 byteBuffer.get(bytes); String msg = new String(bytes,"UTF-8"); System.out.println("服务器收到消息:" + msg); //发送应答消息 String result = "收到"+msg; doWrite(sc,msg); } //没有读取到字节 忽略 // else if(readBytes==0); //链路已经关闭,释放资源 else if(readbytes<0){ key.cancel(); sc.close(); } } } } //异步发送应答消息 private void doWrite(SocketChannel channel,String response) throws IOException{ //将消息编码为字节数组 byte[] bytes = response.getBytes(); //根据数组容量创建ByteBuffer ByteBuffer writeBuffer = ByteBuffer.allocate(bytes.length); //将字节数组复制到缓冲区 writeBuffer.put(bytes); //flip操作 writeBuffer.flip(); //发送缓冲区的字节数组 channel.write(writeBuffer); //****此处不含处理“写半包”的代码 } }
package com.example.demo.aio; import java.io.IOException; /** * Created with IntelliJ IDEA. * User: 简德群 * Date: 2018/3/12 * Time: 18:12 * To change this template use File | Settings | File Templates. */ public class RunServer { private final static int PORT = 12345; private static NioServer nioServer; public static void start(){ start(PORT); } public static synchronized void start(int port){ if(aioServer!=null) nioServer.stop(); try { nioServer = new NioServer(port); } catch (IOException e) { e.printStackTrace(); } new Thread(nioServer,"Server").start(); } public static void main(String[] args){ start(); } }
测试代码:
package com.example.demo.aio; /** * Created with IntelliJ IDEA. * User: 简德群 * Date: 2018/3/13 * Time: 10:07 * To change this template use File | Settings | File Templates. */ public class NioTest { public static void main(String[] args) throws Exception { RunServer.start(); Thread.sleep(1000); RunClient.start(); RunClient.sendMsg("jsjsj"); // while (RunClient.sendMsg(new Scanner(System.in).nextLine())); } }
三、异步非阻塞AIO
NIO 2.0引入了新的异步通道的概念,并提供了异步文件通道和异步套接字通道的实现。
异步的套接字通道时真正的异步非阻塞I/O,对应于UNIX网络编程中的事件驱动I/O(AIO)。他不需要过多的Selector对注册的通道进行轮询即可实现异步读写,从而简化了NIO的编程模型。
客户端代码:
package com.example.demo.aio; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.AsynchronousSocketChannel; import java.nio.channels.CompletionHandler; import java.util.concurrent.CountDownLatch; /** * Created with IntelliJ IDEA. * User: 简德群 * Date: 2018/3/12 * Time: 18:15 * To change this template use File | Settings | File Templates. */ public class AioClient implements CompletionHandler, Runnable{ private String ip; private int port; private AsynchronousSocketChannel asynchronousSocketChannel; private CountDownLatch latch; public AioClient(String ip,int port){ this.ip=ip; this.port=port; try { //创建异步的客户端通道 asynchronousSocketChannel = AsynchronousSocketChannel.open(); } catch (IOException e) { e.printStackTrace(); } } public void run() { //创建CountDownLatch等待 latch = new CountDownLatch(1); //发起异步连接操作,回调参数就是这个类本身,如果连接成功会回调completed方法 asynchronousSocketChannel.connect(new InetSocketAddress(ip, port), this, this); try { latch.await(); } catch (InterruptedException e1) { e1.printStackTrace(); } try { asynchronousSocketChannel.close(); } catch (IOException e) { e.printStackTrace(); } } //连接服务器成功 //意味着TCP三次握手完成 public void completed(Void result, AioClient attachment) { System.out.println("客户端成功连接到服务器..."); } //连接服务器失败 public void failed(Throwable exc, AioClient attachment) { System.err.println("连接服务器失败..."); exc.printStackTrace(); try { asynchronousSocketChannel.close(); latch.countDown(); } catch (IOException e) { e.printStackTrace(); } } //向服务器发送消息 public void sendMsg(String msg){ byte[] req = msg.getBytes(); ByteBuffer writeBuffer = ByteBuffer.allocate(req.length); writeBuffer.put(req); writeBuffer.flip(); //异步写 asynchronousSocketChannel.write(writeBuffer, writeBuffer,new WriteHandler(asynchronousSocketChannel, latch)); } } class WriteHandler implements CompletionHandler { private AsynchronousSocketChannel clientChannel; private CountDownLatch latch; public WriteHandler(AsynchronousSocketChannel clientChannel,CountDownLatch latch) { this.clientChannel = clientChannel; this.latch = latch; } public void completed(Integer result, ByteBuffer buffer) { //完成全部数据的写入 if (buffer.hasRemaining()) { clientChannel.write(buffer, buffer, this); } else { //读取数据 ByteBuffer readBuffer = ByteBuffer.allocate(1024); clientChannel.read(readBuffer,readBuffer,new ReadClientHandler(clientChannel, latch)); } } public void failed(Throwable exc, ByteBuffer attachment) { System.err.println("数据发送失败..."); try { clientChannel.close(); latch.countDown(); } catch (IOException e) { } } } class ReadClientHandler implements CompletionHandler { private AsynchronousSocketChannel clientChannel; private CountDownLatch latch; public ReadClientHandler(AsynchronousSocketChannel clientChannel,CountDownLatch latch) { this.clientChannel = clientChannel; this.latch = latch; } @Override public void completed(Integer result,ByteBuffer buffer) { buffer.flip(); byte[] bytes = new byte[buffer.remaining()]; buffer.get(bytes); String body; try { body = new String(bytes,"UTF-8"); System.out.println("客户端收到结果:"+ body); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } } @Override public void failed(Throwable exc,ByteBuffer attachment) { System.err.println("数据读取失败..."); try { clientChannel.close(); latch.countDown(); } catch (IOException e) { } } }
package com.example.demo.aio; /** * Created with IntelliJ IDEA. * User: 简德群 * Date: 2018/3/13 * Time: 9:13 * To change this template use File | Settings | File Templates. */ public class RunClient { private final static int DEFUALT_PORT=12345; private final static String SERVER_IP="127.0.0.1"; private static AioClient aioClient; public static void start(){ start(DEFUALT_PORT,SERVER_IP); } public synchronized static void start(int port,String ip){ if(aioClient==null){ aioClient = new AioClient(ip,port); new Thread(aioClient,"client").start(); } } //向服务器发送消息 public static boolean sendMsg(String msg) throws Exception{ if(msg.equals("q")) return false; aioClient.sendMsg(msg); return true; } public static void main(String[] args){ start(); } }
服务端代码:
package com.example.demo.aio; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.channels.AsynchronousServerSocketChannel; import java.util.concurrent.CountDownLatch; /** * Created with IntelliJ IDEA. * User: 简德群 * Date: 2018/3/12 * Time: 17:42 * To change this template use File | Settings | File Templates. */ public class AioServer implements Runnable { public CountDownLatch latch; public AsynchronousServerSocketChannel asynchronousServerSocketChannel; public AioServer(int port) throws IOException { //创建服务通道 asynchronousServerSocketChannel = AsynchronousServerSocketChannel.open(); //绑定端口 asynchronousServerSocketChannel.bind(new InetSocketAddress(port),1024); //标记服务器开启 System.out.println("AioServer.AioServer已开启端口:"+port); } public void run() { //CountDownLatch初始化 //它的作用:在完成一组正在执行的操作之前,允许当前的现场一直阻塞 //此处,让现场在此阻塞,防止服务端执行完成后退出 //也可以使用while(true)+sleep //生成环境就不需要担心这个问题,以为服务端是不会退出的 latch = new CountDownLatch(1); //用于接收客户端的连接 asynchronousServerSocketChannel.accept(this,new AcceptHandler()); try { latch.await(); } catch (InterruptedException e) { e.printStackTrace(); } } } package com.example.demo.aio; import java.nio.ByteBuffer; import java.nio.channels.AsynchronousSocketChannel; import java.nio.channels.CompletionHandler; /** * Created with IntelliJ IDEA. * User: 简德群 * Date: 2018/3/13 * Time: 11:35 * To change this template use File | Settings | File Templates. */ //作为handler接收客户端连接 public class AcceptHandler implements CompletionHandler{ public void completed(AsynchronousSocketChannel channel,AioServer aioServer) { //继续接受其他客户端的请求 RunServer.clientCount++; System.out.println("连接的客户端数:" + RunServer.clientCount); aioServer.asynchronousServerSocketChannel.accept(aioServer, this); //创建新的Buffer ByteBuffer buffer = ByteBuffer.allocate(1024); //异步读 第三个参数为接收消息回调的业务Handler channel.read(buffer, buffer, new ReadHandler(channel)); } public void failed(Throwable exc, AioServer aioServer) { exc.printStackTrace(); aioServer.latch.countDown(); } } package com.example.demo.aio; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.nio.ByteBuffer; import java.nio.channels.AsynchronousSocketChannel; import java.nio.channels.CompletionHandler; /** * Created with IntelliJ IDEA. * User: 简德群 * Date: 2018/3/13 * Time: 11:42 * To change this template use File | Settings | File Templates. */ public class ReadHandler implements CompletionHandler { //用于读取半包消息和发送应答 private AsynchronousSocketChannel channel; public ReadHandler(AsynchronousSocketChannel channel) { this.channel = channel; } //读取到消息后的处理 public void completed(Integer result, ByteBuffer attachment) { //flip操作 attachment.flip(); //根据 byte[] message = new byte[attachment.remaining()]; attachment.get(message); try { String expression = new String(message, "UTF-8"); System.out.println("服务器收到消息: " + expression); //向客户端发送消息 doWrite("服务器发送客户端:"+expression); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } } //发送消息 private void doWrite(String result) { byte[] bytes = result.getBytes(); ByteBuffer writeBuffer = ByteBuffer.allocate(bytes.length); writeBuffer.put(bytes); writeBuffer.flip(); //异步写数据 参数与前面的read一样 channel.write(writeBuffer, writeBuffer,new CompletionHandler () { public void completed(Integer result, ByteBuffer buffer) { //如果没有发送完,就继续发送直到完成 if (buffer.hasRemaining()) channel.write(buffer, buffer, this); else{ //创建新的Buffer ByteBuffer readBuffer = ByteBuffer.allocate(1024); //异步读 第三个参数为接收消息回调的业务Handler channel.read(readBuffer, readBuffer, new ReadHandler(channel)); } } public void failed(Throwable exc, ByteBuffer attachment) { try { channel.close(); } catch (IOException e) { } } }); } public void failed(Throwable exc, ByteBuffer attachment) { try { this.channel.close(); } catch (IOException e) { e.printStackTrace(); } } }
package com.example.demo.aio; import java.io.IOException; /** * Created with IntelliJ IDEA. * User: 简德群 * Date: 2018/3/12 * Time: 18:12 * To change this template use File | Settings | File Templates. */ public class RunServer { private final static int PORT = 12345; private static AioServer aioServer; public static int clientCount =0; public static void start(){ start(PORT); } public static synchronized void start(int port){ if(aioServer != null){ try { aioServer = new AioServer(port); } catch (IOException e) { e.printStackTrace(); } } new Thread(aioServer,"Server").start(); } public static void main(String[] args){ start(); } }
各种I/O的对比:
BIO、NIO、AIO适用场景分析:
BIO方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以前的唯一选择,但程序直观简单易理解。
NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4开始支持。
AIO方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,JDK7开始支持。
另外,I/O属于底层操作,需要操作系统支持,并发也需要操作系统的支持,所以性能方面不同操作系统差异会比较明显。