BIO,NIO與AIO的區別

Java NIO : 同步非阻塞,服務器實現模式爲一個請求一個線程,即客戶端發送的鏈接請求都會註冊到多路複用器上,多路複用器輪詢到鏈接有I/O請求時才啓動一個線程進行處理。
Java AIO(NIO.2) : 異步非阻塞,服務器實現模式爲一個有效請求一個線程,客戶端的I/O請求都是由OS先完成了再通知服務器應用去啓動線程進行處理,
NIO方式適用於鏈接數目多且鏈接比較短(輕操做)的架構,好比聊天服務器,併發侷限於應用中,編程比較複雜,JDK1.4開始支持。
AIO方式使用於鏈接數目多且鏈接比較長(重操做)的架構,好比相冊服務器,充分調用OS參與併發操做,編程比較複雜,JDK7開始支持
I/O屬於底層操做,須要操做系統支持,併發也須要操做系統的支持,因此性能方面不一樣操做系統差別會比較明顯。另外NIO的非阻塞,須要一直輪詢,也是一個比較耗資源的。因此出現AIO

  1. 背景:html

    省分短信發送天天都差很少要1000W條上下,遇到特殊節假日和政府通告時量會更大!boss系統中存放的是短信發送內容,而真正完成發送短信指令動做是的華爲方作的短廳,這麼大的通訊量選擇了netty來完成數據傳輸並自定義了一套基於netty的SGIP協議進行通訊; 
    省分boss系統—>短信營業廳(); 
    這裏寫圖片描述java

  2. 基本知識 
    2.1 TCP/IP網絡協議 
    網上不少有關這個協議的解釋,自行google,下面是簡單的理解記憶: 
    tcp/ip的3次握手, 簡單來講就是第一次我鏈接你給你一個標識SYN,你給我返回SYN並給一個新的ACK標記我,而後我再把ACK給你, 
    這樣證實咱們以前傳東西是可靠,的而後就正式傳數據了 
    這裏寫圖片描述 
    圖片來自網上linux

tcp/ip的4次揮手斷開,至關於,你給我一個ACK我給你一個FIN,而後再次彼此交換確認,OK就能夠結束通訊了 
這裏寫圖片描述 
圖片來自網上web

java的socket就是對tcp/ip的一種實現編程

這裏寫圖片描述

基礎代碼,: 
一個簡單的socket實現tcp/ip的樣例,後面的BIO/NIO/AIO都是基本上於這個例子進行變化 
client端:數組

import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.Socket; public class Client { final static String ADDRESS = "127.0.0.1"; final static int PORT = 7788; public static void main(String[] args) Socket socket = null; BufferedReader in = null; PrintWriter out = null; socket = new Socket(ADDRESS, PORT); in = new BufferedReader(new InputStreamReader(socket.getInputStream())); out = new PrintWriter(socket.getOutputStream(), true); //向服務器端發送數據 out.println("接收到客戶端的請求數據..."); out.println("接收到客戶端的請求數據1111..."); String response = in.readLine(); System.out.println("Client: " + response); ...

Server端:瀏覽器

public class Server { final static int PROT = 7788; public static void main(String[] args) { ServerSocket server = null; server = new ServerSocket(PROT); System.out.println(" server start .. "); //進行阻塞 Socket socket = server.accept(); //新建一個線程執行客戶端的任務 new Thread(new ServerHandler(socket)).start(); } } ServerHandler.java 以下: public class ServerHandler implements Runnable{ private Socket socket ; public ServerHandler(Socket socket){ this.socket = socket; } @Override public void run() { BufferedReader in = null; PrintWriter out = null; try { in = new BufferedReader(new InputStreamReader(this.socket.getInputStream())); out = new PrintWriter(this.socket.getOutputStream(), true); String body = null; while(true){ body = in.readLine(); if(body == null) break; System.out.println("Server :" + body); out.println("服務器端回送響的應數據."); } } }

上面這個代碼很簡單轉換成圖型說明就是web瀏覽器發一個請求過來,web服務器就要new 一個線程來處理這個請求,這是傳統的請求處理模型,這也就引來一個很大的問題,當請求越多,服務器端的啓用線程也要越多,咱們都知道linux(window)的文件句柄數有是限的,默認是1024,固然能夠修改,上限好像是65536 ,(一個柄也至關於一個socket也至關於一個thread,linux查看文件句柄Unlimit -a) 其實在實際當中只要併發到1000上下響應請求就會很慢了,因此這種模型是有問題的,這種也就是同步阻塞IO編程(JAVA BIO) 
這裏寫圖片描述
網上查的定義: 
同步阻塞IO(JAVA BIO): 
同步並阻塞,服務器實現模式爲一個鏈接一個線程,即客戶端有鏈接請求時服務器端就須要啓動一個線程進行處理,若是這個鏈接不作任何事情會形成沒必要要的線程開銷.服務器

  1. BIO—>NIO—->AIO的發展歷程 
    上面的BIO是有問題的,也就是出如今jdk1.4那個古時代的產物,如今固然這要改進下,上面的問題無非就是服務器端的線程無限制的增加纔會致使服務器崩掉,那咱們就對徵下藥,加個線程池限制線程的生成,又能夠複用空閒的線程,是的,在jdk1.5也是這樣作的,下面是服務器端改進後的代碼:
public class Server { final static int PORT = 7788; public static void main(String[] args) { ServerSocket server = null; BufferedReader in = null; PrintWriter out = null; server = new ServerSocket(PORT); System.out.println("server start"); Socket socket = null; HandlerExecutorPool executorPool = new HandlerExecutorPool(50, 1000); while(true){ socket = server.accept(); executorPool.execute(new ServerHandler(socket)); } } } HandlerExecutorPool.java import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.ExecutorService; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; public class HandlerExecutorPool { private ExecutorService executor; public HandlerExecutorPool(int maxPoolSize, int queueSize){ this.executor = new ThreadPoolExecutor( Runtime.getRuntime().availableProcessors(), maxPoolSize, 120L, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(queueSize)); } public void execute(Runnable task){ this.executor.execute(task); } }

Jdk1.5創造了一個假的nio 用一個HanderExecutorPool來限定了線程數量,但只是解決了服務器端不會由於併發太多而死掉,但解決不了併發大而響應愈來愈慢的,到時你也會懷疑你是否是真的用了一個假的nio!!!!!!! 
爲了解決這個問題,就要用三板斧來解決!markdown

別急,要解決一個諸葛亮,你必先要造三個臭皮匠,先引入3個NIO相關概念先! 
1> Buffer 緩衝區 
難用的buffer是一個抽象的對象,下面還有ByteBuffer,IntBuffer,LongBuffer等子類,相比老的IO將數據直接讀/寫到Stream對象,NIO是將全部數據都用到緩衝區處理,它本質上是一個數組,提供了位置,容量,上限等操做方法,仍是直接看代碼代碼來得直接網絡

InetSocketAddress address = new InetSocketAddress("127.0.0.1", 7788);//建立鏈接的地址 SocketChannel sc = null;//聲明鏈接通道 ByteBuffer buf = ByteBuffer.allocate(1024);//創建緩衝區 sc = SocketChannel.open();//打開通道 sc.connect(address);//進行鏈接 while(true){ //定義一個字節數組,而後使用系統錄入功能: byte[] bytes = new byte[1024]; System.in.read(bytes); buf.put(bytes);//把數據放到緩衝區中 buf.flip();//對緩衝區進行復位 sc.write(buf);//寫出數據 buf.clear();//清空緩衝區數據 }
...

2>Channel 通道 
如自來水管同樣,支持網絡數據從Channel中讀寫,通道寫流最大不一樣是通道是雙向的,而流是一個方向上移動(InputStream/OutputStream),通道可用於讀/寫或讀寫同時進行,它還能夠和下面要講的selector結合起來,有多種狀態位,方便selector去識別. 通道分兩類,一:網絡讀寫(selectableChannel),另外一類是文件操做(FileChannel),咱們經常使用的是上面例子中的網絡讀寫!

3>Selector 多路複用選擇器 
它是神同樣存在的東西,多路複用選擇器提供選擇已經就緒的任務的能力,也就是selector會不斷輪詢註冊在其上的通道(Channel),若是某個通道發生了讀寫操做,這個通道處於就緒狀態,會被selector輪詢出來,而後經過selectionKey能夠取得就緒的Channel集合,從而進行後續的IO操做. 
一個多路複用器(Selector)能夠負責成千上萬個Channel,沒有上限,這也是JDK使用epoll代替了傳統的selector實現,得到鏈接句柄沒有限制.這也意味着咱們只要一個線程負責selector的輪詢,就能夠接入成千上萬個客戶端,這是JDK,NIO庫的巨大進步.

來張精心整好的圖 
這裏寫圖片描述

這個學習進到深水區了,注意羅,下面是服務器端的代碼,上面例子代碼是client端的,看裏面的註解,若是還不明白,多看幾回,代碼是可運行的,記得要jdk1.7以上版本,多運行,本身意會下,我也只幫到這了!

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; public class Server implements Runnable{ //1 多路複用器(管理全部的通道) private Selector seletor; //2 創建緩衝區 private ByteBuffer readBuf = ByteBuffer.allocate(1024); //3 private ByteBuffer writeBuf = ByteBuffer.allocate(1024); public Server(int port){ try { //1 打開路複用器 this.seletor = Selector.open(); //2 打開服務器通道 ServerSocketChannel ssc = ServerSocketChannel.open(); //3 設置服務器通道爲非阻塞模式 ssc.configureBlocking(false); //4 綁定地址 ssc.bind(new InetSocketAddress(port)); //5 把服務器通道註冊到多路複用器上,而且監聽阻塞事件 ssc.register(this.seletor, SelectionKey.OP_ACCEPT); System.out.println("Server start, port :" + port); } catch (IOException e) { e.printStackTrace(); } } @Override public void run() { while(true){ try { //1 必需要讓多路複用器開始監聽 this.seletor.select(); //2 返回多路複用器已經選擇的結果集 Iterator<SelectionKey> keys = this.seletor.selectedKeys().iterator(); //3 進行遍歷 while(keys.hasNext()){ //4 獲取一個選擇的元素 SelectionKey key = keys.next(); //5 直接從容器中移除就能夠了 keys.remove(); //6 若是是有效的 if(key.isValid()){ //7 若是爲阻塞狀態 if(key.isAcceptable()){ this.accept(key); } //8 若是爲可讀狀態 if(key.isReadable()){ this.read(key); } //9 寫數據 if(key.isWritable()){ //this.write(key); //ssc } } } } catch (IOException e) { e.printStackTrace(); } } } private void write(SelectionKey key){ //ServerSocketChannel ssc = (ServerSocketChannel) key.channel(); //ssc.register(this.seletor, SelectionKey.OP_WRITE); } private void read(SelectionKey key) { try { //1 清空緩衝區舊的數據 this.readBuf.clear(); //2 獲取以前註冊的socket通道對象 SocketChannel sc = (SocketChannel) key.channel(); //3 讀取數據 int count = sc.read(this.readBuf); //4 若是沒有數據 if(count == -1){ key.channel().close(); key.cancel(); return; } //5 有數據則進行讀取 讀取以前須要進行復位方法(把position 和limit進行復位) this.readBuf.flip(); //6 根據緩衝區的數據長度建立相應大小的byte數組,接收緩衝區的數據 byte[] bytes = new byte[this.readBuf.remaining()]; //7 接收緩衝區數據 this.readBuf.get(bytes); //8 打印結果 String body = new String(bytes).trim(); System.out.println("Server : " + body); // 9..能夠寫回給客戶端數據 } catch (IOException e) { e.printStackTrace(); } } private void accept(SelectionKey key) { try { //1 獲取服務通道 ServerSocketChannel ssc = (ServerSocketChannel) key.channel(); //2 執行阻塞方法 SocketChannel sc = ssc.accept(); //3 設置阻塞模式 sc.configureBlocking(false); //4 註冊到多路複用器上,並設置讀取標識 sc.register(this.seletor, SelectionKey.OP_READ); } catch (IOException e) { e.printStackTrace(); } } public static void main(String[] args) { new Thread(new Server(7788)).start();; } }

若是你理解了Java NIO ,下面講的netty也是水到渠成的事,只想說,深水區已過了!

差點忘記還要補下AIO的,這個比NIO先進的技術,最終實現了

netty

這是神同樣存在的java nio框架, 這個偏底層的東西,可能你接觸較少卻又無處不在,好比: 
這裏寫圖片描述

在業界有一篇沒法超越的netty入門文章,我也沒這個能力超越,只能雙手奉上,大家好好研讀,必然學有所成! 
http://ifeve.com/netty5-user-guide/

還有杭州華爲的李林鋒寫的 Netty權威指南 ,我是買了一本,不知如何評論好,中等吧!

相關文章
相關標籤/搜索