最近打算再次整理下Netty的相關內容,但是要把Netty弄的比較清楚,我們首先需要對Java中的BIO,NIO及AIO要比較清楚,所以我們前面會花幾篇文章先把這塊的內容整理出來。
JavaBIO編程模型介紹
1.I/O模型介紹
1.1 什麼是I/O模型
簡單的理解:就是用什麼樣的通道進行數據的發送和接收,很大程度上決定了程序通信的性能。java共支持3種網絡編程模型:
- BIO
- NIO
- AIO
1.2 Java BIO
同步並且阻塞(傳統阻塞型),服務器實現模式爲一個連接一個線程,即客戶端有連接請求時服務端需要啓動一個線程進行處理,如果這個連接不做任何事情,會造成不必要的線程開銷。
1.3 Java NIO
同步非阻塞,服務器實現模式爲一個線程處理多個請求,即客戶端發送的連接請求都會註冊到多路複用器上,多路複用器輪詢到連接有I/O請求就進行處理
1.4 Java AIO
AIO也稱爲NIO2,異步非阻塞,AIO引入異步通道的概念,採用了Proactor模式,簡化了程序的編寫,有效的請求才啓動線程,它的特點是先由操作系統完成後才通知服務端程序啓動線程去處理,一般適用於連接數較多且連接時間較長的應用。
2.BIO、NIO、AIO適用場景介紹
模型 | 場景 |
---|---|
BIO | 方式適用於連接數目比較小且固定的架構,這種方式對服務器資源要求比較高,併發侷限於應用中,JDK1.4以前的唯一選擇,但程序簡單易理解。 |
NIO | 方式適用於連接數目多且連接比較短(輕操作)的架構,比如聊天服務器,彈幕系統,服務器間通訊等。編程比較複雜,JDK1.4開始支持。 |
AIO | 方式使用於連接數目多且連接比較長(重操作)的架構,比如相冊服務器,充分調用OS參與併發操作,編程比較複雜,JDK7開始支持。 |
3.BIO基本介紹
Java BIO 就是傳統的java io 編程,其相關的類和接口在 java.io
BIO(blocking I/O) :同步阻塞
,服務器實現模式爲一個連接一個線程,即客戶端有連接請求時服務器端就需要啓動一個線程進行處理,如果這個連接不做任何事情會造成不必要的線程開銷,可以通過線程池機制改善(實現多個客戶連接服務器)。
BIO方式適用於連接數目比較小且固定的架構,這種方式對服務器資源要求比較高,併發侷限於應用中,JDK1.4以前的唯一選擇,程序簡單易理解
4.BIO工作機制
流程描述:
- 服務器端啓動一個ServerSocket
- 客戶端啓動Socket對服務器進行通信,默認情況下服務器端需要對每個客戶 建立一個線程與之通訊
- 客戶端發出請求後, 先諮詢服務器是否有線程響應,如果沒有則會等待,或者被拒絕
- 如果有響應,客戶端線程會等待請求結束後,在繼續執行
5.BIO應用實例
接下來我們通過一個小的DEMO案例來看看
BioServer
package com.dpb.netty.bio;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @program: netty4demo
* @description:
* @author: 波波烤鴨
* @create: 2019-12-28 10:42
*/
public class BioServer {
public static void main(String[] args) throws Exception {
//線程池機制
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;
}
}
}catch (Exception e) {
e.printStackTrace();
}finally {
System.out.println("關閉和client的連接");
try {
socket.close();
}catch (Exception e) {
e.printStackTrace();
}
}
}
}
BioClient
package com.dpb.netty.bio;
import java.io.OutputStream;
import java.net.Socket;
/**
* @program: netty4demo
* @description:
* @author: 波波烤鴨
* @create: 2019-12-28 10:45
*/
public class BioClient {
public static void main(String[] args) throws Exception{
Socket socket = new Socket("localhost",6666);
OutputStream out = socket.getOutputStream();
out.write("hello ...".getBytes());
out.flush();
out.close();
}
}
執行效果:
6.BIO問題分析
最後我們來分析下BIO所具有的一些問題。
- 每個請求都需要創建獨立的線程,與對應的客戶端進行數據 Read,業務處理,數據 Write 。
- 當併發數較大時,需要創建大量線程來處理連接,系統資源佔用較大。
- 連接建立後,如果當前線程暫時沒有數據可讀,則線程就阻塞在 Read 操作上,造成線程資源浪費