一、傳統BIO的缺點
BIO屬于同步阻塞行IO,在服務器的實現模型為,每一個連接都要對應一個線程。當客戶端有連接請求的時候,服務器端需要啟動一個新的線程與之對應處理,這個模型有很多缺陷。當客戶端不做出進一步IO請求的時候,服務器端的線程就只能掛著,不能去處理其他請求。這樣會對造成不必要的線程開銷。
二、阻塞與同步
同步和異步都是由基于應用程序和操作系統處理IO事件所采用的方式所決定的。
阻塞和非阻塞式指線程在得到調用結果之前是否被掛起,主要針對線程。
三、NIO簡介(同步非阻塞)
- Java NIO全稱java non-blocking IO, 是指JDK提供的新API。從JDK1.4開始,Java提供了一系列改進的輸入/輸出的新特性,被統稱為NIO(即New IO),是同步非阻塞的。
- NIO是一種面向緩沖區的、基于通道的IO操作,NIO有三大核心部分: Channel(通道), Buffer(緩沖區),Selector(選擇器)
- java NIO的運行模式是:客戶端發送的鏈接請求都會被注冊到Selector(選擇器)上,多路復用器輪詢到有I/O請求時才會啟動一個線程去服務。
四、NIO三大核心原理
NIO有三大核心部分: Channel(通道), Buffer(緩沖區),Selector(選擇器)
Buffer(緩沖區)
緩沖區本質上就是一塊內存,數據的讀寫都是通過Buffer類實現的。緩沖區buffer主要是和通道數據交互,即從通道中讀入數據到緩沖區,和從緩沖區中把數據寫入到通道中,通過這樣完成對數據的傳輸。
Channel(通道)
java NIO的類似于流,但是又有些不同:既可以從通道中讀取數據,又可以寫數據到通道。但流的(input和output)讀寫通常是單向的。通道可以非阻塞讀取和寫入通道,通道可以支持讀取或寫入緩沖區,也支持異步讀寫。
Selector選擇器
Selector是一個java NIO組件,可以檢測一個或多個NIO通道,并確定已經準備好進行讀取或者寫入。這樣,一個單獨的線程就可以管理多個Channel,從而管理多個網絡連接,提高效率。
- 每個channel都會對應一個Buffer
- 一個線程對應Selector,一個Selector對應多個Channel
- 程序切換到那個channel是由事件決定
- Selector會根據不同的事件,在各個通道上切換
- Buffer就是一個內存塊,底層就是一個數組,數據的讀取和寫入都是通過Buffer來實現的
五、NIO三板斧
六、NIO實現案例
客戶端
public class NioClient {public static void main(String[] args) throws IOException {SocketChannel socketChannel=SocketChannel.open();socketChannel.configureBlocking(false);InetSocketAddress address = new InetSocketAddress("127.0.0.1", 9000);if (!socketChannel.connect(address)) {while (!socketChannel.finishConnect()){System.out.println("連接中,客戶端可以進行其他工作");}String str="hello world!";ByteBuffer wrap = ByteBuffer.wrap(str.getBytes());socketChannel.write(wrap);//避免客戶端中斷System.in.read();}}
}
服務器端
public class NioServer {public static void main(String[] args) throws IOException {// 獲取一個ServerSocket通道ServerSocketChannel serverChannel = ServerSocketChannel.open();// serverChannel通道一直監聽9000端口serverChannel.socket().bind(new InetSocketAddress(9000));// 設置serverChannel為非阻塞serverChannel.configureBlocking(false);//創建Selector選擇器用來監聽通道Selector selector = Selector.open();// 把ServerSocketChannel注冊到selector中,并且selector對客戶端的連接操作感興趣SelectionKey selectionKey = serverChannel.register(selector, SelectionKey.OP_ACCEPT);System.out.println("服務啟動成功!");while(true){/** 如果事件沒有到達 selector.select() 會一直阻塞等待*/selector.select();Set<SelectionKey> selectionKeys = selector.selectedKeys();Iterator<SelectionKey> iterator = selectionKeys.iterator();while (iterator.hasNext()){SelectionKey key = iterator.next();if (key.isAcceptable()) // 如果是OP_ACCEPT事件,則進行連接獲取和事件注冊{ServerSocketChannel server = (ServerSocketChannel) key.channel(); //連接獲取SocketChannel socketChannel = server.accept(); // 連接獲取socketChannel.configureBlocking(false); // 設置為非阻塞SelectionKey selKey = socketChannel.register(selector, SelectionKey.OP_READ); //這里只注冊了讀事件,如果需要給客戶端寫數據,則需要注冊寫事件System.out.println("客戶端連接成功!");}else if(key.isReadable()) //如果是OP_READ事件,則進行讀取和打印{SocketChannel socketChannel = (SocketChannel) key.channel();ByteBuffer byteBuffer = ByteBuffer.allocate(128);int len = socketChannel.read(byteBuffer);if (len > 0) //如果有數據,則打印數據{System.out.println("接受到客戶端數據"+new String(byteBuffer.array()));}else if(len==-1) //如果客戶端斷開連接,關閉socket{System.out.println("客戶端斷開連接!");socketChannel.close();}}// 從事件集合中刪除本次處理的key,防止下次select重復處理iterator.remove();}}}
}