上篇文章 提到,單執行緒阻塞io 是在等待資料和拷貝資料兩個階段都是阻塞狀態,而程序/執行緒池的方案能處理的連線數,緩解的呼叫io也很有限。因此出現非阻塞io(non-blocking io)
2.1 實現原理
linux下,可以通過設定socket使其變為non-blocking。當對乙個non-blocking socket執行讀操作時,流程是這個樣子:
從圖中可以看出,當使用者程序發出read操作時,如果kernel中的資料還沒有準備好,那麼它並不會block使用者程序,而是立刻返回乙個error。從使用者程序角度講 ,它發起乙個read操作後,並不需要等待,而是馬上就得到了乙個結果。使用者程序判斷結果是乙個error時,它就知道資料還沒有準備好,於是它可以再次傳送read操作。一旦kernel中的資料準備好了,並且又再次收到了使用者程序的system call,那麼它馬上就將資料拷貝到了使用者記憶體,然後返回。
所以,在非阻塞式io中,使用者程序其實是需要不斷的主動詢問kernel資料準備好了沒有,和阻塞型介面的顯著差異在於,在被呼叫之後立即返回。
使用如下的函式可以將某控制代碼fd設為非阻塞狀態:
fcntl( fd, f_setfl, o_nonblock );
下面將給出只用單執行緒實現方式,雖然是單執行緒,但能夠同時從多個連線中檢測資料是否送達,並且接受資料的模型。
圖5 使用非阻塞的接收資料模型
在非阻塞狀態下,recv() 介面在被呼叫後立即返回,返回值代表了不同的含義。如在本例中,可以看到伺服器執行緒可以通過迴圈呼叫recv()介面,可以在單個執行緒內實現對所有連線的資料接收工作。recv() 返回值大於 0,表示接受資料完畢,返回值即是接受到的位元組數;
recv() 返回 0,表示連線已經正常斷開;
recv() 返回 -1,且 errno 等於 eagain,表示 recv 操作還沒執行完成;
recv() 返回 -1,且 errno 不等於 eagain,表示 recv 操作遇到系統錯誤 errno。
2.2 弊端
但是上述模型絕不被推薦。因為,迴圈呼叫recv()將大幅度推高cpu 佔用率,如果要使用此方案,最起碼使用者態要手動執行sleep(),從而緩解cpu。
此外,在這個方案中recv()更多的是起到檢測「操作是否完成」的作用,實際作業系統提供了更為高效的檢測「操作是否完成「作用的介面,例如select()多路復用模式,可以一次檢測多個連線是否活躍。
在下篇文章中,將分享io復用模式。
高階IO 非阻塞IO
通常我們在設定介面是阻塞還是非阻塞的時候,有兩種方案 1 將檔案描述符設定為非阻塞式檔案描述符 2 通過傳遞特殊選項,讓介面本身以非阻塞方式呼叫。乙個檔案描述符,預設都是阻塞io。fcntl的函式原型如下 fcntl可以改變已經開啟的檔案性質。針對cmd的值,fcntl能夠接受第三個引數arg 可變...
高階IO 非阻塞
一 設定檔案描述符標誌 設定檔案描述符標誌 void set fl int fd,int flags 二 清除檔案描述符標誌 清除檔案描述符標誌 void clr fl int fd,int flags 三 主函式 int main void clr fl stdout fileno,o nonbl...
78 高階IO之非阻塞IO
高階io是相對於檔案io說的 檔案io,就是讀寫檔案,一切皆是檔案 高階io要解決讀寫以外的情況 阻塞式 函式呼叫會被卡住 雖然會釋放cpu的使用權,但是 不會往下走 核心會在此處掛起,好多預設的程式都是阻塞式的,常見的阻塞式的函式,sleep,wait,pause等,阻塞式在多路io的時候會有問題...