Java目前有三種IO相關的API了,下面簡單的說一下:java
BIO,阻塞IO,最經常使用的Java IO API,提供通常的流的讀寫功能。相信學習Java的人,都用過。程序員
NIO,非阻塞IO,在JDK1.4中開始出現,大量應用與服務器端編程,用於提升併發訪問的性能,經常使用的NIO框架有Netty,Mina。編程
AIO,異步IO,在JDK1.7開始出現。尚未了解過,等之後瞭解了再說。瀏覽器
在寫這篇文章前,在網上了解了一下,其中爭議最的問題要數阻塞、非阻塞怎麼理解,異步、同步怎麼理解。服務器
因爲每一個人想法的不一樣,很難達到一個一致的答案,又沒有真正的大牛出來給這一個準確的定義。這裏也簡單的說一下,我對這兩組名詞的理解。併發
我認爲,BIO,NIO沒有你們想的那麼複雜,就是底層實現中進行數據的讀寫(IO)採起的兩種方案,只不過非阻塞讀寫要比阻塞IO讀寫更快一些。app
bio中的InputStream#read()是一個block方法。框架
同步與異步,我認爲說的並非IO自己,我認爲說的是程序採用的編程模型,也就是說採用的是同步的編程模型仍是異步的編程模型。運維
BIO、NIO,他們的區別是操做系統讀寫數據採用的方式,他們是Java中的概念,在Java領域,他們的底層實現採用的是同步的編程模型。因此說BIO、NIO都是同步的。異步
AIO的底層實現應當是異步的編程模型,因此說它是異步IO。
這裏我只是闡述了我對它們的理解,沒有與你們爭論到底怎麼去理解他們。也許我沒有你們想的那麼深遠,畢竟我只是學習了NIO不到一天時間而已。
一個程序運行的快慢,通常有會受到兩個因素的影響:1)程序代碼是否高效,2)IO讀寫是否高效。曾經看過這麼一幅圖,大體內容是:一幫不一樣角色的人(程序員、運維、項目經理等角色的人)在一塊兒討論一個應用程序效率地下的問題。
程序員說的是:給我3個月時間,我可以讓程序運行效率提升,固然了,我要調整代碼的總體結構…
運維說:…
項目經理說:換用讀寫更快的硬件設備解決這個問題。
故事我已經沒法還原,可是這個故事說的內容就是程序優化帶來的效率的提高遠不及提升IO速度帶來的提高。
相比於BIO,NIO就是從讀寫來提高效率的。性能對於服務器來講尤其重要,服務器端編程並非都採用了NIO編程。
Tomcat服務器內部,就有BIO、NIO兩種方式。
BIO,是一種阻塞IO,服務器端使用BIO進行數據讀寫時,通常都是採用了一個Socket請求對應一個Thread的方式來提升性能的。
可是一臺服務器上,能夠跑的線程數量也是有限制的:線程不是越多越好,畢竟線程間的切換,也是有不小的開銷。也不是越少越好,線程太少,極端狀況下一個線程,若是用一個線程來解決用戶的併發訪問,服務器接收一個客戶的請求時,其餘人都要處於等待狀態。你訪問網頁,多數狀況下超過5秒,估計你就關掉它了吧。
或者採用線程池方案。
採用選擇器輪詢可用通道,讀寫數據。具體的怎麼作的就不說了,網上一大坨一大坨的,雖然網上你們寫的大可能是copy別人的。下面給會出一個例子,因此這裏就很少說了,不知道的能夠網上找相關的文章。
一個Thread下開一個Selector,一個Selector處理多個Socket通道(也就是多個用於請求),這樣就是一個Thread線程能夠同時處理多個用戶請求。
倘若說,服務器設置同時處理1000個用戶請求(也就是1000個處理用戶請求的線程)。倘若有10000我的來發請求。
若是採用BIO API編程,那麼就同時只能爲1000我的服務,其餘的9000人就處於等待狀態。
若是採用NIO API編程,也開啓1000個線程,由於一個Thread能夠同時處理多個用戶請求,咱不說讓它處理太多了,就處理10個吧,這樣算下來,這個10000個用戶請求,就均可以處理了。
今天學習了NIO,就用NIO來處理瀏覽器用戶請求吧。瀏覽器發送的確定不是採用NIO API發送Socket請求的,確定是使用了阻塞式IO,也就是對應於Java中的BIO了。
package com.fjn.other.nio.socket; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.InetSocketAddress; import java.net.ServerSocket; import java.net.Socket; 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.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @SuppressWarnings({ "unchecked" }) public class NioServer { ServerSocketChannel serverChannel; ServerSocket serverSocket; public final int port; private Selector selector; ByteBuffer buffer = ByteBuffer.allocate(1024); NioServer(final int port) { this.port = port; } void init() throws Exception { // 建立 ServerSocketChannel、ServerSocket serverChannel = ServerSocketChannel.open(); serverSocket = serverChannel.socket(); serverSocket.bind(new InetSocketAddress(port)); // 設置通道爲非阻塞模式 serverChannel.configureBlocking(false); // 開啓通道選擇器,並註冊 ServerSocketChannel selector = Selector.open(); serverChannel.register(selector, SelectionKey.OP_ACCEPT); } void go() throws Exception { while (true) { int num = selector.select(); if (num <= 0) continue; Iterator<SelectionKey> keyIter = selector.selectedKeys().iterator(); while (keyIter.hasNext()) { final SelectionKey key = keyIter.next(); // 接收一個Socket鏈接 // key.isAcceptable()若是爲true,說明channnel支持accept(),也就是說明是一個ServerSocketChannel if (key.isAcceptable()) { SocketChannel clientChannel = serverChannel.accept(); if (clientChannel != null) { clientChannel.configureBlocking(false); clientChannel.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE); } } // 若是isReadable()爲true,說明是一個SocketChannel if (key.isReadable()) { String requestContent = read(key); // 業務處理 // responseContent=doSomthing(requestContent); write(key, "ok" /* responseContent */); } keyIter.remove(); } } } // 從通道讀取數據 String read(SelectionKey key) throws Exception { SocketChannel socketChannel = (SocketChannel) key.channel(); buffer.clear();// 這一步必須有 int len = 0; StringBuffer str=new StringBuffer(); while ((len = socketChannel.read(buffer)) > 0) { byte[] bs = buffer.array(); String block=new String(bs, 0, len); System.out.println("Server read: " + block); str.append(block); } buffer.clear(); return str.toString(); } // 寫數據到通道 void write(SelectionKey key, String str) throws Exception { SocketChannel socketChannel = (SocketChannel) key.channel(); buffer.clear(); buffer.put(str.getBytes()); buffer.flip();// 這一步必須有 socketChannel.write(buffer); } public static void main(String[] args) throws Exception { final int port = 10000; NioServer server = new NioServer(port); server.init(); ///======================================================== // 接下來模擬3個Client併發訪問服務器 int poolsize = 3; ExecutorService pool = Executors.newFixedThreadPool(poolsize); Collection<Callable> tasks = new ArrayList<Callable>(10); final String clientname="clientThread"; for (int i = 0; i < poolsize; i++) { final int n = i; // 若每個Client都保持使用BIO方式發送數據到Server,並讀取數據。 tasks.add(new Callable() { @Override public Object call() throws Exception { Socket socket = new Socket("127.0.0.1", port); final InputStream input = socket.getInputStream(); final OutputStream out = socket.getOutputStream(); final String clientname_n = clientname + "_" + n; // BIO讀取數據線程 new Thread(clientname_n + "_read") { @Override public void run() { byte[] bs = new byte[1024]; while (true) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } int len = 0; try { while ((len = input.read(bs)) != -1) { System.out.println("Clinet thread " + Thread.currentThread() .getName() + " read: " + new String(bs, 0, len)); } } catch (IOException e) { e.printStackTrace(); } } } }.start(); // BIO寫數據線程 new Thread(clientname_n + "_write") { @Override public void run() { int a = 0; while (true) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } String str = Thread.currentThread().getName() + " hello, " + a; try { out.write(str.getBytes()); a++; } catch (IOException e) { e.printStackTrace(); } } } }.start(); return null; } }); } pool.invokeAll((Collection<? extends Callable<Object>>) tasks); server.go(); } }
上面的測試的是3個Client採用BIO API不斷的併發的發送Socket 請求到Server端。Server採用NIO API處理Client的請求並做出響應,而後Client接收響應。