NIO,BIOjava
1、序言編程
在Java的軟件設計開發中,通訊架構是不可避免的,咱們在進行不一樣系統或者不一樣進程之間的數據交互,或者在高併發下的通訊場景都須要用到網絡通訊相關的技術。數組
一、通訊技術總體解決的問題:服務器
1)局域網內的通訊要求;網絡
2)多系統間的底層消息傳遞機制;架構
3)高併發下,大數據量的通訊場景須要;併發
4)遊戲行業。異步
二、在Java中,主要有三種IO模型,分別是:socket
1)同步阻塞IO(BIO);分佈式
2)同步非阻塞IO(NIO);
3)異步IO(AIO)。
2、同步阻塞IO(BIO)
Java BIO(blocking I/O):就是傳統的java io編程,其相關的類和接口在java.io包下。同步並阻塞,服務器實現模式爲一個鏈接一個線程,即每當客戶端有鏈接請求時,服務端都須要啓動一個線程進行處理,以下圖。
在高併發的狀況下,服務端會產生大量線程,線程間會發生競爭和上下文切換,同時要佔用棧空間和CPU資源,並且其中有些線程可能什麼事情都不會作,一直阻塞着,這些狀況都會形成服務端性能降低。
因此BIO方式適合用於鏈接數目固定,並且比較小的架構,這種方式對服務器資源要求比較高,併發侷限於應用中,可是程序簡單易懂。
一、Java中的BIO分佈式分爲兩種:
1)傳統BIO:即上圖中的一請求一應答;
2)僞異步IO:經過線程池固定線程的最大數量,能夠防止資源的浪費。
二、BIO編程簡單流程:
1)服務器啓動一個ServerSocket;
2)客戶端啓動Socket請求與服務器鏈接,默認狀況下服務器端須要對每一個客戶創建一個線程與之通訊;
3)客戶端發出請求以後,先諮詢服務器是否有線程響應,若是沒有則會等待,或者被服務端拒絕;
4)若是有響應,客戶端線程會等待請求結束後,再繼續執行。
三、使用BIO進行通訊的簡單案例
1)服務端代碼
public class Server { public static void main(String[] args) throws IOException { //服務器端開啓一個ServerSocket,並綁定6666端口 ServerSocket ss = new ServerSocket(6666); System.out.println("服務器已開啓!"); while (true){ Socket socket = ss.accept(); System.out.println("來自" + socket.getRemoteSocketAddress() + "的鏈接"); new Handler(socket).start(); } } }//爲每一個客戶端鏈接開啓的線程class Handler extends Thread { Socket socket; public Handler(Socket socket) { this.socket = socket; } @Override public void run() { try (InputStream inputStream = socket.getInputStream()) { try (OutputStream outputStream = socket.getOutputStream()) { handle(inputStream, outputStream); } } catch (IOException e) { try{ //關閉socket socket.close(); }catch (IOException e1){ } } System.out.println("客戶端" + socket.getRemoteSocketAddress()+ "斷開鏈接"); } private void handle(InputStream inputStream, OutputStream outputStream) throws IOException{ //得到一個字符輸入流 var reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)); //得到一個字符輸出流 var writer = new BufferedWriter(new OutputStreamWriter(outputStream, StandardCharsets.UTF_8)); writer.write("鏈接成功!\n"); writer.flush(); while(true){ //每次從管道中讀入一行 String str = reader.readLine(); //當客戶端傳來"Bye"表明斷開鏈接 if("Bye".equals(str)){ writer.write("Bye\n"); writer.flush(); break; } writer.write("已經收到:" + str + "\n"); writer.flush(); } } }
2)客戶端代碼
public class Client { public static void main(String[] args) throws IOException { Socket socket = new Socket("127.0.0.1", 6666); try (InputStream inputStream = socket.getInputStream()){ try (OutputStream outputStream = socket.getOutputStream()){ handle(inputStream, outputStream); } }catch (IOException e){ try{ socket.close(); }catch (IOException e1){ } } } private static void handle(InputStream inputStream, OutputStream outputStream) throws IOException{ var reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)); var writer = new BufferedWriter(new OutputStreamWriter(outputStream, StandardCharsets.UTF_8)); Scanner in = new Scanner(System.in); System.out.println("<<<" + reader.readLine()); while (true){ System.out.print(">>>"); String str = in.nextLine(); writer.write(str); writer.newLine(); writer.flush(); String resp = reader.readLine(); System.out.println("<<<" + resp); if("Bye".equals(str)){ break; } } } }
3)啓動服務器端,再啓動客戶端,使用客戶端與服務端進行通訊
Server的控制檯:
服務器已開啓! 來自/127.0.0.1:51871的鏈接 客戶端/127.0.0.1:51871斷開鏈接
Client的控制檯:
<<<鏈接成功!>>>你好<<<已經收到:你好>>>在嗎<<<已經收到:在嗎>>>吃了嗎<<<已經收到:吃了嗎>>>Bye<<<Bye
3、同步非阻塞IO(NIO)
Java NIO(New IO):也稱java non-blocking IO,是從java1.4版本開始引入的一個新IO API,能夠代替標準的java IO API。NIO與原來的IO具備一樣的做用和目的,可是使用的方式徹底不一樣,NIO支持面向緩衝區的、基於通道的IO操做。NIO將以更加高效的方式進行文件的讀寫操做。NIO能夠理解爲非阻塞IO,傳統IO的read和write只能阻塞執行,線程在讀寫IO期間不能幹其餘事。而NIO則能夠配置socket爲非阻塞模式。
Java NIO的阻塞模式,使一個線程從某通道發送請求或者讀取數據,可是它僅能獲得目前可用的數據,若是目前沒有數據可用,則什麼都不會獲取,而不是保持線程阻塞,因此直至數據變得可用讀取以前,該線程能夠繼續作其餘事情。
工做示意圖以下:
一、BIO 和 NIO 的區別?
1)BIO 以流的方式處理數據,而 NIO 以塊的方式處理數據;
2)BIO 是阻塞的, NIO 是非阻塞的
3)BIO 基於字節流和字符流進行操做,而 NIO 基於 Channel(通道)和 Buffer(緩衝區)進行操做,數據老是從通道讀取到緩衝區,或者從緩衝區寫入到通道中。
二、NIO 的三大組件
1)Buffer 緩衝區:緩衝區本質是一塊能夠寫入數據,而後能夠從中讀取數據的內存。這塊內存被包裝成了 NIO Buffer 對象,並提供了一組方法,用來方便對該快的訪問。
Buffer 就像一個數組,能夠保存多個相同類型的數據。根據數據類型不一樣,有如下經常使用的 Buffer 子類:ByteBuffer, CharBuffer, ShortBuffer, IntBuffer, LongBuffer, FloatBuffer, DoubleBuffer。
建立方法以下:
static XxxBuffer allocate(int capacity) : 建立一個容量爲capacity 的 XxxBuffer 對象
基本屬性:
常見方法:
Buffer clear() 清空緩衝區並返回對緩衝區的引用 Buffer flip() 爲 將緩衝區的界限設置爲當前位置,並將當前位置重置爲 0int capacity() 返回 Buffer 的 capacity 大小boolean hasRemaining() 判斷緩衝區中是否還有元素int limit() 返回 Buffer 的界限(limit) 的位置 Buffer limit(int n) 將設置緩衝區界限爲 n, 並返回一個具備新 limit 的緩衝區對象 Buffer mark() 對緩衝區設置標記int position() 返回緩衝區的當前位置 position Buffer position(int n) 將設置緩衝區的當前位置爲 n , 並返回修改後的 Buffer 對象int remaining() 返回 position 和 limit 之間的元素個數 Buffer reset() 將位置 position 轉到之前設置的 mark 所在的位置 Buffer rewind() 將位置設爲爲 0, 取消設置的 mark
讀取操做:
Buffer 全部子類提供了兩個用於數據操做的方法:get()put() 方法 取獲取 Buffer中的數據 get() :讀取單個字節 get(byte[] dst):批量讀取多個字節到 dst 中 get(int index):讀取指定索引位置的字節(不會移動 position) 放到 入數據到 Buffer 中 中 put(byte b):將給定單個字節寫入緩衝區的當前位置 put(byte[] src):將 src 中的字節寫入緩衝區的當前位置 put(int index, byte b):將指定字節寫入緩衝區的索引位置(不會移動 position)
2)通道(channel)
通道(Channel):由java.nio.channels包下定義的,表示IO源與目標打開的鏈接。Channel相似於傳統的「流」。只不過Channel自己不能直接訪問數據,Channel只能與Buffer進行交互。
NIO通道與流的區別:
Channel在NIO中是一個接口,經常使用的Channel實現類有:
3)選擇器(Selector)
選擇器(Selector)是SelectableChannle對象的多路複用器,Selector能夠同時監控多個SelectorableChannel的IO情況,利用Selector可使一個單獨的線程管理多個Channel。Selector是NIO非阻塞的核心,其能檢測多個註冊的通道上是否有事件發生,若是有事件發生,便獲取事件,而後針對每一個事件做出相應的處理。
經過Selector.open()方法建立一個Selector:
Selector selector = Selector.open();
4)入門案例
/** 客戶端 */public class Client { public static void main(String[] args) throws Exception { //1. 獲取通道 SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9999)); //2. 切換非阻塞模式 sChannel.configureBlocking(false); //3. 分配指定大小的緩衝區 ByteBuffer buf = ByteBuffer.allocate(1024); //4. 發送數據給服務端 Scanner scan = new Scanner(System.in); while(scan.hasNext()){ String str = scan.nextLine(); buf.put((new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").format(System.currentTimeMillis()) + "\n" + str).getBytes()); buf.flip(); sChannel.write(buf); buf.clear(); } //5. 關閉通道 sChannel.close(); } }/** 服務端 */public class Server { public static void main(String[] args) throws IOException { //1. 獲取通道 ServerSocketChannel ssChannel = ServerSocketChannel.open(); //2. 切換非阻塞模式 ssChannel.configureBlocking(false); //3. 綁定鏈接 ssChannel.bind(new InetSocketAddress(9999)); //4. 獲取選擇器 Selector selector = Selector.open(); //5. 將通道註冊到選擇器上, 而且指定「監聽接收事件」 ssChannel.register(selector, SelectionKey.OP_ACCEPT); //6. 輪詢式的獲取選擇器上已經「準備就緒」的事件 while (selector.select() > 0) { System.out.println("輪一輪"); //7. 獲取當前選擇器中全部註冊的「選擇鍵(已就緒的監聽事件)」 Iteratorit = selector.selectedKeys().iterator(); while (it.hasNext()) { //8. 獲取準備「就緒」的是事件 SelectionKey sk = it.next(); //9. 判斷具體是什麼事件準備就緒 if (sk.isAcceptable()) { //10. 若「接收就緒」,獲取客戶端鏈接 SocketChannel sChannel = ssChannel.accept(); //11. 切換非阻塞模式 sChannel.configureBlocking(false); //12. 將該通道註冊到選擇器上 sChannel.register(selector, SelectionKey.OP_READ); } else if (sk.isReadable()) { //13. 獲取當前選擇器上「讀就緒」狀態的通道 SocketChannel sChannel = (SocketChannel) sk.channel(); //14. 讀取數據 ByteBuffer buf = ByteBuffer.allocate(1024); int len = 0; while ((len = sChannel.read(buf)) > 0) { buf.flip(); System.out.println(new String(buf.array(), 0, len)); buf.clear(); } } //15. 取消選擇鍵 SelectionKey it.remove(); } } } }