目錄
一,往期文章
二,基本概念
IO多路復(fù)用
select 模型
poll 模型
epoll 模型
select,poll,epoll 三者對(duì)比
三,函數(shù)清單
1.select 方法
2.fd_set 結(jié)構(gòu)體
3.poll 方法
4.struct pollfd 結(jié)構(gòu)體
5.epoll_create 方法
6.epoll_ctl 方法
7.epoll_wait 方法
8.struct epoll_event 結(jié)構(gòu)體
四,代碼實(shí)現(xiàn)
select 操作流程
select 完整代碼
poll 操作流程
poll 完整代碼
epoll 操作流程
epoll 完整代碼
一,往期文章
【高并發(fā)網(wǎng)絡(luò)通信架構(gòu)】1.Linux下實(shí)現(xiàn)單客戶連接的tcp服務(wù)端
【高并發(fā)網(wǎng)絡(luò)通信架構(gòu)】2.引入多線程實(shí)現(xiàn)多客戶端連接的tcp服務(wù)端
二,基本概念
IO多路復(fù)用
介紹
- I/O多路復(fù)用(IO Multiplexing)是一種并發(fā)編程技術(shù),用于同時(shí)監(jiān)視多個(gè)I/O事件并選擇就緒的事件進(jìn)行處理。它可以通過一個(gè)線程或進(jìn)程同時(shí)處理多個(gè)I/O操作,而不需要為每個(gè)I/O操作創(chuàng)建一個(gè)獨(dú)立的線程或進(jìn)程。I/O多路復(fù)用可以提高系統(tǒng)的并發(fā)性能,減少資源的消耗。
- 在傳統(tǒng)的編程模型中,每個(gè)I/O操作通常都需要一個(gè)獨(dú)立的線程或進(jìn)程來處理。這種方式在面對(duì)大量的并發(fā)連接時(shí),會(huì)導(dǎo)致系統(tǒng)資源的浪費(fèi)和性能下降。而I/O多路復(fù)用通過使用一種事件驅(qū)動(dòng)的方式,可以同時(shí)監(jiān)視多個(gè)I/O事件,只有當(dāng)有事件就緒時(shí)才進(jìn)行相應(yīng)的處理,從而提高了系統(tǒng)的并發(fā)性能。
- 常見的I/O多路復(fù)用函數(shù)包括"select"、"poll"、"epoll"等。這些函數(shù)可以同時(shí)監(jiān)視多個(gè)文件描述符的狀態(tài),并確定哪些文件描述符已經(jīng)準(zhǔn)備好可進(jìn)行相應(yīng)的讀取、寫入或異常處理。通過使用這些函數(shù),可以避免為每個(gè)I/O操作創(chuàng)建一個(gè)獨(dú)立的線程或進(jìn)程,從而減少了系統(tǒng)資源的消耗。
- I/O多路復(fù)用的工作原理是通過一個(gè)事件循環(huán)來監(jiān)視多個(gè)I/O事件。當(dāng)有一個(gè)或多個(gè)事件就緒時(shí),事件循環(huán)會(huì)通知程序進(jìn)行相應(yīng)的處理。這種方式可以大大提高系統(tǒng)的并發(fā)性能,減少了線程或進(jìn)程的切換開銷。
總結(jié)文章來源地址http://www.zghlxwxcb.cn/news/detail-627390.html
- I/O多路復(fù)用是一種并發(fā)編程技術(shù),用于同時(shí)監(jiān)視多個(gè)I/O事件并選擇就緒的事件進(jìn)行處理。它可以提高系統(tǒng)的并發(fā)性能,減少資源的消耗。通過使用I/O多路復(fù)用函數(shù),可以避免為每個(gè)I/O操作創(chuàng)建一個(gè)獨(dú)立的線程或進(jìn)程,從而提高系統(tǒng)的效率。
select 模型
前言
- io多路復(fù)用select模型是一種用于實(shí)現(xiàn)高效的事件驅(qū)動(dòng)I/O的技術(shù)。它的基本思想是在一個(gè)線程中同時(shí)監(jiān)視多個(gè)文件描述符(包括套接字、管道等),并在有事件到達(dá)時(shí)進(jìn)行處理。
- 在傳統(tǒng)的阻塞式I/O中,一個(gè)線程只能處理一個(gè)請(qǐng)求,并且需要等待I/O操作完成后才能處理下一個(gè)請(qǐng)求。而在使用select模型時(shí),它可以同時(shí)監(jiān)視多個(gè)文件描述符的狀態(tài),當(dāng)有一個(gè)或多個(gè)文件描述符準(zhǔn)備好進(jìn)行讀寫時(shí),就可以通過select函數(shù)得知,并進(jìn)行相應(yīng)的處理操作。
- 通過使用select模型,可以在一個(gè)線程中同時(shí)處理多個(gè)文件描述符的I/O操作,從而提高程序的并發(fā)性和響應(yīng)性能。它適用于需要同時(shí)處理多個(gè)客戶端請(qǐng)求的情況,比如服務(wù)器程序中的多連接處理、多用戶聊天程序等。在實(shí)際應(yīng)用中,select模型已被廣泛使用,并且也衍生出其他更高效的模型,如poll、epoll、kqueue等。
定義
- "select函數(shù)"是一種用于在一組文件描述符(文件、套接字、管道等)中選擇就緒的文件描述符的函數(shù)。它在不同的操作系統(tǒng)中有不同的實(shí)現(xiàn)。
功能
- "select函數(shù)"的主要功能是監(jiān)視文件描述符的狀態(tài)并確定哪些文件描述符已經(jīng)準(zhǔn)備好可進(jìn)行相應(yīng)的讀取、寫入或異常處理。
工作原理
- 當(dāng)調(diào)用"select函數(shù)"時(shí),在指定的文件描述符集合上進(jìn)行監(jiān)視。當(dāng)有一個(gè)或多個(gè)文件描述符準(zhǔn)備好進(jìn)行讀取、寫入或出現(xiàn)異常時(shí),"select函數(shù)"將返回,告訴程序哪些文件描述符已經(jīng)就緒。程序可以利用這些就緒的文件描述符進(jìn)行相應(yīng)的I/O操作。
優(yōu)點(diǎn)
- 多路復(fù)用:"select函數(shù)"可以同時(shí)監(jiān)視多個(gè)文件描述符的狀態(tài),這些文件描述符可以是與網(wǎng)絡(luò)連接相關(guān)的套接字、標(biāo)準(zhǔn)輸入/輸出等。通過使用"select函數(shù)",可以避免單獨(dú)為每個(gè)文件描述符創(chuàng)建線程或進(jìn)程來處理事件,從而實(shí)現(xiàn)更高效的并發(fā)編程。
- 非阻塞:使用"select函數(shù)"可以將文件描述符設(shè)置為非阻塞模式,因此在等待事件就緒的同時(shí),可以執(zhí)行其他任務(wù),充分利用CPU資源。
- 兼容性:"select函數(shù)"是一種跨平臺(tái)的解決方案,在大多數(shù)操作系統(tǒng)上都得到支持。
缺點(diǎn)
- 所有文件描述符集合的線性遍歷:在調(diào)用"select函數(shù)"時(shí),需要將待監(jiān)視的文件描述符集合傳遞給函數(shù)。然而,在文件描述符數(shù)量較大時(shí),需要線性遍歷集合以找到就緒的文件描述符,這可能導(dǎo)致性能下降。
- 限制文件描述符數(shù)量:不同操作系統(tǒng)對(duì)最大支持的文件描述符數(shù)量有限制,如果需要監(jiān)視的文件描述符數(shù)量超出此限制,"select函數(shù)"可能無法滿足需求。此外,當(dāng)文件描述符數(shù)量增加時(shí),調(diào)用"select函數(shù)"的開銷也會(huì)增加。
總結(jié)
- "select函數(shù)"是一種常用的函數(shù),用于實(shí)現(xiàn)多路復(fù)用I/O操作,監(jiān)視多個(gè)文件描述符的狀態(tài),并確定哪些文件描述符已準(zhǔn)備好進(jìn)行相應(yīng)的I/O操作。
- 它在網(wǎng)絡(luò)編程中特別有用,可以實(shí)現(xiàn)高效的事件驅(qū)動(dòng)編程。
poll 模型
前言
- "poll函數(shù)"提供了一種替代"select函數(shù)"的方法,用于監(jiān)視一組文件描述符的狀態(tài)。
定義
- "poll函數(shù)"是一個(gè)系統(tǒng)調(diào)用,用于監(jiān)視一組文件描述符的狀態(tài)并確定哪些文件描述符已經(jīng)準(zhǔn)備好可進(jìn)行相應(yīng)的讀取、寫入或異常處理。
功能
- "poll函數(shù)"的主要功能是監(jiān)視文件描述符的狀態(tài),類似于"select函數(shù)"。它可以同時(shí)監(jiān)視多個(gè)文件描述符,以確定它們是否處于可讀、可寫或異常狀態(tài)。
工作原理
- 當(dāng)調(diào)用"poll函數(shù)"時(shí),它會(huì)監(jiān)視指定的文件描述符并等待事件的發(fā)生。如果有一個(gè)或多個(gè)文件描述符準(zhǔn)備好進(jìn)行讀取、寫入或出現(xiàn)異常,"poll函數(shù)"將返回,并標(biāo)記就緒的文件描述符的狀態(tài)。程序可以根據(jù)文件描述符的狀態(tài)進(jìn)行相應(yīng)的I/O操作。
優(yōu)點(diǎn)
- "poll函數(shù)"解決了"select函數(shù)"中線性遍歷文件描述符集合的性能問題,它使用輪詢算法,避免了遍歷集合的開銷。
- "poll函數(shù)"支持監(jiān)視大量文件描述符,沒有文件描述符數(shù)量的限制。
- "poll函數(shù)"比"select函數(shù)"更加可讀,并且在處理異常情況時(shí)更加靈活。
缺點(diǎn)
- "poll函數(shù)"的可移植性可能略低于"select函數(shù)",因?yàn)椴煌牟僮飨到y(tǒng)和編程語言對(duì)"poll函數(shù)"的實(shí)現(xiàn)可能有所不同。
- 當(dāng)處理大量文件描述符時(shí),"poll函數(shù)"的性能可能會(huì)受到影響。
總結(jié)
- "poll函數(shù)"是一種常用的函數(shù),用于實(shí)現(xiàn)多路復(fù)用I/O操作,它可以同時(shí)監(jiān)視多個(gè)文件描述符的狀態(tài)并確定哪些文件描述符已準(zhǔn)備好進(jìn)行相應(yīng)的I/O操作。
- 相對(duì)于"select函數(shù)","poll函數(shù)"提供了更好的性能和可讀性。但仍需要考慮平臺(tái)兼容性和大量文件描述符的性能問題。
epoll 模型
前言
- epoll(事件輪詢)是Linux操作系統(tǒng)提供的一種高性能I/O多路復(fù)用機(jī)制,在處理大規(guī)模的并發(fā)連接時(shí)具有優(yōu)越的效率和可擴(kuò)展性。它通過使用內(nèi)核的事件通知機(jī)制來同時(shí)監(jiān)視和處理多個(gè)文件描述符的事件,大大減少了輪詢的開銷,提高了系統(tǒng)的并發(fā)性能。
- 與傳統(tǒng)的select和poll函數(shù)相比,epoll在處理大規(guī)模并發(fā)連接時(shí)具有更高的效率。
基本流程
- 調(diào)用epoll_create函數(shù)創(chuàng)建一個(gè)epoll實(shí)例,并獲得一個(gè)文件描述符(epfd)。
- 使用epoll_ctl函數(shù)向epoll實(shí)例中加入需要監(jiān)視的文件描述符和對(duì)應(yīng)的事件類型,包括讀、寫、錯(cuò)誤等。
- 使用epoll_wait函數(shù)阻塞等待事件的發(fā)生,當(dāng)有文件描述符就緒時(shí),epoll_wait將返回就緒的文件描述符集合。
- 遍歷就緒的文件描述符集合,進(jìn)行相應(yīng)的I/O操作。
工作原理
- 調(diào)用epoll_create函數(shù)創(chuàng)建一個(gè)epoll實(shí)例,獲得一個(gè)epoll文件描述符(epfd)。
- 使用epoll_ctl函數(shù)向epoll實(shí)例注冊(cè)需要監(jiān)視的文件描述符和相應(yīng)的事件類型,并將其加入到內(nèi)核維護(hù)的事件表中。
- 調(diào)用epoll_wait函數(shù)阻塞等待事件的發(fā)生,此時(shí)線程會(huì)進(jìn)入休眠狀態(tài),不再需要輪詢文件描述符。
- 當(dāng)被監(jiān)視的文件描述符上的事件發(fā)生時(shí),內(nèi)核會(huì)將事件加入到就緒列表中,同時(shí)喚醒處于阻塞狀態(tài)的線程。
- epoll_wait函數(shù)返回時(shí),通過遍歷就緒列表,獲取到就緒的文件描述符集合。
- 應(yīng)用程序根據(jù)文件描述符的就緒事件類型進(jìn)行相應(yīng)的I/O操作,如讀取、寫入數(shù)據(jù)等。
相關(guān)函數(shù)
- epoll_create(int size):創(chuàng)建一個(gè)epoll實(shí)例。
- epoll_ctl(int epfd, int op, int fd, struct epoll_event* event):向epoll實(shí)例注冊(cè)/修改/刪除一個(gè)文件描述符及其對(duì)應(yīng)的事件。
- epoll_wait(int epfd, struct epoll_event* events, int maxevents, int timeout):等待事件的發(fā)生,并獲取就緒的文件描述符。
事件類型(events參數(shù))
- EPOLLIN:文件描述符上有數(shù)據(jù)可讀。
- EPOLLOUT:文件描述符上可以寫入數(shù)據(jù)。
- EPOLLERR:文件描述符發(fā)生錯(cuò)誤。
- EPOLLHUP:文件描述符掛起。
- EPOLLET:邊緣觸發(fā)模式。
- EPOLLONESHOT:一次性事件,事件發(fā)生后會(huì)被自動(dòng)從epoll實(shí)例中刪除。
優(yōu)點(diǎn)
- 高性能:epoll使用紅黑樹(內(nèi)核使用的數(shù)據(jù)結(jié)構(gòu)),就緒列表和事件通知機(jī)制,避免了遍歷整個(gè)文件描述符集合的開銷,在大規(guī)模并發(fā)連接時(shí)性能比傳統(tǒng)的select和poll函數(shù)更好。
- 可擴(kuò)展性:epoll的設(shè)計(jì)支持超大規(guī)模的并發(fā)連接,能夠更好地適應(yīng)高并發(fā)的網(wǎng)絡(luò)環(huán)境。
- 邊緣觸發(fā)模式:epoll提供了邊緣觸發(fā)(ET)模式,只有在文件描述符狀態(tài)發(fā)生變化時(shí)才觸發(fā)事件通知,相比于水平觸發(fā)(LT)模式可以減少事件通知次數(shù),提高效率。
- 持久化的事件注冊(cè):注冊(cè)事件時(shí),可以選擇EPOLLONESHOT標(biāo)志,使得內(nèi)核在將事件通知給應(yīng)用程序后,自動(dòng)將該事件從epoll實(shí)例中刪除,避免重復(fù)通知。
- 支持多種數(shù)據(jù)結(jié)構(gòu):epoll支持紅黑樹和鏈表兩種數(shù)據(jù)結(jié)構(gòu),用于快速查找文件描述符和就緒列表的操作,提高了處理效率。
- 大規(guī)模并發(fā):epoll可以高效地管理大量的文件描述符,支持高并發(fā)的網(wǎng)絡(luò)編程。
總結(jié)
- epoll是Linux操作系統(tǒng)提供的一種高性能I/O多路復(fù)用機(jī)制,它通過內(nèi)核的事件通知和就緒列表,能同時(shí)監(jiān)視和處理大量的文件描述符。它具有高性能、可擴(kuò)展性和邊緣觸發(fā)模式等特點(diǎn),是開發(fā)高并發(fā)網(wǎng)絡(luò)應(yīng)用的重要工具。
select,poll,epoll 三者對(duì)比
共同點(diǎn)
select
,?poll
, 和?epoll
?都是在事件驅(qū)動(dòng)的網(wǎng)絡(luò)編程中用于多路復(fù)用 I/O 的機(jī)制。它們的主要目標(biāo)是在一個(gè)線程內(nèi)同時(shí)處理多個(gè)文件描述符的 I/O 操作,而不是為每個(gè)文件描述符創(chuàng)建一個(gè)單獨(dú)的線程或進(jìn)程。優(yōu)缺點(diǎn)分析
- select
- 優(yōu)點(diǎn):
- 廣泛支持,可以在大部分的操作系統(tǒng)上使用。
- 可以處理多種類型的文件描述符(包括普通文件、套接字文件等)。
- 缺點(diǎn):
- 效率較低,因?yàn)樵谡{(diào)用?
select
?時(shí)需要遍歷整個(gè)文件描述符集合,無論是否有真正的 I/O 事件發(fā)生。- 最大文件描述符數(shù)量有限,默認(rèn)情況下通常為 1024 個(gè)。
select
?函數(shù)在每次調(diào)用時(shí)都需要修改傳入的文件描述符集合,導(dǎo)致效率低下。- poll
- 優(yōu)點(diǎn):
- 不受最大文件描述符數(shù)量限制,可以動(dòng)態(tài)地管理文件描述符的數(shù)量。
- 效率較高,因?yàn)闊o需遍歷整個(gè)文件描述符集合,只處理有事件發(fā)生的文件描述符。
- 缺點(diǎn):
- 與?
select
?類似,需要遍歷整個(gè)文件描述符集合來查找是否有事件發(fā)生。- 對(duì)于大量的文件描述符,性能可能下降。
- epoll
- 優(yōu)點(diǎn):
- 高效的事件通知機(jī)制,能夠快速響應(yīng)大量的并發(fā)連接。
- 沒有最大文件描述符數(shù)量限制。
- 使用回調(diào)機(jī)制,只處理活躍的文件描述符,避免無效的遍歷。
- 支持水平觸發(fā)和邊緣觸發(fā)兩種工作模式。
- 缺點(diǎn):
- 只在 Linux 系統(tǒng)上可用,不具有跨平臺(tái)性。
- API 相對(duì)復(fù)雜,有一定的學(xué)習(xí)成本。
總結(jié)
epoll
?在性能和擴(kuò)展性方面優(yōu)于?select
?和?poll
。它在支持大規(guī)模并發(fā)連接和高效的事件通知方面表現(xiàn)出色。但如果只需要處理數(shù)百個(gè)并發(fā)連接,并且需要在多個(gè)平臺(tái)上運(yùn)行,select
?或?poll
?也可以是合理的選擇。
三,函數(shù)清單
1.select 方法
#include <sys/select.h> int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
功能
- 監(jiān)視文件描述符的狀態(tài)并確定哪些文件描述符已經(jīng)準(zhǔn)備好可進(jìn)行相應(yīng)的讀取、寫入或異常處理。
參數(shù)
- nfds:監(jiān)視文件描述符集合的最大值加1。
- readfds:要監(jiān)視可讀事件的文件描述符集合。
- writefds:要監(jiān)視可寫事件的文件描述符集合。
- exceptfds:要監(jiān)視異常事件的文件描述符集合。
- timeout:超時(shí)時(shí)間,可以設(shè)置為NULL(無限等待),或者指定一個(gè)時(shí)間段。
返回值
- 修改給定的文件描述符集合,并返回就緒的文件描述符的數(shù)量,若超時(shí)則返回0,若出錯(cuò)則返回-1。
2.fd_set 結(jié)構(gòu)體
typedef struct { unsigned long fds_bits[FD_SETSIZE / (8 * sizeof(unsigned long))]; } fd_set;
功能
fd_set
?是一個(gè)用于存儲(chǔ)文件描述符集合的數(shù)據(jù)結(jié)構(gòu)。它通常是一個(gè)位圖,每個(gè)位表示一個(gè)文件描述符的狀態(tài)。在使用?select
?函數(shù)時(shí),通常需要使用?fd_set
?結(jié)構(gòu)體來設(shè)置、檢查和處理文件描述符集合。成員
fds_bits
:用于存儲(chǔ)文件描述符信息的數(shù)組。FD_SETSIZE
?宏定義了數(shù)組的大?。J(rèn)為1024),表明文件描述符集合的最大容量。
fd_set
?結(jié)構(gòu)體用于存儲(chǔ)文件描述符的集合,并通過相應(yīng)的宏來進(jìn)行相關(guān)操作,如添加、刪除和檢查文件描述符。
FD_ZERO(fd_set *set)
:清空?fd_set
?結(jié)構(gòu)體,將其初始化為空集。FD_SET(int fd, fd_set *set)
:將指定的文件描述符?fd
?添加到?fd_set
?結(jié)構(gòu)體中。FD_CLR(int fd, fd_set *set)
:從?fd_set
?結(jié)構(gòu)體中移除指定的文件描述符?fd
。FD_ISSET(int fd, fd_set *set)
:檢查指定的文件描述符?fd
?是否在?fd_set
?結(jié)構(gòu)體中。如果存在于集合中,則返回非零值,否則返回零。FD_COPY(fd_set *src_set, fd_set *dest_set)
:將?src_set
?結(jié)構(gòu)體中的文件描述符復(fù)制到?dest_set
?結(jié)構(gòu)體中。FD_SETSIZE
:表示在一個(gè)?fd_set
?結(jié)構(gòu)體中可容納的最大文件描述符數(shù)量。默認(rèn)情況下,在大多數(shù)系統(tǒng)上都設(shè)置為 1024??梢允褂眠@個(gè)宏來確定在使用?select
?函數(shù)時(shí)?fd_set
?結(jié)構(gòu)體的大小。注意事項(xiàng)
- 需要注意的是,
fd_set
?結(jié)構(gòu)體在使用?select
?函數(shù)時(shí)會(huì)被修改,因此在每次調(diào)用?select
?之前需要重新設(shè)置需要監(jiān)聽的文件描述符。
3.poll 方法
#include <poll.h> int poll(struct pollfd *fds, nfds_t nfds, int timeout);
功能
- 監(jiān)視文件描述符的狀態(tài)并確定哪些文件描述符已經(jīng)準(zhǔn)備好可進(jìn)行相應(yīng)的讀取、寫入或異常處理。
參數(shù)
- fds:指向一個(gè)結(jié)構(gòu)體數(shù)組的指針,每個(gè)結(jié)構(gòu)體中包含待監(jiān)視的文件描述符和監(jiān)視事件類型。
- nfds:結(jié)構(gòu)體數(shù)組的大小,即待監(jiān)視的文件描述符的最大值加1。
- timeout:指定一個(gè)超時(shí)值,以毫秒為單位,控制?
poll
?的阻塞行為,可以設(shè)置為以下幾種情況之一:
timeout
?大于0,表示阻塞?timeout
?毫秒后超時(shí)返回。timeout
?等于0,表示立即返回,不進(jìn)行阻塞。timeout
?小于0,表示無限阻塞,直到有事件發(fā)生。返回值
- 修改給定的結(jié)構(gòu)體數(shù)組,并返回就緒的文件描述符數(shù)量,若超時(shí)則返回0,若出錯(cuò)則返回-1。
4.struct pollfd 結(jié)構(gòu)體
struct pollfd { int fd; // 關(guān)注的文件描述符 short events; // 等待的事件類型 short revents; // 實(shí)際發(fā)生的事件類型 };
功能
- poll函數(shù)通過?
struct pollfd
?結(jié)構(gòu)體來描述文件描述符和關(guān)注的事件類型。成員
fd
:表示文件描述符。events
:表示關(guān)注的事件類型,可以是以下幾種事件之一或它們的結(jié)合:
POLLIN
:可讀事件。POLLOUT
:可寫事件。POLLERR
:錯(cuò)誤事件。POLLHUP
:掛起事件。revents
:在?poll
?返回時(shí),revents
?字段表示實(shí)際發(fā)生的事件類型,由操作系統(tǒng)填充。
5.epoll_create 方法
#include <sys/epoll.h> int epoll_create(int size);
功能
- 創(chuàng)建一個(gè)epoll實(shí)例,并返回一個(gè)文件描述符用于操作該實(shí)例。(會(huì)在內(nèi)核中分配并初始化一個(gè)用于存儲(chǔ)事件的鏈表數(shù)據(jù)結(jié)構(gòu))
參數(shù)
- size:表示實(shí)例能夠監(jiān)視的文件描述符的最大數(shù)量(在Linux 2.6.8之后被忽略)。
返回值
- 如果成功,這些系統(tǒng)調(diào)用返回一個(gè)文件描述符(一個(gè)非負(fù)整數(shù))。如果出現(xiàn)錯(cuò)誤,則返回-1,并設(shè)置errno來指示錯(cuò)誤。
6.epoll_ctl 方法
#include <sys/epoll.h> int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
功能
- 向epoll實(shí)例添加/修改/刪除一個(gè)文件描述符及其對(duì)應(yīng)的事件。
參數(shù)
- epfd:為epoll實(shí)例的文件描述符。
- op:指定操作類型,可以是EPOLL_CTL_ADD(添加)、EPOLL_CTL_MOD(修改)或EPOLL_CTL_DEL(刪除)。
- fd:待操作的文件描述符。
- event:指向一個(gè)epoll_event結(jié)構(gòu)體,描述了待添加/修改/刪除的事件類型。
返回值
- 當(dāng)成功時(shí),epoll_ctl()返回零。當(dāng)發(fā)生錯(cuò)誤時(shí),epoll_ctl()返回-1,并設(shè)置errno來指示錯(cuò)誤。
7.epoll_wait 方法
#include <sys/epoll.h> int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
功能
- 等待事件的發(fā)生并獲取就緒的文件描述符。
參數(shù)
- epfd:epoll實(shí)例的文件描述符。
- events:存儲(chǔ)就緒事件的數(shù)組。
- maxevents:表示events數(shù)組的最大大小。
- timeout:指定超時(shí)時(shí)間(以毫秒為單位),可以設(shè)置為-1(無限等待)或0(立即返回)。
返回值
- 若成功則返回就緒的文件描述符的數(shù)量,若超時(shí)則返回0,若出錯(cuò)則返回-1,并設(shè)置errno來指示錯(cuò)誤。
8.struct epoll_event 結(jié)構(gòu)體
typedef union epoll_data { void *ptr; int fd; uint32_t u32; uint64_t u64; } epoll_data_t; struct epoll_event { uint32_t events; /* Epoll events */ epoll_data_t data; /* User data variable */ };
功能
- 描述一個(gè)文件描述符及其對(duì)應(yīng)的事件類型。
成員
- events:表示關(guān)注的事件類型,包括EPOLLIN(可讀)、EPOLLOUT(可寫)、EPOLLERR(錯(cuò)誤)、EPOLLHUP(掛起)等。
- data:可以是一個(gè)32或64位的整數(shù),或者一個(gè)指向void的指針,用于存儲(chǔ)與文件描述符關(guān)聯(lián)的用戶數(shù)據(jù)。
四,代碼實(shí)現(xiàn)
select 操作流程
- 創(chuàng)建并初始化fd_set集合:fd_set是一個(gè)用于存儲(chǔ)文件描述符的數(shù)據(jù)結(jié)構(gòu)。通過定義一個(gè)fd_set變量,并使用FD_ZERO和FD_SET宏來初始化。
![]()
- 設(shè)置超時(shí)時(shí)間:通過設(shè)置timeval結(jié)構(gòu)體來指定select函數(shù)的超時(shí)時(shí)間。如果不需要設(shè)置超時(shí)時(shí)間,可以將該參數(shù)設(shè)置為NULL。
![]()
- 調(diào)用select函數(shù):使用select函數(shù)來等待文件描述符的就緒事件。
![]()
- 檢查select函數(shù)的返回值:根據(jù)select函數(shù)的返回值,確定是否有文件描述符就緒。
![]()
- 遍歷就緒文件描述符集合:通過遍歷fd_set集合來判斷哪些文件描述符已經(jīng)就緒。使用FD_ISSET宏來判斷文件描述符是否在集合中。
![]()
注意事項(xiàng)
- 在使用select函數(shù)之前,需要手動(dòng)設(shè)置文件描述符的阻塞或非阻塞狀態(tài)。
- 每次調(diào)用select函數(shù)之后,需要重新初始化fd_set集合,并將需要監(jiān)聽的文件描述符重新設(shè)置進(jìn)去。
- select函數(shù)每次只能監(jiān)聽一部分文件描述符,因此在大規(guī)模并發(fā)連接時(shí),性能可能會(huì)受到限制。
- select函數(shù)支持的文件描述符數(shù)量有限,通常取決于操作系統(tǒng)的限制。
總結(jié)
- 通過創(chuàng)建并初始化fd_set集合(準(zhǔn)備需要監(jiān)聽的文件描述符集合),設(shè)置超時(shí)時(shí)間,并使用select函數(shù)進(jìn)行監(jiān)聽文件描述符的就緒事件。然后根據(jù)select函數(shù)的返回值和遍歷就緒文件描述符集合來執(zhí)行相應(yīng)的I/O操作。
select 完整代碼
#include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <errno.h> #include <string.h> #include <stdlib.h> #include <unistd.h> #include <sys/select.h> #define BUFFER_LENGTH 1024 int init_server(int port){ //獲取服務(wù)端fd,通常為3,前面0,1,2用于表示標(biāo)準(zhǔn)輸入,輸出,錯(cuò)誤值 int sfd = socket(AF_INET,SOCK_STREAM,0); if(-1 == sfd){ printf("socket error code: %d codeInfo: %s\n",errno,strerror(errno)); return -1; } struct sockaddr_in servAddr; memset(&servAddr,0,sizeof(struct sockaddr_in)); servAddr.sin_family = AF_INET; //ipv4 servAddr.sin_addr.s_addr = htonl(INADDR_ANY); //0.0.0.0 servAddr.sin_port = htons(port); //綁定IP和端口號(hào) if(-1 == bind(sfd,(struct sockaddr*)&servAddr,sizeof(struct sockaddr_in))) { printf("bind error code: %d codeInfo: %s\n",errno,strerror(errno)); return -1; } //監(jiān)聽該套接字上的連接 if(-1 == listen(sfd,10)) { printf("listen error code: %d codeInfo: %s\n",errno,strerror(errno)); return -1; } return sfd; } int main(int argc,char *argv[]){ if(argc < 2)return -1; int port = atoi(argv[1]); //字符串轉(zhuǎn)整型 int sfd = init_server(port); printf("server fd: %d\n",sfd); if(sfd == -1)return -1; fd_set rfds,rset; //fd位數(shù)組 FD_ZERO(&rfds); FD_SET(sfd,&rfds); int maxfd = sfd; int cfd = 0; struct sockaddr_in clientAddr; socklen_t len = sizeof(struct sockaddr_in); while (1) //表示每周都去東莞 主線程 { rset = rfds; int nready = select(maxfd+1,&rset,NULL,NULL,NULL); //系統(tǒng)調(diào)用 if(FD_ISSET(sfd,&rset)){ cfd = accept(sfd,(struct sockaddr*)&clientAddr,&len); //阻塞等待客戶端連接 printf("client fd: %d\n",cfd); FD_SET(cfd,&rfds); if(cfd > maxfd)maxfd = cfd; //將需要監(jiān)聽的文件描述符重新設(shè)置進(jìn)去 if(--nready == 0)continue; //超時(shí)時(shí)間到,沒有就緒的文件描述符 } int i = 0; //服務(wù)端sfd=3 for(i = sfd+1;i<=maxfd;i++){ if(FD_ISSET(i,&rset)){ //有bug,下一個(gè)客戶端發(fā)送數(shù)據(jù)后,上一個(gè)客戶端發(fā)送數(shù)據(jù)不能接收 char data[BUFFER_LENGTH] = {0}; int recvLen = recv(cfd,data,BUFFER_LENGTH,0); if(recvLen == 0){ printf("cfd: %d disconnect\n",cfd); close(cfd); break; //客戶端斷開連接 } printf("recv cfd: %d data: %s\n",cfd,data); send(cfd,data,recvLen,0); } } } return 0; }
poll 操作流程
- 準(zhǔn)備需要監(jiān)聽的文件描述符集合。
![]()
- 設(shè)置超時(shí)時(shí)間(可選)。
![]()
- 調(diào)用?
poll
?函數(shù)等待就緒事件。![]()
- 檢查?
poll
?函數(shù)的返回值。![]()
注意事項(xiàng)
- 在使用?
poll
?函數(shù)之前,需要設(shè)置文件描述符的阻塞或非阻塞狀態(tài)。- 每次調(diào)用?
poll
?函數(shù)之前,需要重新設(shè)置需要監(jiān)聽的文件描述符集合。poll
?函數(shù)一次可以監(jiān)聽多個(gè)文件描述符,相比于?select
?函數(shù)能更好地適應(yīng)高并發(fā)環(huán)境。poll
?函數(shù)將就緒的文件描述符信息填充到?revents
?字段中,可以通過按位與操作來判斷文件描述符的就緒事件類型。
總結(jié)
- 通過準(zhǔn)備需要監(jiān)聽的文件描述符集合,調(diào)用?
poll
?函數(shù)等待就緒事件,然后根據(jù)返回值和遍歷就緒文件描述符集合來執(zhí)行相應(yīng)的 I/O 操作。
poll 完整代碼
#include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <errno.h> #include <string.h> #include <stdlib.h> #include <unistd.h> #include <poll.h> #define BUFFER_LENGTH 1024 #define POLL_LENGTH 1024 int init_server(int port){ //獲取服務(wù)端fd,通常為3,前面0,1,2用于指定輸入,輸出,錯(cuò)誤值 int sfd = socket(AF_INET,SOCK_STREAM,0); if(-1 == sfd){ printf("socket error code: %d codeInfo: %s\n",errno,strerror(errno)); return -1; } struct sockaddr_in servAddr; memset(&servAddr,0,sizeof(struct sockaddr_in)); servAddr.sin_family = AF_INET; //ipv4 servAddr.sin_addr.s_addr = htonl(INADDR_ANY); //0.0.0.0 servAddr.sin_port = htons(port); //綁定IP和端口號(hào) if(-1 == bind(sfd,(struct sockaddr*)&servAddr,sizeof(struct sockaddr_in))) { printf("bind error code: %d codeInfo: %s\n",errno,strerror(errno)); return -1; } //監(jiān)聽該套接字上的連接 if(-1 == listen(sfd,10)) { printf("listen error code: %d codeInfo: %s\n",errno,strerror(errno)); return -1; } return sfd; } int main(int argc,char *argv[]){ if(argc < 2)return -1; int port = atoi(argv[1]); int sfd = init_server(port); printf("server fd: %d\n",sfd); if(sfd == -1)return -1; struct pollfd fds[POLL_LENGTH] = {0}; // 定義一個(gè) pollfd 數(shù)組,大小根據(jù)需要監(jiān)聽的文件描述符個(gè)數(shù)來設(shè)置 fds[sfd].fd = sfd; // 設(shè)置第一個(gè)需要監(jiān)聽的文件描述符 fds[sfd].events = POLLIN; // 設(shè)置需要監(jiān)聽的事件類型 fds[sfd].revents = 0; // 清空返回的就緒事件 int maxfd = sfd; int cfd; struct sockaddr_in clientAddr; socklen_t len = sizeof(struct sockaddr_in); while (1) { int nready = poll(fds,maxfd+1,-1); if(fds[sfd].revents & POLLIN){ cfd = accept(sfd,(struct sockaddr*)&clientAddr,&len); //阻塞等待客戶端連接 printf("client fd: %d\n",cfd); fds[cfd].fd = cfd; fds[cfd].events = POLLIN; if(cfd > maxfd)maxfd = cfd; if(--nready == 0)continue; } int i = 0; for(i=0;i<=maxfd;i++){ if(fds[i].revents & POLLIN){ char data[BUFFER_LENGTH] = {0}; int recvLen = recv(cfd,data,BUFFER_LENGTH,0); if(recvLen == 0){ //有點(diǎn)問題 fds[cfd].fd = -1; fds[cfd].events = 0; printf("client fd: %d disconnect\n",cfd); close(cfd); break; //客戶端斷開連接 } printf("recv cfd: %d data: %s\n",cfd,data); send(cfd,data,recvLen,0); } } } return 0; }
epoll 操作流程
- 創(chuàng)建 epoll 實(shí)例。
![]()
- 準(zhǔn)備需要監(jiān)聽的文件描述符集合并添加到 epoll 實(shí)例中。
![]()
- 設(shè)置超時(shí)時(shí)間(可選)。
![]()
- 調(diào)用?
epoll_wait
?函數(shù)等待就緒事件。![]()
- 檢查?
epoll_wait
?函數(shù)的返回值。![]()
- 移除文件描述符或關(guān)閉 epoll 實(shí)例(可選)。
![]()
注意事項(xiàng)文章來源:http://www.zghlxwxcb.cn/news/detail-627390.html
- 在使用?
epoll
?函數(shù)之前,需要設(shè)置文件描述符的阻塞或非阻塞狀態(tài)。- 每次調(diào)用?
epoll_wait
?函數(shù)之前,需要重新設(shè)置需要監(jiān)聽的文件描述符集合,或是通過?epoll_ctl
?函數(shù)動(dòng)態(tài)改變監(jiān)聽的文件描述符集合。epoll
?提供了更高效的事件通知機(jī)制,可以同時(shí)監(jiān)聽大量的文件描述符。epoll_wait
?函數(shù)將就緒的文件描述符信息填充到?events
?數(shù)組中,可以通過遍歷數(shù)組來處理就緒事件。
總結(jié)
- 通過調(diào)用?epoll_create 創(chuàng)建 epoll 實(shí)例,準(zhǔn)備需要監(jiān)聽的文件描述符集合并添加到 epoll 實(shí)例中,調(diào)用?
epoll_wait
?函數(shù)等待就緒事件,然后根據(jù)返回值和遍歷就緒事件集合來執(zhí)行相應(yīng)的 I/O 操作。最后,根據(jù)需要,可以移除文件描述符或關(guān)閉 epoll 實(shí)例。
epoll 完整代碼
#include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <errno.h> #include <string.h> #include <stdlib.h> #include <unistd.h> #include <sys/epoll.h> #define BUFFER_LENGTH 1024 #define EPOLL_LENGTH 1024 int init_server(int port){ //獲取服務(wù)端fd,通常為3,前面0,1,2用于指定輸入,輸出,錯(cuò)誤值 int sfd = socket(AF_INET,SOCK_STREAM,0); if(-1 == sfd){ printf("socket error code: %d codeInfo: %s\n",errno,strerror(errno)); return -1; } //設(shè)置服務(wù)端套接字為非阻塞模式 // int flags = fcntl(sfd,F_GETFL,0); // fcntl(sfd,F_SETFL,flags | O_NONBLOCK); struct sockaddr_in servAddr; memset(&servAddr,0,sizeof(struct sockaddr_in)); servAddr.sin_family = AF_INET; //ipv4 servAddr.sin_addr.s_addr = htonl(INADDR_ANY); //0.0.0.0 servAddr.sin_port = htons(port); //綁定IP和端口號(hào) if(-1 == bind(sfd,(struct sockaddr*)&servAddr,sizeof(struct sockaddr_in))) { printf("bind error code: %d codeInfo: %s\n",errno,strerror(errno)); return -1; } //監(jiān)聽該套接字上的連接 if(-1 == listen(sfd,10)) { printf("listen error code: %d codeInfo: %s\n",errno,strerror(errno)); return -1; } return sfd; } int main(int argc,char *argv[]){ if(argc < 2)return -1; int port = atoi(argv[1]); int sfd = init_server(port); printf("server fd: %d\n",sfd); int epfd = epoll_create(1); //在內(nèi)核創(chuàng)建epoll對(duì)象,在內(nèi)核中分配并初始化一個(gè)用于存儲(chǔ)事件的鏈表數(shù)據(jù)結(jié)構(gòu) printf("epoll fd: %d\n",epfd); struct epoll_event event; event.events = EPOLLIN; event.data.fd = sfd; epoll_ctl(epfd,EPOLL_CTL_ADD,sfd,&event); //往epoll實(shí)例中添加服務(wù)端文件描述符和對(duì)應(yīng)的事件 struct epoll_event events[EPOLL_LENGTH] = {0}; struct sockaddr_in clientAddr; socklen_t len = sizeof(struct sockaddr_in); while (1) //mainloop 7*24小時(shí)工作 { int nready = epoll_wait(epfd,events,EPOLL_LENGTH,-1); //無限等待就緒事件的發(fā)生 if(nready < 0)continue; int i = 0; for (i = 0; i < nready; i++) { int connfd = events[i].data.fd; if(sfd == connfd) { //執(zhí)行accept操作,處理新的連接請(qǐng)求 int cfd = accept(sfd,(struct sockaddr*)&clientAddr,&len); if(cfd < 0)continue; printf("client fd: %d\n",cfd); event.events = EPOLLIN; event.data.fd = cfd; epoll_ctl(epfd,EPOLL_CTL_ADD,cfd,&event); //往epoll實(shí)例中添加客戶端文件描述符和對(duì)應(yīng)的事件 } else if(events[i].events & EPOLLIN) { //處理客戶端發(fā)送的數(shù)據(jù) char buffer[BUFFER_LENGTH] = {0}; int recvLen = recv(connfd,buffer,BUFFER_LENGTH,0); if(recvLen > 0){ printf("recv client fd %d data: %s\n",connfd,buffer); send(connfd,buffer,recvLen,0); }else if(recvLen == 0) { printf("client fd %d close\n",connfd); epoll_ctl(epfd,EPOLL_CTL_DEL,connfd,&event); //在epoll實(shí)例中移除斷開的客戶端文件描述符和對(duì)應(yīng)的事件 close(connfd); } } } } return 0; }
到了這里,關(guān)于【高并發(fā)網(wǎng)絡(luò)通信架構(gòu)】3.引入IO多路復(fù)用(select,poll,epoll)實(shí)現(xiàn)高并發(fā)tcp服務(wù)端的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!