NIO
Java NIO 三大核心組件
- Buffer(緩沖區):每個客戶端連接都會對應一個Buffer,讀寫數據通過緩沖區讀寫。
- Channel(通道):每個channel用于連接Buffer和Selector,通道可以進行雙向讀寫。
- Selector(選擇器):一個選擇器對應多個通道,用于監聽多個通道的事件。Selector可以監聽所有的channel是否有數據要讀取,當某個channel有數據時,就去處理,所有channel都沒有數據時,線程可以去執行其他任務。
使用 NIO 模型操作 Socket 步驟:
- 創建 ServerSocketChannel 服務器;
- 創建多路復用器 Selector(每個操作系統創建出來的是不一樣的 ,Windows創建的是 WindowsSelectorImpl)
- ServerSocketChannel 將建立連接事件注冊到 Selector中(register 方法往 EPollArrayWrapper 中添加元素)
- 處理事件
- 如果是建立連接事件,則把客戶端的讀寫請求也注冊到Selector中;
- 如果是讀寫事件則按業務處理。
案例代碼:
public class NioServer {public static void main(String[] args) throws IOException {// 創建服務器ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();serverSocketChannel.bind(new InetSocketAddress(8888));serverSocketChannel.configureBlocking(false); // 配置成非阻塞式的channel// 創建一個IO多路復用選擇器Selector selector = Selector.open();// 注冊serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);while (true) {// 阻塞的方法,返回值代表發生事件的通道的個數// 返回值 0 超時// -1 錯誤// select方法可以傳遞超時時間,如果不傳的話是timeout最后會為-1表示不會超時selector.select();// 如果不設的話客戶端不操作會一直阻塞在這// 只要走到這里,必然說明,發送了事情,有可讀可寫可連接的channelSet<SelectionKey> selectionKeys = selector.selectedKeys();Iterator<SelectionKey> iterator = selectionKeys.iterator();while (iterator.hasNext()) {SelectionKey selectionKey = iterator.next();// 這個事件處理完就刪除if (selectionKey.isAcceptable()) {// 有客戶端來連接了// 三次握手建立連接SocketChannel socketChannel = serverSocketChannel.accept();socketChannel.configureBlocking(false);socketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));System.out.println("某某客戶端連接來啦");}if (selectionKey.isReadable()) {SocketChannel socketChannel = (SocketChannel) selectionKey.channel();ByteBuffer buffer = (ByteBuffer) selectionKey.attachment();buffer.clear();int read = socketChannel.read(buffer);if (read == -1) { // 如果是可讀事件,然后又沒有數據,說明是客戶端與服務器端的連接斷開了,// 這個時候我們關閉通道,不然選擇器會一直監聽通道,導致不必要的業務執行socketChannel.close();} else {System.out.println(new String(buffer.array(), 0, buffer.position()));System.out.println("有信息需要讀取");}}iterator.remove();}}}
}
doSelect 方法是由 WindowsSelectorImpl
類去實現的,這是select方法最后執行的方法,因為加了互斥鎖,也是為什么說這里同步阻塞的原因。
倆問題:
-
當 Selector.select() 方法返回后,它會返回一組 SelectionKey 對象,這些對象代表了已經就緒的 I/O 通道,即對應的文件描述符上有事件發生。這些 SelectionKey 對象Key用來處理對應的事件。但是,如果不將已經處理過的 SelectionKey 對象從 Selector 中刪除,下次調用 Selector.select() 方法時,這些已經處理過的 SelectionKey 對象扔然會被返回,導致多余的事件處理,影響性能問題。
-
刪除的話,我們可以通過
SelectionKey.cancel()
方法來實現,并且使得對應的通道(即文件描述符)不再被Selector監視。(這是有問題的,這樣的話以后這個 SelectionKey 就不會再被監聽了)可以在迭代器使用的時候對其進行刪除。
Netty 封裝好后就幫我們解決了這種問題,不會出現事件處理完后續還會一直處理的現象。