IO
在Linux網絡環境里,IO(Input/Output)指的是網絡數據在系統與外部網絡(像其他設備、服務器或者客戶端)之間進行傳輸的過程。
它是網絡編程和系統性能優化的核心內容。
- IO :INPUT和OUTPUT(站在進程角度)
- 關于IO,read和write都是阻塞式的IO
- 網絡傳輸數據問題,本質上就是IO問題
- 如何更好的理解IO問題,就需要更好的理解什么叫做高效的IO?
- 首先明確,任何通信場景其IO通信場景,效率上一定是會有上限的 。
- 抽象來說:IO=等+拷貝
- 在拷貝的角度來說,我們需要充分利用硬件資源以及網絡資源
- 在“等”的角度來說,我們需要減少IO中“等”的權重
網絡IO的核心流程
網絡IO包含兩個關鍵階段:
- 數據就緒:數據從網絡傳輸到網卡,再由網卡傳至內核緩沖區。
- 數據拷貝:數據從內核緩沖區復制到用戶空間的應用程序。
網絡IO模型
Linux支持多種網絡IO模型,它們的主要差異在于如何處理這兩個階段(阻塞、非阻塞、同步、異步)。
常見的網絡IO模型有:
- 阻塞IO(Blocking IO)
- 非阻塞IO(Non-blocking IO)
- IO多路復用(IO Multiplexing)
- 信號驅動IO(Signal-driven IO)
- 異步IO(Asynchronous IO)
注意:
前四種IO模型都屬于同步IO,等的方式不同,但還是自己在深度參與IO過程,只要是自己參與了IO過程,就是同步IO
釣魚例子
- 張三:眼睛盯著魚漂,魚漂動了,釣魚
- 阻塞IO
- 李四 :查看魚漂是否動了,如果沒有,就看手機刷抖音,期間不斷查看魚漂是否動了
- 非阻塞IO(輪巡)
- 王五 : 帶來一車魚竿插在岸邊,來回檢測哪一個魚竿上的魚漂動了
- IO多路復用
- 趙六 :在魚漂上系上一個鈴鐺,開始釣魚后刷抖音,直到鈴鐺響起,開始釣上魚。
- 信號驅動IO
- 田七:我雇傭一個人給我釣魚,釣到的魚給我。
- 異步IO
上述例子中:
- 魚竿:文件描述符
- 釣魚佬:進程
1. 阻塞IO(最基礎的模型)
- 工作方式:應用程序調用系統調用后會被阻塞,一直等到數據就緒并被復制到用戶空間,或者出現錯誤時才會返回。所有的套接字,默認
都是阻塞方式 - 特點:編程邏輯簡單,但同一時間只能處理一個連接,容易造成性能瓶頸。
2. 非阻塞IO
- 工作方式:應用程序調用系統調用后會立即返回。如果數據未就緒,會返回
EWOULDBLOCK
錯誤。應用程序需要不斷輪詢來檢查數據是否就緒。 - 特點:非阻塞IO能夠避免線程長時間阻塞,但往往需要程序員循環的方式反復嘗試讀寫文件描述符,這個過程稱為輪詢.這對CPU來說是較大的浪費,一般只有特定場景下才使用.
3. IO多路復用(高性能服務器的核心)
- 工作方式:借助
select
、poll
、epoll
等系統調用,一個進程可以同時監視多個文件描述符(FD)。當某個FD的數據就緒時,就會通知應用程序進行處理。 - 優勢:能夠用單線程處理大量并發連接,顯著減少了系統開銷。
雖然從流程圖上看起來和阻塞IO類似.實際上最核心在于IO多路轉接能夠同時等待多個文件描述符的就緒狀態.
4. 信號驅動IO
- 工作方式:應用程序先通過
sigaction
注冊信號處理函數,然后繼續執行其他任務。當數據就緒時,內核會發送SIGIO
信號,應用程序在信號處理函數中進行數據讀取操作。 - 特點:屬于異步通知模式,但數據拷貝階段仍然是同步的。
5. 異步IO(真正的異步模型)
- 工作方式:應用程序通過
aio_read
等接口發起異步IO請求,然后繼續執行后續操作。內核會自動完成數據的讀取和拷貝工作,完成后通過回調函數或者信號通知應用程序。 - 特點:整個IO過程都是異步的,極大地提高了系統的并發處理能力。
小結: - 任何IO過程中,都包含兩個步驟.第一是等待,第二是拷貝.
- 而且在實際的應用場景中,等待消耗的時間往往都遠遠高于拷貝的時間.讓IO更高效,最核心的辦法就是讓等待的時間盡量少
高級IO重要概念
在這里,我們要強調幾個概念
同步通信 vs 異步通信(synchronous communication/ asynchronous communication)
同步和異步是消息通信機制。
- 所謂同步,就是在發出一個調用時,在沒有得到結果之前,該調用就不返回。但是一旦調用返回,就得到返回值了;換句話說,就是由調用者主動等待這個調用的結果;
- 異步則是相反,調用在發出之后,這個調用就直接返回了,所以沒有返回結果;換句話說,當一個異步過程調用發出后,調用者不會立刻得到結果;而是在調用發出后,被調用者通過狀態、通知來通知調用者,或通過回調函數處理這個調用。
另外,我們回憶在講多進程多線程的時候,也提到同步和互斥。這里的同步通信和進程之間的同步是完全不相干的概念。
- 進程/線程同步也是進程/線程之間直接的制約關系。
- 是為完成某種任務而建立的兩個或多個線程,這個線程需要在某些位置上協調他們的工作次序而等待、傳遞信息所產生的制約關系。尤其是在訪問臨界資源的時候。
同學們以后在看到“同步”這個詞,一定要先搞清楚大背景是什么。這個同步,是同步通信異步通信的同步,還是同步與互斥的同步。
阻塞 vs 非阻塞
阻塞和非阻塞關注的是程序在等待調用結果(消息,返回值)時的狀態。
- 阻塞調用是指調用結果返回之前,當前線程會被掛起。調用線程只有在得到結果之后才會返回。
- 非阻塞調用指在不能立刻得到結果之前,該調用不會阻塞當前線程。
其他高級IO
非阻塞IO,紀錄鎖,系統V流機制,I/O多路轉接(也叫I/O多路復用),readv和writev函數以及存儲映射IO(mmap),這些統稱為高級IO。
我們此處重點討論的是I/O多路轉接
非阻塞IO
fcntl
一個文件描述符,默認都是阻塞IO。
函數原型如下:
#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd, ... /* arg */ );
fcntl的cmd的值不同,后面追加的參數也不相同。
fcntl函數有5種功能:
- 復制一個現有的描述符(cmd=F_DUPFD)。
- 獲得/設置文件描述符標記(cmd=F_GETFD或F_SETFD)。
- 獲得/設置文件狀態標記(cmd=F_GETFL或F_SETFL)。
- 獲得/設置異步I/O所有權(cmd=F_GETOWN或F_SETOWN)。
- 獲得/設置記錄鎖(cmd=F_GETLK,F_SETLK或F_SETLKW)。
我們此處只是用第三種功能,獲取/設置文件狀態標記,就可以將一個文件描述符設置為非阻塞。
實現函數SetNoBlock
基于fcntl,我們實現一個SetNoBlock函數,將文件描述符設置為非阻塞。
void SetNoBlock(int fd) {int fl = fcntl(fd, F_GETFL);if (fl < 0) {perror("fcntl");return;}fcntl(fd, F_SETFL, fl | O_NONBLOCK);
}
- 使用F_GETFL將當前的文件描述符的屬性取出來(這是一個位圖)。
- 然后再使用F_SETFL將文件描述符設置回去。設置回去的同時,加上一個O_NONBLOCK參數。
輪詢方式讀取標準輸入
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>void SetNoBlock(int fd) {int fl = fcntl(fd, F_GETFL);if (fl < 0) {perror("fcntl");return;}fcntl(fd, F_SETFL, fl | O_NONBLOCK);
}int main() {SetNoBlock(0);while (1) {char buf[1024] = {0};ssize_t read_size = read(0, buf, sizeof(buf) - 1);if (read_size < 0) {perror("read");sleep(1);continue;}printf("input:%s\n", buf);}return 0;
}