偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

Netty學習基礎:BIO、NIO、AIO

開發(fā) 架構
Netty是一個提供異步事件驅(qū)動的網(wǎng)絡應用程序框架,用以快速開發(fā)高性能、高可靠的網(wǎng)絡服務器和客戶端程序。

其實我的重點呢,是來和大家一起學習接下來的Netty篇。

然而嘞,這個Netty又不太合適直接講,為啥呢,我們學習一門技術必須知道這門技術的由來的初衷是啥,對吧。

先來給大家簡單的介紹一下Netty是什么

Netty是一個提供異步事件驅(qū)動的網(wǎng)絡應用程序框架,用以快速開發(fā)高性能、高可靠的網(wǎng)絡服務器和客戶端程序。

Netty簡化了網(wǎng)絡程序的開發(fā),屬于BIO、NIO、AIO的演變中的產(chǎn)物,屬于一種NIO框架。

在我們平時使用的很多中間件中,很多底層通信都是采用的Netty,比如rocketmq、dubbo,這些我們最常見的底層通信都是用的netty,足以可見這個的性能是多么的優(yōu)秀了。

ok,接下來再來理解一下同步、異步、阻塞、非阻塞這四個概念。

從簡單的開始,我們以經(jīng)典的讀取文件的模型舉例。(對操作系統(tǒng)而言,所有的輸入輸出設備都被抽象成文件。)

在發(fā)起讀取文件的請求時,應用層會調(diào)用系統(tǒng)內(nèi)核的I/O接口。

阻塞和非阻塞

如果應用層調(diào)用的是阻塞型I/O,那么在調(diào)用之后,應用層即刻被掛起,一處于等待數(shù)據(jù)返回的狀態(tài),直到系統(tǒng)內(nèi)核從磁盤讀取完數(shù)據(jù)并返回給應用層,應用層才用獲得的數(shù)據(jù)進行接下來的其他操作。

如果應用層調(diào)用的是非阻塞I/O,那么調(diào)用后,系統(tǒng)內(nèi)核會立即返回(雖然還沒有文件內(nèi)容的數(shù)據(jù)),應用層并不會被掛起,它可以做其他任意它想做的操作。(至于文件內(nèi)容數(shù)據(jù)如何返回給應用層,這已經(jīng)超出了阻塞和非阻塞的辨別范疇。)

這便是(脫離同步和異步來說之后)阻塞和非阻塞的區(qū)別??偨Y(jié)來說,是否是阻塞還是非阻塞,關注的是接口調(diào)用(發(fā)出請求)后等待數(shù)據(jù)返回時的狀態(tài)。被掛起無法執(zhí)行其他操作的則是阻塞型的,可以被立即「抽離」去完成其他「任務」的則是非阻塞型的。

同步和異步

阻塞和非阻塞解決了應用層等待數(shù)據(jù)返回時的狀態(tài)問題,那系統(tǒng)內(nèi)核獲取到的數(shù)據(jù)到底如何返回給應用層呢?這里不同類型的操作便體現(xiàn)的是同步和異步的區(qū)別。

對于同步型的調(diào)用,應用層需要自己去向系統(tǒng)內(nèi)核問詢,如果數(shù)據(jù)還未讀取完畢,那此時讀取文件的任務還未完成,應用層根據(jù)其阻塞和非阻塞的劃分,或掛起或去做其他事情(所以同步和異步并不決定其等待數(shù)據(jù)返回時的狀態(tài));如果數(shù)據(jù)已經(jīng)讀取完畢,那此時系統(tǒng)內(nèi)核將數(shù)據(jù)返回給應用層,應用層即可以用取得的數(shù)據(jù)做其他相關的事情。

而對于異步型的調(diào)用,應用層無需主動向系統(tǒng)內(nèi)核問詢,在系統(tǒng)內(nèi)核讀取完文件數(shù)據(jù)之后,會主動通知應用層數(shù)據(jù)已經(jīng)讀取完畢,此時應用層即可以接收系統(tǒng)內(nèi)核返回過來的數(shù)據(jù),再做其他事情。

這便是(脫離阻塞和非阻塞來說之后)同步和異步的區(qū)別。也就是說,是否是同步還是異步,關注的是任務完成時消息通知的方式。由調(diào)用方盲目主動問詢的方式是同步調(diào)用,由被調(diào)用方主動通知調(diào)用方任務已完成的方式是異步調(diào)用。

上面這幾個概念大家一定要搞懂,這是基礎,必須好好理解上面這些,才能真正理解netty的出處,這也是面試常被問到的點之一。

總結(jié)一下

阻塞和非阻塞,關注的是發(fā)起請求之后等待數(shù)據(jù)返回時的狀態(tài),被掛起無法執(zhí)行其他操作的是阻塞型的,可以立即去進行其他作業(yè)的是非阻塞型的。

同步和異步,關注的是任務完成時的消息通知的方式,由調(diào)用方主動去詢問的方式屬于同步調(diào)用,而被調(diào)用方主動通知調(diào)用方該任務已完成的方式屬于異步調(diào)用。

這個在網(wǎng)上最常見的一個例子就是燒水的例子了,我也繼續(xù)給大家啰嗦一下咯。

老王燒水,老王把水放在爐子上,在這里干等著,啥也沒有去做,并且需要隨時看著水是否開了,這叫阻塞同步,阻塞是因為老王啥也不能去做,同步是因為水開他得自己看著。

老王后來學精了,不在這里傻等著了,把水放在爐子上之后,然后就去開了一把緊張又刺激的lol手游,這叫非阻塞同步,非阻塞是因為老王在等水期間自己打游戲了,同步是因為水開他還是得自己看著。

后來,老王覺得自己看著水太麻煩了,于是買了個升級版的水壺,牛了啊,這個水壺把水煮開了之后,會吹哨,哎。

老王不需要每隔幾分鐘就去看一眼水是否開了,只需要聽這個哨聲即可,做水期間可以打游戲,并且水開了還會主動通知老王,這就是異步非阻塞,非阻塞就是因為老王可以去玩游戲,異步就是水壺的那個哨子。

這下大家應該很好理解了吧!

接下來繼續(xù)看BIO、NIO、AIO

Socket 網(wǎng)絡通信過程簡單來說分為下面 4 步:

  1. 建立服務端并且監(jiān)聽客戶端請求。
  2. 客戶端請求,服務端和客戶端建立連接。
  3. 兩端之間可以傳遞數(shù)據(jù)。
  4. 關閉資源。

傳統(tǒng)的阻塞式通信BIO流程

BIO就是屬于最傳統(tǒng)的一種阻塞同步的通信方式,也是屬于最簡單的一種,使用起來比較方便,但是處理并發(fā)能力低,通信比較耗時。

服務器會通過一個線程負責監(jiān)聽客戶端請求和為每一個客戶端創(chuàng)建一個新的線程進行鏈路的處理,屬于一種典型的請求應答模式,若客戶端數(shù)量增加,則需要頻繁的創(chuàng)建和銷毀線程,會給服務器增加很大的壓力。

服務器提供IP地址和監(jiān)聽的端口,客戶端通過TCP的三次握手和服務器建立連接通信,連接成功之后,雙方進行通過,之后通過四次揮手進行斷開連接。

即使用線程池的方式來改進新增加線程,這也是屬于一種偽異步IO,這樣實現(xiàn)能夠為少數(shù)的客戶端提供服務,如果客戶端并發(fā)量足夠多,還是會因為線程池滿導致OOM的問題。

給大家看一個簡單的Demon

public class SocketServer {
public static void main(String[] args) throws IOException {
SocketServer socketServer = new SocketServer();
socketServer.start(9000);
}
public void start(int port) {
//1.創(chuàng)建 ServerSocket 對象并且綁定一個端口
try (ServerSocket server = new ServerSocket(port);) {
System.out.println("server start");
Socket socket;
//2.通過 accept()方法監(jiān)聽客戶端請求, 這個方法會一直阻塞到有一個連接建立
while ((socket = server.accept()) != null) {
System.out.println("client connected");
try (ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());
ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream())) {
//3.通過輸入流讀取客戶端發(fā)送的請求信息
String message = (String) objectInputStream.readObject();
System.out.println("server receive message:" + message);
//4.通過輸出流向客戶端發(fā)送響應信息
objectOutputStream.writeObject(message);
objectOutputStream.flush();
} catch (IOException | ClassNotFoundException e) {
System.out.println("occur exception:");
}
}
} catch (IOException e) {
System.out.println("occur IOException:");
}
}
}

這是服務端的代碼:

public class Client {
public Object send(String message, String host, int port) {
//1. 創(chuàng)建Socket對象并且指定服務器的地址和端口號
try (Socket socket = new Socket(host, port)) {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
//2.通過輸出流向服務器端發(fā)送請求信息
objectOutputStream.writeObject(message);
//3.通過輸入流獲取服務器響應的信息
ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());
return objectInputStream.readObject();
} catch (ClassNotFoundException | IOException e) {
System.out.println("occur exception:");
}
return null;
}
public static void main(String[] args) {
Client helloClient = new Client();
helloClient.send("content from client", "127.0.0.1", 9000);
System.out.println("發(fā)送數(shù)據(jù)成功");
}
}

這是客戶端的代碼,我們接下來先運行服務器,再運行客戶端,看效果。

。

服務器啟動之后,便會一直阻塞在這里,等待客戶端的連接處理。

接著我們啟動客戶端,然后看到發(fā)送數(shù)據(jù)成功,此時我們再切換到服務器的控制臺,看下效果。

我們也可以通過命令行直接執(zhí)行telnet localhost 9000去連接服務端,效果如下:

從上面例子看出的問題

我們看到服務器和客戶端成功的進行通信了,也就是這段服務器的代碼只能同時為一個客戶端服務,當然有改進方法,我們監(jiān)聽到連接之后,就立刻new Thread().start()創(chuàng)建一個線程用于這個客戶端接下來的處理。

這也就意味著,每一個客戶端都要建立一個線程為其處理,如果客戶端數(shù)量很多,或者說客戶端處理很慢,那就很糟糕了。

我們從線程文章中也介紹過線程是一個很寶貴的資源,我們需要合理的利用這些資源,需要根據(jù)機器的性能去合理的控制線程的數(shù)量。

即使線程池可以優(yōu)化上面的例子,讓線程創(chuàng)建和銷毀的成本降低,我們也可以執(zhí)行線程池的最大數(shù)量,控制線程資源的使用,但是,即使如何改進,我們并沒有從根本上解決這個問題,根本上還是屬于BIO,也就是同步阻塞IO的模式。

NIO

同步非阻塞模型,在JDK1.4中引入了NIO的框架,NIO 中的 N 可以理解為 Non-blocking,NIO是面向緩沖Buffer的,基于通道Channel的操作。

NIO提供了和傳統(tǒng)BIO模型中的ServerSocket和Socket相對應的ServerSocketChannel和SocketChannel兩種不同的套接字通道,對應服務端和客戶端。

兩種通道都支持阻塞和非阻塞的模式。

阻塞模式一般不會被使用,既然使用了阻塞,那就意味著使用起來就像上面的BIO一樣了,性能和可靠性都不是很好。

非阻塞模式,對于高負載和高并發(fā)的網(wǎng)絡應用是很友好的,后續(xù)我們要說的Netty就是基于這個改進的。

NIO 相對于BIO來說一大進步??蛻舳撕头掌髦g通過Channel通信。NIO可以在Channel進行讀寫操作。這些Channel都會被注冊在Selector多路復用器上。Selector通過一個線程不停的輪詢這些Channel。找出已經(jīng)準備就緒的Channel執(zhí)行IO操作。

NIO 通過一個線程輪詢,實現(xiàn)千萬個客戶端的請求,這就是非阻塞NIO的特點。

NIO核心組件

Channel:和流不同,通道是雙向的。NIO可以通過Channel進行數(shù)據(jù)的讀,寫和同時讀寫操作。通道分為兩大類:一類是網(wǎng)絡讀寫(SelectableChannel),一類是用于文件操作(FileChannel),我們使用的SocketChannel和ServerSocketChannel都是SelectableChannel的子類。

Buffer:它是NIO與BIO的一個重要區(qū)別。BIO是將數(shù)據(jù)直接寫入或讀取到Stream對象中。而NIO的數(shù)據(jù)操作都是在緩沖區(qū)中進行的。緩沖區(qū)實際上是一個數(shù)組。

Selector和Selection Key:多路復用器提供選擇已經(jīng)就緒的任務的能力。就是Selector會不斷地輪詢注冊在其上的通道(Channel),如果某個通道處于就緒狀態(tài),會被Selector輪詢出來,然后通過SelectionKey可以取得就緒的Channel集合,從而進行后續(xù)的IO操作。服務器端只要提供一個線程負責Selector的輪詢,就可以接入成千上萬個客戶端。

接下來我們看使用的例子:

public class NioServer {
static List<SocketChannel> channelList = new ArrayList<>();
public static void main(String[] args) throws IOException {
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket().bind(new InetSocketAddress(9000));
//設置serverSocketChannel為非阻塞
serverSocketChannel.configureBlocking(false);
System.out.println("服務器啟動成功");
while (true){
//非阻塞模式的accept不會阻塞,否則會阻塞
//NIO的非阻塞是由操作系統(tǒng)實現(xiàn)的,底層調(diào)用了Linux內(nèi)核的accept函數(shù)
SocketChannel socketChannel = serverSocketChannel.accept();
if(socketChannel != null){ //此時有客戶端連接
System.out.println("有客戶端連接");
socketChannel.configureBlocking(false);
channelList.add(socketChannel);
}
//遍歷
Iterator<SocketChannel> iterator = channelList.iterator();
while (iterator.hasNext()){
SocketChannel channel = iterator.next();
ByteBuffer byteBuffer = ByteBuffer.allocate(128);
int read = channel.read(byteBuffer);
if(read > 0){
System.out.println("接收到消息:" + new String(byteBuffer.array()));
}else if(read == -1){
iterator.remove();
System.out.println("客戶端斷開連接");
}
}
}
}
}

這里我們只寫了服務端的代碼,客戶端就通過telnet來模擬就行了。

我們用debug的模式看下服務端。

啟動成功之后,發(fā)現(xiàn)NIO模式下竟然沒有在accept函數(shù)這里阻塞,而是直接執(zhí)行過去了。

NIO優(yōu)點

NIO最大的優(yōu)點,就是引入了IO多路復用機制,使得一個服務器可以同時為大量的客戶端提供服務的同時,效率也不會低,而這個IO多路復用這里,經(jīng)常遇到的一個面試題就是select、poll、epoll的區(qū)別,這個我會單獨開一篇給大家說清楚,這一篇放不下了。

NIO存在的問題

NIO跨平臺和兼容性問題

使用NIO的時候需要考慮Linux平臺和Windows平臺的兼容性問題,如果該程序運行在多個平臺,則需要考慮測試多個平臺。

NIO2看起來很理想,但是NIO2只支持Jdk1.7+,若你的程序在Java1.6上運行,則無法使用NIO2。另外,Java7的NIO2中沒有提供DatagramSocket的支持,所以NIO2只支持TCP程序,不支持UDP程序。

NIO對緩沖區(qū)的聚合和分散操作可能會導致內(nèi)存泄露

很多Channel的實現(xiàn)支持Gather和Scatter。這個功能允許從從多個ByteBuffer中讀入或?qū)懭?,這樣做可以有更好的性能。

例如,你可能希望header在一個ByteBuffer中,而body在另外的ByteBuffer中。

下圖顯示的是Scatter(分散),將ScatteringByteBuffer中的數(shù)據(jù)分散讀取到多個ByteBuffer中:

下圖顯示的是Gather(聚合),將多個ByteBuffer的數(shù)據(jù)寫入到GatheringByteChannel:

可惜Gather/Scatter功能會導致內(nèi)存泄露,知道Java7才解決內(nèi)存泄露問題。使用這個功能必須小心編碼和Java版本。

Squashing the famous epoll bug(壓碎著名的epoll bug)

著名的epoll-bug也可能會導致無效的狀態(tài)選擇和100%的CPU利用率。要解決epoll-bug的唯一方法是回收舊的選擇器,將先前注冊的通道實例轉(zhuǎn)移到新創(chuàng)建的選擇器上。

不是十分的清楚這里,感興趣的可以去更深的了解下這里。

還有一個很真實貼切的問題,就是這個對于開發(fā)者來說太不友好了,開發(fā)成本和維護成本都比較高。

AIO

AIO 也就是 NIO 2。在 Java 7 中引入了 NIO 的改進版 NIO 2,它是異步非阻塞的IO模型。

異步 IO 是基于事件和回調(diào)機制實現(xiàn)的,也就是應用操作之后會直接返回,不會堵塞在那里,當后臺處理完成,操作系統(tǒng)會通知相應的線程進行后續(xù)的操作。

AIO 是異步IO的縮寫,雖然 NIO 在網(wǎng)絡操作中,提供了非阻塞的方法,但是 NIO 的 IO 行為還是同步的。對于 NIO 來說,我們的業(yè)務線程是在 IO 操作準備好時,得到通知,接著就由這個線程自行進行 IO 操作,IO操作本身是同步的。

AIO 并沒有采用NIO的多路復用器,而是使用異步通道的概念。其read,write方法的返回類型都是Future對象。

而Future模型是異步的,其核心思想是:去主函數(shù)等待時間。AIO模型中通過AsynchronousSocketChannel和AsynchronousServerSocketChannel完成套接字通道的實現(xiàn)。非阻塞,異步。

責任編輯:姜華 來源: 左耳君
相關推薦

2020-10-10 19:37:27

BIO 、NIO 、A

2020-04-16 15:20:43

PHP前端BIO

2020-10-14 08:50:38

搞懂 Netty 線程

2023-07-11 08:40:02

IO模型后臺

2019-10-18 08:22:43

BIONIOAIO

2021-08-12 18:48:31

響應式編程Bio

2021-06-11 17:26:06

代碼Java網(wǎng)絡編程

2023-06-26 07:39:10

2021-03-04 08:34:55

同步阻塞非阻塞

2011-03-31 10:41:49

BIONIOIO

2025-08-26 02:24:00

JavaI/O模型

2019-12-10 09:20:30

NettyBIO開發(fā)

2011-12-15 11:39:25

JavaNIO

2019-05-05 08:50:42

阻塞非阻塞BIO

2022-12-08 09:10:11

I/O模型Java

2019-04-24 23:49:57

宜人貸蜂巢API網(wǎng)關Netty

2018-09-19 14:53:02

NIOBIO運行

2023-03-07 08:00:12

netpollGo

2024-11-06 16:38:51

IO網(wǎng)絡

2021-12-27 10:20:46

JavaNetty網(wǎng)絡
點贊
收藏

51CTO技術棧公眾號