1. 程式人生 > >Java Nio選擇器Selector

Java Nio選擇器Selector

Selector(選擇器)是Java NIO中能夠檢測一到多個NIO通道,並能夠知曉通道是否為諸如讀寫事件做好準備的元件。這樣,一個單獨的執行緒可以管理多個channel,從而管理多個網路連線 ,減少伺服器的效能開銷。

建立Selector

  • 通過Selector 提供的靜態方法建立    Selector selector = Selector.open();

  • 通過SelectorProvider獲取選擇器

SelectorProvider selectorProvider = SelectorProvider.provider();
Selector selector = selectorProvider.openSelector();

將通道註冊到選擇器上

通過ServerSocketChannel.register(Selector sel, int ops)方法註冊的selecor中

//獲取通道
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//切換到非堵塞模式
serverSocketChannel.configureBlocking(false);
//繫結埠號
serverSocketChannel.bind(new InetSocketAddress(8080));
//獲取選擇器
Selector selector = Selector.open();
//將通道註冊到選擇器上,並且指定“監聽接收事件”
SelectionKey key = serverSocketChannel.register(selector,SelectionKey.OP_ACCEPT);

與selector使用,channel必須處於非堵塞狀態下,FileChannel無法切換到非堵塞狀態下將不能與selector一起使用。

register()中的第二個引數表示通道監聽的事件一共四種狀態

  • SelectionKey.OP_CONNECT 連線
  • SelectionKey.OP_ACCEPT 接收
  • SelectionKey.OP_READ 讀
  • SelectionKey.OP_WRITE 寫

單一個通道監聽多種事件時使用位或多種事件如: SelectionKey.OP_CONNECT|SelectionKey.OP_ACCEPT

Selector的select()方法

Selector中註冊了一或多個通道,就可以呼叫幾個過載的select()方法。這些方法返回通道的事件(如連線、接受、讀或寫)已經準備就緒的那些通道。換句話說,如果你對“讀就緒”的通道感興趣,select()方法會返回讀事件已經就緒的那些通道。

下面是select()方法:

  • int select()
  • int select(long timeout)
  • int selectNow()

select()阻塞到至少有一個通道在你註冊的事件上就緒了。

select(long timeout)和select()一樣,除了最長會阻塞timeout毫秒(引數)。

selectNow()不會阻塞,不管什麼通道就緒都立刻返回(此方法執行非阻塞的選擇操作。如果自從前一次選擇操作後,沒有通道變成可選擇的,則此方法直接返回零。)。

select()方法返回的int值表示有多少通道已經就緒。亦即,自上次呼叫select()方法後有多少通道變成就緒狀態。如果呼叫select()方法,因為有一個通道變成就緒狀態,返回了1,若再次呼叫select()方法,如果另一個通道就緒了,它會再次返回1。如果對第一個就緒的channel沒有做任何操作,現在就有兩個就緒的通道,但在每次select()方法呼叫之間,只有一個通道就緒了。

selectedKeys()

一旦呼叫了select()方法,並且返回值表明有一個或更多個通道就緒了,然後可以通過呼叫selector的selectedKeys()方法,訪問“選擇健(已就緒的監聽事件)”中的就緒通道。如下所示:

Set selectedKeys = selector.selectedKeys()

使用Selector實現非堵塞Socket

服務端

//1.獲取通道
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//2.切換到非堵塞模式
serverSocketChannel.configureBlocking(false);
//3.繫結埠號
serverSocketChannel.bind(new InetSocketAddress(8080));
//4.獲取選擇器
Selector selector = Selector.open();
//5.將通道註冊到選擇器上,並且指定“監聽接收事件”
serverSocketChannel.register(selector,SelectionKey.OP_ACCEPT);
//6輪詢式的獲取選擇器上已經‘準備就緒’的事件
while (selector.select()>0){
    //7 。獲取當前選擇器中所有註冊的"選擇健(已就緒的監聽事件)"
    Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
    while (iterator.hasNext()){
        //8.獲取“準備就緒”的事件
        SelectionKey selectionKey = iterator.next();
         //9.判斷具體事件,就緒
        if (selectionKey.isAcceptable()){
            //10.接收就緒,獲取客戶端連線
            SocketChannel socketChannel = serverSocketChannel.accept();
            //11,切換到非堵塞模式
            socketChannel.configureBlocking(false);
            //12.將客戶端通道註冊到選擇器上
            socketChannel.register(selector,SelectionKey.OP_READ);
        }else if (selectionKey.isReadable()){
            //獲取當前選擇器上“讀就緒”狀態的通道
            SocketChannel socketChannel = (SocketChannel) selectionKey.channel();

            ByteBuffer buffer = ByteBuffer.allocate(1024);
            //讀取客戶端傳過來的資料
            int len = 0;
            while ((len = socketChannel.read(buffer))>0){
              buffer.flip();
              System.out.println(new String(buffer.array(),0,len));
              buffer.clear();  
          }
        }
        //取消選擇鍵selectionKey
        iterator.remove();
    }
}

客戶端

//1.獲取通道
SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1",8080));
//2.設定為非堵塞模式
socketChannel.configureBlocking(false);
ByteBuffer buf = ByteBuffer.allocate(1024);
//3.傳送資料給服務端
//控制檯輸入資料
Scanner scanner = new Scanner(System.in);
while (scanner.hasNext()){
    String msg = scanner.next();
    buf.put(msg.getBytes());
    buf.flip();
    socketChannel.write(buf);
    buf.clear();
}
//4.關閉連線
socketChannel.close();

部落格地址

更多示例程式碼

本文參考