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