背景
整理之前學習socket編程的時候復習到了多路復用,搜索了有關資料,了解到多路復用也有局限性,本着打破砂鍋問到底的精神,最終找到了關於IO模型的知識點。
在《Unix網絡編程》一書中提到了五種IO模型,分別是:阻塞IO、非阻塞IO、多路復用IO、信號驅動IO以及異步IO。
我們在這里就介紹並實現這5種模型。介紹之前,請允許我引用某段比喻
阻塞IO, 給女神發一條短信, 說我來找你了, 然后就默默的一直等着女神下樓, 這個期間除了等待你不會做其他事情, 屬於備胎做法.
非阻塞IO, 給女神發短信, 如果不回, 接着再發, 一直發到女神下樓, 這個期間你除了發短信等待不會做其他事情, 屬於專一做法.
IO多路復用, 是找一個宿管大媽來幫你監視下樓的女生, 這個期間你可以些其他的事情. 例如可以順便看看其他妹子,玩玩王者榮耀, 上個廁所等等. IO復用又包括 select, poll, epoll 模式. 那么它們的區別是什么?
1) select大媽 每一個女生下樓, select大媽都不知道這個是不是你的女神, 她需要一個一個詢問, 並且select大媽能力還有限, 最多一次幫你監視1024個妹子
2) poll大媽不限制盯着女生的數量, 只要是經過宿舍樓門口的女生, 都會幫你去問是不是你女神
3) epoll大媽不限制盯着女生的數量, 並且也不需要一個一個去問. 那么如何做呢? epoll大媽會為每個進宿舍樓的女生臉上貼上一個大字條,上面寫上女生自己的名字, 只要女生下樓了, epoll大媽就知道這個是不是你女神了, 然后大媽再通知你.
上面這些同步IO有一個共同點就是, 當女神走出宿舍門口的時候, 你已經站在宿舍門口等着女神的, 此時你屬於阻塞狀態接下來是異步IO的情況:
你告訴女神我來了, 然后你就去打游戲了, 一直到女神下樓了, 發現找不見你了, 女神再給你打電話通知你, 說我下樓了, 你在哪呢? 這時候你才來到宿舍門口。 此時屬於逆襲做法
阻塞IO 與 非阻塞IO
在Linux 系統編程中,我們 在 有關概念 章節中介紹了阻塞的概念。那么也很容易理解什么是阻塞IO與非阻塞IO。直接看圖
阻塞IO
最傳統的一種IO模型,即在讀寫數據過程中會發生阻塞現象。
當用戶線程發出IO請求之后,內核會去查看數據是否就緒,如果沒有就緒就會等待數據就緒,而用戶線程就會處於阻塞狀態,用戶線程交出CPU。當數據就緒之后,內核會將數據拷貝到用戶線程,並返回結果給用戶線程,用戶線程才解除block狀態。
代碼如下:
printf("Calling recv(). \n");
ret = recv(socket, recv_buf, sizeof(recv_buf), 0);
printf("Had called recv(). \n");
也許有人會說,可以采用多線程+ 阻塞IO 來解決效率問題,但是由於在多線程 + 阻塞IO 中,每個socket對應一個線程,這樣會造成很大的資源占用,並且尤其是對於長連接來說,線程的資源一直不會釋放,如果后面陸續有很多連接的話,就會造成性能上的瓶頸。
非阻塞IO
當用戶線程發起一個IO操作后,並不需要等待,而是馬上就得到了一個結果。如果結果是一個error時,它就知道數據還沒有准備好,於是它可以再次發送IO操作。一旦內核中的數據准備好了,並且又再次收到了用戶線程的請求,那么它馬上就將數據拷貝到了用戶線程,然后返回。
在非阻塞IO模型中,用戶線程需要不斷地詢問內核數據是否就緒,也就說非阻塞IO不會交出CPU,而會一直占用CPU。
對於非阻塞IO就有一個非常嚴重的問題,在while循環中需要不斷地去詢問內核數據是否就緒,這樣會導致CPU占用率非常高,因此一般情況下很少使用while循環這種方式來讀取數據。
代碼如下:
while(1)
{
printf("Calling recv(). \n");
ret = recv(socket, recv_buf, sizeof(recv_buf), 0);
if (EAGAIN == ret) {continue;}
else if(ret > -1) { break;}
printf("Had called recv(), retry.\n");
}
