1. 程式人生 > 實用技巧 >Java 網路IO模型簡介

Java 網路IO模型簡介

之前自學以及在公司裡的一年多都沒有接觸過網路IO相關的知識,六月份二面的時候有位面試官問了我一些有關網路IO的相關問題,結果一臉懵逼。趁著現在有空,正好入門一下。

基礎概念

正式開始之前,需要鋪墊一些基本概念,以免接下來看到一臉懵逼。

我們都知道,在作業系統中,CPU負責執行指令,這些指令有些來自應用程式,有些是底層系統的自呼叫。有些指令是非常危險的,如清除記憶體,網路連線等等,如果錯誤呼叫的話有可能導致系統崩潰。因而CPU將指令分為特權指令和非特權指令,對於某些特定的指令,只需要作業系統及其相關模組進行呼叫。因而,根據這個特點,作業系統內部也劃分出了核心態使用者態

核心態

核心態擁有完全的底層資源控制權限,可以執行任何的CPU

指令,訪問任何記憶體地址,其佔有的處理機是不允許被搶佔的。

使用者態

使用者程式是執行在作業系統之上,這些程式執行時稱之為使用者態,使用者態下不能直接訪問底層硬體和記憶體地址,只能通過委託系統呼叫的方式來訪問底層硬體和記憶體。

使用者態到核心態如何切換

從使用者態切換到核心態有三種方式:

  • 系統呼叫:這是使用者態主動要求切換到核心態的一種方式。使用者程序通過系統呼叫申請使用作業系統提供的某些服務以便完成工作,比如,呼叫fork()指令實際上就是執行了一個建立新程序的系統呼叫。系統呼叫的機制其核心在於**使用了作業系統為使用者特別開放的一箇中斷來實現的,例如Linuxint 80h中斷;
  • 外設中斷
    :當外圍裝置完成使用者請求的操作後,會向CPU發出相應的中斷訊號。這時CPU會暫停執行下一條即將要執行的指令轉而去執行與中斷訊號對應的處理程式。如果先前執行的是使用者態下的指令,那麼這個切換過程就是使用者態轉為核心態。比如硬碟讀寫操作完成,系統會切換到硬碟讀寫的中斷處理程式中執行後續操作;
  • 異常:當CPU在執行執行處於使用者態的程式時,發生了一些不可知的異常,這個時候就會觸發由當前執行進行切換到處理此異常的核心相關程式中,也就是轉到了核心態,比如缺頁異常;

這三種是使用者態切換到核心態的主要方式,系統呼叫是主動的,後面兩種是被動的。

Linux的整體架構圖如下所示:

同步/非同步

同步/非同步關注的是訊息通訊機制。

同步:所謂同步,就是在發出一個功能呼叫時,在沒有得到結果之前,該呼叫就不返回。等前一件做完了才能做下一件事。

非同步:非同步的概念和同步相對。當一個非同步過程呼叫發出後,呼叫者若不能立刻得到結果,此時可以直接返回然後執行其他任務,等到獲得了結果之後通過狀態、通知或者回調等手段通知呼叫者。

同步、非同步一般發生在不同的執行緒/程序之間,如Thread1Thread2是同步執行還是非同步執行的。

阻塞和非阻塞

阻塞和非阻塞關注的是程式在等待呼叫結果時的狀態。

阻塞: 阻塞呼叫是指呼叫返回之前,當前執行緒會被掛起,只有當呼叫得到結果後才返回。

非阻塞:與阻塞相反,非阻塞呼叫是指在不能立即得到結果之前,該函式不會將當前執行緒阻塞,而是立即返回。

五種 IO 模型

IO一般分為磁碟IO網路IO,這裡我們主要關注網路IO。一次完整的網路IO過程如下所示:

從上圖可以看出,資料無論從網絡卡到使用者空間還是從使用者空間到網絡卡都需要經過核心。

阻塞IO模型

當應用程式呼叫一個 IO 函式,其底層會委託作業系統的recvfrom()去完成,當資料還沒有準備好時,revfrom會一直阻塞,等待資料準備好。當資料準備好後,從核心拷貝到使用者空間,recvfrom 返回成功,IO函式呼叫完成。過程如下所示:

阻塞IO模型的優點是程式設計簡單,但缺點是需要配合大量執行緒使用。應用程序沒接收一個連線,就需要為此連線建立一個執行緒來處理該連線上的讀寫任務。

非阻塞IO模型

呼叫程序在等待資料的過程中不會被阻塞,而是會不斷地輪詢檢視資料有沒有準備好。當資料準備好後,將資料從核心空間拷貝到使用者空間,完成IO函式的呼叫。等待資料的過程是非阻塞的,但資料拷貝時仍是阻塞的。過程如下所示:

非阻塞io的優點在於可以實現使用一個執行緒同時處理多個連線的需求,減少執行緒的大量使用。缺點在於要不斷地去輪詢檢查資料是否準備好,比較耗費CPU

IO複用模型

為了解決非阻塞IO不斷輪詢導致CPU佔用升高的問題,出現了IO複用模型。IO複用中,使用其他執行緒幫助去檢查多個執行緒資料的完成情況,提高效率。

Linux中提供了selectpollepoll三種方式來實現IO複用。一個執行緒可以對多個IO埠進行監聽,當有讀寫事件產生時會分發到具體的執行緒進行處理。過程如下所示:

IO複用只需要阻塞在selectpoll或者epoll,可以同時處理和管理多個連線。缺點是當 selectpoll或者epoll 管理的連線數過少時,這種模型將退化成阻塞 IO 模型。並且還多了一次系統呼叫:一次 selectpoll或者epoll 一次 recvfrom

訊號驅動IO模型

應用程式可以建立一個訊號驅動程式SIGIO,當資料沒有處理好時,應用程式繼續執行,不會被阻塞。當資料準備好之後,作業系統嚮應用程式傳送訊號,之後訊號驅動程式就會執行,在訊號處理函式中呼叫 IO函式處理資料。過程如下所示:

訊號驅動IO模型的優點在於非阻塞,缺點在於序列處理訊號驅動程式,當前一個SIGIO沒有被處理的情況下,後一個訊號也不能被處理。在訊號量大的時候會導致後面的訊號不能被及時感知。

非同步IO模型

相比於同步IO,非同步IO不是順序執行的。應用程序在執行aio_read系統呼叫之後,無論資料是否準備好,都會直接返回給使用者程序,然後應用程序可以去做別的事情。當資料準備好之後,核心直接複製資料給使用者程序,然後核心向程序傳送通知。過程如下:

訊號驅動IO模型中核心通知應用程序資料何時準備好,而在非同步IO模型中核心將資料複製完成之後告知應用程序IO操作已完成。

在非同步IO模型中,應用程序呼叫aio_read以及資料被拷貝到使用者空間這兩個過程都是非阻塞的。

總結

IO模型公有五種,前四種模型區別在於第一部分,即系統呼叫,但是第二部分都是一樣的,即將資料從核心空間拷貝到使用者空間這個過程,程序阻塞於redvfrom的呼叫。而最後一種,非同步IO模型,在系統呼叫和資料拷貝過程都是非阻塞的。

參考

理解Linux使用者態和核心態

執行緒,程序,協程, 併發,並行,同步,非同步概念解析

伺服器網路程式設計之 IO 模型

圖解Java IO模型(一)