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

原來(lái)你是這樣的 IO 模型

開(kāi)發(fā) 前端
在網(wǎng)絡(luò)通信中,客戶端和服務(wù)端通過(guò)一個(gè)雙向的通信連接實(shí)現(xiàn)數(shù)據(jù)的交換,連接的任意一端都可稱為一個(gè) Socket。

在學(xué)習(xí) Netty 框架前有一個(gè)話題是無(wú)法繞過(guò)的,就是:網(wǎng)絡(luò)編程 IO 模型,聽(tīng)見(jiàn) IO 模型有些同學(xué)就開(kāi)始背八股文了,Java 常見(jiàn) IO 模型有:

  • 同步阻塞 BIO
  • 同步非阻塞 NIO
  • 異步非阻塞 AIO

今天跟大家一起重溫下這些知識(shí)點(diǎn)。

Socket 網(wǎng)絡(luò)編程

網(wǎng)絡(luò)編程中有一個(gè)重要的概念就是:Socket,我們簡(jiǎn)單了解一下。

在網(wǎng)絡(luò)通信中,客戶端和服務(wù)端通過(guò)一個(gè)雙向的通信連接實(shí)現(xiàn)數(shù)據(jù)的交換,連接的任意一端都可稱為一個(gè) Socket。

Talk is cheap, show me the diagram,Socket 網(wǎng)絡(luò)通信基本過(guò)程如下圖所示:

總結(jié)一下流程,可以簡(jiǎn)單描述為這四步:

(1)服務(wù)端啟動(dòng),監(jiān)聽(tīng)指定端口,等待客戶端連接;

(2)客戶端嘗試與服務(wù)端連接,建立可信數(shù)據(jù)傳輸通道;

(3)客戶端與服務(wù)端進(jìn)行數(shù)據(jù)交換;

(4)客戶端或者服務(wù)端斷開(kāi)連接,終止通信;

了解了基本流程,有些小伙伴可能對(duì) Socket 這玩意很感興趣了,Socket 到底是什么東西呢?Socket 中文翻譯過(guò)來(lái)就是套接字,是網(wǎng)絡(luò)通信對(duì)象的抽象表達(dá),聽(tīng)起來(lái)還是很模糊,從編碼者視角來(lái)看,本質(zhì)上就是一套編程接口,是對(duì)復(fù)雜的 TCP/IP 協(xié)議進(jìn)行封裝供上層應(yīng)用使用,這樣總明白了吧。

那 Socket 對(duì)象一般包括什么東西呢?一般包括五種信息:連接使用的協(xié)議、本地主機(jī)的IP地址、本地進(jìn)程的協(xié)議端口、遠(yuǎn)端主機(jī)的IP地址、遠(yuǎn)端進(jìn)程的協(xié)議端口。從這里可以看到 Socket 包含的信息非常豐富,也就是說(shuō)拿到一個(gè) Socket 對(duì)象就相當(dāng)于知己知彼了。

傳統(tǒng) BIO 模式

上面小節(jié)從理論角度講解了什么是Socket,現(xiàn)在我們回到開(kāi)發(fā)語(yǔ)言實(shí)現(xiàn)層面上來(lái),以 Java 為例,Java 語(yǔ)言從 1.0 版本就已經(jīng)封裝了 Socket 相關(guān)的接口供開(kāi)發(fā)者使用,對(duì)這部分代碼感興趣的小伙伴可以出門向左拐,在java.net 包下面查看源碼。

我們嘗試用一個(gè) demo 來(lái)演示一下傳統(tǒng)的網(wǎng)絡(luò)編程:

服務(wù)端代碼:

public static void main(String[] args) throws IOException {
// 創(chuàng)建一個(gè)ServerSocket,監(jiān)聽(tīng)端口8888
ServerSocket ss = new ServerSocket(8888);

// 循環(huán)方式監(jiān)聽(tīng)客戶端的請(qǐng)求
while (true) {
// 這里一直會(huì)阻塞,直到客戶端連接上
Socket socket = ss.accept();

// 輸入流用于接收消息
InputStream inputStream = socket.getInputStream();
BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);

// 輸出流用于回復(fù)消息
OutputStream outputStream = socket.getOutputStream();
final PrintStream printStream = new PrintStream(outputStream);

// 循環(huán)接收并回復(fù)客戶端發(fā)送的消息
byte[] bytes = new byte[1024];
int len;
while ((len = bufferedInputStream.read(bytes)) != -1) {
printStream.print("服務(wù)端收到:" + new String(bytes, 0, len));
}
}
}

效果演示:

服務(wù)端運(yùn)行起來(lái)后,使用 telnet 命令來(lái)模擬客戶端發(fā)送消息:

telnet 127.0.0.1 8888

客戶端每發(fā)送一條消息,服務(wù)端都會(huì)回復(fù),演示效果如下:

仔細(xì)想一下,上面的代碼可能會(huì)有問(wèn)題,如果前面一個(gè)客戶端一直不斷開(kāi),服務(wù)端就不能處理其他客戶端的消息了,也就是說(shuō)程序不具備并發(fā)的能力。

我們稍加改造一下,將前面的處理邏輯代碼全部抽取到一個(gè)新的handle()方法, 每當(dāng)有客戶端連接上就新開(kāi)一個(gè)線程處理:

public static void main(String[] args) throws IOException {
// 創(chuàng)建一個(gè)ServerSocket,監(jiān)聽(tīng)端口8888
ServerSocket ss = new ServerSocket(8888);

// 循環(huán)方式監(jiān)聽(tīng)客戶端的請(qǐng)求
while (true) {
// 這里一直會(huì)阻塞,直到客戶端連接上
Socket socket = ss.accept();
// 啟動(dòng)一個(gè)新的線程處理
new Thread(() -> handle(socket)).start();
}
}

這里為了演示方便直接新起了一個(gè)線程,當(dāng)然更好的辦法是用線程池,但是也解決不了根本性問(wèn)題。

看了兩段代碼,先簡(jiǎn)單總結(jié)一下 BIO 模式的劣勢(shì):

  • 如果 BIO 使用單線程接收連接,則會(huì)阻塞其他連接,效率較低。
  • 如果使用多線程,雖然減弱了單線程帶來(lái)的影響,但當(dāng)有大并發(fā)進(jìn)來(lái)時(shí),會(huì)導(dǎo)致服務(wù)器線程太多,壓力太大而崩潰。
  • 就算使用線程池,也只能同時(shí)允許有限個(gè)數(shù)的線程進(jìn)行連接,如果并發(fā)量遠(yuǎn)大于線程池設(shè)置的數(shù)量,還是與單線程無(wú)異。
  • IO 代碼里 read 操作是阻塞操作,如果連接不做數(shù)據(jù)讀寫(xiě)操作會(huì)導(dǎo)致線程阻塞,就是說(shuō)只占用連接,不發(fā)送數(shù)據(jù),則會(huì)浪費(fèi)資源。比如線程池中 500個(gè)連接,只有 100 個(gè)是頻繁讀寫(xiě)的連接,其他占著茅坑不拉屎,浪費(fèi)資源!
  • 另外多線程也會(huì)有線程切換帶來(lái)的消耗。

綜上所述,BIO 模式不能滿足大并發(fā)業(yè)務(wù)場(chǎng)景,僅適用于連接數(shù)目比較小且固定的架構(gòu)。

同步阻塞 BIO 模式

根據(jù)上面的例子我們?cè)佼?huà)圖抽象一下 BIO 網(wǎng)絡(luò)編程場(chǎng)景:

傳統(tǒng) BIO 的特點(diǎn)是只要來(lái)了一個(gè)新客戶端連接,服務(wù)端就會(huì)開(kāi)辟一個(gè)線程處理客戶端請(qǐng)求,但是客戶端連接后并不是一直都對(duì)服務(wù)端進(jìn)行 IO 操作,這樣會(huì)導(dǎo)致服務(wù)端阻塞,一直占用著線程資源,造成很多非要的開(kāi)銷。

為了解決這個(gè)問(wèn)題,Java 引入了 NIO,我們接著往下看。

NIO

在 Java 1.4 版本之前 BIO 是開(kāi)發(fā)者唯一的選擇,1.4 版本開(kāi)始引入了 NIO 框架。

NIO 的 N 有兩層含義,一層是:New IO,另一層是 Non Blocking IO。

「New」是相對(duì)于傳統(tǒng) BIO 來(lái)說(shuō)的,在當(dāng)時(shí)確實(shí)挺新的;Non Blocking IO 又被稱為:同步非阻塞 IO,同步非阻塞體現(xiàn)在:

  • 同步:調(diào)用的結(jié)果會(huì)在本次調(diào)用后返回,不存在異步線程回調(diào)之類的。
  • 非阻塞:表現(xiàn)為線程不會(huì)一直在等待,把連接加入集合后,線程會(huì)一直輪詢集合中的連接,有則處理,無(wú)則繼續(xù)接受請(qǐng)求。

NIO 三大基礎(chǔ)組件

學(xué)習(xí) NIO必須得知道下面這三個(gè)基礎(chǔ)組件:

(1)Buffer(緩沖區(qū))

IO 是面向流(字節(jié)流或者字符流)的,而 NIO 是面向塊的,塊指的是 Buffer 緩沖區(qū)。面向塊的方式一次性可以獲取或者寫(xiě)入一整塊數(shù)據(jù),而不需要一個(gè)字節(jié)一個(gè)字節(jié)的從流中讀取,這樣處理數(shù)據(jù)的速度會(huì)比流方式更快。

Buffer 緩沖區(qū)的底層實(shí)現(xiàn)是數(shù)組,根據(jù)數(shù)組類型可以細(xì)分為:ByteBuffe、CharBuffer、DoubleBuffer、FloatBuffer、IntBuffer、LongBuffer、ShortBuffer等。

(2)Channel(通道)

Channel 翻譯成中文是通道的意思,作用類似于 IO 中的 Stream 流。但是 Channel 和 Stream 不同之處在于 Channel 是雙向的,Stream 只是在一個(gè)方向移動(dòng),而且 Channel 可以用于讀、寫(xiě)或者同時(shí)用于讀寫(xiě)。

常見(jiàn) Channel 通道類型:

  • FileChannel 用于文件操作場(chǎng)景;
  • ServerSocketChannel 和 SocketChannel 主要用于 TCP 網(wǎng)絡(luò)通信 IO,這是本文的重點(diǎn);
  • DatagramChannel: 從 UDP 網(wǎng)絡(luò)中讀取或者寫(xiě)入數(shù)據(jù)。

Channel 與 Buffer 之間的關(guān)系:

每個(gè) Channel 對(duì)應(yīng)一個(gè) Buffer 緩沖區(qū),永遠(yuǎn)無(wú)法將數(shù)據(jù)直接寫(xiě)入到Channel或者從Channel中讀取數(shù)據(jù)。需要通過(guò)Buffer與Channel交互。

(3)Selector(多路復(fù)用器)

NIO 服務(wù)端的實(shí)現(xiàn)模式是把多個(gè)連接(請(qǐng)求)放入集合中,只用一個(gè)線程可以處理多個(gè)請(qǐng)求(連接),也就是多路復(fù)用,Linux 環(huán)境下多路復(fù)用底層主要用的是內(nèi)核函數(shù)(select,poll)來(lái)實(shí)現(xiàn)的,為了提升效率,Java 1.5 版本開(kāi)始使用 epoll。

關(guān)于 select、poll、epoll 之間的對(duì)比,感興趣的小伙伴可以自行上網(wǎng)查詢。

在 NIO 中多路復(fù)用器我們稱之為:Selector,Channel 會(huì)注冊(cè)到 Selector 上,由 Selector 根據(jù) Channel 讀寫(xiě)事件的發(fā)生將其交由某個(gè)空閑的線程處理。

Buffer、Channel、Selector 這三個(gè)組件的之間的關(guān)系可以用下面的圖來(lái)描述:

基本的工作流程如下:

(1)首先將 Channel 注冊(cè)到 Selector 中;

(2)初始化 Selector,調(diào)用 select() 方法,select 方法會(huì)阻塞直到感興趣的事件來(lái)臨;

(3)當(dāng)某個(gè) Channel 有連接或者讀寫(xiě)事件時(shí),該 Channel 就會(huì)處于就緒狀態(tài);

(4)Selector 開(kāi)始輪詢所有處于就緒狀態(tài)的SelectionKey,通過(guò) SelectionKey 可以獲取對(duì)應(yīng)的Channel 集合;

NIO 比 BIO 好用在哪?

NIO 相對(duì)于 BIO 最大的改進(jìn)就是使用了多路復(fù)用技術(shù),用少量線程處理大量客戶端 IO 請(qǐng)求,提高了并發(fā)量并減少了資源消耗;

另外NIO 的操作時(shí)非阻塞的,比如說(shuō),單線程中從通道讀取數(shù)據(jù)到buffer,同時(shí)可以繼續(xù)做別的事情,當(dāng)數(shù)據(jù)讀取到buffer中后,線程再繼續(xù)處理數(shù)據(jù)。寫(xiě)數(shù)據(jù)也是一樣的。

NIO 存在的問(wèn)題

NIO這么牛了,是不是就是終極解決方案了?其實(shí)也不是,NIO 也存在很多問(wèn)題。

我們來(lái)看看 NIO 有哪些問(wèn)題?

(1)NIO 的 API 使用起來(lái)非常麻煩,門檻比較高,開(kāi)發(fā)者需要熟練掌握:Selector、ServerSocketChannel、SocketChannel、ByteBuffer 等類。

(2)NIO 編程涉及到 Reactor 模式,開(kāi)發(fā)者需要對(duì)多線程和網(wǎng)絡(luò)編程非常熟悉才能寫(xiě)出高質(zhì)量的 NIO 程序;

(3)異常場(chǎng)景處理麻煩,比如:客戶端斷連重連、網(wǎng)絡(luò)閃斷、拆包粘包、網(wǎng)絡(luò)擁塞等等;

(4)NIO 有 bug,不穩(wěn)定,比如:臭名昭著的 Epoll bug,會(huì)導(dǎo)致 Selector 空輪詢,最終導(dǎo)致 CPU 100%。

NIO 問(wèn)題這么多,有些開(kāi)發(fā)者終于不能忍了,最終 Netty 框架橫空出世。

Netty 框架到底解決了什么問(wèn)題,有哪些優(yōu)秀的特性,我們下期接著聊。

責(zé)任編輯:武曉燕 來(lái)源: 愛(ài)笑的架構(gòu)師
相關(guān)推薦

2009-03-11 14:42:57

面試求職案例

2021-08-17 07:00:00

雙重檢查鎖Nacos

2020-06-08 17:35:27

Redis集群互聯(lián)網(wǎng)

2022-12-14 07:32:40

InnoDBMySQL引擎

2022-01-12 19:59:19

Netty 核心啟動(dòng)

2022-05-10 10:19:04

AI深度學(xué)習(xí)模型

2018-12-05 08:39:28

IOTITOT

2023-05-08 07:52:29

JSXReactHooks

2021-11-10 09:45:06

Lambda表達(dá)式語(yǔ)言

2024-12-17 12:00:00

C++對(duì)象模型

2016-12-02 20:13:38

2018-04-02 15:13:21

網(wǎng)絡(luò)

2023-02-15 08:17:38

2024-04-30 08:22:51

Figma圖形編輯變換矩陣

2025-02-17 09:22:16

MySQLSQL語(yǔ)句

2017-01-05 15:07:33

2017-01-16 13:34:21

2022-05-05 08:55:12

工業(yè)物聯(lián)網(wǎng)IIoT

2024-02-06 09:30:25

Figma矩形矩形物理屬性

2023-05-22 15:58:11

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)