
bit::Shadow?(? ? ??目錄IO認識同步 VS 異步阻塞 VS非阻塞fcntl常用cmdF_GETFL與F_GETFD舉例read返回值-1Select原型timeoutfd_set結構Select通信原理0.怎么解決單進程下復原問題三狀態參數1.怎么區分新鏈接與信息Echo ServerFD_ISSET第一個參數fd正確判斷IO認識IO分作等待 拷貝先前直接使用的recv / read / readfrom / get / fread等讀取均是IO緩慢因為它們大多數時間都在“等待”同步 VS 異步同步通信 vs 異步通信(synchronous communication/ asynchronous communication)同步和異步關注的是消息通信機制。所謂同步就是在發出一個調用時在沒有得到結果之前該調用就不返回。但是一旦調用返回就得到得到結果了換句話說就是由調用者主動等待這個調用的結果異步則是相反調用在發出之后這個調用就直接返回了所以沒有返回結果換句話說當一個異步過程調用發出后調用者不會立刻得到結果而是在調用發出后被調用者通過狀態、通知來通知調用者或通過回調函數處理這個調用阻塞 VS非阻塞阻塞和非阻塞關注的是程序在等待調用結果消息返回值時的狀態。阻塞IO在內核將數據準備好之前系統調用會一直等待。所有的套接字默認都是阻塞的方式。阻塞IO是最常見的IO方式。非阻塞IO如果內核還未將數據準備好系統調用仍會直接返回并且返回EWOULDBLOCK錯誤碼。注意非阻塞IO往往需要程序員反復嘗試讀寫文件描述符這個過程稱之為輪詢。這對CPU來講是極大的浪費一般只有特定場景下才會使用。fcntlfcntlFile Control是設置阻塞/非阻塞IO的瑞士軍刀。#include fcntl.h #include unistd.h int fcntl(int fd, int cmd, ...);fd:設置阻塞/非阻塞的文件對應描述符。cmd可變參數本質上是宏設置。第二位及以后參數參數第二位必須是“命令”像F_GETFL 或 F_SETFL 而第三位必須是“狀態類型”像O_NONBLOACK。二者絕不可以為了省事而直接按位或全部一次性傳入第二個參數常用cmdF_GETFL/F_SETFL獲得/設置文件描述符狀態標記。O_NONBLOCK設置非阻塞IOF_DUPFD復制一個現有的文件描述符。F_GETFD/F_SETFD獲得/設置文件描述符標記.獲得/設置異步I/O所有權(cmdF_GETOWN或F_SETOWN).獲得/設置記錄鎖(cmdF_GETLK,F_SETLK或F_SETLKW)F_GETFL //獲取fd狀態 常用來獲取fd原狀態F_SETFL //設置狀態 常用來設置其他狀態像O_NONBLOCKF_GETFL與F_GETFDF_GETFL(Get File Status Flags)操作的是文件描述符的屬性。它獲取的是該描述符的訪問模式只讀、只寫、讀寫和狀態標志如追加O_APPEND、非阻塞O_NONBLOCK、同步O_SYNC等。這些標志是在open()系統調用時設定的并且會影響后續的read()、write()操作。F_SETFD(Set File Descriptor Flags)操作的是文件描述符本身的標志。它主要設置的是FD_CLOEXECClose-on-exec標志。這個標志不影響讀寫只影響當進程執行exec系列函數時該描述符是否會被自動關閉。防止子進程繼承不必要的文件描述符安全加固僅有FD_CLOEXEC實際開發中幾乎只用這個舉例#includeunistd.h #includefcntl.h void SetNoBlock(int fd) { //1.獲取原fd描述,由于保留原fd狀態信息 int fl fcntl(fd, F_GETFL); if(fl 0) { perror(fcntl); return; } //2.設置狀態標記 fcntl(fd, F_SETFL, fl | O_NONBLOCK); }exec系列函數回顧exec系列是Unix/Linux中替換進程映像的系統調用組。它們的作用是用一個新的程序替換當前進程的代碼段、數據段、堆和棧但保留進程的PID、文件描述符表、信號處理等。#include unistd.h int execl (const char *path, const char *arg, ..., NULL); int execlp(const char *file, const char *arg, ..., NULL); int execle(const char *path, const char *arg, ..., NULL, char *const envp[]); int execv (const char *path, char *const argv[]); int execvp(const char *file, char *const argv[]); int execvpe(const char *file, char *const argv[], char *const envp[]);// 1. execl逐個列出參數以 NULL 結尾 execl(/bin/ls, ls, -l, /home, NULL); // 2. execlp不用寫完整路徑自動搜 PATH execlp(ls, ls, -l, /home, NULL); // 3. execle自己指定環境變量 char *envp[] {PATH/bin:/usr/bin, USERubuntu, NULL}; execle(/bin/ls, ls, -l, /home, NULL, envp);特點返回必然錯誤fcntl后read返回值-1read返回-1有兩種可能1.出錯像文件描述符關閉2.數據未就緒在非阻塞情況下read后因為是非阻塞讀取當未及時發送數據就會導致“數據未就緒”無論何種情況錯誤碼均被設置錯誤碼認識 C/C進階知識6.0必要//測試非阻塞IO #includeiostream #includeunistd.h #includefcntl.h using namespace std; void SetNoBlock (int fd) { int fl fcntl (fd, F_GETFL);//獲取fd的默認描述碼 if(fl 0) { perror(fcntl); return; } fcntl (fd, F_SETFL, fl | O_NONBLOCK);//保持fd的原權限碼非阻塞0 // 注意此處的獨立 } int main() { // cout sizeof (fd_set) * 8 endl;//1024大小 SetNoBlock (0); while(true) { char buf[1024] { 0 }; int n read (0, buf, 1023); if(n 0) { //正常讀取到了 cout buf; } else if(n 0) { //n 0 //1.出錯了 2.底層沒有數據準備好 if(errno EAGAIN || errno EWOULDBLOCK) //含義均是現在無數據請稍后再試,使用一個就夠了 { sleep (1); cout 數據沒有準備好... endl; continue; } else if(errno EINTR ) { cout 被信號打斷 endl; continue; } //真正讀取出錯 break; } else { //原為讀到文件(0)末尾。因為是終端末尾(Ctrld) cout 末尾n: n endl; break; } } }.mp4效果演示fcntl非阻塞IOSelect“一種兼容性極好較為古老的IO模式”select系統調用是用來讓我們的程序監視多個文件描述符的狀態變化的原型#include sys/select.h #include sys/time.h #include sys/types.h #include unistd.h int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);對于select正常返回后的fd默認情況下讀事件不就緒寫事件是就緒的。nfds等待的所有文件描述符中最大的值加1后的值。timeout輸入輸出型參數struct timeval { time_t tv_sec; //單位秒 time_t tv_usec; //單位毫秒 }①nullptr阻塞性一旦有某個fd就緒就 . ↓ .如果如果當時沒有就緒的就阻塞。/*了解fd的遍歷*/ // 1. schedule() 返回進程重新獲得CPU // 2. 從 do_select_wait() 的 schedule() 后繼續執行 // 3. 移除等待隊列條目 // 4. 重新遍歷fd集合檢查哪些就緒了 // 5. 返回就緒的fd數量 // 6. 返回到用戶態②0非阻塞性僅檢測一輪若無就緒就立即返回。不再等待③ 0作為輸入的等待最大時間容忍度輸出時輸出被消耗后的剩余時間。fd_set結構fd_set是一種位圖結構作為輸入輸出型參數。基于不同的Linux系統可能會有差異輸入用戶告訴內核位圖上為1的值對應的下標就是你要關心的相應的 fd 值。輸出表示關心的fd內已經狀態就緒設置為1的fd是哪些。此處的“狀態”指參數位于參數對應的狀態情況分別是讀、寫、異常。返回值 r當新鏈接到達后也是“立即返回”返回值 0 。r 0有 r 個 fd 就緒了。r 0超時。因為當為nullptr時為阻塞性所以沒有這種情況r 0出錯像打開一個從未打開的文件描述符注意select由于資歷較老所以其兼容性極好。Select通信原理實現了單進程/線程多路IO0.怎么解決單進程下復原問題三狀態參數加入輔助數組 int fd_array[8*sizeof(fd_set)] -1;select前將上一輪或許新納入了的fd納入狀態參數。1.怎么區分新鏈接與信息答根據三狀態參數 均是輸入輸出型參數可知若是新鏈接則一定select后對應輔助數組參數就緒因此應先判斷listenfd對應的fd_array[0]是否就緒。就緒新鏈接到來。因為listenfd是監聽fd未就緒信息到來注意創建fd_set后一定要第一時間初始化FD_ZERO()Echo Server必看核心是Dispatch函數的因果邏輯實現基于select的echo server注意TCP與UDP的差異測試代碼int n select (_sockfd 1, rfds, nullptr, nullptr, nullptr);//阻塞性 if(n 0) { //新鏈接到來/鏈接發送了信息!!! // Dispatch (buf); // 派遣 cout test; continue; }我發了一個信息為什么一直打印不應該打印一次后阻塞嗎答因為沒有服務端沒有將接收緩沖區內的數據讀走意識read不僅僅是拷貝還將接收緩沖區內數據清除監聽套接字是用來監聽是否有新鏈接的不能作為通信套接字否則FD_ISSETFD_ISSET的底層含位運算使用fd參數不可以帶負數。第一個參數fdFD_ISSET的第一個參數可能是-1無法進行位運算正確判斷//正確寫法 if(FD_ISSET (fd_array[i], rfds)) //錯誤寫法 // if (FD_ISSET (fd_array[i], rfds) ! 0) // 在集合rfds中返回非零 // if (FD_ISSET (fd_array[i], rfds) true) // 在集合rfds中返回非零注意是輸入輸出型參數。select特點與缺點特點1.可檢測文件描述符的個數取決于sizeof(fd_set)值。ubuntu賬號sizeof(fd_set) 128每字節8比特共計128*8 1024個文件描述符。2.將需要監控狀態的fdrfds wfds efds符加入select監控后常需額外一個數據結構存儲歷史關心情況。3.每次調用select都會將rfd/wfd/efd集合從用戶態拷貝到內核態這個開銷在fd很多時會大很多。缺點1.每次調用select都需要手動設置fd集合很不方便。2.select支持的文件描述符太少。下節我們將講述poll的使用感謝支持長期連載歡迎關注