BIO, NIO, AIO,自己的描述都是在Java語言的基礎上的。 而描述IO,咱們須要從三個層面:java
BIO, NIO, AIO以Java的角度理解:linux
在JDK1.4以前,用Java編寫網絡請求,都是創建一個ServerSocket,而後,客戶端創建Socket時就會詢問是否有線程能夠處理,若是沒有,要麼等待,要麼被拒絕。即:一個鏈接,要求Server對應一個處理線程。編程
public class PlainEchoServer { public void serve(int port) throws IOException { final ServerSocket socket = new ServerSocket(port); //Bind server to port try { while (true) { //Block until new client connection is accepted final Socket clientSocket = socket.accept(); System.out.println("Accepted connection from " + clientSocket); //Create new thread to handle client connection new Thread(new Runnable() { @Override public void run() { try { BufferedReader reader = new BufferedReader(new InputStreamReader(clientSocket.getInputStream())); PrintWriter writer = new PrintWriter(clientSocket.getOutputStream(), true); //Read data from client and write it back while (true) { writer.println(reader.readLine()); writer.flush(); } } catch (IOException e) { e.printStackTrace(); try { clientSocket.close(); } catch (IOException ex) { // ignore on close } } } }).start(); //Start thread } } catch (IOException e) { e.printStackTrace(); } } }
在Java裏的由來,在JDK1.4及之後版本中提供了一套API來專門操做非阻塞I/O,咱們能夠在java.nio包及其子包中找到相關的類和接口。因爲這套API是JDK新提供的I/O API,所以,也叫New I/O,這就是包名nio的由來。這套API由三個主要的部分組成:緩衝區(Buffers)、通道(Channels)和非阻塞I/O的核心類組成。在理解NIO的時候,須要區分,說的是New I/O仍是非阻塞IO,New I/O是Java的包,NIO是非阻塞IO概念。這裏講的是後面一種。windows
NIO自己是基於事件驅動思想來完成的,其主要想解決的是BIO的大併發問題:在使用同步I/O的網絡應用中,若是要同時處理多個客戶端請求,或是在客戶端要同時和多個服務器進行通信,就必須使用多線程來處理。也就是說,將每個客戶端請求分配給一個線程來單獨處理。這樣作雖然能夠達到咱們的要求,但同時又會帶來另一個問題。因爲每建立一個線程,就要爲這個線程分配必定的內存空間(也叫工做存儲器),並且操做系統自己也對線程的總數有必定的限制。若是客戶端的請求過多,服務端程序可能會由於不堪重負而拒絕客戶端的請求,甚至服務器可能會所以而癱瘓。 NIO基於Selector,當socket有流可讀或可寫入socket時,操做系統會相應的通知引用程序進行處理,應用再將流讀取到緩衝區或寫入操做系統。也就是說,這個時候,已經不是一個鏈接就要對應一個處理線程了,而是有效的請求,對應一個線程,當鏈接沒有數據時,是沒有工做線程來處理的。服務器
public class PlainNioEchoServer { public void serve(int port) throws IOException { System.out.println("Listening for connections on port " + port); ServerSocketChannel serverChannel = ServerSocketChannel.open(); ServerSocket ss = serverChannel.socket(); InetSocketAddress address = new InetSocketAddress(port); //Bind server to port ss.bind(address); serverChannel.configureBlocking(false); Selector selector = Selector.open(); //Register the channel with the selector to be interested in new Client connections that get accepted serverChannel.register(selector, SelectionKey.OP_ACCEPT); while (true) { try { //Block until something is selected selector.select(); } catch (IOException ex) { ex.printStackTrace(); //handle in a proper way break; } //Get all SelectedKey instances Set<SelectionKey> readyKeys = selector.selectedKeys(); Iterator<SelectionKey> iterator = readyKeys.iterator(); while (iterator.hasNext()) { SelectionKey key = (SelectionKey) iterator.next(); //Remove the SelectedKey from the iterator iterator.remove(); try { if (key.isAcceptable()) { ServerSocketChannel server = (ServerSocketChannel) key.channel(); //Accept the client connection SocketChannel client = server.accept(); System.out.println("Accepted connection from " + client); client.configureBlocking(false); //Register connection to selector and set ByteBuffer client.register(selector, SelectionKey.OP_WRITE | SelectionKey.OP_READ, ByteBuffer.allocate(100)); } //Check for SelectedKey for read if (key.isReadable()) { SocketChannel client = (SocketChannel) key.channel(); ByteBuffer output = (ByteBuffer) key.attachment(); //Read data to ByteBuffer client.read(output); } //Check for SelectedKey for write if (key.isWritable()) { SocketChannel client = (SocketChannel) key.channel(); ByteBuffer output = (ByteBuffer) key.attachment(); output.flip(); //Write data from ByteBuffer to channel client.write(output); output.compact(); } } catch (IOException ex) { key.cancel(); try { key.channel().close(); } catch (IOException cex) { } } } } } }
與NIO不一樣,當進行讀寫操做時,只須直接調用API的read或write方法便可。這兩種方法均爲異步的,對於讀操做而言,當有流可讀取時,操做系統會將可讀的流傳入read方法的緩衝區,並通知應用程序;對於寫操做而言,當操做系統將write方法傳遞的流寫入完畢時,操做系統主動通知應用程序。網絡
便可以理解爲,read/write方法都是異步的,完成後會主動調用回調函數。多線程
在JDK1.7中,這部份內容被稱做NIO.2,主要在java.nio.channels包下增長了下面四個異步通道:併發
其中的read/write方法,會返回一個帶回調函數的對象,當執行完讀取/寫入操做後,直接調用回調函數。app
public class PlainNio2EchoServer { public void serve(int port) throws IOException { System.out.println("Listening for connections on port " + port); final AsynchronousServerSocketChannel serverChannel = AsynchronousServerSocketChannel.open(); InetSocketAddress address = new InetSocketAddress(port); // Bind Server to port serverChannel.bind(address); final CountDownLatch latch = new CountDownLatch(1); // Start to accept new Client connections. Once one is accepted the CompletionHandler will get called. serverChannel.accept(null, new CompletionHandler<AsynchronousSocketChannel, Object>() { @Override public void completed(final AsynchronousSocketChannel channel, Object attachment) { // Again accept new Client connections serverChannel.accept(null, this); ByteBuffer buffer = ByteBuffer.allocate(100); // Trigger a read operation on the Channel, the given CompletionHandler will be notified once something was read channel.read(buffer, buffer, new EchoCompletionHandler(channel)); } @Override public void failed(Throwable throwable, Object attachment) { try { // Close the socket on error serverChannel.close(); } catch (IOException e) { // ingnore on close } finally { latch.countDown(); } } }); try { latch.await(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } private final class EchoCompletionHandler implements CompletionHandler<Integer, ByteBuffer> { private final AsynchronousSocketChannel channel; EchoCompletionHandler(AsynchronousSocketChannel channel) { this.channel = channel; } @Override public void completed(Integer result, ByteBuffer buffer) { buffer.flip(); // Trigger a write operation on the Channel, the given CompletionHandler will be notified once something was written channel.write(buffer, buffer, new CompletionHandler<Integer, ByteBuffer>() { @Override public void completed(Integer result, ByteBuffer buffer) { if (buffer.hasRemaining()) { // Trigger again a write operation if something is left in the ByteBuffer channel.write(buffer, buffer, this); } else { buffer.compact(); // Trigger a read operation on the Channel, the given CompletionHandler will be notified once something was read channel.read(buffer, buffer, EchoCompletionHandler.this); } } @Override public void failed(Throwable exc, ByteBuffer attachment) { try { channel.close(); } catch (IOException e) { // ingnore on close } } }); } @Override public void failed(Throwable exc, ByteBuffer attachment) { try { channel.close(); } catch (IOException e) { // ingnore on close } } } }
說道實現原理,還要從操做系統的IO模型上了解 按照《Unix網絡編程》的劃分,IO模型能夠分爲:阻塞IO、非阻塞IO、IO複用、信號驅動IO和異步IO,按照POSIX標準來劃分只分爲兩類:同步IO和異步IO。 如何區分呢?首先一個IO操做其實分紅了兩個步驟:發起IO請求和實際的IO操做,同步IO和異步IO的區別就在於第二個步驟是否阻塞,若是實際的IO讀寫阻塞請求進程,那麼就是同步IO,所以阻塞IO、非阻塞IO、IO複用、信號驅動IO都是同步IO,若是不阻塞,而是操做系統幫你作完IO操做再將結果返回給你,那麼就是異步IO。阻塞IO和非阻塞IO的區別在於第一步,發起IO請求是否會被阻塞,若是阻塞直到完成那麼就是傳統的阻塞IO,若是不阻塞,那麼就是非阻塞IO。異步
收到操做系統的IO模型,又不得不提select/poll/epoll/iocp。 能夠理解的說明是:在Linux 2.6之後,java NIO的實現,是經過epoll來實現的,這點能夠經過jdk的源代碼發現。而AIO,在windows上是經過IOCP實現的,在linux上仍是經過epoll來實現的。 這裏強調一點:AIO,這是I/O處理模式,而epoll等都是實現AIO的一種編程模型;換句話說,AIO是一種接口標準,各家操做系統能夠實現也能夠不實現。在不一樣操做系統上在高併發狀況下最好都採用操做系統推薦的方式。Linux上尚未真正實現網絡方式的AIO。
在windows上,AIO的實現是經過IOCP來完成的,看JDK的源代碼,能夠發現
WindowsAsynchronousSocketChannelImpl
看實現接口:
implements Iocp.OverlappedChannel
再看實現方法:裏面的read0/write0方法是native方法,調用的jvm底層實現。
在linux上,AIO的實現是經過epoll來完成的,看JDK源碼,能夠發現,實現源碼是:
UnixAsynchronousSocketChannelImpl
看實現接口:
implements Port.PollableChannel
這是與windows最大的區別,poll的實現,在linux2.6後,默認使用epoll。