
目錄1. 五種IO模型2. 高級IO重要概念2.1 同步通信 vs 異步通信(synchronous communication/ asynchronouscommunication)2.2 阻塞 VS 非阻塞3. 非阻塞IO3.1 fcntl3.2 實現函數SetNoBlock3.3 代碼的實現1. 五種IO模型五種IO模型就是解決IO通信時如何高效通信的問題。例子釣魚釣魚 等 釣 高效的釣魚 等的比重非常低單位時間內釣魚的效率很高阻塞IO: 在內核將數據準備好之前, 系統調用會?直等待. 所有的套接字, 默認都是阻塞方式。阻塞IO是最常見的IO模型。非阻塞IO: 如果內核還未將數據準備好, 系統調用仍然會直接返回, 并且返回EWOULDBLOCK錯誤碼。非阻塞IO往往需要程序員循環的方式反復嘗試讀寫文件描述符, 這個過程稱為輪詢。這對CPU來說是較大的浪費, ?般只有特定場景下才使用。信號驅動IO: 內核將數據準備好的時候, 使用SIGIO信號通知應?程序進行IO操作。(用的不多SIGIO信號默認是沒有開啟的)IO多路轉接: 雖然從流程圖上看起來和阻塞IO類似。實際上最核心在于IO多路轉接能夠同時等待多個?件描述符的就緒狀態。select 、poll、epoll 只解決等的問題拷貝依舊是之前老的IO函數來做但是一次只能等到 1個fd。select 、poll、epoll一次能等待多個 fd。異步IO: 由內核在數據拷貝完成時, 通知應用程序(而信號驅動是告訴應用程序何時可以開始拷貝數據不太常用)。小結任何IO過程中, 都包含兩個步驟. 第?是等待, 第二是拷貝。而且在實際的應用場景中, 等待消耗的時間往往都遠遠高于拷貝的時間。 讓IO更高效, 最核心的辦法就是讓等待的時間盡量少。2. 高級IO重要概念2.1 同步通信 vs 異步通信(synchronous communication/ asynchronouscommunication)同步和異步關注的是消息通信機制。所謂同步就是在發出?個調?時在沒有得到結果之前該調?就不返回. 但是?旦調?返回就得到返回值了; 換句話說就是由調?者主動等待這個調?的結果;異步則是相反調?在發出之后這個調?就直接返回了所以沒有返回結果; 換句話說當?個異步過程調?發出后調?者不會?刻得到結果; ?是在調?發出后被調?者通過狀態、通知來通知調?者或通過回調函數處理這個調?。另外, 在之前的多進程多線程的時候, 也提到同步和互斥. 這?的同步通信和進程之間的同步是完全不相?的概念。進程/線程同步也是進程/線程之間直接的制約關系是為完成某種任務而簡歷的兩個或多個線程這個線程需要在某些位置上協調他們的?作次序而等待、傳遞信息所產?的制約關系.。尤其是在訪問臨界資源的時候。在以后看到 “同步” 這個詞的時候一定要搞清楚是同步通信異步通信的同步還是同步與互斥中的同步。二者是沒有任何關系的。同步IO和異步IO的區分只要參加了 等 拷貝的任意一項就是同步IO一項都沒參加的話就是異步IO。什么叫做IO模型模型就是一種規律IO的情況只有這五種要么是五種當中的一種要么是任意多個的組合大部分都是阻塞式IO2.2 阻塞 VS 非阻塞阻塞和?阻塞關注的是程序在等待調?結果消息返回值時的狀態。阻塞調?是指調?結果返回之前當前線程會被掛起. 調?線程只有在得到結果之后才會返回。非阻塞調?指在不能?刻得到結果之前該調?不會阻塞當前線程。3. 非阻塞IO3.1 fcntl例子scanf()scanf() 函數鍵盤不進行輸入的話就會被阻塞住阻塞住的原因是讀條件不就緒讀條件不就緒就會阻塞住。阻塞是OS自動做的。代碼將阻塞變為非阻塞open() 函數中的 flag 參數默認是阻塞的方式打開的flag當中可以設置一個選項O_NONBLOCKrecv() 函數中的 flag 參數也是可以進行設置的一旦設置了如果底層文件描述符沒有數據的話此時recv就會出錯返回。如何設計非阻塞將 fd 直接設置為非阻塞的方式 - fcntl上層即便用阻塞的接口它也是非阻塞的。任何文件在底層都有 struct file 結構體包含了 int flag 、int mode一個文件究竟是阻塞還是非阻塞本質就是 struct file 結構體中的flag標志位是否被設置NONBLOCK此時上層是阻塞的接口來調用的話依舊是非阻塞的。fcntl 函數常見的5種功能復制?個現有的描述符cmdF_DUPFD獲得/設置?件描述符標記 (cmdF_GETFD或F_SETFD)獲得/設置?件狀態標記 (cmdF_GETFL或F_SETFL)獲得/設置異步I/O所有權 (cmdF_GETOWN或F_SETOWN)獲得/設置記錄鎖 (cmdF_GETLK,F_SETLK或F_SETLKW)此處只是?第三種功能, 獲取/設置?件狀態標記, 就可以將?個?件描述符設置為非阻塞。3.2 實現函數SetNoBlockvoid SetNoBlock(int fd) { int fl fcntl(fd, F_GETFL); if (fl 0) { perror(fcntl); return; } fcntl(fd, F_SETFL, fl | O_NONBLOCK); // 設置fd為非阻塞 }之前是阻塞的就是因為沒有設置上面的代碼中的標志位。返回值失敗返回 -13.3 代碼的實現以 read() 為例因為 read() 是默認的阻塞式的IO。// testNonBlock.cc #include iostream #include unistd.h #include fcntl.h void SetNonBlock(int sockfd) { int f1 fcntl(sockfd, F_GETFL); if (f1 0) { perror(fcntl); return; } fcntl(sockfd, F_SETFL, f1 | O_NONBLOCK); // O_NONBLOCK設置fd為非阻塞 } int main() { // 默認標準輸入是阻塞的 char buffer[64]; while (true) { ssize_t n read(0, buffer, sizeof(buffer) - 1); // sizeof(buffer)期望讀取的大小 n實際讀取的大小 if (n 0) { buffer[n] 0; printf(##%s, buffer); } else if (n 0) { printf(read end of file!\n); } else { perror(read); } } }運行結果阻塞式IO變為非阻塞式IO運行結果沒有卡住以上信息是它打出來的#include unistd.hssize_t read(int fd, void *buf, size_t count);返回值調用系統函數失敗最關心的就是為什么會失敗全局變量errno當一個庫函數或系統調用失敗該函數自動設置errno表明該函數所對應的錯誤碼多進程中errno會寫時拷貝多線程中errno是線程局部存儲errno變量每個線程一旦修改調用庫函數修改errno每個線程都有自己的errno互相不沖突。errno會線程安全的在多線程多進程中使用。錯誤碼就會告訴我們是真的出錯了還是其他情況printf(read error, reat ret: %ld, errno:%d\n, n, errno);將錯誤碼準換成錯誤碼的字符串描述printf(read error, reat ret: %ld, errno:%d, err msg:%s\n, n, errno, strerror(errno));之前用到的perror函數就是拿著錯誤碼轉成錯誤碼描述上個函數出錯了將全局變量errno設置了。// errno 11 - EAGAIN or EWOULDBLOCK (兩個宏值)#include iostream #include unistd.h #include fcntl.h #include string.h void SetNonBlock(int sockfd) { int f1 fcntl(sockfd, F_GETFL); if (f1 0) { perror(fcntl); return; } fcntl(sockfd, F_SETFL, f1 | O_NONBLOCK); // O_NONBLOCK設置fd為非阻塞 } int main() { SetNonBlock(0); // 設置非阻塞只需要設置一次就行了 // 默認標準輸入是阻塞的 char buffer[64]; while (true) { ssize_t n read(0, buffer, sizeof(buffer) - 1); // sizeof(buffer)期望讀取的大小 n實際讀取的大小 if (n 0) { buffer[n] 0; printf(##%s, buffer); } else if (n 0) { printf(read end of file!\n); break; } else { // IO非阻塞你就是不輸入 - 讀條件不滿足 - 以后討論IO問題全部站在內存(進程)視角看待IO // read 會以出錯形式返回 // 讀條件不滿足算真的出錯了嗎---- 嚴格來說不算出錯 // 為什么read()返回值 0 ??? // a.真的IO出錯了 // b.其他情況被迫返回小于0 // perror(read); // Resource temporarily unavailable // printf(read error, reat ret: %ld, errno:%d, err msg:%s\n, n, errno, strerror(errno)); // EAGAIN or EWOULDBLOCK // errno 11 - EAGAIN or EWOULDBLOCK (兩個宏值) if(errno EAGAIN || errno EWOULDBLOCK ) { printf(你的數據沒有準備好下次再來\n); // 做其他事情 continue; } else { perror(raed error\n); } sleep(1); } } }輪詢檢測在讀數據之前IO函數可能會被信號給中斷。回來的時候繼續read()if (errno EAGAIN || errno EWOULDBLOCK) { printf(你的數據沒有準備好下次再來\n); // 做其他事情 sleep(1); continue; } if (errno EINTR) { sleep(1); // 信號中斷回來的時候繼續read continue; } else { perror(raed error\n); }為什么用標準輸入來演示為什么不用標準輸出---- 因為標準輸出大部分的情況下輸出條件都是滿足的輸出緩沖區默認是空的輸出條件默認是滿足的所以printf這些接口不會卡住看不到卡住的現象很難看到現象。標準輸入沒有數據時比較容易看到現象。