1. 程式人生 > >Java網路程式設計 -- BIO 阻塞式網路程式設計

Java網路程式設計 -- BIO 阻塞式網路程式設計

阻塞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協議請求資料包解析

HTTP協議響應資料包解析

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 OK\r\n".getBytes());
              outputStream.write("Content-Length: 11\r\n\r\n".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網路程式設計,該部分內容我們將在下一部分繼續。