從同步與異步&阻塞與非阻塞的概念,到具體的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開始支持。
另外要清楚理解的: