第 2 章 Java BIO 編程

I/O 模型

I/O 模型基本說明

  1. I/O 模型簡單的理解:就是用什麼樣的通道進行數據的發送和接收,很大程度上決定了程序通信的性能

  2. Java 共支持 3 種網絡編程模型/IO 模式:BIO、NIO、AIO

  3. Java BIO同步並阻塞(傳統阻塞型),服務器實現模式爲一個連接一個線程,即客戶端有連接請求時服務器端就需要啓動一個線程進行處理,如果這個連接不做任何事情會造成不必要的線程開銷
    在這裏插入圖片描述

  4. Java NIO同步非阻塞,服務器實現模式爲一個線程處理多個請求(連接),即客戶端發送的連接請求都會註冊到多路複用器上,多路複用器輪詢到連接有 I/O 請求就進行處理
    在這裏插入圖片描述

  5. Java AIO(NIO.2) : 異步非阻塞,AIO 引入異步通道的概念,採用了 Proactor 模式,簡化了程序編寫,有效的請求才啓動線程,它的特點是先由操作系統完成後才通知服務端程序啓動線程去處理,一般適用於連接數較多且連接時間較長的應用

BIO、NIO、AIO 適用場景分析

  1. BIO 方式適用於連接數目比較小且固定的架構,這種方式對服務器資源要求比較高,併發侷限於應用中,JDK1.4以前的唯一選擇,但程序簡單易理解。

  2. NIO 方式適用於連接數目多且連接比較短(輕操作)的架構,比如聊天服務器,彈幕系統,服務器間通訊等。編程比較複雜,JDK1.4 開始支持

  3. AIO 方式使用於連接數目多且連接比較長(重操作)的架構,比如相冊服務器,充分調用 OS 參與併發操作,編程比較複雜,JDK7 開始支持

Java BIO 基本介紹

  1. Java BIO 就是傳統的 java io 編程,其相關的類和接口在 java.io

  2. BIO(blocking I/O) : 同步阻塞,服務器實現模式爲一個連接一個線程,即客戶端有連接請求時服務器端就需要啓動一個線程進行處理,如果這個連接不做任何事情會造成不必要的線程開銷,可以通過線程池機制改善(實現多個客戶連接服務器)。 【後有應用實例】

  3. BIO 方式適用於連接數目比較小且固定的架構,這種方式對服務器資源要求比較高,併發侷限於應用中,JDK1.4以前的唯一選擇,程序簡單易理解

Java BIO 工作機制

在這裏插入圖片描述
對 BIO 編程流程的梳理

  1. 服務器端啓動一個 ServerSocket
  2. 客戶端啓動 Socket 對服務器進行通信,默認情況下服務器端需要對每個客戶 建立一個線程與之通訊
  3. 客戶端發出請求後, 先諮詢服務器是否有線程響應,如果沒有則會等待,或者被拒絕
  4. 如果有響應,客戶端線程會等待請求結束後,在繼續執行

Java BIO 應用實例

實例說明:

  1. 使用 BIO 模型編寫一個服務器端,監聽 6666 端口,當有客戶端連接時,就啓動一個線程與之通訊。
  2. 要求使用線程池機制改善,可以連接多個客戶端.
  3. 服務器端可以接收客戶端發送的數據(telnet 方式即可)。
  4. 代碼演示
public class BIOServer {
    public static void main(String[] args) throws Exception {

        //線程池機制

        //思路
        //1. 創建一個線程池
        //2. 如果有客戶端連接,就創建一個線程,與之通訊(單獨寫一個方法)
        ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
        //創建ServerSocket
        ServerSocket serverSocket = new ServerSocket(6666);

        System.out.println("服務器啓動了");
        while (true) {

            System.out.println("線程信息 id =" + Thread.currentThread().getId() + " 名字=" + Thread.currentThread().getName());

            //監聽,等待客戶端連接
            System.out.println("等待連接....");
            final Socket socket = serverSocket.accept();//阻塞的,等待客戶端連接,連接了就啓動線程處理它
            System.out.println("連接到一個客戶端");

            //就創建一個線程,與之通訊(單獨寫一個方法)
            newCachedThreadPool.execute(new Runnable() {
                public void run() { //我們重寫
                    //可以和客戶端通訊
                    handler(socket);
                }
            });
        }
    }

    //編寫一個handler方法,和客戶端通訊
    public static void handler(Socket socket) {

        try {
            System.out.println("線程信息 id =" + Thread.currentThread().getId() + " 名字=" + Thread.currentThread().getName());
            byte[] bytes = new byte[1024];
            //通過socket 獲取輸入流
            InputStream inputStream = socket.getInputStream();
            //循環的讀取客戶端發送的數據
            while (true) {

                System.out.println("線程信息 id =" + Thread.currentThread().getId() + " 名字=" + Thread.currentThread().getName());

                System.out.println("read....");
               int read =  inputStream.read(bytes);//阻塞的,等到客戶端的輸入,如果沒有就阻塞在這裏了
               if(read != -1) {
                   System.out.println(new String(bytes, 0, read)); //輸出客戶端發送的數據
               } else {
                   break;
               }
            }

            System.out.println("客戶端退出....");//當客戶端斷開發生

        }catch (Exception e) {
            e.printStackTrace();
        }finally {
            System.out.println("關閉和client的連接");
            try {
                socket.close();
            }catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

在這裏插入圖片描述
在這裏插入圖片描述

Java BIO 問題分析

  1. 每個請求都需要創建獨立的線程,與對應的客戶端進行數據 Read,業務處理,數據 Write 。

  2. 當併發數較大時,需要創建大量線程來處理連接,系統資源佔用較大。

  3. 連接建立後,如果當前線程暫時沒有數據可讀,則線程就阻塞在 Read 操作上,造成線程資源浪費。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章