IO模型及多路複用IO(select、poll、epoll)

4中IO模型
同步和異步的概念描述的是用戶線程與內核的交互方式:同步是指用戶線程發起IO請求後需要等待或者輪詢內核IO操作完成後才能繼續執行;而異步是指用戶線程發起IO請求後仍繼續執行,當內核IO操作完成後會通知用戶線程,或者調用用戶線程註冊的回調函數。
阻塞和非阻塞的概念描述的是用戶線程調用內核IO操作的方式:阻塞是指IO操作需要徹底完成後才返回到用戶空間;而非阻塞是指IO操作被調用後立即返回給用戶一個狀態值,無需等到IO操作徹底完成。
常見的IO模型有四種:
(1)同步阻塞IO(Blocking IO):即傳統的IO模型。(用戶線程在內核進行IO操作時被阻塞);
僞碼如下:{
read(socket, buffer);
process(buffer);
}

如下圖:用戶線程通過系統調用read發起IO讀操作,由用戶空間轉到內核空間。內核等到數據包到達後,然後將接收的數據拷貝到用戶空間,完成read操作。
這裏寫圖片描述

(2)同步非阻塞IO(Non-blocking IO):默認創建的socket都是阻塞的,非阻塞IO要求socket被設置爲NONBLOCK。
僞碼如下:
{
while(read(socket, buffer) != SUCCESS);
process(buffer);
}
同步非阻塞IO是在同步阻塞IO的基礎上,將socket設置爲NONBLOCK。這樣做用戶線程可以在發起IO請求後可以立即返回。如圖:由於socket是非阻塞的方式,因此用戶線程發起IO請求時立即返回。但並未讀取到任何數據,用戶線程需要不斷地發起IO請求,直到數據到達後,才真正讀取到數據,繼續執行。
這裏寫圖片描述
(3)IO多路複用(IO Multiplexing):即經典的Reactor設計模式,有時也稱爲異步阻塞IO。
IO多路複用模型是建立在內核提供的多路分離函數select基礎之上的,使用select函數可以避免同步非阻塞IO模型中輪詢等待的問題。用戶首先將需要進行IO操作的socket添加到select中,然後阻塞等待select系統調用返回。當數據到達時,socket被激活,select函數返回。用戶線程正式發起read請求,讀取數據並繼續執行。從流程上來看,使用select函數進行IO請求和同步阻塞模型沒有太大的區別,甚至還多了添加監視socket,以及調用select函數的額外操作,效率更差。但是,使用select以後最大的優勢是用戶可以在一個線程內同時處理多個socket的IO請求。用戶可以註冊多個socket,然後不斷地調用select讀取被激活的socket,即可達到在同一個線程內同時處理多個IO請求的目的。而在同步阻塞模型中,必須通過多線程的方式才能達到這個目的。
這裏寫圖片描述
(4)異步IO(Asynchronous IO):即經典的Proactor設計模式,也稱爲異步非阻塞IO。
在IO多路複用模型中,事件循環將文件句柄的狀態事件通知給用戶線程,由用戶線程自行讀取數據、處理數據。而在異步IO模型中,當用戶線程收到通知時,數據已經被內核讀取完畢,並放在了用戶線程指定的緩衝區內,內核在IO完成後通知用戶線程直接使用即可。
異步IO模型中,用戶線程直接使用內核提供的異步IO API發起read請求,且發起後立即返回,繼續執行用戶線程代碼。不過此時用戶線程已經將調用的AsynchronousOperation和CompletionHandler註冊到內核,然後操作系統開啓獨立的內核線程去處理IO操作。當read請求的數據到達時,由內核負責讀取socket中的數據,並寫入用戶指定的緩衝區中。最後內核將read的數據和用戶線程註冊的CompletionHandler分發給內部Proactor,Proactor將IO完成的信息通知給用戶線程(一般通過調用用戶線程註冊的完成事件處理函數),完成異步IO。

多路複用IO(select、poll、epoll)
I/O多路複用通過一種機制,可以監視多個描述符,一旦某個描述符就緒(一般是讀就緒或者寫就緒,就是這個文件描述符進行讀寫操作之前),能夠通知程序進行相應的讀寫操作。select(),poll(),epoll()都是I/O多路複用的機制。本質上都是同步I/O,因爲他們都需要在讀寫事件就緒後自己負責進行讀寫,也就是說這個讀寫過程是阻塞的,而異步I/O則無需自己負責進行讀寫,異步I/O的實現會負責把數據從內核拷貝到用戶空間。
與多線程和多進程相比,I/O 多路複用的最大優勢是系統開銷小,系統不需要建立新的進程或者線程,也不必維護這些線程和進程。
爲什麼使用多路複用IO?
IO多路複用是高性能網絡編程一個重要的手段。I/O 多路複用技術是爲了解決進程或線程阻塞到某個 I/O 系統調用而出現的技術,使進程不阻塞於某個特定的 I/O 系統調用。以前我們用多線程來處理併發的請求,現在可以只用單線程來實現。單線程,通過記錄跟蹤每個每個I/O流(sock)的狀態,來達到同時管理多個I/O流的目的,提高了服務器的吞吐能力。

1)、select 監視並等待多個文件描述符的屬性變化(可讀、可寫或錯誤異常)。調用後 select() 函數會阻塞,直到有描述符就緒(有數據可讀、可寫、或者有錯誤異常),或者超時( timeout 指定等待時間),函數才返回。當 select()函數返回後,可以通過遍歷 fdset,來找到就緒的描述符。
Demo程序:
While(1)
{
FD_ZERO(&rfds); // 清空
FD_SET(0, &rfds); // 標準輸入描述符 0 加入集合
FD_SET(fd, &rfds); // 有名管道描述符 fd 加入集合
// FD_SETSIZE 爲“

發佈了48 篇原創文章 · 獲贊 34 · 訪問量 18萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章