本文闡述了socket編程、IO網路模型,以及各種IO模型的適用場景。 RPC架構設計 文章更新曆史 2022/03/01 初稿。 2022/05/04 修改相關描述。 socket socket網路編程 socket概述 socket套接字是兩台主機之間邏輯連接的端點。 TCP/IP協議是傳輸層協 ...
本文闡述了socket編程、IO網路模型,以及各種IO模型的適用場景。
RPC架構設計
文章更新曆史
2022/03/01 初稿。
2022/05/04 修改相關描述。
socket
socket網路編程
socket概述
socket套接字是兩台主機之間邏輯連接的端點。
TCP/IP協議是傳輸層協議,主要解決數據在網路中的傳輸
socket是網路通信之間的抽象介面,它包含網路通信的五種基礎信息:連接使用的協議、本地主機的ip地址、本地進程協議埠、遠程主機ip地址、遠程進程的協議埠。
socket整體流程
socket編程主要包括客戶端和服務端兩個方面。
首先在服務端創建一個服務端套接字(ServerSocket),並把它附加到一個埠上,服務端從這個埠監聽鏈接。
埠範圍是 0-65536
,註意 0-1024
是特權服務保留的埠。可選擇任意一個不被其他進程使用的埠。
客戶端請求與服務端連接時,根據服務端的功能變數名稱或者ip地址,加上埠號,打開一個套接字。當伺服器接受連接後,伺服器和客戶端之間的操作可以像輸入輸出流一樣操作。
代碼實現
-
服務端代碼
/** * 服務端 * * @name: ServerDemo * @author: terwer * @date: 2022-04-17 14:20 **/ public class ServerDemo { public static void main(String[] args) throws IOException { // 1.創建一個線程池,如果有客戶端鏈接就創建一個線程與之通信 ExecutorService executorService = Executors.newCachedThreadPool(); // 2.創建ServerSocket ServerSocket serverSocket = new ServerSocket(9999); System.out.println("伺服器已啟動"); while (true) { // 3.監聽客戶端 Socket socket = serverSocket.accept(); System.out.println("有客戶端鏈接"); executorService.execute(new Runnable() { @Override public void run() { handle(socket); } }); } } private static void handle(Socket socket) { try { System.out.println("線程ID:" + Thread.currentThread().getId() + ",線程名稱:" + Thread.currentThread().getName()); // 從連接中取出輸入流 InputStream inputStream = socket.getInputStream(); byte[] b = new byte[1024]; int read = inputStream.read(b); System.out.println("客戶端" + new String(b, 0, read)); // 鏈接中取出輸出流並回話 OutputStream outputStream = socket.getOutputStream(); outputStream.write("沒有".getBytes()); } catch (Exception e) { e.printStackTrace(); } finally { try { socket.close(); } catch (IOException e) { e.printStackTrace(); } } } }
-
客戶端代碼
/** * 客戶端 * * @name: ClientDemo * @author: terwer * @date: 2022-04-17 15:30 **/ public class ClientDemo { public static void main(String[] args) throws IOException { while (true) { // 1.創建客戶端socket Socket s = new Socket("127.0.0.1", 9999); // 2.從連接中獲取輸出流併發送消息 OutputStream os = s.getOutputStream(); System.out.println("請輸入:"); Scanner sc = new Scanner(System.in); String msg = sc.nextLine(); os.write(msg.getBytes()); // 3.從連接中取出輸入流並接受會話 InputStream is = s.getInputStream(); byte[] b = new byte[1024]; // 下麵寫法錯了 // int read = is.read(); // 應該是 int read = is.read(b); System.out.println("老闆說:" + new String(b, 0, read).trim()); s.close(); } } }
IO模型
IO模型說明
-
簡單理解:用什麼樣的通道進行數據的發送和接收。在很大程度上決定了程式通信的性能。
-
Java支持三種網路編程模型I/O模式:BIO(同步阻塞)、NIO(同步非阻塞)、AIO(非同步非阻塞)
阻塞與非阻塞
指的是網路IO的線程是否處於阻塞或者等待狀態
線程訪問資源,該資源是否準備就緒的一種處理方式
同步和非同步
指的是數據的請求方式,同步和非同步是請求數據的一種方式。
BIO(同步阻塞)
Java BIO就是傳統的socket編程
BIO(blocking IO):同步阻塞,伺服器實現方式為一個鏈接一個線程,即客戶端有一個鏈接時,服務端就要啟動一個線程進行處理,如果這個鏈接不作任何事情,會造成不必要的線程開銷,可以通過線程池改善,實現多個客戶端鏈接伺服器。
工作機制
生活中的例子:
BIO的問題分析
- 每個請求都要創建獨立的線程,與對應的客戶端進行read,業務處理,數據write
- 併發量大的時候,需要創建大量的線程來處理鏈接,系統資源占用較大
- 鏈接建立後,如果當前線程沒有數據可讀,線程就阻塞在read上,造成線程資源浪費
NIO(同步非阻塞)
同步非阻塞,伺服器實現模式為一個線程處理多個請求(鏈接),客戶端發送的鏈接請求會註冊到多路復用器上,多路復用器輪訓到鏈接有IO請求就進行處理。
生活中的例子:
AIO(非同步非阻塞)
AIO引入了非同步通道的概念,採用了Proactor模式,簡化了程式編寫,有效的請求才啟動線程。
他的特點是先由操作系統完成後才通知服務端啟動線程去處理,一般適用於連接數較多且連接時間較長的應用。
Proactor模式是一個消息非同步通知的模式,Proactor通知的不是就緒事件,而是操作完成事件,這也是操作系統非同步IO的主要模型。
https://www.zhihu.com/question/26943938
生活中的例子:
BIO、NIO、AIO的適用場景分析
- BIO(同步阻塞模式)適用於連接數比較小,且固定的架構。對伺服器資源的要求比較高,併發局限於應用中,jdk1.4以前的唯一選擇,代碼容易理解。
- NIO(同步非阻塞模式)適用於鏈接數目多且鏈接比較短(輕操作)的架構,比如聊天伺服器、彈幕系統、伺服器之間的通訊等。編程比較複雜,jdk1.4開始支持。
- AIO(非同步非阻塞模式)適用於連接數目比較多並且連接時間比較長(重操作)的架構,比如相冊伺服器,充分調用OS參與併發操作。編程比較複雜,jdk1.7開始支持。