天天看點

網絡模型(Java中的IO模型)一、IO讀寫原理二、四種主要的IO模型

文章目錄

  • 一、IO讀寫原理
  • 二、四種主要的IO模型
    • 概念
    • 1.同步阻塞 BIO
      • BIO程式設計
    • 2.同步非阻塞 NIO
    • 3.IO多路複用
    • 4.異步非阻塞 AIO

一、IO讀寫原理

  1. 檔案的讀寫還是socket讀寫,再Java應用層開發,都是 input或者 output處理。使用者程式進行的IO讀寫,會用到 read&write(核心态) 兩大系統調用。

    read:将資料從核心緩沖區複制到程序緩沖區

    write:把資料從程序緩沖區複制到核心緩沖區

  2. 核心緩沖區 和 程序緩存區

    使用者程序(N個):處于使用者态(使用者空間)

    系統空間:核心态。在使用者态需要通路系統資源,需要借助核心态,系統資源主要有:

    1)CPU:控制一個程式的執行

    2)輸入輸出:一切都是流,所有流都需要借助核心态

    3)程序管理:程序建立、銷毀、阻塞、喚醒之間的排程

    4)記憶體:記憶體的申請、釋放

    5)程序間通信:程序之間不能互相通路記憶體,是以程序

    以上所提到的系統資源,再使用者程序中是無法被直接通路的,隻有通過作業系統來通路,是以把作業系統通路這一功能稱之為系統調用。

  • 緩沖區的目的,是為了減少頻繁的系統IO調用

    系統調用需要從使用者态切換到核心态,切換之後儲存使用者程序的資料和狀态等資訊。結束調用之後會需要恢複之前的資訊,為了減少這種損耗的時間,還有損耗性能的時間,是以出現了緩沖區。

  • 有了緩沖區,作業系統使用 read從核心緩沖區複制到程序緩沖區,write從程序緩沖區複制到核心緩沖區,隻有緩沖區中的資料達到一定的量,再進行IO的系統調用,提升了系統的性能。
  • Java IO讀寫底層的流程圖:
    網絡模型(Java中的IO模型)一、IO讀寫原理二、四種主要的IO模型

二、四種主要的IO模型

概念

  • 阻塞IO:需要核心IO操作徹底完成之後,才傳回到使用者空間,執行使用者的操作

    非阻塞IO:不需要等待核心IO操作徹底完成之後,才傳回到使用者空間

    阻塞/非阻塞 指的是使用者空間程式的執行狀态

  • 同步IO:是使用者空間線程和核心空間線程的互動,使用者空間線程是主動發起IO請求的一方,核心空間線程指的是被動接收的一方

    異步IO:與上面相反

1.同步阻塞 BIO

  • jdk1.4之前,IO模型都采用的BIO模型。

    需要先在伺服器端啟動一個ServerSocket,然後在用戶端啟動Socket與伺服器端進行通信,伺服器端調用accept方法來接收用戶端的連接配接請求,一旦接收上連接配接請求,就可以建立套接字,在這個套接字上進行讀寫操作,此時不能再接收其他用戶端的連接配接請求,隻能等待目前連接配接的用戶端的執行操作完成。

  • JDK1.4開始,出現同步阻塞式IO

    處理多個用戶端請求,使用多線程

    網絡模型(Java中的IO模型)一、IO讀寫原理二、四種主要的IO模型

    優點:使用者線程阻塞等待資料期間,不會占用CPU資源

    缺點:BIO模型在高并發的場景下是不可用的

BIO程式設計

  • 伺服器端

    1)建立ServerSocket執行個體

    2)綁定端口

    3)通過accept方法監聽用戶端的連接配接,有用戶端連接配接會傳回socket執行個體

    4)進行讀寫操作

    5)關閉資源

  • 伺服器端如何設計為可處理很多用戶端的連接配接?

    主線程負責接收用戶端連接配接,子線程負責和用戶端互動。

    代碼實作:

//子線程
class HandlerThread extends Thread{                                                                          
    private Socket socket;                                                                                                                                                                                            
    public HandlerThread(Socket socket){                                                                     
        this.socket = socket;                                                                                
    }                                                                                                                                                                                                                  
    @Override                                                                                                
    public void run() {//重寫run方法                                                                                      
        try {                                                                                                
            //讀取用戶端的請求資訊                                                                                     
            OutputStream output = null;                                                                      
            BufferedReader reader = null;                                                                    
            while(true){                                                                                     
                reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));                 
                String msg = reader.readLine();                                                              
                System.out.println("用戶端:"+socket.getRemoteSocketAddress()+",發送的資訊為:"+msg);                   
                //給用戶端回複                                                                                     
                output = socket.getOutputStream();//擷取輸出流                                                           
                //控制台讀取資料                                                                                    
                reader = new BufferedReader(new InputStreamReader(System.in));                               
                String info = reader.readLine();                                                             
                output.write((info+"\n").getBytes());                                                        
                output.flush();                                                                              
                if("".equals(info) || "exit".equals(info)) break;                                            
            }   
            //關閉資源                                                                                             
            reader.close();                                                                                  
            output.close();                                                                                  
        } catch (IOException e) {                                                                            
            e.printStackTrace();                                                                             
        } finally {                                                                                          
            try {                                                                                            
                System.out.println("用戶端:"+socket.getRemoteSocketAddress()+"關閉了");                            
                socket.close();                                                                              
            } catch (IOException e) {                                                                        
                e.printStackTrace();                                                                         
            }                                                                                                
        }                                                                                                    
    }                                                                                                        
}     
//主線程                                                                                                       
public class MyBIOServer {                                                                                   
    public static void main(String[] args) {                                                                 
        ServerSocket ssocket = null;                                                                         
        try {                                                                                                
            //建立ServerSocket執行個體                                                                               
            ssocket = new ServerSocket();                                                                    
            //綁定端口                                                                                           
            ssocket.bind(new InetSocketAddress(9999));                                                       
            System.out.println("伺服器端啟動了...");                                                                                                                                                                
            while(true){                                                                                     
                //通過accept方法監聽用戶端的連接配接,有用戶端連接配接會傳回socket執行個體                                                       
                Socket socket = ssocket.accept(); //這個方法是一個阻塞方法                                              
                System.out.println("有新的用戶端連接配接:"+socket.getRemoteSocketAddress());                             
                //将socket執行個體交給子線程去處理                                                                          
                new HandlerThread(socket).start();                                                           
            }                                                                                                                                                                            
        } catch (IOException e) {                                                                            
            e.printStackTrace();                                                                             
        } finally {                                                                                          
            if(ssocket != null){                                                                             
                try {                                                                                        
                    ssocket.close();                                                                         
                } catch (IOException e) {                                                                    
                    e.printStackTrace();                                                                     
                }                                                                                            
            }                                                                                                
        }                                                                                                    
    }                                                                                                        
}                                                                                                            
           
  • 用戶端

    1)建立socket執行個體

    2)通過connect去指定伺服器端的IP和端口

    3)進行讀寫操作

    4)關閉資源

    代碼實作:

public class MyBIOClient {
    public static void main(String[] args) {
        Socket socket = null;
        try {
            //建立Socket執行個體
            socket = new Socket();
            //連接配接伺服器端,通過connect綁定IP位址和端口号
            socket.connect(new InetSocketAddress("127.0.0.1", 9999));
            System.out.println("用戶端啟動了...");
            //進行讀寫操作
            while(true){
                //寫操作
                OutputStream output = socket.getOutputStream();
                BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
                output.write((reader.readLine()+"\n").getBytes());
                output.flush();
                //讀操作
                reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                String msg = reader.readLine();
                System.out.println("伺服器響應資訊為:"+msg);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(socket != null){
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }
}
           

2.同步非阻塞 NIO

NIO的内容在NIO模型中具體描述。

3.IO多路複用

  • 通過一個新的系統調用 select/epoll 系統調用,實作一個程序監視多個檔案描述符(唯一ID)。一旦某個描述符就緒(核心緩沖區可讀/可寫),核心能夠通知使用者程式進行IO系統調用。
  • 以線程為例:單個線程不斷的輪詢 select/epoll 系統調用 所負責多個socket連接配接,當某個socket連接配接有資料到達,就傳回這些可以讀寫的連接配接。
    網絡模型(Java中的IO模型)一、IO讀寫原理二、四種主要的IO模型
  • 特點:

    與NIO模型類似,多路複用IO需要輪詢負責select/epoll查詢調用的線程,查找出可以進行IO操作的一個連接配接,對于每一個可以查詢的socket,一般需要設定為non-blocking。

  • 優點:

    select/epoll 可以同時處理成百上千的連接配接,與之前的一個線程維護一個連接配接相比,IO多路複用則不需要建立線程,也就不需要維護,進而減少系統開銷。

  • 缺點:

    select/epoll系統調用,屬于阻塞的模式,同步IO。讀寫事件就緒之後,使用者自己進行讀寫,這個讀寫過程也是阻塞的。

4.異步非阻塞 AIO

  • 使用者線程發起系統調用,告知核心啟動某個IO操作,使用者線程直接傳回。核心在整個IO操作(資料準備,資料複制完成之後,調用回調函數通知使用者程式(已就緒),使用者執行後續的業務操作。
    網絡模型(Java中的IO模型)一、IO讀寫原理二、四種主要的IO模型
  • 特點:

    分為兩個等待過程,等待資料就緒,等待資料拷貝,而在這兩個等待過程中使用者線程都不是block的,等這兩個操作完成之後,使用者線程就會收到一個信号。是以AIO又稱之為信号驅動IO。

  • 缺點:

    需要事件的注冊,就需要作業系統,對系統資源和記憶體資源消耗大。

  • java.nio.channels包:

    AsynchronousServerSocketChannel 伺服器資料通道

    AsynchronousSocketChannel 用戶端通道

    AsynchronousDatagramChannel 基于UDP的通道

    AsynchronousFIleChannel 操作檔案

  • 異步非阻塞,伺服器端實作一個一個線程,異步執行目前用戶端請求,用戶端的請求都是作業系統完成再去通知伺服器端處理,是以這個AIO主要用于連接配接數多同時請求比較複雜的系統架構。

BIO NIO AIO三個網絡模型的适用場景:

模型 适用場景
BIO 連接配接數目少的系統架構,同時也需要伺服器資源充足,雖然效率不高,但是程式…
NIO 連接配接數目比較多以及連接配接時間短的系統架構,例如聊天伺服器,程式設計比較複雜
AIO 連接配接數目比較多以及連接配接時間長的系統架構,充分調用作業系統參與并發,不管是業務邏輯,還是并發邏輯,都需要作業系統的支援,是以不同的作業系統,性能也是不同的

繼續閱讀