java BIO、NIO學習

在IO執行有兩個階段很重要:html

  • 等待數據準備
  • 將數據從內核複製到進程中

BIO(Blocking I/O)阻塞I/O模型




當用戶進程調用了recvfrom這個系統調用,內核就開始了io的第一個階段:等待數據準備。若是數據還沒準備好(好比尚未收到一個完整的udp包),這時候內核要等待足夠的數據到來,而在用戶進程這邊,進程會被阻塞。當內核等到數據準備好,進程將數據從內核中拷貝到用戶空間,而後內核返回結果,用戶進程才解除block狀態,從新運行起來。數組

BIO在IO執行的兩個階段都被阻塞了。bash

示例代碼:socket

public static void server(){
        ServerSocket serverSocket = null;
        InputStream in = null;
        try
        {
            serverSocket = new ServerSocket(8080);
            int recvMsgSize = 0;
            byte[] recvBuf = new byte[1024];
            while(true){
                Socket clntSocket = serverSocket.accept();
                SocketAddress clientAddress = clntSocket.getRemoteSocketAddress();
                System.out.println("Handling client at "+clientAddress);
                in = clntSocket.getInputStream();
                while((recvMsgSize=in.read(recvBuf))!=-1){
                    byte[] temp = new byte[recvMsgSize];
                    System.arraycopy(recvBuf, 0, temp, 0, recvMsgSize);
                    System.out.println(new String(temp));
                }
            }
        }
        catch (IOException e)
        {
            e.printStackTrace();
        }
        finally{
            try{
                if(serverSocket!=null){
                    serverSocket.close();
                }
                if(in!=null){
                    in.close();
                }
            }catch(IOException e){
                e.printStackTrace();
            }
        }
    }
複製代碼

NIO(Non-Blocking I/O)非阻塞I/O模型

NIO是一種同步非阻塞的IO模型。同步指線程不斷輪詢IO事件是否就緒,非阻塞是指線程在等待IO的時候,能夠作其餘任務。同步的核心是Selector,Selector代替了線程自己輪詢IO事件,避免了阻塞同時減小了沒必要要的線程消耗;非阻塞的核心是通道和緩衝區,當IO事件就緒時,能夠經過寫入緩衝區,保證IO的成功,無需線程阻塞式地等待。學習


當用戶進程調用recvfrom時,系統不會阻塞用戶進程,若是數據還沒準備好,系統馬上返回一個ewouldblock錯誤給進程,用戶進程知道數據還沒準備好,用戶進程就能夠去作其餘事了。進程輪詢內核查看數據是否準備好。直到數據準備好了,而且收到用戶進程的system call,進程複製數據報,而後返回。ui

NIO有三大核心部分:Channel(通道),Buffer(緩衝區),Selector(多路複用器)



Channel

基本上,全部的IO在NIO中都從一個Channel開始。數據能夠從Channel讀到Buffer中,也能夠從Buffer寫到Channel。spa

Buffer

Buffer經過如下幾個變量來保存這個數據的當前位置狀態:線程

capacity:緩衝區數組的總長度
position:下一個要操做的數據元素的位置
limit:緩衝區數組中不可操做的下一個元素的位置
mark:用於記錄當前position的前一個位置或者默認是-1

0 <= mark <= position <= limit <= capacity複製代碼


開始時Buffer的position爲0,limit爲capacity,程序寫入數據到緩衝區,position日後移。當Buffer寫入數據結束以後,調用flip()方法以後(此時limit=position,position=0),Buffer爲輸出數據作好準備;當Buffer輸出數據結束以後,調用clear()方法(此時limit=capacity,position=0),clear()方法不是清空Buffer的數據,它僅僅將position置爲0,將limit置爲capacity,爲再次向Buffer裝入數據作準備;3d

Buffer的使用:code

  • 分配空間:ByteBuffer buf = ByteBuffer.allocate(1024);
  • 寫入數據到Buffer:int bytesRead = fileChannel.read(buf);
  • 調用flip()方法:buf.flip();
  • 從Buffer中讀取數據:buf.get();
  • 調用clear()方法或compact()方法

Buffer一些重要方法:

  • flip():把limit設置爲position,position設置爲0,爲輸出數據作好準備
  • clear():把position設置爲0,limit設置爲capacity,爲再次向Buffer裝入數據作準備
  • compact():將全部未讀的數據拷貝到Buffer起始處,把position設置到最後一個未讀元素的正後面,limit設置爲capacity
  • mark():能夠標記Buffer中的一個特定的position,以後能夠經過reset()恢復到position的位置
  • rewind():將position設回爲0,limit保持不變,這樣能夠重讀Buffer中的全部數據。

示例代碼:

public static void client(){
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        SocketChannel socketChannel = null;
        try
        {
            socketChannel = SocketChannel.open();
            socketChannel.configureBlocking(false);
            socketChannel.connect(new InetSocketAddress("10.10.195.115",8080));
            if(socketChannel.finishConnect())
            {
                int i=0;
                while(true)
                {
                    TimeUnit.SECONDS.sleep(1);
                    String info = "I'm "+i+++"-th information from client";
                    buffer.clear();
                    buffer.put(info.getBytes());
                    buffer.flip();
                    while(buffer.hasRemaining()){
                        System.out.println(buffer);
                        socketChannel.write(buffer);
                    }
                }
            }
        }
        catch (IOException | InterruptedException e)
        {
            e.printStackTrace();
        }
        finally{
            try{
                if(socketChannel!=null){
                    socketChannel.close();
                }
            }catch(IOException e){
                e.printStackTrace();
            }
        }
    }
複製代碼

Selector

Channel和Selector配合使用,必須先將Channel註冊到Selector上,經過register()方法來實現。Channel.register()方法會返回一個SelectionKey對象。這個對象表明了註冊到該Selector的通道。

一旦向Selector註冊了一個或多個通道,就能夠調用select()方法。

select()方法返回的int值表示有多少通道已經就緒。

參考資料:瘋狂Java講義

Java NIO

Java BIO/NIO/AIO學習

相關文章
相關標籤/搜索