NIO編程介紹

I/O模型

java支持3種網絡編程模型I/O模式:BIO(同步並阻塞)、NIO(同步非阻塞)、AIO(異步非阻塞)

阻塞指的是訪問IO的線程是否會阻塞(或等待)。線程訪問資源,該資源是否準備就緒的一種處理方式。

阻塞與非阻塞:

同步與異步:

1.1 BIO

BIO:同步阻塞,服務器實現模式為一個連接一個線程,即客戶端有連接請求時服務器端就需要啟動一個線程進行處理,如果這個連接不做任何事情會造成不必要的線程開銷,可以通過線程池機制改善。

代碼示例:

public class ServerDemo {

    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket=new ServerSocket(9999);
        ExecutorService executorService = Executors.newCachedThreadPool();
        while (true){
            Socket socket=serverSocket.accept();
            System.out.println("有客戶端連接");
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    InputStream inputStream = null;
                    try {
                        inputStream = socket.getInputStream();
                        byte[] b=new byte[1024];
                        int read = inputStream.read(b);
                        System.out.println("收到客戶端信息:"+new String(b,0,read));
                        OutputStream outputStream = socket.getOutputStream();
                        outputStream.write("你好客戶端".getBytes(Charset.defaultCharset()));
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    }
}

public class ClientDemo {

    public static void main(String[] args) throws IOException {
        Socket socket=new Socket("127.0.0.1",9999);
        OutputStream outputStream = socket.getOutputStream();
        outputStream.write("你好服務端".getBytes(Charset.defaultCharset()));
        InputStream inputStream = socket.getInputStream();
        byte[] bytes=new byte[1024];
        inputStream.read(bytes);
        System.out.println("收到服務端消息:"+new String(bytes));
        socket.close();

    }
}

BIO問題分析:

  1. 每個請求都需要創建獨立的線程,與對應的客戶端進行數據Read、業務處理、數據Write
  2. 並發數較大時,需要創建大量線程來處理連接,系統資源佔用較大
  3. 連接建立後,如果當前線程暫時沒有數據可讀,則線程就阻塞在Read操作上,造成資源阻塞。

1.2 NIO

NIO:同步非阻塞,服務器實現模式為一個線程處理多個請求(連接),即客戶端發送的連接請求都會註冊到多路復用器上,多路復用器輪詢到連接有I/O請求就進行處理。

代碼示例:

public class NioSelectorServer {


    public static void main(String[] args) throws IOException, InterruptedException {
        //1.打開服務器通道
        ServerSocketChannel serverSocketChannel=ServerSocketChannel.open();
        //2.綁定端口號
        serverSocketChannel.bind(new InetSocketAddress(9999));
        //3.設置通道為非阻塞
        serverSocketChannel.configureBlocking(false);
        //4.創建選擇器
        Selector selector = Selector.open();
        //5.將服務端通道註冊到選擇器上,並指定註冊監聽事件為OP_ACCEPT
        serverSocketChannel.register(selector,SelectionKey.OP_ACCEPT);
        while (true){
            //6.檢查選擇器釋放有事件
//            int select = selector.select(2000);
            int select = selector.select();
            if(select==0){
                System.out.println("無連接");
                continue;
            }
            //7.獲取事件集合
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = selectionKeys.iterator();
            while (iterator.hasNext()){
                SelectionKey selectionKey = iterator.next();
                //8.判斷事件是否是連接事件
                if(selectionKey.isAcceptable()){
                    //9.得到客戶端通道,並將通道註冊到選擇器上
                    SocketChannel socketChannel = serverSocketChannel.accept();
                    System.out.println("有客戶端連接");
                    socketChannel.configureBlocking(false);
                    socketChannel.register(selector,SelectionKey.OP_READ);
                }
                //10.判斷是否是讀就緒事件
                else if(selectionKey.isReadable()){
                    SocketChannel channel = (SocketChannel)selectionKey.channel();
                    ByteBuffer byteBuffer=ByteBuffer.allocate(1024);
                    //11.得到客戶端通道,讀取數據到緩衝區
                    int read = 0;
                    read = channel.read(byteBuffer);
                    System.out.println("客戶端消息:"+new String(byteBuffer.array()));
                    System.out.println("模擬業務處理中。。。");
                    Thread.sleep(5000);
                    if(read>0){
                        //12.回寫數據給客戶端
                        channel.write(ByteBuffer.wrap("你好客戶端".getBytes(StandardCharsets.UTF_8)));
                        channel.close();
                    }
                }
                //13.從集合刪除對應事件,防止二次處理
                iterator.remove();
            }
        }
    }
}

1.3 AIO

AIO引入了異步通道的概念,採用了Proactor模式,簡化了程序編寫。它的特點是先由操作系統完成後才通知服務端程序啟動線程去處理,一般適用於連接數較多且連接時間比較長的應用。

Proactor模式是一個消息異步通知的設計模式,Proactor通知的部署就緒事件,而是操作完成事件。

適用場景

  1. BIO適用於連接數比較小且固定的架構,這種方式對服務器資源要求比較高
  2. NIO適用於連接數目多且比較短的架構,比如聊天服務器,彈幕系統,服務期間通訊等
  3. AIO適用於連接數多且連接比較長的架構,比如相冊服務器。

NIO編程

2.1 NIO介紹

Java NIO是JDK提供的新API。NIO有三個核心部分:Channel,Buffer,Selector。NIO是面向緩衝區編程的。JAVA NIO的非阻塞模式,是一個線程從某個通道發送或讀取數據,僅能得到目前可用的數據,如果目前沒有數據可用時,就什麼都不會獲取,而不是阻塞線程。

通俗理解:NIO是可以做到用一個線程來處理多個操作的。假設有1000個請求過來,根據實際情況,可以分配50或100個線程來處理。不像BIO那樣,非得分配1000個線程。

NIO和BIO比較

  1. BIO是以流的方式處理數據,而NIO是以緩衝區的方式處理數據
  2. BIO是阻塞的,NIO是非阻塞的
  3. BIO基於位元組流和字符流操作,而NIO是基於Channel和Buffer進行操作。NIO基於Selector監聽多個通道的事件。