一文了解Linux 网络 I/O 模型
目錄
1 什么是I/O
2 同步IO模型
2.1 阻塞IO模型
2.2 非阻塞IO模型
2.3 信號(hào)驅(qū)動(dòng)IO模型
2.4 IO復(fù)用模型
2.5 小結(jié)
3 異步IO模型
4 五種IO模型對(duì)比
1 什么是I/O
IO中的阻塞、非阻塞、同步、異步概念分析詳解?
通過(guò)上面這篇文章你可以知道同步、異步、阻塞、非阻塞這些概念,并且可以了解到j(luò)ava中I/O編程的三種模型,阻塞IO(BIO)、非阻塞IO(NIO)和異步IO(AIO)。
Java中提供的IO有關(guān)的API,在文件處理的時(shí)候,其實(shí)依賴(lài)操作系統(tǒng)層面的IO操作實(shí)現(xiàn)的。比如在Linux 2.6以后,Java中NIO和AIO都是通過(guò)epoll來(lái)實(shí)現(xiàn)的,而在Windows上,AIO是通過(guò)IOCP來(lái)實(shí)現(xiàn)的??梢园袹ava中的BIO、NIO和AIO理解為是Java語(yǔ)言對(duì)操作系統(tǒng)的各種IO模型的封裝。程序員在使用這些API的時(shí)候,不需要關(guān)心操作系統(tǒng)層面的知識(shí),也不需要根據(jù)不同操作系統(tǒng)編寫(xiě)不同的代碼。只需要使用Java的API就可以了。
網(wǎng)絡(luò)IO的本質(zhì)是socket的讀取,socket在linux系統(tǒng)被抽象為流,IO可以理解為對(duì)流的操作。?對(duì)于一次IO訪問(wèn)(以read舉例),數(shù)據(jù)會(huì)先被拷貝到操作系統(tǒng)內(nèi)核的緩沖區(qū)中,然后才會(huì)從操作系統(tǒng)內(nèi)核的緩沖區(qū)拷貝到應(yīng)用程序的地址空間。所以說(shuō),
當(dāng)一個(gè)read操作發(fā)生時(shí),它會(huì)經(jīng)歷兩個(gè)階段:
-
等待數(shù)據(jù)準(zhǔn)備 (Waiting for the data to be ready)。
-
將數(shù)據(jù)從內(nèi)核拷貝到進(jìn)程中 (Copying the data from the kernel to the process)。
對(duì)于socket流而言,
-
第一步:通常涉及等待網(wǎng)絡(luò)上的數(shù)據(jù)分組到達(dá),然后被復(fù)制到內(nèi)核的某個(gè)緩沖區(qū)。
-
第二步:把數(shù)據(jù)從內(nèi)核緩沖區(qū)復(fù)制到應(yīng)用進(jìn)程緩沖區(qū)。
網(wǎng)絡(luò)應(yīng)用需要處理的無(wú)非就是兩大類(lèi)問(wèn)題,網(wǎng)絡(luò)IO,數(shù)據(jù)計(jì)算。相對(duì)于后者,網(wǎng)絡(luò)IO的延遲,給應(yīng)用帶來(lái)的性能瓶頸大于后者。如果要想提高IO效率,需要將等的時(shí)間降低。
可以通過(guò)釣魚(yú)的例子來(lái)理解下:
是在魚(yú)塘里面的,我們的釣魚(yú)動(dòng)作的最終結(jié)束標(biāo)志是魚(yú)從魚(yú)塘中被我們釣上來(lái),放入魚(yú)簍中。
這里面的魚(yú)塘就可以映射成磁盤(pán),中間過(guò)渡的魚(yú)鉤可以映射成內(nèi)核空間,最終放魚(yú)的魚(yú)簍可以映射成用戶(hù)空間。一次完整的釣魚(yú)(IO)操作,是魚(yú)(文件)從魚(yú)塘(硬盤(pán))中轉(zhuǎn)移(拷貝)到魚(yú)簍(用戶(hù)空間)的過(guò)程
在Linux(UNIX)操作系統(tǒng)中,共有五種IO模型,分別是:阻塞IO模型、非阻塞IO模型、IO復(fù)用模型、信號(hào)驅(qū)動(dòng)IO模型以及異步IO模型。
2 同步IO模型
2.1 阻塞IO模型
我們釣魚(yú)的時(shí)候,有一種方式比較愜意,比較輕松,那就是我們坐在魚(yú)竿面前,這個(gè)過(guò)程中我們什么也不做,雙手一直把著魚(yú)竿,就靜靜的等著魚(yú)兒咬鉤。一旦手上感受到魚(yú)的力道,就把魚(yú)釣起來(lái)放入魚(yú)簍中。然后再釣下一條魚(yú)。
映射到Linux操作系統(tǒng)中,這就是一種最簡(jiǎn)單的IO模型,即阻塞IO。 阻塞 I/O 是最簡(jiǎn)單的 I/O 模型,一般表現(xiàn)為進(jìn)程或線程等待某個(gè)條件,如果條件不滿(mǎn)足,則一直等下去。條件滿(mǎn)足,則進(jìn)行下一步操作。?
應(yīng)用進(jìn)程通過(guò)系統(tǒng)調(diào)用 recvfrom 接收數(shù)據(jù),但由于內(nèi)核還未準(zhǔn)備好數(shù)據(jù)報(bào),應(yīng)用進(jìn)程就會(huì)阻塞住,直到內(nèi)核準(zhǔn)備好數(shù)據(jù)報(bào),recvfrom 完成數(shù)據(jù)報(bào)復(fù)制工作,應(yīng)用進(jìn)程才能結(jié)束阻塞狀態(tài)。
這種釣魚(yú)方式相對(duì)來(lái)說(shuō)比較簡(jiǎn)單,對(duì)于釣魚(yú)的人來(lái)說(shuō),不需要什么特制的魚(yú)竿,拿一根夠長(zhǎng)的木棍就可以悠閑的開(kāi)始釣魚(yú)了(實(shí)現(xiàn)簡(jiǎn)單)。缺點(diǎn)就是比較耗費(fèi)時(shí)間,比較適合那種對(duì)魚(yú)的需求量小的情況(并發(fā)低,時(shí)效性要求低)。
BIO模型最大的問(wèn)題就是當(dāng)客戶(hù)端并發(fā)訪問(wèn)量增加后,每個(gè)客戶(hù)端的請(qǐng)求服務(wù)端都需要一個(gè)對(duì)應(yīng)的線程來(lái)處理,客戶(hù)端并發(fā)數(shù):服務(wù)端線程=1:1 ,Java中的線程是比較寶貴的系統(tǒng)資源,線程數(shù)量快速膨脹后,系統(tǒng)的性能將急劇下降,隨著訪問(wèn)量的繼續(xù)增大,系統(tǒng)很可能"崩潰"
2.2 非阻塞IO模型
我們釣魚(yú)的時(shí)候,在等待魚(yú)兒咬鉤的過(guò)程中,我們可以做點(diǎn)別的事情,比如玩一把王者榮耀、看一集《延禧攻略》等等。但是,我們要時(shí)不時(shí)的去看一下魚(yú)竿,一旦發(fā)現(xiàn)有魚(yú)兒上鉤了,就把魚(yú)釣上來(lái)。
映射到Linux操作系統(tǒng)中,這就是非阻塞的IO模型。應(yīng)用進(jìn)程與內(nèi)核交互,目的未達(dá)到之前,不再一味的等著,而是直接返回EWOULDBLOCK錯(cuò)誤。然后通過(guò)輪詢(xún)的方式,不停的去問(wèn)內(nèi)核數(shù)據(jù)準(zhǔn)備有沒(méi)有準(zhǔn)備好。如果某一次輪詢(xún)發(fā)現(xiàn)數(shù)據(jù)已經(jīng)準(zhǔn)備好了,那就把數(shù)據(jù)拷貝到用戶(hù)空間中。?"非阻塞就是將大的整片時(shí)間的阻塞分成N多的小的阻塞, 所以進(jìn)程不斷地有機(jī)會(huì) '被' CPU光顧"
?
應(yīng)用進(jìn)程通過(guò) recvfrom 調(diào)用不停的去和內(nèi)核交互,直到內(nèi)核準(zhǔn)備好數(shù)據(jù)。如果沒(méi)有準(zhǔn)備好,內(nèi)核會(huì)返回error,應(yīng)用進(jìn)程在得到error后,過(guò)一段時(shí)間再發(fā)送recvfrom請(qǐng)求。在兩次發(fā)送請(qǐng)求的時(shí)間段,進(jìn)程可以先做別的事情。
這種方式釣魚(yú),和阻塞IO比,所使用的工具沒(méi)有什么變化,但是釣魚(yú)的時(shí)候可以做些其他事情,增加時(shí)間的利用率。
優(yōu)點(diǎn):能夠在輪詢(xún)的間隔時(shí)間里處理其他事情(包括提交其他任務(wù),也就是 “后臺(tái)” 可以有多個(gè)任務(wù)在同時(shí)執(zhí)行)。
缺點(diǎn):任務(wù)完成的響應(yīng)延遲增大了,因?yàn)槊窟^(guò)一段時(shí)間才去輪詢(xún)一次,而數(shù)據(jù)準(zhǔn)備可能在兩次輪詢(xún)之間的任意時(shí)間完成。這會(huì)導(dǎo)致整體數(shù)據(jù)吞吐量的降低。
2.3 信號(hào)驅(qū)動(dòng)IO模型
我們釣魚(yú)的時(shí)候,為了避免自己一遍一遍的去查看魚(yú)竿,我們可以給魚(yú)竿安裝一個(gè)重力報(bào)警器。當(dāng)有魚(yú)兒咬鉤的時(shí)候立刻報(bào)警。然后我們?cè)偈盏綀?bào)警后,去把魚(yú)釣起來(lái)。
映射到Linux操作系統(tǒng)中,這就是信號(hào)驅(qū)動(dòng)IO。注冊(cè)一個(gè)信號(hào)處理函數(shù),然后應(yīng)用進(jìn)程告訴內(nèi)核:當(dāng)數(shù)據(jù)報(bào)準(zhǔn)備好的時(shí)候,給我發(fā)送一個(gè)信號(hào)。收到信號(hào)后,對(duì)SIGIO信號(hào)進(jìn)行捕捉,并且調(diào)用信號(hào)處理函數(shù)處理數(shù)據(jù)并獲取結(jié)果。?
?
應(yīng)用進(jìn)程預(yù)先向內(nèi)核注冊(cè)一個(gè)信號(hào)處理函數(shù),然后用戶(hù)進(jìn)程返回,并且不阻塞,當(dāng)內(nèi)核數(shù)據(jù)準(zhǔn)備就緒時(shí)會(huì)發(fā)送一個(gè)信號(hào)給進(jìn)程,用戶(hù)進(jìn)程便在信號(hào)處理函數(shù)中開(kāi)始把數(shù)據(jù)拷貝的用戶(hù)空間中。
這種方式釣魚(yú),和前幾種相比,所使用的工具有了一些變化,需要有一些定制(實(shí)現(xiàn)復(fù)雜)。但是釣魚(yú)的人就可以在魚(yú)兒咬鉤之前徹底做別的事兒去了。等著報(bào)警器響就行了。
2.4 IO復(fù)用模型
我們釣魚(yú)的時(shí)候,為了保證可以最短的時(shí)間釣到最多的魚(yú),我們同一時(shí)間擺放多個(gè)魚(yú)竿,同時(shí)釣魚(yú)。然后哪個(gè)魚(yú)竿有魚(yú)兒咬鉤了,我們就把哪個(gè)魚(yú)竿上面的魚(yú)釣起來(lái)。
由于同步非阻塞方式需要不斷主動(dòng)輪詢(xún),輪詢(xún)占據(jù)了很大一部分過(guò)程,會(huì)消耗大量的CPU資源,而 “后臺(tái)” 可能有多個(gè)任務(wù)在同時(shí)進(jìn)行,那么就想到了循環(huán)查詢(xún)多個(gè)任務(wù)的完成狀態(tài),只要有任何一個(gè)任務(wù)完成(數(shù)據(jù)報(bào)準(zhǔn)備號(hào)),就去處理它。如果輪詢(xún)不是使用的進(jìn)程的用戶(hù)態(tài),而是有其他線程幫忙就更好了。那么這就是所謂的 “IO 多路復(fù)用”
生產(chǎn)中經(jīng)常會(huì)用到的一種模型(Java的NIO就是基于IO復(fù)用模型)
多個(gè)進(jìn)程的IO可以注冊(cè)到同一個(gè)管道上,這個(gè)管道會(huì)統(tǒng)一和內(nèi)核進(jìn)行交互。當(dāng)管道中的某一個(gè)請(qǐng)求需要的數(shù)據(jù)準(zhǔn)備好之后,進(jìn)程再把對(duì)應(yīng)的數(shù)據(jù)拷貝到用戶(hù)空間中。
IO多路復(fù)用使用了Linux提供的select/poll命令,進(jìn)程通過(guò)將一個(gè)或多個(gè)fd傳遞給select或poll系統(tǒng)調(diào)用,當(dāng)用戶(hù)進(jìn)程調(diào)用該select,select會(huì)監(jiān)聽(tīng)所有注冊(cè)好的fd,如果所有被監(jiān)聽(tīng)的fd需要的數(shù)據(jù)都沒(méi)有準(zhǔn)備好時(shí),select調(diào)用進(jìn)程會(huì)阻塞。當(dāng)任意一個(gè)fd處于就緒狀態(tài)后,select調(diào)用就會(huì)返回,然后進(jìn)程再通過(guò)recvfrom來(lái)進(jìn)行數(shù)據(jù)拷貝。
select/poll是順序掃描fd是否就緒,而且支持的fd數(shù)量有限,因此它的使用受到了一些制約。
Linux還提供一個(gè)epoll系統(tǒng)調(diào)用,epoll使用基于事件驅(qū)動(dòng)方式代替順序掃描,因此性能更高。當(dāng)有fd就緒時(shí),立即回調(diào)函數(shù)rollback。
這里的IO復(fù)用模型,并沒(méi)有向內(nèi)核注冊(cè)信號(hào)處理函數(shù),所以,他并不是非阻塞的。進(jìn)程在發(fā)出select后,要等到select/epoll監(jiān)聽(tīng)的所有IO操作中至少有一個(gè)需要的數(shù)據(jù)準(zhǔn)備好,才會(huì)有返回,并且也需要再次發(fā)送請(qǐng)求去進(jìn)行文件的拷貝。
這種方式的釣魚(yú),通過(guò)增加魚(yú)竿的方式,可以有效的提升效率。
2.5 小結(jié)
上面的阻塞IO模型、非阻塞IO模型、IO復(fù)用模型和信號(hào)驅(qū)動(dòng)IO模型都是同步的IO模型。原因是因?yàn)?#xff0c;無(wú)論以上那種模型,真正的數(shù)據(jù)拷貝過(guò)程,都是同步進(jìn)行的。
信號(hào)驅(qū)動(dòng)是內(nèi)核是在數(shù)據(jù)準(zhǔn)備好之后通知進(jìn)程,然后進(jìn)程在通過(guò)recvfrom操作進(jìn)行數(shù)據(jù)拷貝。我們可以認(rèn)為數(shù)據(jù)準(zhǔn)備階段是異步的,但是,數(shù)據(jù)拷貝操作是同步的。所以,整個(gè)IO過(guò)程也不能認(rèn)為是異步的。
而IO復(fù)用模型中,對(duì)于每一個(gè)socket,一般都設(shè)置成為non-blocking,但是,整個(gè)用戶(hù)的進(jìn)程其實(shí)是一直被block的。只不過(guò)進(jìn)程是被select這個(gè)函數(shù)block,而不是被socket IO給block。所以IO多路復(fù)用是阻塞在select,epoll這樣的系統(tǒng)調(diào)用之上,而沒(méi)有阻塞在真正的I/O系統(tǒng)調(diào)用如recvfrom之上。
我們把釣魚(yú)過(guò)程,可以拆分為兩個(gè)步驟:1、魚(yú)咬鉤(數(shù)據(jù)準(zhǔn)備)。2、把魚(yú)釣起來(lái)放進(jìn)魚(yú)簍里(數(shù)據(jù)拷貝)。無(wú)論以上提到的哪種釣魚(yú)方式,在第二步,都是需要人主動(dòng)去做的,并不是魚(yú)竿自己完成的。所以,這個(gè)釣魚(yú)過(guò)程其實(shí)還是同步進(jìn)行的。
3 異步IO模型
我們釣魚(yú)的時(shí)候,采用一種高科技釣魚(yú)竿,即全自動(dòng)釣魚(yú)竿??梢宰詣?dòng)感應(yīng)魚(yú)上鉤,自動(dòng)收竿,更厲害的可以自動(dòng)把魚(yú)放進(jìn)魚(yú)簍里。然后,通知我們魚(yú)已經(jīng)釣到了,他就繼續(xù)去釣下一條魚(yú)去了。
映射到Linux操作系統(tǒng)中,這就是異步IO模型。應(yīng)用進(jìn)程把IO請(qǐng)求傳給內(nèi)核后,完全由內(nèi)核去操作文件拷貝。內(nèi)核完成相關(guān)操作后,會(huì)發(fā)信號(hào)告訴應(yīng)用進(jìn)程本次IO已經(jīng)完成。?
用戶(hù)進(jìn)程發(fā)起aio_read操作之后,給內(nèi)核傳遞描述符fd、緩沖區(qū)指針、緩沖區(qū)大小等,告訴內(nèi)核當(dāng)整個(gè)操作完成時(shí),如何通知進(jìn)程,然后就立刻去做其他事情了。當(dāng)內(nèi)核收到aio_read后,會(huì)立刻返回,然后內(nèi)核開(kāi)始等待數(shù)據(jù)準(zhǔn)備,數(shù)據(jù)準(zhǔn)備好以后,直接把數(shù)據(jù)拷貝到用戶(hù)控件,然后再通知進(jìn)程本次IO已經(jīng)完成。
這種方式的釣魚(yú),無(wú)疑是最省事兒的。啥都不需要管,只需要交給魚(yú)竿就可以了。
4 五種IO模型對(duì)比
參考:微信公眾號(hào) 漫畫(huà)編程
總結(jié)
以上是生活随笔為你收集整理的一文了解Linux 网络 I/O 模型的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 手写实现RPC框架基础功能
- 下一篇: linux常见问题及其解决方案集锦