Netty系列3-BIO、AIO、NIO
客戶端和服務端通信本質上就是服務端監聽端口,客戶端發起連接請求,通過三次握手連接,如果連接成功建立,雙方就可以通過套接字socket進行通信。
根據通信實現方式的不同又分為BIO、AIO、NIO三種。
1.BIO
BIO是同步阻塞模型。通常由一個Acceptor線程監聽客戶端的連接,接收到連接請求后為每個客戶端都創建一個新線程進行處理,最后將響應通過輸出流返回給客戶端,線程銷毀。
BIO最大的缺點是并發訪問量增加后,服務端的線程個數和客戶端并發訪問數呈1:1的關系,隨著線程數量快速膨脹,系統性能將急劇下降,當線程達到一定數量,系統宕機。
為了改進這個問題,提出了對線程使用線程池進行管理,這種通常被稱為偽異步I/O模型,并沒有解決問題。當大量高并發的時候,尤其是大量長連接或者讀取數據較慢的時候,線程數量還是急劇增加。使用固定數量線程池,可以解決線程增加的問題,但會導致大量用戶線程等待,系統有瓶頸,不友好。
服務端核心代:
server = new ServerSocket(8080);
while(true){
Socket socket= server.accept();
//當有新的客戶端接入時,投入線程池
executorService.execute(new BioServerHandler(socket));
}
public class BioServerHandler implements Runnable{
private Socket socket;
public BioServerHandler(Socket socket) {
this.socket = socket;
}
public void run() {
try(
BufferedReader in = new BufferedReader(
new InputStreamReader(socket.getInputStream()));
PrintWriter out = new PrintWriter(socket.getOutputStream(),
true)){
String message;
String result;
while((message = in.readLine())!=null){
result = response(message);
out.println(result);
}
}catch(Exception e){
e.printStackTrace();
}finally{
if(socket != null){
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
socket = null;
}
}
}
}
客戶端核心代碼:
Socket socket = new Socket(DEFAULT_SERVER_IP,DEFAULT_PORT);
pw = new PrintWriter(socket.getOutputStream());
pw.println(“hello”);
pw.flush();
2.AIO
AIO是異步非阻塞通信模型,是java在1.7后提供的。本質上就是通過回調函數,直接上代碼。
服務端核心代碼:
public class AioServerHandler implements Runnable {
/*異步通信通道*/
public AsynchronousServerSocketChannel channel;
public AioServerHandler(int port) {
try {
//創建服務端通道
channel = AsynchronousServerSocketChannel.open();
//綁定端口
channel.bind(new InetSocketAddress(8080));
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void run() {
//accept函數有兩個參數,第一個是傳遞給回調函數的附件,第二個就是回調函數,回調函數需要實現 //CompletionHandler<AsynchronousSocketChannel, ? super AioServerHandler>
//CompletionHandler內部有兩個方法,completed函數在連接成功后調用,failed在失敗時候調用,兩個方法的參數 //都有兩個,第一個代表IO操作完成后返回的結果,第二個參數就是我們的附件
channel.accept(this,new AioAcceptHandler());
try {
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class AioAcceptHandler
implements CompletionHandler<AsynchronousSocketChannel,
AioServerHandler> {
@Override
//第一個參數是代碼連接成功后返回的結果,第二個就是我們傳遞的附件
public void completed(AsynchronousSocketChannel channel,
AioServerHandler serverHandler) {
//重新注冊監聽,讓別的客戶端也可以連接
serverHandler.channel.accept(serverHandler,this);
ByteBuffer readBuffer = ByteBuffer.allocate(1024);
//這個是讀事件發生的時候,又有另外一個回調,讀事件的回調函數不貼了
channel.read(readBuffer,readBuffer,
new AioReadHandler(channel));
}
@Override
//第一個參數是連接失敗返回的結果,這里是個異常
//第二個參數是我們傳遞的附件
public void failed(Throwable exc, AioServerHandler serverHandler) {
exc.printStackTrace();
serverHandler.latch.countDown();
}
客戶端核心代碼:
public class AioClientHandler
implements CompletionHandler<Void,AioClientHandler>,Runnable {
private AsynchronousSocketChannel clientChannel;
private String host;
private int port;
public AioClientHandler(String host, int port) {
this.host = host;
this.port = port;
try {
//創建客戶端通道
clientChannel = AsynchronousSocketChannel.open();
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void run() {
//這里的連接成功后回調函數就是自己
clientChannel.connect(new InetSocketAddress(host,port),
null,this);
try {
clientChannel.close();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
//連接成功回調方法
@Override
public void completed(Void result, AioClientHandler attachment) {
}
//連接失敗回調方法
@Override
public void failed(Throwable exc, AioClientHandler attachment) {
}
}
3.NIO
3.1 Reactor模式
說起NIO不得不先說下Reactor模式。
(1)Reactor模式定義
Reactor模式是事件驅動模型,有一個或多個并發輸入源,一個Service Handler和多個Request Handlers,Service Handler同步的將輸入的請求(Event)以多路復用的方式,并且根據Event類型分發給相應的Request Handler。
(2)Reactor模式元素
- EventHandler:事件處理器
- Handle:操作系統中的句柄,是對資源在操作系統層面上的一種抽象,它可以是打開的文件、一個連接等。在網絡編程中一般指Socket Handle,即一個網絡連接(在Java NIO中的Channel)。這個Channel注冊到Synchronous Event Demultiplexer中,以監聽Handle中發生的事件,可以是CONNECT、READ、WRITE、CLOSE等事件
- InitiationDispatcher:事件處理調度器,用來管理EventHandler,將接收到網絡請求分發給相應的處理器去異步處理
- Demultiplexer:阻塞等待一系列的Handle中的事件到來,在Java NIO中用Selector來封裝。
(3)Reactor模式請求處理流程
- 初始化InitiationDispatcher,并初始化一個Map,用來放Handle和EventHandler的映射。
- 注冊EventHandler到InitiationDispatcher中,把EventHandler和對應Handle放入到map中
- 調用InitiationDispatcher的handle_events()方法以啟動Event Loop。在Event Loop中,調用Synchronous Event Demultiplexer的select()方法阻塞等待Event發生。
- 當Event發生后,select()方法返回,InitiationDispatcher根據返回的Handle找到對應的EventHandler,并回調該EventHandler的handle_events()方法,在handle_events()方法中還可以向InitiationDispatcher中注冊新的Eventhandler
3.2 Reactor模式的java實現
(1)單線程Reactor模式
這里的單線程指的是服務端的Reactor是單線程
- 服務器端的Reactor使用Selector來實現多路復用,并且啟動事件循環。服務端會注冊一個Acceptor事件處理器到Reactor中,這樣Reactor會監聽客戶端向服務器端發起的連接請求事件(ACCEPT事件)。
- 客戶端向服務器發起一個連接請求,Reactor監聽到了該ACCEPT事件并將該事件派發給對應的Acceptor處理器。Acceptor處理器通過accept()方法得到連接(SocketChannel),然后將該連接所關注的READ事件以及對應的事件處理器注冊到Reactor中,這樣Reactor就會監聽該連接的READ事件。
- 當Reactor監聽到有讀或者寫事件發生時,將相關的事件派發給對應的處理器進行處理。
- 處理完所有就緒的感興趣的I/O事件后,Reactor線程會再次執行select()阻塞等待新的事件就緒并將其分派給對應處理器進行處理。
Reactor的單線程主要是針對于I/O操作而言,也就是accept()、read()、write()以及connect()操作都在一個線程上完成。
但這里單線程Reactor模式中,不僅I/O操作在該Reactor線程上,連非I/O的業務操作也在該線程上,這會大大延遲I/O請求的響應。
所以出現了第二種模式單線程Reactor,工作者線程池。
(2)單線程Reactor,工作者線程池
與單線程Reactor模式不同的是,添加了一個工作者線程池,并將非I/O操作從Reactor線程中移出轉交給工作者線程池。這樣可以提高Reactor線程的I/O響應,不至于因為一些耗時的業務邏輯而延遲對后面I/O請求的處理。
線程池的優勢:
- 重用現線程,減少線程創建和銷毀過程的開銷。
- 當請求到達時,不會由于等待創建線程,提高了響應性。
- 合理調整線程池的大小,可以創建足夠多的線程使處理器保持忙碌狀態,還可防止過多線程耗盡內存。
缺點:
I/O操作還是一個Reactor來完成,對于高負載、大并發或大數據量的應用場景有性能瓶頸: - 一個NIO線程同時處理成百上千的鏈路,性能上無法支撐
- 當NIO線程負載過重時處理速度將變慢,這會導致大量客戶端連接超時,超時之后往往會進行重發,這更加重了NIO線程的負載,最終會導致大量消息積壓和處理超時
這就出現了第三種模式:多Reactor線程模式
(3)多Reactor線程模式
多Reactor線程模式中有一個mainReactor,多個subReactor。Reactor線程池中的每一Reactor線程都會有自己的Selector、線程和分發的事件循環邏輯。mainReactor線程主要負責接收客戶端的連接請求,然后將接收到的SocketChannel傳遞給subReactor,由subReactor來完成和客戶端的通信。
流程:
- 注冊一個Acceptor事件處理器到mainReactor中,啟動mainReactor的事件循環。
- 客戶端向服務器端發起一個連接請求,mainReactor監聽到了該ACCEPT事件并將該事件派發給Acceptor處理器
- Acceptor處理器通過accept()方法得到與這個客戶端對應的連接(SocketChannel),然后將這個SocketChannel傳遞給subReactor線程池
- subReactor線程池分配一個subReactor線程給這個SocketChannel,將SocketChannel關注的READ事件或者以及對應的事件處理器注冊到subReactor線程
- 當有I/O事件就緒時,相關的subReactor就將事件派發給響應的處理器。這里subReactor線程只負責完成I/O的read()操作,在讀取到數據后將業務邏輯的處理放入到線程池中完成,若完成業務邏輯后需要返回數據給客戶端,則相關的I/O的write操作還是會被提交回subReactor線程
多Reactor線程模式將接受客戶端的連接請求和與該客戶端的通信分在了兩個Reactor線程來完成。mainReactor完成接收客戶端連接請求的操作,將建立好的連接轉交給subReactor線程,subReactor線程完成與客戶端的通信。這里所有的I/O操作(accept()、read()、write()、connect())還是在Reactor線程(mainReactor線程 或 subReactor線程)中完成的。Thread Pool(線程池)僅用來處理非I/O操作的邏輯。
優點:
多Reactor線程模式在大量并發請求的情況下,將大量連接分發給多個subReactor線程,在多核的操作系統中這能大大提升應用的負載和吞吐量。
同時不會因為read()數據耗時而導致后面的客戶端連接請求得不到即時處理。
Netty服務端使用了多Reactor線程模式
上述分析完后你會發現reactor模式與觀察者模式有點像。不過,觀察者模式與單個事件源關聯,而反應器模式則與多個事件源關聯 。當一個主體發生改變時,所有依屬體都得到通知。
3.3 Selector、Channels、SelectionKey
(1)Selector
Selector也就是NIO中的選擇器,用做事件訂閱和Channel管理。
應用程序向Selector注冊需要它關注的Channel,以及具每一個Channel感興趣的IO事件。
(2)Channels
通道,應用程序和操作系統通信的渠道。應用程序可以通過通道讀寫數據。所有在Selector注冊的通道必須繼承SelectableChannel類
- ServerSocketChannel:服務器程序的監聽通道,只能通過這個通道向操作系統注冊支持多路復用IO的端口監聽。可以支持UDP和TCP
- ScoketChannel:TCPSocket套接字的監聽通道,一個Socket套接字對應了一個客戶端
- DatagramChannel:UDP數據報文的監聽通道。
(3)SelectionKey
SelectionKey是NIO中的操作類型。一共四種操作類型:OP_READ(讀)、OP_WRITE(寫)、OP_CONNECT(請求連接)、OP_ACCEPT(接受連接)。 - ServerSocketChannel:可以注冊OP_ACCEPT
- 服務器SocketChannel:OP_READ、OP_WRITE
- 客戶端SocketChannel:OP_READ、OP_WRITE、OP_CONNECT
每個操作類型就緒條件:
- OP_READ: 當操作系統讀緩沖區有數據可讀時就緒。
- OP_WRITE:當操作系統寫緩沖區有空閑空間時就緒。一般情況下寫緩沖區都有空閑空間,小塊數據無需注冊,直接寫入即可,否則該條件不斷就緒浪費CPU。但如果是寫密集型的任務,有可能寫滿緩存,這時需要注冊,并且寫完后取消注冊。
- OP_CONNECT: 請求連接成功后就緒。
- OP_ACCEPT 當收到客戶端連接請求時就緒。
智能推薦
BIO、NIO、AIO
一:BIO 1、網絡編程的基本模型是C/S模型,即兩個進程間的通信。 2、服務端提供IP和監聽端口,客戶端通過連接操作想服務端監聽的地址發起連接請求,通過三次握手連接,如果連接成功建立,雙方就可以通過套接字進行通信。 3、傳統的同步阻塞模型開發中,ServerSocket負責綁定IP地址,啟動監聽端口;Socket負責發起連接操作。連接成功后,雙方通過輸入和輸出流進行同步阻塞式通信。 ...
BIO,NIO,AIO總結
BIO,NIO,AIO總結 通過對Linux系統的網絡IO模型映射到java的IO實現.從而解釋為什么BIO是同步阻塞,NIO是同步非阻塞,AIO是異步. Linux系統I/O基礎 Linux系統用戶態與內核態 系統內存分為: 用戶態內存和內核態內存 如上圖所示,從宏觀上來看,Linux操作系統的體系架構分為用戶態和內核態(或者用戶空間和內核)。內核從本質上看是一種軟件——...
BIO、NIO、AIO 講解
一、前沿 在通信框架中經常使用到的三種通信模式,即 BIO、NIO 和 AIO,它們也是面試中經常被問到的,如果學會了它們將會給你帶來薪資的變化哦。下面分別對三者介紹一下,通過示例理解其用法 下面先通過一張圖來簡單了解一下三者,如下所示: 同步阻塞IO : 用戶進程發起一個IO操作以后,必須等待IO操作的真正完成后,才能繼續運行 同步非阻塞IO: 用戶進程發起一個IO操作以后,可做其它事情,但用戶...
BIO, NIO, AIO
同步,異步,阻塞,非阻塞 同步:a 事件必須等到 b 事件完成才可以繼續執行/返回 異步:a 事件可以先執行/返回,不需要等待 b 事件的完成,而是通過回調處理 b 事件的返回結果 阻塞:當發起一次請求時,調用者一直等待結果的返回,只有當條件滿足時,才繼續處理后續的工作 非阻塞:當發起一次請求時,調用者不用一直等待結果的返回,可以先去做其他的事情 1、BIO(Blocking IO) 同步阻塞 i...
猜你喜歡
freemarker + ItextRender 根據模板生成PDF文件
1. 制作模板 2. 獲取模板,并將所獲取的數據加載生成html文件 2. 生成PDF文件 其中由兩個地方需要注意,都是關于獲取文件路徑的問題,由于項目部署的時候是打包成jar包形式,所以在開發過程中時直接安照傳統的獲取方法沒有一點文件,但是當打包后部署,總是出錯。于是參考網上文章,先將文件讀出來到項目的臨時目錄下,然后再按正常方式加載該臨時文件; 還有一個問題至今沒有解決,就是關于生成PDF文件...
電腦空間不夠了?教你一個小秒招快速清理 Docker 占用的磁盤空間!
Docker 很占用空間,每當我們運行容器、拉取鏡像、部署應用、構建自己的鏡像時,我們的磁盤空間會被大量占用。 如果你也被這個問題所困擾,咱們就一起看一下 Docker 是如何使用磁盤空間的,以及如何回收。 docker 占用的空間可以通過下面的命令查看: TYPE 列出了docker 使用磁盤的 4 種類型: Images:所有鏡像占用的空間,包括拉取下來的鏡像,和本地構建的。 Con...
requests實現全自動PPT模板
http://www.1ppt.com/moban/ 可以免費的下載PPT模板,當然如果要人工一個個下,還是挺麻煩的,我們可以利用requests輕松下載 訪問這個主頁,我們可以看到下面的樣式 點每一個PPT模板的圖片,我們可以進入到詳細的信息頁面,翻到下面,我們可以看到對應的下載地址 點擊這個下載的按鈕,我們便可以下載對應的PPT壓縮包 那我們就開始做吧 首先,查看網頁的源代碼,我們可以看到每一...