从同步与异步&阻塞与非阻塞的概念,到具体的I/O模型,再到具体的Java语言实现,都是层层递进,本篇就从Java语言来看I/O模型的大概状况。html
整个Java I/O模型,大体能够分为三类java
BIO,即为Blocking I/O,阻塞IO,大体流程为:linux
public class BlockingIOServer { public static void main(String[] args) throws IOException { int port = 10000; ExecutorService threadPool = Executors.newFixedThreadPool(10); ServerSocket server = new ServerSocket(port); while(true){ Socket client = server.accept(); //从线程池取线程处理client threadPool.execute(()->{ try{ InputStream input = client.getInputStream(); //TODO read input String req = null; String res = "response:"+req; //TODO response client.getOutputStream().write(res.getBytes()); }catch(IOException e){ e.printStackTrace(); }finally { try { client.close(); } catch (Exception e) { e.printStackTrace(); } } }); } } }
若是请求量过大,线程池不够用,那么会严重影响性能。CPU疲于切换线程,执行的效率下降。git
如今tomcat I/O模型默认仍是BIO。编程
可是链接不大,该模型仍是很是具备优越性,代码编写简单,只须要关注该线程内的链接便可。windows
BIO模型,也就是同步阻塞模型。tomcat
NIO,便是Non Blocking I/O,非阻塞IO。服务器
在JDK1.4及之后版本中提供了一套API来专门操做非阻塞I/O,接口以及类定义在java.nio包。因为这套API是JDK新提供的I/O API,所以,也叫New I/O。网络
NIO API由四个主要的部分组成:缓冲区(Buffers)、通道(Channels)、选择器(Selector)和非阻塞I/O的核心类组成。多线程
NIO 的工做大体流程为:
public class NonBlockingIOServer { private int BLOCK = 4096; private ByteBuffer sendBuffer = ByteBuffer.allocate(BLOCK); private ByteBuffer receiveBuffer = ByteBuffer.allocate(BLOCK); private Selector selector; public NonBlockingIOServer(int port) throws IOException { //1.open ServerSocketChannel ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); //2.configureBlocking false serverSocketChannel.configureBlocking(false); //3.bind port serverSocketChannel.socket().bind(new InetSocketAddress(port)); //4.open Selector selector = Selector.open(); //5.serverSocketChannel register select serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); System.out.println("Server Start,port:"+port); } private void accept() throws IOException { while (true) { // 1.select,block selector.select(); // 2.SelectionKey iterator Iterator<SelectionKey> iterator = selector.selectedKeys().iterator(); while (iterator.hasNext()) { SelectionKey selectionKey = iterator.next(); iterator.remove(); try { doAccept(selectionKey); } catch (IOException e) { selectionKey.cancel(); e.printStackTrace(); } } } } private void doAccept(SelectionKey selectionKey)throws IOException{ if (selectionKey.isAcceptable()) { // ServerSocketChannel 的 selectionKey ServerSocketChannel server = (ServerSocketChannel) selectionKey.channel(); if(null == server){ return; } //接受到此通道套接字的链接,block here SocketChannel client = server.accept(); // 配置为非阻塞 client.configureBlocking(false); // 注册读到selector,等待读的selectionKey client.register(selector, SelectionKey.OP_READ); } else if (selectionKey.isReadable()) { // SocketChannel 的 selectionKey SocketChannel client = (SocketChannel) selectionKey.channel(); receiveBuffer.clear(); int count = client.read(receiveBuffer); if (count > 0) { String receiveText = new String( receiveBuffer.array(),0,count); System.out.println(receiveText); //注册写到selector,等待读的selectionKey SelectionKey key = client.register(selector, SelectionKey.OP_WRITE); //这里能够做为设计框架的扩展之处 key.attach(receiveText); } } else if (selectionKey.isWritable()) { // SocketChannel selectionKey SocketChannel client = (SocketChannel) selectionKey.channel(); //取出read 的 attachment String request = (String) selectionKey.attachment(); String sendText="response--" + request; sendBuffer.clear(); sendBuffer.put(sendText.getBytes()); sendBuffer.flip(); //输出到通道 client.write(sendBuffer); System.out.println(sendText); client.register(selector, SelectionKey.OP_READ); } } /** * [[[@param](http://my.oschina.net/u/2303379)](http://my.oschina.net/u/2303379)](http://my.oschina.net/u/2303379) args * [[[@throws](http://my.oschina.net/throws)](http://my.oschina.net/throws)](http://my.oschina.net/throws) IOException */ public static void main(String[] args) throws IOException { int port = 10000; NonBlockingIOServer server = new NonBlockingIOServer(port); server.accept(); } }
主要流程为:
NIO自己是基于事件驱动思想来完成的,便是Reactor模式。
在使用传统同步I/O模型若是要同时处理多个客户端请求,就必须使用多线程来处理。也就是说,将每个客户端请求分配给一个线程来单独处理。这样能够达到咱们的要求,可是若是客户端的请求过多,服务端程序可能会由于不堪重负而拒绝客户端的请求,甚至服务器可能会所以而瘫痪。
而NIO基于Selector,当有感兴趣的事件发生时,就通知对应的事件处理器去处理事件,若是没有,则不处理。当socket有流可读或可写入socket时,操做系统会相应的通知引用程序进行处理,应用再将流读取到缓冲区或写入操做系统。因此使用一个线程作轮询就能够了。
Buffer,也是NIO的一个新特性,能够块状的读/写数据,效率获得极大的提升。
因此NIO提升了线程的利用率,减小系统在管理线程和线程上下文切换的开销。
AIO主要工做流程为:
public class AsynchronousIOServer { private static Charset charset = Charset.forName("UTF-8"); public static void main(String[] args) { int port = 10000; int processors = Runtime.getRuntime().availableProcessors(); ExecutorService threadPool = Executors.newFixedThreadPool(processors); try { AsynchronousChannelGroup group = AsynchronousChannelGroup.withThreadPool(threadPool); AsynchronousServerSocketChannel server = AsynchronousServerSocketChannel.open(group); server.bind(new InetSocketAddress(port)); doAccept(server); group.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS); } catch (IOException | InterruptedException e) { e.printStackTrace(); System.out.println("close server"); System.exit(0); } } private static void doAccept(AsynchronousServerSocketChannel server) { server.accept(null, new CompletionHandler<AsynchronousSocketChannel, Void>() { @Override public void completed(AsynchronousSocketChannel client, Void attachment) { server.accept(null, this);// accept next client connect doRead(client, attachment); } @Override public void failed(Throwable exc, Void attachment) { exc.printStackTrace(); } }); } private static void doRead(AsynchronousSocketChannel client, Void attachment) { ByteBuffer buffer = ByteBuffer.allocate(1024); client.read(buffer, buffer, new CompletionHandler<Integer, ByteBuffer>() { @Override public void completed(Integer result, ByteBuffer attachment) { if (result <= 0) { try { System.out.println("客户端断线:" + client.getRemoteAddress().toString()); attachment = null; } catch (IOException e) { e.printStackTrace(); } return; } attachment.flip(); String req = charset.decode(attachment).toString(); attachment.compact(); client.read(attachment, attachment, this);// next client read /** do service code **/ System.out.println(req); ByteBuffer resBuffer = ByteBuffer.wrap(("response:" + req).getBytes()); doWrite(client, resBuffer, resBuffer); } @Override public void failed(Throwable exc, ByteBuffer attachment) { exc.printStackTrace(); } }); } private static <V> void doWrite(AsynchronousSocketChannel client, ByteBuffer resBuffer, ByteBuffer attachment) { client.write(attachment, attachment, new CompletionHandler<Integer, ByteBuffer>() { @Override public void completed(Integer result, ByteBuffer attachment) { // TODO write success if (result <= 0) { try { System.out.println("客户端断线:" + client.getRemoteAddress().toString()); attachment = null; } catch (IOException e) { e.printStackTrace(); } } } @Override public void failed(Throwable exc, ByteBuffer attachment) { exc.printStackTrace(); } }); } }
主要流程为:
与NIO不一样,NIO每次都是事件通知,代码处理时异常复杂,而AIO当进行读写操做时,只须直接调用API的read或write方法便可。这两种方法均为异步的
对于读操做而言,当有流可读取时,操做系统会将可读的流传入read方法的缓冲区,并异步回调通知应用程序;
对于写操做而言,当操做系统将write方法传递的流写入完毕时,操做系统主动通知应用程序。
在JDK1.7中,这部份内容被称做NIO.2
select/poll/epoll/iocp。在Linux 2.6之后,java NIO的实现,是经过epoll来实现的,这点能够经过jdk的源代码发现。
而AIO,在windows上是经过IOCP实现的,在linux上仍是经过epoll来实现的。
这里强调一点:AIO,这是I/O处理模式,而epoll等都是实现AIO的一种编程模型;换句话说,AIO是一种接口标准,各家操做系统能够实现也能够不实现。在不一样操做系统上在高并发状况下最好都采用操做系统推荐的方式。Linux上尚未真正实现网络方式的AIO。
BIO方式适用于链接数量小,链接时间短,计算密集,代码编写直观,程序直观简单易理解,JDK1.4以前。
NIO方式适用于链接数量大,链接时间短,好比Http服务器,并发局限于应用中,编程比较复杂,JDK1.4开始支持。
AIO方式使用于链接数量大,链接时间长,IO密集型,好比聊天服务器,充分调用OS参与并发操做,编程比较复杂,JDK7开始支持。
另外要清楚理解的: