自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

硬核圖解網(wǎng)絡(luò)IO模型!

系統(tǒng) Linux
本文系統(tǒng)的講解了Linux內(nèi)核的IO模型、Java網(wǎng)絡(luò)IO模型以及兩者之間的關(guān)系!希望對(duì)您有所幫助!

本文轉(zhuǎn)載自微信公眾號(hào)「日常加油站」,作者月伴飛魚。轉(zhuǎn)載本文請(qǐng)聯(lián)系日常加油站公眾號(hào)。

背景介紹

  • 在互聯(lián)網(wǎng)的時(shí)代下,絕大部分?jǐn)?shù)據(jù)都是通過網(wǎng)絡(luò)來進(jìn)行獲取的。
  • 在服務(wù)端的架構(gòu)中,絕大部分?jǐn)?shù)據(jù)也是通過網(wǎng)絡(luò)來進(jìn)行交互的。

而且作為服務(wù)端的開發(fā)工程師來說,都會(huì)進(jìn)行一系列服務(wù)設(shè)計(jì)、開發(fā)以及能力開放,而服務(wù)能力開放也是需要通過網(wǎng)絡(luò)來完成的,因此對(duì)網(wǎng)絡(luò)編程以及網(wǎng)絡(luò)IO模型都不會(huì)太陌生。

由于有很多優(yōu)秀的框架(比如Netty、HSF、Dubbo、Thrift等)已經(jīng)把底層網(wǎng)絡(luò)IO給封裝了,通過提供的API能力或者配置就能完成想要的服務(wù)能力開發(fā),因此大部分工程師對(duì)網(wǎng)絡(luò)IO模型的底層不夠了解。

本文系統(tǒng)的講解了Linux內(nèi)核的IO模型、Java網(wǎng)絡(luò)IO模型以及兩者之間的關(guān)系!

什么是IO

我們都知道在Linux的世界,一切皆文件。

而文件就是一串二進(jìn)制流,不管Socket、FIFO、管道還是終端,對(duì)我們來說,一切都是流。

  • 在信息的交換過程中,我們都是對(duì)這些流進(jìn)行數(shù)據(jù)收發(fā)操作,簡(jiǎn)稱為I/O操作。
  • 往流中讀取數(shù)據(jù),系統(tǒng)調(diào)用Read,寫入數(shù)據(jù),系統(tǒng)調(diào)用Write。

通常用戶進(jìn)程的一個(gè)完整的IO分為兩個(gè)階段:

磁盤IO:

網(wǎng)絡(luò)IO:

操作系統(tǒng)和驅(qū)動(dòng)程序運(yùn)行在內(nèi)核空間,應(yīng)用程序運(yùn)行在用戶空間,兩者不能使用指針傳遞數(shù)據(jù),因?yàn)長(zhǎng)inux使用的虛擬內(nèi)存機(jī)制,必須通過系統(tǒng)調(diào)用請(qǐng)求內(nèi)核來完成IO動(dòng)作。

IO有內(nèi)存IO、網(wǎng)絡(luò)IO和磁盤IO三種,通常我們說的IO指的是后兩者!

為什么需要IO模型

如果使用同步的方式來通信的話,所有的操作都在一個(gè)線程內(nèi)順序執(zhí)行完成,這么做缺點(diǎn)是很明顯的:

  • 因?yàn)橥降耐ㄐ挪僮鲿?huì)阻塞同一個(gè)線程的其他任何操作,只有這個(gè)操作完成了之后,后續(xù)的操作才可以完成,所以出現(xiàn)了同步阻塞+多線程(每個(gè)Socket都創(chuàng)建一個(gè)線程對(duì)應(yīng)),但是系統(tǒng)內(nèi)線程數(shù)量是有限制的,同時(shí)線程切換很浪費(fèi)時(shí)間,適合Socket少的情況。

因該需要出現(xiàn)IO模型。

Linux的IO模型

在描述Linux IO模型之前,我們先來了解一下Linux系統(tǒng)數(shù)據(jù)讀取的過程:

以用戶請(qǐng)求index.html文件為例子說明

基本概念

用戶空間和內(nèi)核空間

操作系統(tǒng)的核心是內(nèi)核,獨(dú)立于普通的應(yīng)用程序,可以訪問受保護(hù)的內(nèi)存空間,也有訪問底層硬件設(shè)備的所有權(quán)限。

  • 為了保證內(nèi)核的安全,用戶進(jìn)程不能直接操作內(nèi)核,操作系統(tǒng)將虛擬空間劃分為兩部分,一部分為內(nèi)核空間,一部分為用戶空間。

進(jìn)程切換

為了控制進(jìn)程的執(zhí)行,內(nèi)核必須有能力掛起正在CPU上運(yùn)行的進(jìn)程,并恢復(fù)以前掛起的某個(gè)進(jìn)程的執(zhí)行。

這種行為被稱為進(jìn)程切換。

因此可以說,任何進(jìn)程都是在操作系統(tǒng)內(nèi)核的支持下運(yùn)行的,是與內(nèi)核緊密相關(guān)的。

進(jìn)程的阻塞

正在執(zhí)行的進(jìn)程,由于期待的某些事件未發(fā)生,如請(qǐng)求系統(tǒng)資源失敗、等待某種操作的完成、新數(shù)據(jù)尚未到達(dá)或無新工作做等,則由系統(tǒng)自動(dòng)執(zhí)行阻塞原語(Block),使自己由運(yùn)行狀態(tài)變?yōu)樽枞麪顟B(tài)。

可見,進(jìn)程的阻塞是進(jìn)程自身的一種主動(dòng)行為,也因此只有處于運(yùn)行態(tài)的進(jìn)程(獲得CPU),才可能將其轉(zhuǎn)為阻塞狀態(tài)。

當(dāng)進(jìn)程進(jìn)入阻塞狀態(tài),是不占用CPU資源的。

文件描述符

文件描述符(File Descriptor)是計(jì)算機(jī)科學(xué)中的一個(gè)術(shù)語,是一個(gè)用于表述指向文件的引用的抽象化概念。

文件描述符在形式上是一個(gè)非負(fù)整數(shù),實(shí)際上,它是一個(gè)索引值,指向內(nèi)核為每一個(gè)進(jìn)程所維護(hù)的該進(jìn)程打開文件的記錄表。

當(dāng)程序打開一個(gè)現(xiàn)有文件或者創(chuàng)建一個(gè)新文件時(shí),內(nèi)核向進(jìn)程返回一個(gè)文件描述符。

緩存IO

大多數(shù)文件系統(tǒng)的默認(rèn) IO 操作都是緩存 IO。

其讀寫過程如下:

  • 讀操作:操作系統(tǒng)檢查內(nèi)核的緩沖區(qū)有沒有需要的數(shù)據(jù),如果已經(jīng)緩存了,那么就直接從緩存中返回;否則從磁盤、網(wǎng)卡等中讀取,然后緩存在操作系統(tǒng)的緩存中;
  • 寫操作:將數(shù)據(jù)從用戶空間復(fù)制到內(nèi)核空間的緩存中。這時(shí)對(duì)用戶程序來說寫操作就已經(jīng)完成,至于什么時(shí)候再寫到磁盤、網(wǎng)卡等中由操作系統(tǒng)決定,除非顯示地調(diào)用了 sync 同步命令。

假設(shè)內(nèi)核空間緩存無需要的數(shù)據(jù),用戶進(jìn)程從磁盤或網(wǎng)絡(luò)讀數(shù)據(jù)分兩個(gè)階段:

  • 階段一: 內(nèi)核程序從磁盤、網(wǎng)卡等讀取數(shù)據(jù)到內(nèi)核空間緩存區(qū);
  • 階段二: 用戶程序從內(nèi)核空間緩存拷貝數(shù)據(jù)到用戶空間。

緩存 IO 的缺點(diǎn):

數(shù)據(jù)在傳輸過程中需要在應(yīng)用程序地址空間和內(nèi)核空間進(jìn)行多次數(shù)據(jù)拷貝操作,這些數(shù)據(jù)拷貝操作所帶來的CPU以及內(nèi)存開銷非常大。

同步阻塞

用戶空間的應(yīng)用程序執(zhí)行一個(gè)系統(tǒng)調(diào)用,這會(huì)導(dǎo)致應(yīng)用程序阻塞,什么也不干,直到數(shù)據(jù)準(zhǔn)備好,并且將數(shù)據(jù)從內(nèi)核復(fù)制到用戶進(jìn)程,最后進(jìn)程再處理數(shù)據(jù),在等待數(shù)據(jù)到處理數(shù)據(jù)的兩個(gè)階段,整個(gè)進(jìn)程都被阻塞,不能處理別的網(wǎng)絡(luò)IO。

  • 調(diào)用應(yīng)用程序處于一種不再消費(fèi) CPU 而只是簡(jiǎn)單等待響應(yīng)的狀態(tài),因此從處理的角度來看,這是非常有效的。

這也是最簡(jiǎn)單的IO模型,在通常FD較少、就緒很快的情況下使用是沒有問題的。

同步非阻塞

非阻塞的系統(tǒng)調(diào)用調(diào)用之后,進(jìn)程并沒有被阻塞,內(nèi)核馬上返回給進(jìn)程,如果數(shù)據(jù)還沒準(zhǔn)備好,此時(shí)會(huì)返回一個(gè)error。

  • 進(jìn)程在返回之后,可以干點(diǎn)別的事情,然后再發(fā)起系統(tǒng)調(diào)用。
  • 重復(fù)上面的過程,循環(huán)往復(fù)的進(jìn)行系統(tǒng)調(diào)用。這個(gè)過程通常被稱之為輪詢。
  • 輪詢檢查內(nèi)核數(shù)據(jù),直到數(shù)據(jù)準(zhǔn)備好,再拷貝數(shù)據(jù)到進(jìn)程,進(jìn)行數(shù)據(jù)處理。
  • 需要注意,拷貝數(shù)據(jù)整個(gè)過程,進(jìn)程仍然是屬于阻塞的狀態(tài)。
  • 這種方式在編程中對(duì)Socket設(shè)置O_NONBLOCK即可。

IO多路復(fù)用

IO多路復(fù)用,這是一種進(jìn)程預(yù)先告知內(nèi)核的能力,讓內(nèi)核發(fā)現(xiàn)進(jìn)程指定的一個(gè)或多個(gè)IO條件就緒了,就通知進(jìn)程。

使得一個(gè)進(jìn)程能在一連串的事件上等待。

IO復(fù)用的實(shí)現(xiàn)方式目前主要有Select、Poll和Epoll。

偽代碼描述IO多路復(fù)用:

while(status == OK) { // 不斷輪詢
ready_fd_list = io_wait(fd_list); //內(nèi)核緩沖區(qū)是否有準(zhǔn)備好的數(shù)據(jù)
for(fd in ready_fd_list) {
data = read(fd) // 有準(zhǔn)備好的數(shù)據(jù)讀取到用戶緩沖區(qū)
process(data)
}
}

信號(hào)驅(qū)動(dòng)

首先我們?cè)试SSocket進(jìn)行信號(hào)驅(qū)動(dòng)IO,并安裝一個(gè)信號(hào)處理函數(shù),進(jìn)程繼續(xù)運(yùn)行并不阻塞。

當(dāng)數(shù)據(jù)準(zhǔn)備好時(shí),進(jìn)程會(huì)收到一個(gè)SIGIO信號(hào),可以在信號(hào)處理函數(shù)中調(diào)用I/O操作函數(shù)處理數(shù)據(jù)。

流程如下:

  • 開啟套接字信號(hào)驅(qū)動(dòng)IO功能
  • 系統(tǒng)調(diào)用Sigaction執(zhí)行信號(hào)處理函數(shù)(非阻塞,立刻返回)
  • 數(shù)據(jù)就緒,生成Sigio信號(hào),通過信號(hào)回調(diào)通知應(yīng)用來讀取數(shù)據(jù)

此種IO方式存在的一個(gè)很大的問題:Linux中信號(hào)隊(duì)列是有限制的,如果超過這個(gè)數(shù)字問題就無法讀取數(shù)據(jù)

異步非阻塞

異步IO流程如下所示:

  • 當(dāng)用戶線程調(diào)用了aio_read系統(tǒng)調(diào)用,立刻就可以開始去做其它的事,用戶線程不阻塞
  • 內(nèi)核就開始了IO的第一個(gè)階段:準(zhǔn)備數(shù)據(jù)。當(dāng)內(nèi)核一直等到數(shù)據(jù)準(zhǔn)備好了,它就會(huì)將數(shù)據(jù)從內(nèi)核內(nèi)核緩沖區(qū),拷貝到用戶緩沖區(qū)
  • 內(nèi)核會(huì)給用戶線程發(fā)送一個(gè)信號(hào),或者回調(diào)用戶線程注冊(cè)的回調(diào)接口,告訴用戶線程Read操作完成了
  • 用戶線程讀取用戶緩沖區(qū)的數(shù)據(jù),完成后續(xù)的業(yè)務(wù)操作

相對(duì)于同步IO,異步IO不是順序執(zhí)行。

  • 用戶進(jìn)程進(jìn)行aio_read系統(tǒng)調(diào)用之后,無論內(nèi)核數(shù)據(jù)是否準(zhǔn)備好,都會(huì)直接返回給用戶進(jìn)程,然后用戶態(tài)進(jìn)程可以去做別的事情。

等到數(shù)據(jù)準(zhǔn)備好了,內(nèi)核直接復(fù)制數(shù)據(jù)給進(jìn)程,然后從內(nèi)核向進(jìn)程發(fā)送通知。

對(duì)比信號(hào)驅(qū)動(dòng)IO,異步IO的主要區(qū)別在于:

  • 信號(hào)驅(qū)動(dòng)由內(nèi)核告訴我們何時(shí)可以開始一個(gè)IO操作(數(shù)據(jù)在內(nèi)核緩沖區(qū)中),而異步IO則由內(nèi)核通知IO操作何時(shí)已經(jīng)完成(數(shù)據(jù)已經(jīng)在用戶空間中)。

異步IO又叫做事件驅(qū)動(dòng)IO,在Unix中,為異步方式訪問文件定義了一套庫(kù)函數(shù),定義了AIO的一系列接口。

  • 使用aio_read或者aio_write發(fā)起異步IO操作,使用aio_error檢查正在運(yùn)行的IO操作的狀態(tài)。

目前Linux中AIO的內(nèi)核實(shí)現(xiàn)只對(duì)文件IO有效,如果要實(shí)現(xiàn)真正的AIO,需要用戶自己來實(shí)現(xiàn)。

目前有很多開源的異步IO庫(kù),例如libevent、libev、libuv。

Java網(wǎng)絡(luò)IO模型

BIO

BIO是一個(gè)典型的網(wǎng)絡(luò)編程模型,是通常我們實(shí)現(xiàn)一個(gè)服務(wù)端程序的方法,對(duì)應(yīng)Linux內(nèi)核的同步阻塞IO模型,發(fā)送數(shù)據(jù)和接收數(shù)據(jù)的過程如下所示:

步驟如下:

  • 主線程accept請(qǐng)求
  • 請(qǐng)求到達(dá),創(chuàng)建新的線程來處理這個(gè)套接字,完成對(duì)客戶端的響應(yīng)
  • 主線程繼續(xù)accept下一個(gè)請(qǐng)求

服務(wù)端處理偽代碼如下所示:

這是經(jīng)典的一個(gè)連接對(duì)應(yīng)一個(gè)線程的模型,之所以使用多線程,主要原因在于socket.accept()、socket.read()、socket.write()三個(gè)主要函數(shù)都是同步阻塞的。

當(dāng)一個(gè)連接在處理I/O的時(shí)候,系統(tǒng)是阻塞的,如果是單線程的話必然就阻塞,但CPU是被釋放出來的,開啟多線程,就可以讓CPU去處理更多的事情。

其實(shí)這也是所有使用多線程的本質(zhì):

利用多核,當(dāng)I/O阻塞時(shí),但CPU空閑的時(shí)候,可以利用多線程使用CPU資源。

當(dāng)面對(duì)十萬甚至百萬級(jí)連接的時(shí)候,傳統(tǒng)的BIO模型是無能為力的。

隨著移動(dòng)端應(yīng)用的興起和各種網(wǎng)絡(luò)游戲的盛行,百萬級(jí)長(zhǎng)連接日趨普遍,此時(shí),必然需要一種更高效的I/O處理模型。

NIO

JDK1.4開始引入了NIO類庫(kù),主要是使用Selector多路復(fù)用器來實(shí)現(xiàn)。

Selector在Linux等主流操作系統(tǒng)上是通過IO復(fù)用Epoll實(shí)現(xiàn)的。

NIO的實(shí)現(xiàn)流程,類似于Select:

  • 創(chuàng)建ServerSocketChannel監(jiān)聽客戶端連接并綁定監(jiān)聽端口,設(shè)置為非阻塞模式
  • 創(chuàng)建Reactor線程,創(chuàng)建多路復(fù)用器(Selector)并啟動(dòng)線程
  • 將ServerSocketChannel注冊(cè)到Reactor線程的Selector上,監(jiān)聽Accept事件
  • Selector在線程run方法中無線循環(huán)輪詢準(zhǔn)備就緒的Key
  • Selector監(jiān)聽到新的客戶端接入,處理新的請(qǐng)求,完成TCP三次握手,建立物理連接
  • 將新的客戶端連接注冊(cè)到Selector上,監(jiān)聽讀操作,讀取客戶端發(fā)送的網(wǎng)絡(luò)消息
  • 客戶端發(fā)送的數(shù)據(jù)就緒則讀取客戶端請(qǐng)求,進(jìn)行處理

簡(jiǎn)單處理模型是用一個(gè)單線程死循環(huán)選擇就緒的事件,會(huì)執(zhí)行系統(tǒng)調(diào)用(Linux 2.6之前是Select、Poll,2.6之后是Epoll,Windows是IOCP),還會(huì)阻塞的等待新事件的到來。

新事件到來的時(shí)候,會(huì)在Selector上注冊(cè)標(biāo)記位,標(biāo)示可讀、可寫或者有連接到來,簡(jiǎn)單處理模型的偽代碼如下所示:

NIO由原來的阻塞讀寫(占用線程)變成了單線程輪詢事件,找到可以進(jìn)行讀寫的網(wǎng)絡(luò)描述符進(jìn)行讀寫。

除了事件的輪詢是阻塞的(沒有可干的事情必須要阻塞),剩余的I/O操作都是純CPU操作,沒有必要開啟多線程。

并且由于線程的節(jié)約,連接數(shù)大的時(shí)候因?yàn)榫€程切換帶來的問題也隨之解決,進(jìn)而為處理海量連接提供了可能。

AIO

JDK1.7引入NIO2.0,提供了異步文件通道和異步套接字通道的實(shí)現(xiàn)。

  • 其底層在Windows上是通過IOCP實(shí)現(xiàn),在Linux上是通過IO復(fù)用Epoll來模擬實(shí)現(xiàn)的。

在JAVA NIO框架中,Selector它負(fù)責(zé)代替應(yīng)用查詢中所有已注冊(cè)的通道到操作系統(tǒng)中進(jìn)行IO事件輪詢、管理當(dāng)前注冊(cè)的通道集合,定位發(fā)生事件的通道等操作。

但是在JAVA AIO框架中,由于應(yīng)用程序不是輪詢方式,而是訂閱-通知方式,所以不再需要Selector(選擇器)了,改由Channel通道直接到操作系統(tǒng)注冊(cè)監(jiān)聽 。

JAVA AIO框架中,只實(shí)現(xiàn)了兩種網(wǎng)絡(luò)IO通道:

  • AsynchronousServerSocketChannel(服務(wù)器監(jiān)聽通道)
  • AsynchronousSocketChannel(Socket套接字通道)。

具體過程如下所示:

  • 創(chuàng)建AsynchronousServerSocketChannel,綁定監(jiān)聽端口
  • 調(diào)用AsynchronousServerSocketChannel的accpet方法,傳入自己實(shí)現(xiàn)的CompletionHandler,包括上一步,都是非阻塞的
  • 連接傳入,回調(diào)CompletionHandler的completed方法,在里面,調(diào)用AsynchronousSocketChannel的read方法,傳入負(fù)責(zé)處理數(shù)據(jù)的CompletionHandler
  • 數(shù)據(jù)就緒,觸發(fā)負(fù)責(zé)處理數(shù)據(jù)的CompletionHandler的completed方法,繼續(xù)做下一步處理即可
  • 寫入操作類似,也需要傳入CompletionHandler

責(zé)任編輯:武曉燕 來源: 月伴飛魚
相關(guān)推薦

2024-07-26 10:23:52

2022-01-05 08:30:31

BIONIO AIO

2022-04-12 08:00:17

socket 編程網(wǎng)絡(luò)編程網(wǎng)絡(luò) IO 模型

2023-02-27 07:22:53

RPC網(wǎng)絡(luò)IO

2020-09-23 12:32:18

網(wǎng)絡(luò)IOMySQL

2020-10-21 09:17:52

Redis面試內(nèi)存

2020-11-05 13:12:47

紅黑樹

2024-01-05 08:00:00

大型語言模型自然語言處理BERT

2021-03-10 07:20:45

網(wǎng)絡(luò)IO同步

2016-08-04 15:10:12

服務(wù)器虛擬化網(wǎng)絡(luò)

2017-07-07 16:36:28

BIOIO模型 NIO

2016-11-04 12:51:46

Unix網(wǎng)絡(luò)IO 模型

2021-09-30 07:26:15

磁盤IO網(wǎng)絡(luò)

2019-11-08 15:11:03

Java架構(gòu)數(shù)據(jù)

2021-07-09 08:55:23

LinuxTCPIP

2019-10-24 10:25:32

Kubernetes網(wǎng)絡(luò)集群

2023-05-10 08:26:33

IO模型API

2023-04-07 08:54:19

IO流服務(wù)端通信

2010-01-13 10:52:46

Rational Ro

2023-03-02 08:12:21

Linux系統(tǒng)IO
點(diǎn)贊
收藏

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