Java NIO —Selector編程模型總結

前言

NIO編程一直不太熟悉,一方面沒有系統地學習過,另外一方面NIO編程確實有些複雜,一下沒有用了過陣子容易忘記,因此,寫篇小博客就頗有必要,這裏來總結一下selector組件用法,Buffer和Channel有空補上。java


Selector

一張你可能熟悉的圖(IO多路複用)編程


1. 什麼是selector? 有什麼用?

selector(選擇器),IO多路複用的組件, 和它直接關聯的組件是Channel, 它的做用就是不斷的輪詢綁定在他身上的channel。一旦有通道發生了它感興趣的事件,接下來處理該事件。
socket


2. selector維護的set集合

selector維護了三個set集合,裏面封裝的是 SelectionKey,用它來獲取channel。學習

2.1 key set
spa

全集,每當channel經過register方法註冊進選擇器時,會把包含Channel本身信息的key添加到這個全集中來,註冊的信息就會以SelectionKey的封裝形式保存在這個集合中, 用這個SelectionKey來獲取channel。操作系統

2.2 selected key setcode

感興趣的key的集合,每次遍歷selected key時咱們會執行這行代碼:Set<SelectionKey> selectionKeys = selector.selectedKeys();
cdn

2.3 cannelled key setserver

通常會在客戶端主動斷開鏈接的時候使用它,表明原來感興趣的事件,如今不感興趣了,下一次輪詢,進行select() 本集合中的SelectionKey會從key set中移除, 意味着它所關聯的channel將會被選擇器丟棄掉,再也不進行監聽。
對象


NIO編程模型

1. 服務端建立表明服務端的Channel,綁定好端口,設置成非阻塞的通道 而且初始化選擇器,而後開始輪詢綁定在本身身上的通道,此時的通道只有一個ServerSocketChannel,而選擇器只關心ServerSocketChannel上發生的OP_ACCEPT事件,而又沒有客戶端來連接 因此他被阻塞在了select()

// 服務端
// 獲取服務端的SerSokcetChannel
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
// 配置成非阻塞的
serverSocketChannel.configureBlocking(false);

// 從通道中獲取服務端的對象
ServerSocket serverSocket = serverSocketChannel.socket();
serverSocket.bind(new InetSocketAddress(1234));

// 建立選擇器
Selector selector = Selector.open();
// 把通到註冊到選擇器上
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

while (true) {
            // 阻塞式等待 channel上有事件發生
            int select = selector.select(); 
} 

複製代碼

select 方法概要

select(long); // 設置超時時間,在這個時間範圍內輪詢關心的事件(如上面的OP_ACCEPT事件)

selectNow(); // 當即返回,不阻塞

select(); // 阻塞輪詢複製代碼
  • 第一步, cannelled-key中的每個元素會從全集key set中剔除,表示這些能夠關聯的通道不會被註冊 
  • 第二步操做系統幫咱們輪詢每個通道是否有選擇器關心的事情發生,對於一條準備就緒的channel(發生事件通道),他至少會發生下面兩件事之一: 
    • 它的key會被添加進selected-key-set中,來標識它將被選中,進而處理 
    • 若是它的key,已經存在於selected-key-set集合中了,下一步就是它的 read-operation將被更新 
  • 第三步: 若是在輪詢時發現了有任何key被放置在了cannelled-key-set中,重複第一步,再也不註冊它關聯的通道


2.  客戶端建立表明本身的SocketChannel, 建立選擇器,把本身感興趣的事件註冊在上面,以下代碼, 初始化本身,SocketChannel, 把客戶端的通道註冊進選擇器,並告訴選擇器SocketChannel的感興趣事件是OP_CONNECT鏈接事件

// 獲取客戶端的通道
SocketChannel socketChannel = SocketChannel.open();
socketChannel.configureBlocking(false);

Selector selector = Selector.open();
// 把客戶端的通道註冊進選擇器
socketChannel.register(selector, SelectionKey.OP_CONNECT);
// 鏈接客戶端, 執行完這行代碼後, 服務端就能就收到通知
socketChannel.connect(new InetSocketAddress("localhost", 1234));

while (true) {
    int number = selector.select(); // 選擇器阻塞式的等待 Channel上發生它關心的事件
    System.out.println(" 發生了感興趣的事件: " + number);
    Set<SelectionKey> keySet = selector.selectedKeys();
// 驗證
    for (SelectionKey selectionKey : keySet) {
        SocketChannel client = null;
if (selectionKey.isConnectable()) {
    // 強轉成 有鏈接事件發生的Channel
    client = (SocketChannel) selectionKey.channel();
    // 完成鏈接
    if (client.isConnectionPending()) {
        client.finishConnect();
        ByteBuffer byteBuffer = ByteBuffer.allocate(512);
        byteBuffer.put((LocalDate.now() + "鏈接成功").getBytes());
        byteBuffer.flip();
        client.write(byteBuffer);
    }
}

複製代碼
相關文章
相關標籤/搜索