Linux 網絡編程的5種IO模型:阻塞IO與非阻塞IO


背景

整理之前學習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狀態。

%% 時序圖 sequenceDiagram title : 阻塞IO participant application participant kernel Note right of application: 應用程序調用系統調用 application ->> kernel: recv() kernel ->> kernel: 判斷數據是否就緒(否則等待) kernel ->> kernel: 准備好數據,拷貝到用戶空間 kernel ->> application: 拷貝完成,喚醒應用程序

代碼如下:

	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循環這種方式來讀取數據。

%% 時序圖 sequenceDiagram title : 非阻塞IO participant application participant kernel Note right of application: 應用程序調用系統調用 application ->> kernel: fread kernel ->> kernel: 數據未就緒 kernel ->> application: 返回EAGAIN application ->> application: 判斷返回值 application ->> kernel: fread kernel ->> kernel: 准備好數據,拷貝到用戶空間 kernel ->> application: 拷貝完成,返回成功

代碼如下:

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");
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM