Java網絡編程 — BIO 阻塞式網絡編程

  • 2019 年 10 月 3 日
  • 筆記

阻塞IO的含義

阻塞(blocking)IO :阻塞是指結果返回之前,線程會被掛起,函數只有在得到結果之後(或超時)才會返回

非阻塞(non-blocking)IO :非阻塞和阻塞的概念相對應,指在不能立刻得到結果之前,該函數不會阻塞當前線程,而會立刻返回

同步(synchronous)IO :應用阻塞在發送或接受數據的狀態,直至數據成功傳輸(或返回失敗),簡單來說就是必須一件一件事做,等前一件做完了才能做下一件事

異步(asynchronous)IO :應用發送或接受數據後立即返回,實際處理這個調用的程序在完成後,通過狀態、通知和回調來通知調用者

阻塞和非阻塞是獲取資源的方式,同步和異步是程序如何處理資源的邏輯。

BIO網絡編程

首先我們來看一段最基礎的Java網絡編程代碼示例:

服務器端代碼示例:

public class BIOServerV1 {      public static void main(String[] args) throws Exception {      ServerSocket serverSocket = new ServerSocket(8080);      System.out.println("服務器啟動成功");      while (!serverSocket.isClosed()) {        Socket request = serverSocket.accept(); // 阻塞        System.out.println("收到新連接:" + request.toString());        try {          InputStream inputStream = request.getInputStream(); // 獲取數據流          BufferedReader bufferedReader =              new BufferedReader(new InputStreamReader(inputStream, "utf-8"));          String message;          while ((message = bufferedReader.readLine()) != null) {            if (message.length() == 0) {              break;            }            System.out.println("消息內容為:" + message);          }          System.out.println("收到數據,來自:" + request.toString());        } catch (IOException e) {          e.printStackTrace();        } finally {          request.close();        }      }        serverSocket.close();    }  }  

客戶端代碼示例:

public class BIOClient {      public static void main(String[] args) throws IOException {      Socket socket = new Socket("localhost", 8080);      OutputStream outputStream = socket.getOutputStream();        Scanner scanner = new Scanner(System.in);      System.out.println("請輸入:");      String message = scanner.nextLine();      outputStream.write(message.getBytes(Charset.forName("UTF-8")));      scanner.close();      socket.close();    }  }  

這個版本服務器端的代碼同一時刻只能支持一個網絡連接,在建立連接之後服務端線程會被阻塞,只有在已建立連接的客戶端處理完數據關閉連接之後,後續的連接請求才能一個一個的處理,而為了能並發的處理多個請求我們在下一個版本中加入多線程的代碼。

public class BIOServerV2 {      private static ExecutorService executorService = Executors.newCachedThreadPool();      public static void main(String[] args) throws IOException {      ServerSocket serverSocket = new ServerSocket(8080);      System.out.println("服務器啟動成功");      while (!serverSocket.isClosed()) {        Socket request = serverSocket.accept();        System.out.println("收到新連接:" + request.toString());          // 多線程接收多個連接        executorService.submit(            () -> {              try {                InputStream inputStream = request.getInputStream();                BufferedReader bufferedReader =                    new BufferedReader(new InputStreamReader(inputStream, "utf-8"));                String message;                while ((message = bufferedReader.readLine()) != null) {                  if (message.length() == 0) {                    break;                  }                  System.out.println("消息內容為:" + message);                }                System.out.println("收到數據,來自:" + request.toString());              } catch (IOException e) {                e.printStackTrace();              } finally {                try {                  request.close();                } catch (IOException e) {                  e.printStackTrace();                }              }            });      }      serverSocket.close();    }  }  

這個版本的代碼在加入多線程後可以並發的處理多個連接,但是它只能處理Java客戶端的連接不能處理瀏覽器端的連接,而為了能與瀏覽器端交互我們需要了解HTTP協議的內容。

HTTP協議

HTTP協議請求數據包解析

img

HTTP協議響應數據包解析

img

HTTP協議響應狀態碼:

狀態碼 描述
1xx 臨時響應,表示臨時響應並需要請求者繼續執行操作的狀態碼
2xx 成功,表示成功處理了請求的狀態碼
3xx 重定向,表示要完成請求,需要進一步操作。通常,這些狀態碼用來重定向
4xx 請求錯誤,這些狀態碼錶示請求可能出錯,妨礙了服務器的處理
5xx 服務器錯誤,這些狀態碼錶示服務器在嘗試處理請求時發生內部錯誤。這些錯誤可能是服務器本身的錯誤,而不是請求出錯

BIO網絡編程處理瀏覽器請求

在了解了HTTP協議的內容之後我們就可以依據HTTP協議的內容編寫程序來處理瀏覽器請求。在之前多線程版本的代碼之上我們需要對數據根據HTTP協議的內容進行處理,代碼示例如下:

public class BIOServerV3 {      private static ExecutorService executorService = Executors.newCachedThreadPool();      public static void main(String[] args) throws IOException {      ServerSocket serverSocket = new ServerSocket(8080);      System.out.println("服務器啟動成功");      while (!serverSocket.isClosed()) {        Socket request = serverSocket.accept();        System.out.println("收到新連接:" + request.toString());          // 多線程接收多個連接        executorService.submit(            () -> {              try {                InputStream inputStream = request.getInputStream();                BufferedReader bufferedReader =                    new BufferedReader(new InputStreamReader(inputStream, "utf-8"));                String message;                while ((message = bufferedReader.readLine()) != null) {                  if (message.length() == 0) {                    break;                  }                  // 拿到消息後可以解析消息拿到請求方法,請求數據等內容                  System.out.println("消息內容為:" + message);                }                System.out.println("收到數據,來自:" + request.toString());                  // 根據HTTP協議響應數據包返回數據給瀏覽器                OutputStream outputStream = request.getOutputStream();                outputStream.write("HTTP/1.1 200 OKrn".getBytes());                outputStream.write("Content-Length: 11rnrn".getBytes());                outputStream.write("Hello World".getBytes());                outputStream.flush();                } catch (IOException e) {                e.printStackTrace();              } finally {                try {                  request.close();                } catch (IOException e) {                  e.printStackTrace();                }              }            });      }      serverSocket.close();    }  }  

以上就是Java BIO網絡編程的基本內容,對於BIO來說一個請求對應一個線程,上下文切換佔用的資源很重,同時由於大量並發情況下,其他接入的消息,只能一直等待,而目前對於性能,響應速度等的卻要求越老越高,BIO網絡編程使用的已經越來越少。使用的比較多的是Java NIO網絡編程,該部分內容我們將在下一部分繼續。