JAVA BIO與NIO的對比
阿新 • • 發佈:2019-02-17
一、BIO
1、機制
採用BIO通訊模型的服務端,通常由一個獨立的Acceptor執行緒負責監聽客戶端的連結,它接收到客戶端的連線請求之後為每個客戶端請求建立一個新的執行緒進行鏈路處理,處理完成之後通過輸出流將響應返回給客戶端,執行緒銷燬,這就是典型的一請求一應答的通訊模型。
2、分析
當客戶端併發訪問量增加後,服務端的執行緒個數和客戶端併發訪問按1:1的正比關係遞增,執行緒膨脹之後,系統的效能會急劇下降,甚至會發生執行緒堆疊溢位、建立失敗,最終發生宕機或將死的慘狀。
當然我們可以在服務端使用執行緒池的方式,來保護我們的系統受到高併發的衝擊,但是即使執行緒池在大畢竟也是有限的,這樣會出現大量的請求等待執行緒池的資源,從而效能、時延、併發量還是會面臨很糟糕的情況。
二、NIO
1、機制
概念組成就不說了,網上很多。
採用nio通訊模型的服務端,通常由一個獨立的執行緒selector(選擇器)來管理一個或多個channel,當channel註冊了selector之後,selector會監聽channel的各種事件,如SelectionKey.OP_ACCEPT-接收事件,當註冊的事件發生後,通過迭代器獲取選中的事件-SelectionKey,如果SelectionKey為請求連線事件,則儲存客戶端的SocketChannel並設定非阻塞,再新增可讀監聽事件,這樣在資料可讀之前,selector可以做一些其他的事情;如果SelectionKey為可讀事件,則可以通過執行緒池用SocketChannel獲取資料,進行接下來的邏輯處理,最後將
2、分析
由於selector可以判斷資料的接收狀態,所以可以節省掉等待io資料的時間,而監聽狀態的時間會很快,可以由單執行緒完成,這樣也避免了執行緒的上下文切換。
三、程式碼
1、服務端
Java程式碼- public class NIOServer {
- //選擇器
- private Selector selector;
- /**
- * 對該通道做一些初始化的工作
- */
- public void initServer(int port) throws IOException {
- // 獲得ServerSocket通道
- ServerSocketChannel serverChannel = ServerSocketChannel.open();
- // 設定通道為非阻塞
- serverChannel.configureBlocking(false);
- // 繫結到port埠
- serverChannel.socket().bind(new InetSocketAddress(port));
- // 獲得選擇器
- this.selector = Selector.open();
- //為該通道註冊SelectionKey.OP_ACCEPT事件
- serverChannel.register(selector, SelectionKey.OP_ACCEPT);
- }
- /**
- * 採用輪詢的方式監聽selector上是否有需要處理的事件,如果有,則進行處理
- */
- @SuppressWarnings("unchecked")
- public void listen() throws IOException {
- // 輪詢訪問selector
- while (true) {
- //當註冊的事件到達時,方法返回;否則,該方法會一直阻塞
- selector.select();
- // 獲得selector中選中的項的迭代器,選中的項為註冊的事件
- Iterator ite = this.selector.selectedKeys().iterator();
- while (ite.hasNext()) {
- SelectionKey key = (SelectionKey) ite.next();
- // 刪除已選的key,以防重複處理
- ite.remove();
- // 客戶端請求連線事件
- if (key.isAcceptable()) {
- ServerSocketChannel server = (ServerSocketChannel) key
- .channel();
- // 獲得和客戶端連線的通道
- SocketChannel channel = server.accept();
- // 設定成非阻塞
- channel.configureBlocking(false);
- //可以給客戶端傳送資訊
- channel.write(ByteBuffer.wrap(new String("abc").getBytes()));
- //在和客戶端連線成功之後,為了可以接收到客戶端的資訊,需要給通道設定讀的許可權。
- channel.register(this.selector, SelectionKey.OP_READ);
- } else if (key.isReadable()) {
- // 獲得了可讀的事件
- read(key);
- }
- }
- }
- }
- /**
- * 處理邏輯
- */
- public void read(SelectionKey key) throws IOException{
- // 伺服器可讀取訊息:得到事件發生的Socket通道
- SocketChannel channel = (SocketChannel) key.channel();
- // 建立讀取的緩衝區
- ByteBuffer buffer = ByteBuffer.allocate(10);
- channel.read(buffer);
- byte[] data = buffer.array();
- String msg = new String(data).trim();
- System.out.println("服務端收到資訊:"+msg);
- ByteBuffer outBuffer = ByteBuffer.wrap(msg.getBytes());
- channel.write(outBuffer);// 將訊息回送給客戶端
- }
- /**
- * 啟動服務端測試
- */
- public static void main(String[] args) throws IOException {
- NIOServer server = new NIOServer();
- server.initServer(8000);
- server.listen();
- }
- }
2、客戶端
Java程式碼- public class NIOClient {
- //選擇器
- private Selector selector;
- /**
- * 對該通道做一些初始化的工作
- */
- public void initClient(String ip,int port) throws IOException {
- // 獲得一個Socket通道
- SocketChannel channel = SocketChannel.open();
- // 設定通道為非阻塞
- channel.configureBlocking(false);
- // 選擇器
- this.selector = Selector.open();
- // 客戶端連線伺服器,其實方法執行並沒有實現連線,需要在listen()方法中調
- //用channel.finishConnect();才能完成連線
- channel.connect(new InetSocketAddress(ip,port));
- //將通道管理器和該通道繫結,併為該通道註冊SelectionKey.OP_CONNECT事件。
- channel.register(selector, SelectionKey.OP_CONNECT);
- }
- /**
- * 採用輪詢的方式監聽selector上是否有需要處理的事件,如果有,則進行處理
- */
- @SuppressWarnings("unchecked")
- public void listen() throws IOException {
- // 輪詢訪問selector
- while (true) {
- selector.select();
- // 獲得selector中選中的項的迭代器
- Iterator ite = this.selector.selectedKeys().iterator();
- while (ite.hasNext()) {
- SelectionKey key = (SelectionKey) ite.next();
- // 刪除已選的key,以防重複處理
- ite.remove();
- // 連線事件發生
- if (key.isConnectable()) {
- SocketChannel channel = (SocketChannel) key
- .channel();
- // 如果正在連線,則完成連線
- if(channel.isConnectionPending()){
- channel.finishConnect();
- }
- // 設定成非阻塞
- channel.configureBlocking(false);
- //在這裡可以給服務端傳送資訊哦
- channel.write(ByteBuffer.wrap(new String("abc").getBytes()));
- //在和服務端連線成功之後,為了可以接收到服務端的資訊,需要給通道設定讀的許可權。
- channel.register(this.selector, SelectionKey.OP_READ);
- // 獲得了可讀的事件
- } else if (key.isReadable()) {
- read(key);
- }
- }
- }
- }
- /**
- * 處理邏輯
- */
- public void read(SelectionKey key) throws IOException{
- SocketChannel channel = (SocketChannel) key.channel();
- // 建立讀取的緩衝區
- ByteBuffer buffer = ByteBuffer.allocate(10);
- channel.read(buffer);
- byte[] data = buffer.array();
- String msg = new String(data).trim();
- System.out.println("客戶端收到資訊:"+msg);
- }
- /**
- * 啟動客戶端測試
- */
- public static void main(String[] args) throws IOException {
- NIOClient client = new NIOClient();
- client.initClient("localhost",8000);
- client.listen();
- }
- }