目錄
前言:
五種IO模型
什么是IO
IO模型
非阻塞IO
前言:
前文我們已經將網絡的基本原理介紹完了,都是通過圍繞TCP/IP四層協議,將應用層,傳輸層,網絡層,數據鏈路層全部介紹完畢,至于部分小主題,比如ARP欺騙,HTTP協議的原理,cookie以及session等內容,我們放在后面介紹。
本文以及之后的內容,主要通過是介紹IO相關的問題,通過IO模型的介紹,從而引出多路復用的具體內容,通過介紹select,poll,epoll,最后再單獨介紹一下Reactor,網絡的基本原理我們就介紹完了。
那么廢話不多說,我們直接進入五種IO模型。
五種IO模型
什么是IO
對于IO這個話題,我們從語言階段,一直到了現在都經常談論,因為IO問題不管在哪個階段都是非常重要的,從C語言階段的printf scanf,到文件操作的read和write涉及到了IO,即便是Linux,我們從一開始的文件系統,引出了文件描述符,到后面的網絡也是一直使用文件描述符的概念,足以看出IO在編程中的重要性了。
那么問題來了,一開始我們只是粗略的將IO認定為是輸入輸出,今天我們進一步探討,當我們第一次使用scanf的時候,我們發現只要我們不輸入,系統就會阻塞住,系統在干什么呢?
系統在等我們輸入,所以IO有一個非常重要的過程是:等待。當我們輸入數據之后,系統也收到了數據也就不再等待,就執行了下一步操作了。這個過程還有讀取的操作,或者是寫入的操作,但是不管怎么說,都是從發送緩沖區/接收緩沖區拷貝數據到應用層的緩沖區。
所以實際上的IO = 等待 + 拷貝。
而我們從一開始的學習到現在,使用到的基本上都是阻塞IO,自然也就存在非阻塞的IO,我們放在IO模型里面介紹。那么我們在MySQL中介紹索引的時候,我們提及到了IO次數如果多了,就一定會降低效率,所以我們就要使用高效的IO,那么高效的IO實際上減少了等待操作在IO中的比例。
IO模型
對于IO模型我們拿釣魚舉例子,假設有五個人,分別在魚塘中釣魚,張三拿個魚竿就坐在那里一直等待,當別人問他什么事,他也不理睬,這是第一種IO模型;李四拿個魚竿放在那里,就不管了,轉身去做自己的事兒了,然后定期來查看魚竿的情況,這是第二種IO模型;王五拿個魚竿,釣魚的時候放個鈴鐺,當魚上鉤了,就放在自己手里的事兒,然后釣魚,這是第三種IO模型;趙六就很有說法了,拿了一卡車的魚竿過來了,同時使用很多的魚竿,然后一個一個的檢查魚竿情況,這是第四種IO模型;田七的釣魚操作是派遣一個人,讓這個人像張三一樣,完成釣魚的等待和釣操作,自己就轉身去干其他事兒了,也就是田七只負責吃的操作,這是第五種IO模型。
那么上述都是一個口語化的介紹,實際上的名稱叫做:
第一種,阻塞IO,比如我們常見的read recvfrom scanf都是阻塞IO的代表,如果系統的數據還沒有準備好,強行非阻塞的話就會返回錯誤碼11,一會兒我們可以實驗。
第二種,非阻塞IO,非阻塞IO顧名思義,IO的同時干自己的事兒,那么這意味著需要反反復復的嘗試讀寫文件描述符,這個過程稱為輪詢,但是因為會反反復復的操作文件操作符,所以會對CPU資源造成較大的浪費,一般根據特定情況使用:
第三種,信號驅動IO,同王五一樣,鈴鐺就像信號,內核將數據處理好之后,就使用信號SIGIO通知應用程序,這種IO模型實際上也是非阻塞IO模型:
第四種,IO多路轉接,它實際上是阻塞IO的plus版本,也就是說張三一個人進行阻塞IO的時候,趙六已經創建了100個張三的分身了,所以實際上的流程圖也就是IO的流程圖,那么多路轉接也是我們后面的主題,介紹select poll epoll就是從這里引出的:
第五種,異步IO,異步IO實際上就是先讓內核吧數據報準備好,再進行拷貝的,最后OS給應用程序發信號通知準備好了,這個過程田七只是作為發起IO,但是等的過程交給了內核,然后他再來負責拷貝,那么被稱為異步IO就是因為發出 I/O 請求的線程不會阻塞等待,后續的 I/O 完成由內核負責異步處理,應用程序通過信號、回調或輪詢某種狀態(如 aio_error()
)來獲取 I/O 是否完成,所以它是異步的。
那么以上五種IO中,第四種IO多路復用的效率是最高的,因為大大減少了等待了時間,這也是之后我們為什么圍繞它展開的理由。
非阻塞IO
對于阻塞IO來說,是我們最常見,也是最經常使用的IO模型,但是如果我們今天想嘗試一下非阻塞IO怎么辦呢?我們可以使用函數fcntl:
第一個參數是對應的文件描述符,第二個參數是執行的控制命令,第三個參數取決于第三個參數,可以忽略。
一般常用的cmd有F_SETFL,F_GETFL,GET用來獲取狀態標志,SET用來獲取標志,比如我們想要將一個文件描述符設置為非阻塞的就可以這樣操作:
void SetNoBlock()
{int n = ::fcntl(0, F_GETFL);if(n < 0)std::cout << "Error" << std::endl;else fcntl(0, F_SETFL, n | O_NONBLOCK);
}int main()
{SetNoBlock();while(true){char buffer[1024];ssize_t n = ::read(0, buffer, sizeof(buffer));if(n > 0){std::string str = buffer;std::cout << str << std::endl;}else if(n == 0){std::cout << "read done!" << std::endl;break;}else{// errno == 11 EWOULDBLOCK EAGAINif(errno == EWOULDBLOCK){continue;}std::cout << "GG" << std::endl;sleep(1);}}return 0;
}
我們會發現如果我們強行讓read這種阻塞IO變成非阻塞IO,就會導致返回的值是負數,所以這個時候就有這種情況:發生錯誤是因為read本身出錯還是因為非阻塞下沒有數據可讀導致read不會阻塞直接返回-1?所以我們可使用EWOULDBLOCK比較一下,當然了,它的本質是11,也是EAGAIN:
那么IO模型先到這里,算是一個開胃小菜~
感謝閱讀!