Java NIO -- 阻塞和非阻塞

  1. 傳統的 IO 流都是阻塞式的。也就是說,當一個線程調用 read() 或 write()時,該線程被阻塞,直到有一些數據被讀取或寫入,該線程在此期間不能執行其餘任務。所以,在完成網絡通訊進行 IO操做時,因爲線程會阻塞,因此服務器端必須爲每一個客戶端都提供一個獨立的線程進行處理,當服務器端須要處理大量客戶端時,性能急劇降低。
  2. Java NIO 是非阻塞模式的。當線程從某通道進行讀寫數據時,若沒有數據可用時,該線程能夠進行其餘任務。線程一般將非阻塞 IO 的空閒時間用於在其餘通道上執行 IO 操做,因此單獨的線程能夠管理多個輸入和輸出通道。所以,NIO 可讓服務器端使用一個或有限幾個線程來同時處理鏈接到服務器端的全部客戶端。

選擇器(Selector)java

  1. 選擇器(Selector) 是 SelectableChannle 對象的多路複用器,Selector 能夠同時監控多個 SelectableChannel 的 IO 情況,也就是說,利用 Selector可以使一個單獨的線程管理多個 Channel。Selector 是非阻塞 IO 的核心。
  2.  SelectableChannle 的結構以下圖:

阻塞式IO流操做示例:服務器

package com.soyoungboy.nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;

import org.junit.Test;

public class TestBlockingNIO2 {
    
    //客戶端
    @Test
    public void client() throws IOException{
        SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9898));
        
        FileChannel inChannel = FileChannel.open(Paths.get("1.jpg"), StandardOpenOption.READ);
        
        ByteBuffer buf = ByteBuffer.allocate(1024);
        
        while(inChannel.read(buf) != -1){
            buf.flip();
            sChannel.write(buf);
            buf.clear();
        }
        
        sChannel.shutdownOutput();
        
        //接收服務端的反饋
        int len = 0;
        while((len = sChannel.read(buf)) != -1){
            buf.flip();
            System.out.println(new String(buf.array(), 0, len));
            buf.clear();
        }
        
        inChannel.close();
        sChannel.close();
    }
    
    //服務端
    @Test
    public void server() throws IOException{
        ServerSocketChannel ssChannel = ServerSocketChannel.open();
        
        FileChannel outChannel = FileChannel.open(Paths.get("2.jpg"), StandardOpenOption.WRITE, StandardOpenOption.CREATE);
        
        ssChannel.bind(new InetSocketAddress(9898));
        
        SocketChannel sChannel = ssChannel.accept();
        
        ByteBuffer buf = ByteBuffer.allocate(1024);
        
        while(sChannel.read(buf) != -1){
            buf.flip();
            outChannel.write(buf);
            buf.clear();
        }
        
        //發送反饋給客戶端
        buf.put("服務端接收數據成功".getBytes());
        buf.flip();
        sChannel.write(buf);
        
        sChannel.close();
        outChannel.close();
        ssChannel.close();
    }

}

非阻塞式IO流網絡

1、使用 NIO 完成網絡通訊的三個核心:

1. 通道(Channel):負責鏈接

  java.nio.channels.Channel 接口:
    |--SelectableChannel
    |--SocketChannel
    |--ServerSocketChannel
    |--DatagramChannel

    |--Pipe.SinkChannel
    |--Pipe.SourceChannel

2. 緩衝區(Buffer):負責數據的存取

3. 選擇器(Selector):是 SelectableChannel 的多路複用器。用於監控 SelectableChannel 的 IO 情況性能

public class TestNonBlockingNIO {
    
    //客戶端
    @Test
    public void client() throws IOException{
        //1. 獲取通道
        SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9898));
        
        //2. 切換非阻塞模式
        sChannel.configureBlocking(false);
        
        //3. 分配指定大小的緩衝區
        ByteBuffer buf = ByteBuffer.allocate(1024);
        
        //4. 發送數據給服務端
        Scanner scan = new Scanner(System.in);
        
        while(scan.hasNext()){
            String str = scan.next();
            buf.put((new Date().toString() + "\n" + str).getBytes());
            buf.flip();
            sChannel.write(buf);
            buf.clear();
        }
        
        //5. 關閉通道
        sChannel.close();
    }

    //服務端
    @Test
    public void server() throws IOException{
        //1. 獲取通道
        ServerSocketChannel ssChannel = ServerSocketChannel.open();
        
        //2. 切換非阻塞模式
        ssChannel.configureBlocking(false);
        
        //3. 綁定鏈接
        ssChannel.bind(new InetSocketAddress(9898));
        
        //4. 獲取選擇器
        Selector selector = Selector.open();
        
        //5. 將通道註冊到選擇器上, 而且指定「監聽接收事件」
        ssChannel.register(selector, SelectionKey.OP_ACCEPT);
        
        //6. 輪詢式的獲取選擇器上已經「準備就緒」的事件
        while(selector.select() > 0){
            
            //7. 獲取當前選擇器中全部註冊的「選擇鍵(已就緒的監聽事件)」
            Iterator<SelectionKey> it = selector.selectedKeys().iterator();
            
            while(it.hasNext()){
                //8. 獲取準備「就緒」的是事件
                SelectionKey sk = it.next();
                
                //9. 判斷具體是什麼事件準備就緒
                if(sk.isAcceptable()){
                    //10. 若「接收就緒」,獲取客戶端鏈接
                    SocketChannel sChannel = ssChannel.accept();
                    
                    //11. 切換非阻塞模式
                    sChannel.configureBlocking(false);
                    
                    //12. 將該通道註冊到選擇器上
                    sChannel.register(selector, SelectionKey.OP_READ);
                }else if(sk.isReadable()){
                    //13. 獲取當前選擇器上「讀就緒」狀態的通道
                    SocketChannel sChannel = (SocketChannel) sk.channel();
                    
                    //14. 讀取數據
                    ByteBuffer buf = ByteBuffer.allocate(1024);
                    
                    int len = 0;
                    while((len = sChannel.read(buf)) > 0 ){
                        buf.flip();
                        System.out.println(new String(buf.array(), 0, len));
                        buf.clear();
                    }
                }
                
                //15. 取消選擇鍵 SelectionKey
                it.remove();
            }
        }
    }
}
相關文章
相關標籤/搜索