嵌入式解謎日志—多路I/O復用

多路 I/O復用(Multiplexed I/O):

1.定義:系統提供的I/O事件通知機制

2.應用:是一種 I/O 編程模型,用于在單線程中同時處理多個(阻塞)?I/O 操作,避免因等待某個 I/O 操作完成而阻塞整個程序的執行,及時處理。

3.I/O模型:①阻塞I/O(默認),閑等待()沒有就等待

? ? ? ? ? ? ? ? ? ②非阻塞(fcntl,NONBLOCK)I/O,忙等待(不停詢問)。cpu使用率高

? ? ? ? ? ? ? ? ? ③信號驅動I/O(不要求)SIGIO:使用少

? ? ? ? ? ? ? ? ? ④并發I/O:進程線程(開銷大,浪費內存)

? ? ? ? ? ? ? ? ?⑤多路I/O:select,epoll,poll

一,基于 有名管道(FIFO)?的簡單 IO 交互程序,核心功能是同時監聽 “有名管道” 和 “終端輸入”,并分別打印接收到的數據。下面從?功能邏輯、關鍵技術、代碼細節?三方面逐步解析

(1)核心功能總覽

程序的本質是一個 “雙源數據監聽器”:

  1. 先創建一個名為?myfifo?的命名管道(用于進程間通信);
  2. 以 “只讀 + 非阻塞” 模式打開管道,同時監聽終端輸入(標準輸入?stdin);
  3. 進入死循環,不斷嘗試:
    • 從管道讀取數據(非阻塞,沒數據也不卡),讀到就打印;
    • 從終端讀取用戶輸入(默認阻塞),讀到就打印;
  4. 實現 “管道數據” 和 “終端輸入” 的并行處理(本質是輪詢非阻塞 IO)。

讀:

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
int	main(int argc, char **argv)
{//創建有名管道int ret=mkfifo("myfifo",0666);{if(EEXIST==errno)//管道已存在不報錯(正常情況){}else//其他錯誤,報錯退出{perror("mkfifo errro\n");return 1;}}//打開管道并設置“非阻塞模式”//一只讀模式打開管道int fd=open("myfifo",O_RDONLY);if(-1==fd){perror("open error\n");return 1;}//將管道設置為“非阻塞IO”模式int flag=fcntl(fd,F_GETFL,0);//獲取當前fd的狀態(阻塞,只讀)fcntl(fd,F_SETFL,flag|O_NONBLOCK);// F_SETFL:設置標志,在原有的基礎上添加 O_NONBLOCK(非阻塞)
//fileno():將流指針轉換成整型flag=fcntl(fileno(stdin),F_GETFL,0);//獲取到了輸入端的狀態值//死循環監聽數據while(1){char buf[512]={0};if(read(fd,buf,sizeof(buf)>0))//將fd中的數據讀到buf里面{printf("fifo:%s\n",buf);}//清空緩沖區buf,準備接收終端輸入bzero(buf,sizeof(buf));// ② 從終端讀取用戶輸入(默認阻塞)if (fgets(buf, sizeof(buf), stdin)){// fgets返回非NULL:讀到了輸入printf("terminal:%s", buf);        // 打印終端輸入fflush(stdout);                    // 強制刷新 stdout(避免輸出緩存)}}close(fd);//system("pause");return 0;
}
  • fcntl?作用:修改文件描述符的屬性(File Control):

    • F_GETFL:獲取當前?fd?的狀態(比如是否是阻塞、只讀 / 寫等);
    • flag | O_NONBLOCK:在原有標志基礎上,添加 “非阻塞” 標志(O_NONBLOCK)—— 設置后,read(fd, ...)?若沒數據,不會阻塞等待,而是立即返回?-1?并設置?errno=EAGAIN

寫:

#include <errno.h>      // 提供錯誤碼定義,用于錯誤處理
#include <fcntl.h>      // 提供文件控制操作函數(如open)的聲明
#include <stdio.h>      // 標準輸入輸出函數
#include <stdlib.h>     // 標準庫函數(如exit)
#include <string.h>     // 字符串處理函數(如strlen)
#include <sys/stat.h>   // 提供文件狀態相關定義(如mkfifo所需的權限位)
#include <sys/types.h>  // 提供基本系統數據類型定義
#include <unistd.h>     // 提供POSIX操作系統API(如write、sleep、close)int main(int argc, char *argv[])
{// 創建名為"myfifo"的命名管道,權限為0666(所有用戶可讀寫)int ret = mkfifo("myfifo", 0666);// 檢查mkfifo調用是否失敗if (-1 == ret){// 如果錯誤碼是EEXIST,表示FIFO已存在,屬于正常情況,不做處理if (EEXIST == errno){// 空語句塊:已存在則無需重新創建,繼續執行后續代碼}// 其他錯誤情況(如權限不足),打印錯誤信息并退出else{perror("mkfifo error\n");  // 打印具體錯誤原因return 1;                  // 非0返回值表示程序異常退出}}// 以只寫模式(O_WRONLY)打開FIFO,獲取文件描述符int fd = open("myfifo", O_WRONLY);// 檢查open調用是否失敗if (-1 == fd){perror("open error\n");  // 打印打開失敗的原因return 1;                // 異常退出}// 無限循環:持續向FIFO寫入數據while (1){// 定義緩沖區并初始化要發送的字符串char buf[512] = "hello,this is fifo tested...\n";// 向FIFO寫入數據:參數為文件描述符、緩沖區、數據長度(包含結束符)write(fd, buf, strlen(buf) + 1);//strlen(buf) + 1確保包含字符串結束符\0// 暫停3秒,控制發送頻率,使程序每 3 秒寫入一次數據sleep(3);}// 關閉文件描述符(注:由于上面是無限循環,此句實際永遠不會執行)close(fd);return 0;  // 程序正常退出(實際不會執行到此處)
}

二,信號驅動I/O:

使用命名管道(FIFO)進行異步讀取的程序,它通過信號機制實現當 FIFO 中有數據到來時進行處理

寫:

#include <errno.h>      // 錯誤處理相關定義
#include <fcntl.h>      // 文件控制操作(fcntl等)
#include <signal.h>     // 信號處理相關函數和定義
#include <stdio.h>      // 標準輸入輸出
#include <stdlib.h>     // 標準庫函數
#include <string.h>     // 字符串處理函數
#include <sys/stat.h>   // 文件狀態相關定義
#include <sys/types.h>  // 基本系統數據類型
#include <unistd.h>     // POSIX系統APIint fd;  // 全局文件描述符,供信號處理函數使用// 信號處理函數:當接收到SIGIO信號時被調用
void myhandle(int num)
{char buf[512] = {0};// 從FIFO讀取數據read(fd, buf, sizeof(buf));// 打印從FIFO接收到的數據printf("fifo :%s\n", buf);
}int main(int argc, char *argv[])
{// 注冊SIGIO信號的處理函數為myhandlesignal(SIGIO, myhandle);// 創建命名管道"myfifo",權限0666int ret = mkfifo("myfifo", 0666);if (-1 == ret){// 如果管道已存在,不做處理if (EEXIST == errno){}// 其他錯誤則打印信息并退出else{perror("mkfifo error\n");return 1;}}// 以只讀模式打開FIFOfd = open("myfifo", O_RDONLY);if (-1 == fd){perror("open error\n");return 1;}// 獲取文件描述符當前的標志int flag = fcntl(fd, F_GETFL);// 設置文件描述符為異步模式(O_ASYNC)fcntl(fd, F_SETFL, flag | O_ASYNC);// 設置異步I/O的所有者為當前進程,當有數據時內核會向該進程發送SIGIO信號fcntl(fd, F_SETOWN, getpid());// 主循環:持續從終端讀取輸入并打印while (1){char buf[512] = {0};bzero(buf, sizeof(buf));  // 清空緩沖區// 從標準輸入讀取數據fgets(buf, sizeof(buf), stdin);// 打印終端輸入的數據printf("terminal:%s", buf);fflush(stdout);  // 刷新輸出緩沖區}// 關閉文件描述符(實際不會執行,因為上面是無限循環)close(fd);// remove("myfifo");  // 注釋掉了刪除FIFO的操作return 0;
}

程序核心功能說明:

  1. 異步 I/O 處理機制

    • 使用fcntl設置 FIFO 為異步模式(O_ASYNC
    • 當 FIFO 中有數據到達時,內核會自動向進程發送SIGIO信號
    • 注冊了SIGIO信號的處理函數myhandle,在信號到來時讀取并處理數據
  2. 程序工作流程

    • 創建并打開 FIFO(只讀模式)
    • 配置 FIFO 為異步通知模式,并設置當前進程為接收通知的進程
    • 主循環負責從終端讀取用戶輸入并打印
    • 當有數據寫入 FIFO 時,觸發SIGIO信號,調用myhandle讀取并打印 FIFO 中的數據
  3. 特點

    • 實現了非阻塞式的 FIFO 讀取,主程序可以同時處理其他任務(這里是終端輸入)
    • 信號驅動的 I/O 模型提高了效率,不需要主動輪詢 FIFO 狀態

讀:

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc, char *argv[])
{int ret = mkfifo("myfifo", 0666);if (-1 == ret){if (EEXIST == errno){}else{perror("mkfifo error\n");return 1;}}int fd = open("myfifo", O_WRONLY);if (-1 == fd){perror("open error\n");return 1;}while (1){char buf[512] = "hello,this is fifo tested...\n";write(fd, buf, strlen(buf) + 1);sleep(3);}close(fd);return 0;
}

三,I/O多路復用:select,ep

(1) select函數:

????????通過監聽一組文件描述符(fd_set),來判斷其中是否有描述符就緒(可讀、可寫或異常)。程序會阻塞在?select?調用上,直到有描述符就緒或者超時。

核心功能:幫你同時盯著多個 “消息入口”(文件描述符:比如套接字、鍵盤輸入),對應的信息放入對應的集合里,等某個 / 某些入口有消息了,再通知你處理 —— 避免你自己挨個蹲守,讓程序更高效、不卡頓。

select?是最早的多路 I/O 實現方式,在 POSIX 標準中定義,具有較好的跨平臺性(在 Linux、Windows 等系統中都有支持)。

1.使用步驟:①創建集合

? ? ? ? ? ? ? ? ? ②加入fd;

? ? ? ? ? ? ? ? ? ③select輪詢

? ? ? ? ? ? ? ? ? ④找到對應的fd? r/w

2. 函數原型
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

返回值:就緒的文件描述符數量;-1 表示出錯;0 表示超時

3. 核心參數
  • nfds:需監聽的最大文件描述符 + 1(因 FD 從 0 開始編號)。
  • readfds:監聽 “可讀事件” 的 FD 集合(輸入參數,內核修改后返回就緒 FD)。
  • writefds:監聽 “可寫事件” 的 FD 集合(同上)。
  • exceptfds:監聽 “異常事件” 的 FD 集合(同上)。
  • timeout:超時時間(NULL?表示永久阻塞;struct timeval{0,0}?表示非阻塞;其他值表示等待指定時間)。

select?阻塞等待,程序暫停,不占用 CPU

4. 輔助宏(操作 FD 集合)
FD_ZERO(fd_set *set);      // 清空 FD 集合
FD_SET(int fd, fd_set *set);  // 將 FD 添加到集合
FD_CLR(int fd, fd_set *set);  // 從集合中移除 FD
FD_ISSET(int fd, fd_set *set); // 檢查 FD 是否在就緒集合中(返回非 0 表示就緒)

基本原理
select?函數通過監聽一組文件描述符(fd_set),來判斷其中是否有描述符就緒(可讀、可寫或異常)。程序會阻塞在?select?調用上,直到有描述符就緒或者超時。

5. 特點與局限
  • 優點:跨平臺性好,接口簡單,適合監聽少量 FD(如 < 1000)。
  • 缺點
    • FD 數量限制(默認最大 1024,由?FD_SETSIZE?定義)。
    • 每次調用需將 FD 集合從用戶空間拷貝到內核空間,效率低。
    • 需遍歷所有 FD 才能判斷就緒狀態(時間復雜度 O (n))。

使用?select?多路復用機制同時監聽管道(fifo)和標準輸入(終端)的程序,功能與之前的?epoll?版本類似,但使用了不同的 I/O 多路復用技術。

讀端:重點處理

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>/* 包含select相關頭文件 */
/* 根據POSIX.1-2001, POSIX.1-2008標準 */
#include <sys/select.h>/* 根據早期標準 */
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>int main(int argc, char *argv[])
{// 創建命名管道"myfifo",權限為0666(所有用戶可讀寫)int ret = mkfifo("myfifo", 0666);if (-1 == ret)  // 創建失敗{if (EEXIST == errno)  // 錯誤為"文件已存在",屬于正常情況,不處理{}else  // 其他錯誤(如權限不足),輸出錯誤信息并退出{perror("mkfifo error\n");return 1;}}// 以只讀方式打開管道文件int fd = open("myfifo", O_RDONLY);if (-1 == fd)  // 打開失敗{perror("open error\n");return 1;}// 定義select所需的文件描述符集合// rd_set:用于select調用的臨時集合(會被select修改)// tmp_set:保存初始集合(用于每次循環恢復rd_set)fd_set rd_set, tmp_set;// 初始化集合,清空所有位FD_ZERO(&rd_set);FD_ZERO(&tmp_set);// 向集合中添加需要監聽的文件描述符(對應集合類型)FD_SET(0, &tmp_set);    // 添加標準輸入(終端,文件描述符為0)FD_SET(fd, &tmp_set);   // 添加管道文件描述符// 事件循環:持續監聽輸入事件while (1){// 每次循環前,從備份集合恢復rd_set// 因為select會修改集合,只保留就緒的文件描述符rd_set = tmp_set;// 調用select等待事件發生// 參數1:最大文件描述符值+1(fd是管道描述符,比0大)// 參數2:監聽讀事件的集合// 參數3、4:NULL,表示不監聽寫事件和異常事件// 參數5:NULL,表示無限期等待,直到有事件發生select(fd + 1, &rd_set, NULL, NULL, NULL);// 緩沖區,用于存儲讀取的數據char buf[512] = {0};// FD_ISSET函數:檢查管道是否有數據可讀(文件描述符在就緒集合中)if (FD_ISSET(fd, &rd_set))//如果fd文件準備就緒,就執行他{// 從管道讀取數據read(fd, buf, sizeof(buf));// 打印管道中的數據printf("fifo :%s\n", buf);}// 檢查終端(標準輸入)是否有輸入if (FD_ISSET(0, &rd_set))//如果終端有輸入,就執行他{// 清空緩沖區bzero(buf, sizeof(buf));// 從終端讀取一行輸入fgets(buf, sizeof(buf), stdin);// 打印終端輸入的數據printf("terminal:%s", buf);// 刷新標準輸出緩沖區,確保內容立即顯示fflush(stdout);}}// 關閉管道文件描述符(實際運行中循環不會退出,此句很少執行)close(fd);// 注釋:刪除管道文件(此處注釋掉,保留文件供后續測試)// remove("myfifo");return 0;
}

寫端:

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc, char *argv[])
{int ret = mkfifo("myfifo", 0666);if (-1 == ret){if (EEXIST == errno){}else{perror("mkfifo error\n");return 1;}}int fd = open("myfifo", O_WRONLY);if (-1 == fd){perror("open error\n");return 1;}while (1){char buf[512] = "hello,this is fifo tested...\n";write(fd, buf, strlen(buf) + 1);sleep(3);}close(fd);return 0;
}

(2)epoll?函數(Linux 特有):精準告訴你 “哪些設備有數據要處理了”,讓程序不用瞎等、不用瞎查,直接處理有動靜的設備就行。(加強的select)

epoll?是 Linux 專為高并發設計的多路 I/O 復用機制,性能遠超?select/poll,支持海量 FD 且時間復雜度為 O (1)。

(1)epoll 工作流程總結

  1. 創建實例:通過?epoll_create?創建 epoll 實例(epfd),默認創建兩個集合(二叉樹);
  2. 注冊事件:通過?epoll_ctl?向?epfd?中添加需要監聽的 FD 及事件(如?EPOLLIN)結構體;
  3. 等待就緒:通過?epoll_wait?阻塞等待,內核自動將就緒事件寫入?events?數組;
  4. 處理事件:遍歷?events?數組,根據就緒的 FD 和事件類型(如可讀)進行處理;
  5. 循環監聽:重復步驟 3~4,持續處理新的就緒事件。

(2)核心函數詳解

1.?epoll_create:創建 epoll 實例

功能:在內核中創建一個 epoll 實例(事件表),用于管理后續需要監聽的文件描述符(FD)和事件。
原型

#include <sys/epoll.h>
int epoll_create(int size);

參數

  • size:早期版本用于指定監聽的 FD 數量上限,現在已廢棄(內核不再使用此值),傳入任意正整數即可(通常傳 2)。

返回值

  • 成功:返回一個 epoll 實例的文件描述符(epfd),后續操作通過該描述符進行;
  • 失敗:返回?-1,并設置?errno(如?ENFILE?表示系統文件描述符耗盡)。
int epfd = epoll_create(1);  // 創建 epoll 實例
if (epfd == -1) {perror("epoll_create failed");exit(EXIT_FAILURE);
}
2.?epoll_ctl:管理 epoll 實例中的事件

功能:向 epoll 實例中添加、修改或刪除需要監聽的文件描述符及其事件(如 “可讀”“可寫”)。
原型

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

參數

  • epfdepoll_create?返回的 epoll 實例描述符;
  • op:操作類型,可選值:
    • EPOLL_CTL_ADD:向 epoll 實例添加一個新的 FD 及事件;
    • EPOLL_CTL_MOD:修改已添加的 FD 的監聽事件;
    • EPOLL_CTL_DEL:從 epoll 實例中刪除一個 FD(此時?event?可設為?NULL);
  • fd:需要監聽的文件描述符(如 socket、管道 FD 等);
  • event:指向?struct epoll_event?的指針,用于描述監聽的事件及用戶數據:
    struct epoll_event {uint32_t events;  // 監聽的事件類型(如 EPOLLIN 表示可讀)epoll_data_t data; // 用戶數據(通常存儲 FD 或自定義指針)
    };// 用戶數據聯合體(可存儲多種類型)
    typedef union epoll_data {void    *ptr;   // 自定義指針(如指向業務數據結構)int      fd;    // 最常用:存儲當前監聽的 FDuint32_t u32;uint64_t u64;
    } epoll_data_t;
    

常見事件類型(events?字段)

  • EPOLLIN:FD 可讀(如 socket 有數據、管道有輸入);
  • EPOLLOUT:FD 可寫(如 socket 發送緩沖區空閑);
  • EPOLLERR:FD 發生錯誤(無需手動設置,內核自動觸發);
  • EPOLLET:邊緣觸發模式(ET,高效模式,需配合非阻塞 IO);
  • EPOLLONESHOT:只監聽一次事件,事件觸發后需重新添加才會再次監聽。

返回值

  • 成功:返回?0
  • 失敗:返回?-1,并設置?errno(如?EEXIST?表示添加已存在的 FD)。
struct epoll_event ev;
// 監聽 FD=5 的“可讀事件”,并設為邊緣觸發(ET)
ev.events = EPOLLIN | EPOLLET;
ev.data.fd = 5;  // 存儲 FD 到用戶數據中// 向 epoll 實例添加該 FD 及事件
if (epoll_ctl(epfd, EPOLL_CTL_ADD, 5, &ev) == -1) {perror("epoll_ctl add failed");exit(EXIT_FAILURE);
}
3.?epoll_wait:等待并獲取就緒事件

功能:阻塞等待 epoll 實例中監聽的 FD 發生就緒事件(如可讀、可寫),返回就緒的事件列表。
原型

int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);

參數

  • epfd:epoll 實例描述符;
  • events:用戶空間數組,內核會將所有就緒的事件寫入該數組(輸出參數);
  • maxeventsevents?數組的最大長度(必須 ≥ 1,且不能超過?epoll_create?時的?size?早期限制);
  • timeout:超時時間(毫秒):
    • -1:永久阻塞,直到有事件就緒;
    • 0:立即返回,無論是否有事件就緒;
    • 正數:等待?timeout?毫秒后返回(若期間有事件就緒則提前返回)

返回值

  • 成功:返回就緒事件的數量(>0);
  • 超時:返回?0timeout?非?-1?時);
  • 失敗:返回?-1,并設置?errno(如?EINTR?表示被信號中斷)。
struct epoll_event events[10];  // 最多存儲 10 個就緒事件
int nfds;// 永久阻塞等待事件(-1)
nfds = epoll_wait(epfd, events, 10, -1);
if (nfds == -1) {perror("epoll_wait failed");exit(EXIT_FAILURE);
}// 遍歷所有就緒事件并處理
for (int i = 0; i < nfds; i++) {if (events[i].events & EPOLLIN) {// FD 可讀,處理數據(events[i].data.fd 為就緒的 FD)handle_read(events[i].data.fd);}
}
2. 事件類型與觸發模式
  • 常見事件EPOLLIN(可讀)、EPOLLOUT(可寫)、EPOLLERR(錯誤)。
  • 觸發模式
    • 水平觸發(LT,默認):只要 FD 就緒,每次?epoll_wait?都會返回。
    • 邊緣觸發(ET):僅在 FD 從 “未就緒” 變為 “就緒” 時觸發一次(需配合非阻塞 IO 使用)。
3. 特點
  • 優點
    • 無 FD 數量限制(支持上萬甚至百萬級 FD)。
    • FD 僅在添加時拷貝到內核,后續無需重復拷貝。
    • 內核直接返回就緒 FD 列表,無需遍歷(O (1) 復雜度)。
  • 缺點:僅支持 Linux 系統,不跨平臺。

poll?多路復用機制,實現了同時監聽管道(fifo)和標準輸入(終端)的功能

讀端:

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/epoll.h>   // epoll相關函數頭文件
#include <sys/stat.h>    // 文件狀態相關函數
#include <sys/types.h>   // 基本數據類型定義
#include <unistd.h>      // POSIX系統調用/*** 向epoll實例添加需要監聽的文件描述符* @param epfd epoll實例的文件描述符* @param fd 要添加的文件描述符* @return 0表示成功,1表示失敗*/
int add_fd(int epfd, int fd)
{struct epoll_event ev;       // epoll事件結構體,用于描述監聽的事件類型和關聯數據ev.events = EPOLLIN;         // 監聽讀事件(當有數據可讀時觸發)ev.data.fd = fd;             // 將文件描述符與事件綁定,方便后續識別// 調用epoll_ctl添加文件描述符到epoll實例// 參數:epoll實例、操作類型(添加)、目標文件描述符、事件結構體int ret = epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev);if (-1 == ret){perror("add fd");  // 添加失敗時輸出錯誤信息return 1;}return 0;  // 成功添加
}int main(int argc, char *argv[])
{// 創建命名管道"myfifo",權限為0666(所有用戶可讀寫)int ret = mkfifo("myfifo", 0666);if (-1 == ret)  // 創建失敗{if (EEXIST == errno)  // 錯誤碼為EEXIST,表示管道已存在,屬于正常情況{// 管道已存在,無需處理,繼續執行}else  // 其他錯誤(如權限不足){perror("mkfifo error\n");  // 輸出錯誤信息return 1;                  // 異常退出}}// 以只讀方式打開管道文件int fd = open("myfifo", O_RDONLY);if (-1 == fd)  // 打開失敗{perror("open error\n");  // 輸出錯誤信息return 1;                // 異常退出}// 定義epoll事件數組,用于存儲epoll_wait返回的就緒事件// 大小為2,因為我們最多監聽2個文件描述符(終端和管道)struct epoll_event rev[2];// 1. 創建epoll實例// 參數2表示期望處理的文件描述符數量(僅供內核參考,非嚴格限制)int epfd = epoll_create(2);if (-1 == epfd)  // 創建失敗{perror("epoll_create");  // 輸出錯誤信息return 1;                // 異常退出}// 2. 向epoll實例添加需要監聽的文件描述符add_fd(epfd, 0);    // 添加標準輸入(終端,文件描述符固定為0)add_fd(epfd, fd);   // 添加管道文件描述符// 事件循環:持續監聽并處理事件while (1){char buf[512] = {0};  // 數據緩沖區,用于存儲讀取到的數據// 等待事件發生,epoll_wait會阻塞直到有事件觸發// 參數:epoll實例、存儲就緒事件的數組、數組大小、超時時間(-1表示無限等待)int ep_ret = epoll_wait(epfd, rev, 2, -1);// 遍歷所有就緒的事件for (int i = 0; i < ep_ret; i++){// 判斷就緒的是管道文件描述符if (rev[i].data.fd == fd){read(fd, buf, sizeof(buf));  // 從管道讀取數據printf("fifo :%s\n", buf);   // 打印管道中的數據}// 判斷就緒的是標準輸入(終端)else if (0 == rev[i].data.fd){bzero(buf, sizeof(buf));          // 清空緩沖區fgets(buf, sizeof(buf), stdin);   // 從終端讀取輸入printf("terminal:%s", buf);       // 打印終端輸入的數據fflush(stdout);                   // 刷新標準輸出,確保內容立即顯示}}}// 關閉管道文件描述符(實際運行中因無限循環,此句很少執行)close(fd);// 注釋:刪除管道文件(此處保留注釋,實際運行時不刪除,方便后續測試)// remove("myfifo");return 0;
}

寫端:

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc, char *argv[])
{int ret = mkfifo("myfifo", 0666);if (-1 == ret){if (EEXIST == errno){}else{perror("mkfifo error\n");return 1;}}int fd = open("myfifo", O_WRONLY);if (-1 == fd){perror("open error\n");return 1;}while (1){char buf[512] = "hello,this is fifo tested...\n";write(fd, buf, strlen(buf) + 1);sleep(3);}close(fd);return 0;
}
對比維度selectepoll
底層機制輪詢(遍歷所有監聽 fd)主動上報(有設備的中斷觸發)
文件描述符限制有上限(默認 1024,由?FD_SETSIZE?定義),修改需重新編譯內核無上限(僅受系統內存和進程 fd 限制)
性能隨 fd 增長趨勢性能急劇下降(O (n),n 為監聽 fd 數量)性能穩定(O (1),與監聽 fd 數量無關)
用戶態 / 內核態交互每次調用需拷貝整個 fd_set 到內核(高開銷)僅注冊 / 修改時拷貝 fd 到內核,后續無拷貝
就緒 fd 通知方式僅告知 “有就緒”,需用戶二次遍歷檢查直接返回就緒 fd 列表,無需二次檢查
支持的事件類型僅支持水平觸發(LT)支持水平觸發(LT)和邊緣觸發(ET)
接口復雜度簡單(3 個函數:select/FD_SET/FD_ISSET稍復雜(3 個函數:epoll_create/epoll_ctl/epoll_wait
可移植性高(POSIX 標準,支持 Linux/Windows/macOS)低(僅 Linux 特有,非 POSIX 標準)
適用場景監聽 fd 數量少(如 < 1000)、簡單場景監聽 fd 數量多(如萬級 / 十萬級)、高性能場景(如服務器)

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/95967.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/95967.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/95967.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

關于嵌入式學習——單片機4

ds18b20溫度傳感器的使用一、傳感器分類&#xff1a;數字溫度傳感器&#xff0c;實現簡單&#xff0c;不需要額外轉換電路&#xff0c;采集過來的就是數字溫度值模擬溫度傳感器->熱敏電阻->AD轉換電路->數字值二、傳感器接口&#xff1a;GPIO接口&#xff1a;&#xf…

Kali搭建sqli-labs靶場

1.輸入apt-get install docker.io即可下載靶場鏡像。 下載好后&#xff0c;我們輸入docker search sqli-labs搜索sqli-labs靶場。2.我們選擇第一個&#xff0c;輸入docker pull acgpiano/sqli-labs&#xff0c;將該靶場裝到本地。此時輸入docker images&#xff0c;發現本地有s…

電腦外接顯示屏字體和圖標過大

當外接顯示屏的分辨率過高時&#xff0c;可以調整顯示器設置來解決字體和圖標過大的問題。具體操作包括在桌面右擊選擇顯示設置&#xff0c;切換到外接顯示器&#xff0c;將分辨率調至推薦的1920x1080&#xff0c;或根據個人偏好進行適當調節&#xff0c;然后保存更改。 原因&a…

Linux 網絡流量監控 Shell 腳本詳解(支持郵件告警)

前言 一、腳本功能 二、實現原理 三、Shell 腳本實現 四、關鍵知識點解析 1. Bash 關聯數組 2. 命令組 { } 與子 Shell ( ) 3. 字符串拼接換行 4. 流量計算邏輯 五、測試方法 六、優化建議 七、總結 前言 在生產環境中&#xff0c;監控服務器的 網絡流量 非常重要…

【牛客刷題-劍指Offer】BM18 二維數組中的查找:一題四解,從暴力到最優

文章目錄 一、題目介紹 1.1 描述 1.2 示例1 1.3 示例2 1.4 給的部分代碼 二、題解 方法一:暴力遍歷 方法二:二分查找(逐行) 方法三:Z字形查找(最優解) 方法四:遞歸分治(拓展思路) 三、總結 心得體會 一、題目介紹 原題鏈接:https://www.nowcoder.com/practice/abc3…

使用pyspark對上百億行的hive表生成稀疏向量

背景&#xff1a;一張上百億行的hive表&#xff0c;只有id和app兩列&#xff0c;其中app的去重量是8w多個&#xff08;原app有上百萬枚舉值&#xff0c;此處已經用id數量進行過篩選&#xff0c;只留下有一定規模的app&#xff09;&#xff0c;id的去重量大概有八九億&#xff0…

【設計模式】關于學習《重學Java設計模式》的一些成長筆記

【設計模式】關于學習《重學Java設計模式》的一些成長筆記 沒有幾個人是一說就會的,掌握一些技能,不僅要用心,而且還需要從溫故中知新。 為此,好記性不如爛筆頭,我干脆一步一腳印地系統學習一遍設計模式! (關注不迷路哈!!!) 文章目錄 【設計模式】關于學習《重學Jav…

【基礎-判斷】@Entry裝飾的自定義組件將作為頁面的入口。在單個頁面中可以使用多個@Entry裝飾不同自定義組件。

@Entry裝飾的自定義組件將作為頁面的入口。在單個頁面中可以使用多個@Entry裝飾不同自定義組件。 解釋: @Entry 的核心作用與唯一性:@Entry 裝飾器用于明確聲明該組件是一個頁面的入口組件,即整個頁面的“根”和“起點”。當UIAbility實例加載并顯示頁面時,系統需要明確知道…

醫學影像AI應用-實踐:使用MONAI實現肺部CT圖像分割的原理與實踐

?? 博主簡介:CSDN博客專家、CSDN平臺優質創作者,高級開發工程師,數學專業,10年以上C/C++, C#,Java等多種編程語言開發經驗,擁有高級工程師證書;擅長C/C++、C#等開發語言,熟悉Java常用開發技術,能熟練應用常用數據庫SQL server,Oracle,mysql,postgresql等進行開發應用…

如何訓練一個簡單的Transformer模型(附源碼)李宏毅2025大模型-作業4

摘要&#xff1a;一、作業目標&#xff1a;使用只有2層transformer的GPT-2&#xff0c;生成完整寶可夢圖像。二、源碼&解析&#xff1a;使用提供的Transformer模型&#xff08;GPT-2&#xff09;進行訓練&#xff0c;FID Score: 96.3425一、作業目標1&#xff09;目標使用T…

leetcode211.添加與搜索單詞-數據結構設計

與208.前綴樹的設計是一樣的&#xff0c;關鍵點在于word中存在通配符“."&#xff0c;所以針對該特殊情況&#xff0c;在search時針對這里進行全子節點的深度搜索class WordDictionary {TrieNode root;private class TrieNode {char val;// 當前節點的值&#xff0c;冗余了…

項目中的一些比較實用的自定義控件

本文是記錄項目開發中一些相對復雜但都比較實用的控件&#xff0c;這些控件都是基于自定義的方式去實現&#xff0c;如果有需要的朋友&#xff0c;這個可以作為一個參考&#xff0c;同時也做一個自我總結。 &#xff08;1&#xff09;子項大小不一致的RecyclerView&#xff08;…

[iOS] 折疊 cell

目錄 前言 1.原理 2.折疊 cell 的點擊選中 3.折疊 cell 高度的變化 4.實現效果 5.總結 前言 折疊 cell 是在 3GShare 中寫過的一個小控件&#xff0c;這篇博客是一個小小的總結。 1.原理 在這里的核心就是我們可以通過改變按鈕的 tag 值來判斷我們是否應該展開還是回收…

MySQL的組復制(MGR)高可用集群搭建

一、MySQL 組復制&#xff08;MGR&#xff09;核心概念 MySQL Group Replication&#xff08;簡稱 MGR&#xff09;是 MySQL 官方推出的 高可用&#xff08;HA&#xff09; 強一致性 解決方案&#xff0c;基于改進的 Paxos 協議實現&#xff0c;核心能力可概括為 3 點&#xf…

使用Shell腳本實現Linux系統資源監控郵件告警

前言 1. 問題背景與需求 2. Bash 腳本監控資源 3. Bash 腳本判斷閾值 4. 配置 msmtp 發送郵件 4.1 安裝 msmtp 4.2 創建配置文件 /etc/msmtprc 5. 發送郵件 5.1 給別人發郵件 6. 完整示例腳本 7. 測試方法 8. 常見問題解答 9. 總結 前言 在運維過程中&#xff0c…

設計整體 的 序分(三“釋”)、正宗分(雙“門”)和流通分(統一的通行表達式) 之3 “自明性”(騰訊元寶 之2)

Q&AQ11、可能還需要補充 魂軸、體軸 和 中心軸 并行 上升 的內容Q11.1、我剛才說“可能還需要補充 魂軸、體軸 和 中心軸 并行 上升 的內容” 是指的 我們今天前面討論 得出的整體設計 的一個概念整體 的一個雙螺旋上升結構中的三個軸。 您剛才是這樣 理解的嗎&#xff1f;…

使用Ansible自動化部署Hadoop集群(含源碼)--環境準備

現在我們有5臺虛擬機&#xff0c;已經配置好了主機名和網絡我們的目標是通過Ansible實現自動化部署hadoop集群。在此之前&#xff0c;我們先編寫一個shell腳本來配置hadoop集群的環境&#xff0c;包括安裝軟件、安裝配置Ansible&#xff08;一個主節點四個工作節點&#xff09;…

C#海康車牌識別實戰指南帶源碼

C#海康車牌識別實戰指南帶源碼前言車牌識別技術在智能交通、停車場管理等領域有著廣泛的應用。海康威視作為國內領先的安防廠商&#xff0c;其車牌識別相機提供了豐富的SDK接口供開發者使用。本文將詳細介紹如何使用C#語言結合海康威視SDK實現車牌識別功能&#xff0c;并解析關…

智慧能源新范式:數字孿生平臺如何驅動風電場的精細化管理?

摘要你有沒有想過&#xff0c;一座風力發電場背后&#xff0c;藏著一個“看不見的孿生兄弟”&#xff1f;它能提前預知風機故障&#xff0c;實時模擬極端天氣的影響&#xff0c;甚至能“訓練”運維人員在虛擬場景中演練搶修。這就是數字孿生——一個讓風電場從“靠經驗管理”轉…

STM32-FreeRTOS操作系統-任務管理

引言 隨著嵌入式技術的飛速發展&#xff0c;STM32與FreeRTOS的融合愈發緊密。本文聚焦于STM32平臺下FreeRTOS操作系統的任務管理&#xff0c;旨在為開發者提供清晰的思路與實用的技巧&#xff0c;助力高效開發。 為什么要進行任務管理&#xff1f; 在嵌入式系統中&#xff0c;…