詳解C/C++中的select、poll和epoll
1. select
1.1 概述
select函數(shù)是UNIX和Linux中常用的多路復(fù)用IO機(jī)制,它允許程序同時(shí)監(jiān)控多個(gè)文件描述符(可以是套接字socket,也可以是普通文件)的讀、寫和異常事件。它使進(jìn)程能夠告訴內(nèi)核等待多個(gè)事件中的任何一個(gè)發(fā)生,并只在有一個(gè)或多個(gè)事件發(fā)生或經(jīng)歷一段指定時(shí)間后才喚醒它。這樣做的優(yōu)點(diǎn)是,不需要應(yīng)用程序自行檢測(cè)和處理每個(gè)客戶端連接的狀態(tài),可以節(jié)省大量的系統(tǒng)資源,提高應(yīng)用程序的效率。
1.2 函數(shù)詳解
首先,我們需要包含一些必要的頭文件以使用select函數(shù)和相關(guān)的數(shù)據(jù)結(jié)構(gòu):
#include <sys/select.h> #include <sys/time.h> #include <sys/types.h> #include <unistd.h>
接下來是select函數(shù)的原型:
int select(int maxfdpl, fd_set *readset, fd_set *writeset,
fd_set *exceptset, struct timeval *timeout);select函數(shù)接收以下參數(shù):
- maxfdpl: 是需要檢查的所有文件描述符中的最大值加1,因?yàn)檫@會(huì)告訴內(nèi)核檢查的文件描述符的數(shù)量。
- readset: 是一個(gè)文件描述符集合,用于檢查是否有可讀的數(shù)據(jù)。這是輸入-輸出參數(shù),select會(huì)改變其值。
- writeset: 是一個(gè)文件描述符集合,用于檢查是否可以寫入數(shù)據(jù)。這也是輸入-輸出參數(shù),select會(huì)改變其值。
- exceptset: 是一個(gè)文件描述符集合,用于檢查是否有異常情況發(fā)生(如帶外數(shù)據(jù)到達(dá))。這同樣是輸入-輸出參數(shù),select會(huì)改變其值。
- timeout: 是一個(gè)timeval結(jié)構(gòu),用于指定select的阻塞等待時(shí)間。如果設(shè)定時(shí)間為NULL,select將會(huì)一直等待;如果設(shè)定時(shí)間為特定值,select將會(huì)等待指定時(shí)間;如果設(shè)定時(shí)間為0,select將立即返回,這稱為輪詢。
struct timeval {
long tv_sec; /* seconds */
long tv_usec; /* microseconds */
};關(guān)于文件描述符的操作,以下四個(gè)函數(shù)是用于處理fd_set類型數(shù)據(jù)的:
- FD_ZERO(fd_set *set): 這個(gè)函數(shù)用于清除一個(gè)fd_set的所有位,即初始化一個(gè)fd_set。
- FD_SET(int fd, fd_set *set): 這個(gè)函數(shù)用于將特定的文件描述符fd加入到fd_set中。
- FD_CLR(int fd, fd_set *set): 這個(gè)函數(shù)用于將特定的文件描述符fd從fd_set中移除。
- FD_ISSET(int fd, fd_set *set): 這個(gè)函數(shù)用于檢查特定的文件描述符fd是否在fd_set中,如果在,函數(shù)返回非零值,否則返回0。
使用這些函數(shù),我們可以方便地對(duì)文件描述符集合進(jìn)行操作,以便于使用select函數(shù)進(jìn)行IO操作的復(fù)用。
1.3 例子
下面給出一個(gè)使用select創(chuàng)建一個(gè)可以同時(shí)處理多個(gè)客戶端的連接的服務(wù)器:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <arpa/inet.h>
#define MAX_CLIENTS 5
#define BUFFER_SIZE 1024
int main(int argc , char *argv[])
{
int listener, newsockfd, portno, clilen;
char buffer[BUFFER_SIZE];
fd_set master; // 主文件描述符列表
fd_set read_fds; // 用select()的臨時(shí)文件描述符列表
struct sockaddr_in serv_addr, cli_addr;
int FD_MAX; // 最大文件描述符號(hào)
listener = socket(AF_INET, SOCK_STREAM, 0);
memset(&serv_addr, '0', sizeof(serv_addr));
portno = 5000;
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
serv_addr.sin_port = htons(portno);
bind(listener, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
listen(listener, 10);
FD_ZERO(&master); // 清除主監(jiān)聽輸入端口
FD_ZERO(&read_fds); // 清除臨時(shí)集合
// 添加監(jiān)聽器到主集合
FD_SET(listener, &master);
// 追蹤最大的文件描述符
FD_MAX = listener;
while(1)
{
read_fds = master; // 拷貝它
if(select(FD_MAX+1, &read_fds, NULL, NULL, NULL) == -1)
{
perror("Select error!");
exit(1);
}
for(int i = 0; i <= FD_MAX; i++)
{
if(FD_ISSET(i, &read_fds))
{
if(i == listener)
{
// handle new connections
clilen = sizeof(cli_addr);
newsockfd = accept(listener, (struct sockaddr*)&cli_addr, &clilen);
if(newsockfd == -1)
{
perror("accept error");
}
else
{
FD_SET(newsockfd, &master); // 添加到主集合
if(newsockfd > FD_MAX)
{
FD_MAX = newsockfd; // 持續(xù)追蹤最大的文件描述符
}
printf("selectserver: new connection from %s on socket %d\n",inet_ntoa(cli_addr.sin_addr), newsockfd);
}
}
else
{
// 處理來自客戶端的數(shù)據(jù)
if((recv(i, buffer, sizeof(buffer), 0)) <= 0)
{
// got error or connection closed by client
close(i);
FD_CLR(i, &master); // 從主集合中移除
}
else
{
// 我們得到了一些數(shù)據(jù)!
for(int j = 0; j <= FD_MAX; j++)
{
// 發(fā)送數(shù)據(jù)到所有連接
if(FD_ISSET(j, &master))
{
if(j != listener && j != i)
{
send(j, buffer, strlen(buffer), 0);
}
}
}
}
}
}
}
}
return 0;
}上面這個(gè)例子會(huì)創(chuàng)建一個(gè)在端口5000監(jiān)聽的服務(wù)器。使用select,我們可以在單個(gè)線程中同時(shí)處理多個(gè)客戶端的連接。這就是使用select的優(yōu)點(diǎn):我們可以同時(shí)處理多個(gè)連接,而不需要為每個(gè)連接創(chuàng)建一個(gè)單獨(dú)的線程或進(jìn)程。
1.4 總結(jié)
- 優(yōu)點(diǎn):
- 跨平臺(tái)性:select是遵循POSIX標(biāo)準(zhǔn)的,所以在多種平臺(tái)上都可以使用,具有較好的跨平臺(tái)性。
- 精確的超時(shí)等待時(shí)間:select可以設(shè)置超時(shí)時(shí)間,對(duì)時(shí)間的精確度可以達(dá)到微秒級(jí)別。
- 缺點(diǎn):
- 文件描述符上限:select所能監(jiān)聽的文件描述符的數(shù)量是有限的,取決于_FD_SETSIZE的值,默認(rèn)為1024。如果需要處理的并發(fā)連接數(shù)過多,select可能無法滿足需求。
- 性能下降:select在內(nèi)核中通過輪詢所有文件描述符的方式來檢查其狀態(tài),當(dāng)監(jiān)控的文件描述符數(shù)量增多時(shí),性能會(huì)下降。
- 使用復(fù)雜:select在返回時(shí),只會(huì)告訴用戶哪些描述符集合是就緒的,但并不會(huì)直接告訴用戶哪一個(gè)具體的文件描述符就緒,用戶需要自己去遍歷這些集合,操作比較復(fù)雜。
- 多次數(shù)據(jù)拷貝:每次調(diào)用select都需要將文件描述符集合從用戶空間拷貝到內(nèi)核空間,這增加了額外的開銷。
- 重復(fù)操作:每次select返回后,所有未就緒的文件描述符都會(huì)被移除,因此每次使用都需要重新向集合中添加描述符。
- 注意事項(xiàng):
- 一般情況下,我們無法通過改變進(jìn)程打開的文件描述符個(gè)數(shù)來改變select能夠監(jiān)聽的文件描述符個(gè)數(shù),這個(gè)數(shù)量受限于_FD_SETSIZE。
- 當(dāng)套接字上發(fā)生錯(cuò)誤時(shí),select會(huì)將其標(biāo)記為既可讀又可寫。
- 接收和發(fā)送低水位標(biāo)記的目的在于,讓應(yīng)用程序可以控制在多少數(shù)據(jù)可讀或有多少空間可寫時(shí)喚醒select。例如,當(dāng)有64字節(jié)的數(shù)據(jù)可讀時(shí),select才會(huì)被喚醒。這樣可以避免頻繁打斷應(yīng)用程序來處理IO操作,提高程序的效率。
2. poll
1.1 概述
poll函數(shù)提供了類似于select的功能,允許進(jìn)程向內(nèi)核指示等待多個(gè)事件中的任何一個(gè)發(fā)生,它只在有一個(gè)或多個(gè)事件發(fā)生或經(jīng)歷一段指定時(shí)間后才喚醒進(jìn)程。不過,與select相比,poll在處理流設(shè)備時(shí)能夠提供更豐富的信息。它能有效地管理多個(gè)輸入/輸出源,并且在特定事件發(fā)生時(shí)進(jìn)行響應(yīng),這使得對(duì)多任務(wù)并發(fā)處理的支持更為高效。
1.2 函數(shù)詳解
poll函數(shù)的聲明如下:
#include <poll.h> int poll(struct pollfd *fds, nfds_t nfds, int timeout);
這里是poll函數(shù)的參數(shù)列表:
- fds:一個(gè)指向一個(gè)pollfd結(jié)構(gòu)體數(shù)組的指針。這個(gè)數(shù)組中的每一個(gè)成員都代表一個(gè)特定的文件描述符以及對(duì)它感興趣的事件和發(fā)生的事件。
- nfds:fds數(shù)組的成員數(shù)量。
- timeout:調(diào)用應(yīng)該等待的最大毫秒數(shù),以阻塞的方式等待文件描述符變?yōu)榫途w。如果這個(gè)值是-1,poll將會(huì)無限期的阻塞。如果這個(gè)值是0,poll將立即返回。
pollfd結(jié)構(gòu)體的定義如下:
struct pollfd {
int fd; /* 文件描述符 */
short events; /* 要監(jiān)控的事件 */
short revents; /* 實(shí)際發(fā)生的事件 */
};- fd:需要被poll監(jiān)控的文件描述符。
- events:一組位標(biāo)志,表示對(duì)應(yīng)的文件描述符上我們感興趣的事件。比如:POLLIN(有數(shù)據(jù)可讀),POLLOUT(寫數(shù)據(jù)不會(huì)阻塞),POLLERR(錯(cuò)誤事件),POLLHUP(掛起事件)等。
- revents:一組位標(biāo)志,表示在對(duì)應(yīng)的文件描述符上實(shí)際發(fā)生了哪些事件。這是poll調(diào)用返回后由內(nèi)核填充的。
當(dāng)調(diào)用poll函數(shù)時(shí),內(nèi)核會(huì)檢查每個(gè)pollfd結(jié)構(gòu)體中列出的文件描述符,看看是否有任何指定的事件發(fā)生。如果有,內(nèi)核將會(huì)在revents字段中設(shè)置相應(yīng)的位,以指示哪些事件已經(jīng)發(fā)生。然后poll函數(shù)返回,應(yīng)用程序可以檢查每個(gè)pollfd結(jié)構(gòu)體的revents字段來確定每個(gè)文件描述符上發(fā)生了哪些事件。
1.3 例子
在下面這個(gè)示例中,我們會(huì)創(chuàng)建兩個(gè)管道(pipe),然后使用 poll 來等待這兩個(gè)管道中的任何一個(gè)變得可讀。
#include <stdio.h>
#include <unistd.h>
#include <poll.h>
#define TIMEOUT 5
int main (void)
{
struct pollfd fds[2];
int ret;
// 創(chuàng)建兩個(gè)管道
int pipefd[2];
pipe(pipefd);
// watch stdin (fd 0) for input
fds[0].fd = 0;
fds[0].events = POLLIN;
// watch pipe for input
fds[1].fd = pipefd[0];
fds[1].events = POLLIN;
ret = poll(fds, 2, TIMEOUT * 1000);
if (ret == -1) {
perror ("poll");
return 1;
}
if (!ret) {
printf ("%d seconds elapsed.\n", TIMEOUT);
return 0;
}
if (fds[0].revents & POLLIN)
printf ("stdin is readable\n");
if (fds[1].revents & POLLIN)
printf ("pipe is readable\n");
return 0;
}在上述示例中,我們使用 poll 來同時(shí)監(jiān)聽標(biāo)準(zhǔn)輸入和管道的輸入。如果在5秒鐘內(nèi),標(biāo)準(zhǔn)輸入或管道有任何數(shù)據(jù)可讀,那么 poll 就會(huì)返回,并通過檢查 revents 標(biāo)志來通知我們哪一個(gè)文件描述符已經(jīng)就緒。如果在5秒內(nèi)沒有任何數(shù)據(jù)可讀,那么 poll 也會(huì)返回,此時(shí)我們可以打印一個(gè)超時(shí)信息。這就是使用 poll 的優(yōu)點(diǎn):我們可以同時(shí)處理多個(gè)輸入源,而不需要為每個(gè)輸入源創(chuàng)建一個(gè)單獨(dú)的線程或進(jìn)程。
1.4 總結(jié)
- 1.優(yōu)點(diǎn):
- 無最大文件描述符限制:不像select有FD_SETSIZE限制,poll沒有這個(gè)限制,所以可以處理更多的文件描述符。
- 調(diào)用方式簡(jiǎn)單:poll使用一個(gè)pollfd的數(shù)組作為參數(shù),而不是像select一樣使用一組文件描述符集合。這使得設(shè)置和處理poll的調(diào)用更加簡(jiǎn)單。
- 無需重新設(shè)置文件描述符:每次調(diào)用select后,由于select函數(shù)會(huì)改變文件描述符集合,所以需要重新設(shè)置。而在poll中,不需要這么做,因?yàn)閜oll并不會(huì)更改pollfd的數(shù)組。
- 提供了更多的事件類型:除了常規(guī)的讀、寫和異常條件,poll還支持帶外數(shù)據(jù)、優(yōu)先數(shù)據(jù)等更多的事件類型。
- 2.缺點(diǎn):
- 仍需要遍歷所有文件描述符:雖然poll沒有最大文件描述符限制,但返回后仍需要遍歷整個(gè)文件描述符列表來找出哪些文件描述符已就緒,當(dāng)文件描述符數(shù)量較大時(shí),這也可能會(huì)影響性能。
- 不支持文件描述符的優(yōu)先級(jí):poll函數(shù)處理所有的文件描述符是平等的,不像某些其他的I/O多路復(fù)用技術(shù)可以設(shè)置優(yōu)先級(jí)。
- 缺乏廣泛的跨平臺(tái)支持:雖然poll在許多系統(tǒng)上都可用,但它的實(shí)現(xiàn)并不總是完全可靠,特別是在一些老的系統(tǒng)上。
- 3.注意事項(xiàng):
- 移除文件描述符:如果你不再關(guān)心某個(gè)文件描述符,可以將對(duì)應(yīng)的pollfd結(jié)構(gòu)中的fd設(shè)置為-1,這樣poll將不再監(jiān)視這個(gè)文件描述符,并且在返回時(shí)將會(huì)將revents設(shè)置為0。"事件-結(jié)果"模式:與select使用"值-結(jié)果"參數(shù)傳遞方式不同,pollfd包含了要監(jiān)視的events和實(shí)際發(fā)生的revents,這樣應(yīng)用程序可以在同一地方查看它關(guān)心的事件,并確定哪些已經(jīng)發(fā)生。
- 處理大量文件描述符:雖然poll沒有文件描述符數(shù)量的限制,但是如果監(jiān)視的文件描述符過多,可能會(huì)導(dǎo)致性能下降,因?yàn)闊o論是否有事件發(fā)生,poll返回后都需要遍歷整個(gè)文件描述符列表。
- 在poll返回后處理結(jié)果:像select一樣,當(dāng)poll返回后,你需要遍歷所有的pollfd來找出哪些文件描述符已經(jīng)就緒。這是因?yàn)閜oll只告訴你有多少文件描述符就緒,但并不告訴你具體是哪些。
3. epoll
1.1 概述
在許多并發(fā)連接中只有少數(shù)活路的場(chǎng)景下,epoll是Linux下I/O多路復(fù)用接口select/poll的增強(qiáng)版本,能有效提升系統(tǒng)CPU的使用率。區(qū)別于select和poll每次等待事件之前都需要重新設(shè)置監(jiān)視的文件描述符集,epoll能復(fù)用文件描述符集來傳遞結(jié)果,減少了重復(fù)的準(zhǔn)備工作。
獲取事件時(shí),epoll無需像select和poll一樣遍歷整個(gè)被偵聽的描述符集,只需遍歷被內(nèi)核IO事件異步喚醒并加入到就緒隊(duì)列的描述符集即可。這使得處理大量文件描述符時(shí),只有實(shí)際產(chǎn)生活動(dòng)的文件描述符才需要被處理,從而大大提升了效率。
當(dāng)前,在大規(guī)模并發(fā)網(wǎng)絡(luò)程序中,epoll已經(jīng)成為首選模型。除了提供select/poll的IO事件電平觸發(fā)(Level Triggered)模式,epoll還額外提供了邊沿觸發(fā)(Edge Triggered)模式,這使得用戶空間程序可以緩存IO狀態(tài),減少epoll_wait/epoll_pwait的調(diào)用,從而進(jìn)一步提升了程序的運(yùn)行效率。
1.2 函數(shù)詳解
epoll 是 Linux 中的 I/O 多路復(fù)用接口,常用的API有 epoll_create、epoll_ctl 和 epoll_wait。以下是這些API的詳細(xì)介紹:
1.epoll_create:創(chuàng)建一個(gè)epoll的句柄。
#include <sys/epoll.h> int epoll_create(int size);
參數(shù):size參數(shù)現(xiàn)在并不起作用,但是必須大于0。
返回值:如果成功,返回一個(gè)非負(fù)的文件描述符。失敗時(shí),返回-1。
2.epoll_ctl:控制某個(gè)epoll文件描述符上的事件,可以注冊(cè)、修改、刪除。
#include <sys/epoll.h> int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
參數(shù):
epfd:epoll_create函數(shù)返回的文件描述符。op:要進(jìn)行的操作。EPOLL_CTL_ADD(添加)、EPOLL_CTL_MOD(修改)、EPOLL_CTL_DEL(刪除)。fd:關(guān)聯(lián)的文件描述符。event:指向epoll_event的指針。
??????struct epoll_event {
uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
?events成員是一組標(biāo)記,系統(tǒng)所感興趣的事件和可能發(fā)生的返回事件,如EPOLLIN(可讀)、EPOLLOUT(可寫)、EPOLLET(設(shè)置為邊緣觸發(fā)模式)、EPOLLONESHOT(一次性的)、EPOLLRDHUP(對(duì)端斷開連接或者關(guān)閉寫操作的一種表示)等。data成員用于存儲(chǔ)用戶數(shù)據(jù),可以是一個(gè)指針,也可以是一個(gè)整型的標(biāo)識(shí)符。
返回值:成功時(shí),返回0。失敗時(shí),返回-1。
3.epoll_wait:等待epoll上的I/O事件。
#include <sys/epoll.h> int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
參數(shù):
epfd:epoll_create函數(shù)返回的文件描述符。events:用來從內(nèi)核得到事件的集合。maxevents:告訴內(nèi)核這個(gè)events的大小,不能大于創(chuàng)建epoll_create時(shí)的size。timeout:等待I/O事件發(fā)生的超時(shí)值(單位:毫秒)。0表示立即返回,-1表示一直等待。
返回值:成功時(shí),返回需要處理的事件數(shù)目。如返回0表示已經(jīng)超時(shí)。失敗時(shí),返回-1。
1.3 例子
以下是一個(gè)使用epoll的示例,其中包含了epoll_create、epoll_ctl和epoll_wait等函數(shù)的使用,主要展示了epoll的IO多路復(fù)用和邊緣觸發(fā)(ET)模式特性:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/epoll.h>
#include <fcntl.h>
#define MAX_EVENTS 10
void set_nonblock(int fd) {
int flags = fcntl(fd, F_GETFL, 0);
flags |= O_NONBLOCK;
fcntl(fd, F_SETFL, flags);
}
int main() {
struct epoll_event ev, events[MAX_EVENTS];
int listen_sock, conn_sock, nfds, epollfd;
/* Code to set up listening socket, 'listen_sock',
(socket(), bind(), listen()) omitted */
epollfd = epoll_create1(0);
if (epollfd == -1) {
perror("epoll_create1");
exit(EXIT_FAILURE);
}
ev.events = EPOLLIN | EPOLLET;
ev.data.fd = listen_sock;
if (epoll_ctl(epollfd, EPOLL_CTL_ADD, listen_sock, &ev) == -1) {
perror("epoll_ctl: listen_sock");
exit(EXIT_FAILURE);
}
for (;;) {
nfds = epoll_wait(epollfd, events, MAX_EVENTS, -1);
if (nfds == -1) {
perror("epoll_wait");
exit(EXIT_FAILURE);
}
for (int n = 0; n < nfds; ++n) {
if (events[n].data.fd == listen_sock) {
conn_sock = accept(listen_sock, (struct sockaddr *) &local, &addrlen);
if (conn_sock == -1) {
perror("accept");
exit(EXIT_FAILURE);
}
set_nonblock(conn_sock);
ev.events = EPOLLIN | EPOLLET;
ev.data.fd = conn_sock;
if (epoll_ctl(epollfd, EPOLL_CTL_ADD, conn_sock, &ev) == -1) {
perror("epoll_ctl: conn_sock");
exit(EXIT_FAILURE);
}
} else {
do_use_fd(events[n].data.fd);
}
}
}
close(epollfd);
return 0;
}在上述代碼中,首先創(chuàng)建了一個(gè)epoll對(duì)象,然后將監(jiān)聽套接字添加到epoll事件集合中,并注冊(cè)了EPOLLIN和EPOLLET事件,EPOLLIN代表對(duì)應(yīng)的文件描述符可讀,EPOLLET代表以邊緣觸發(fā)模式對(duì)事件進(jìn)行處理。
在無限循環(huán)中,調(diào)用epoll_wait來等待I/O事件的發(fā)生,當(dāng)新的連接進(jìn)來時(shí),使用accept接受新的連接,然后將新的連接設(shè)為非阻塞模式,并添加到epoll事件集合中。當(dāng)連接上有數(shù)據(jù)可讀時(shí),調(diào)用do_use_fd函數(shù)進(jìn)行處理。
此代碼展示了epoll可以動(dòng)態(tài)地添加、修改和刪除關(guān)注的文件描述符,也展示了邊緣觸發(fā)模式的使用,這些都是epoll的主要特點(diǎn)。
1.4 總結(jié)
1.優(yōu)點(diǎn):
- 沒有最大并發(fā)連接的限制,能打開的FD的上限遠(yuǎn)大于1024(一般來說是系統(tǒng)的最大文件句柄數(shù))。
- 效率提升,不會(huì)隨著FD數(shù)目的增加而線性下降。不管有多少FD,都只有活躍的FD會(huì)調(diào)用callback(在epoll_wait中返回),所以在活動(dòng)連接較少的情況下,使用epoll不僅在存儲(chǔ)空間上有優(yōu)勢(shì),且效率上也有優(yōu)勢(shì)。
- 提供了更多的觸發(fā)模式選擇,除了水平觸發(fā)模式外,還支持邊緣觸發(fā)模式,使得用戶空間程序有可能緩存IO狀態(tài),減少epoll_wait的調(diào)用次數(shù),提高應(yīng)用程序效率。
2.缺點(diǎn):
- 只能在Linux系統(tǒng)上運(yùn)行,不具備跨平臺(tái)性。
- 對(duì)于文件描述符較少的情況,使用select/poll的性能并不差。
- 使用邊緣觸發(fā)模式時(shí),需要更細(xì)心地處理各種情況,以防止消息遺漏。
3.注意事項(xiàng):
- 在使用epoll的ET(邊緣觸發(fā))模式時(shí),需要在讀、寫操作時(shí)都檢查EAGAIN錯(cuò)誤,確保所有可用數(shù)據(jù)或所有可寫的空間都被使用。
- 在使用ET模式時(shí),如果一次讀取的數(shù)據(jù)沒有達(dá)到socket的接收緩沖區(qū)的大小,那么下一次epoll_wait可能不會(huì)返回該socket的讀事件。
4. 三者的區(qū)別
| 選擇方式 | select | poll | epoll |
|---|---|---|---|
| 操作方式 | 遍歷 | 遍歷 | 回調(diào) |
| 底層實(shí)現(xiàn) | 數(shù)組 | 鏈表 | 紅黑樹 |
| IO效率 | 每次調(diào)用都進(jìn)行線性遍歷,時(shí)間復(fù)雜度為O(n) | 每次調(diào)用都進(jìn)行線性遍歷,時(shí)間復(fù)雜度為O(n) | 事件通知方式,每當(dāng)fd就緒,系統(tǒng)注冊(cè)的回調(diào)函數(shù)就會(huì)被調(diào)用,將就緒fd放到readyList里面,時(shí)間復(fù)雜度O(1) |
| 最大連接數(shù) | 1024(x86)或2048(x64) | 無上限 | 無上限 |
| fd拷貝 | 每次調(diào)用select,都需要把fd集合從用戶態(tài)拷貝到內(nèi)核態(tài) | 每次調(diào)用poll,都需要把fd集合從用戶態(tài)拷貝到內(nèi)核態(tài) | 調(diào)用epoll_ctl時(shí)拷貝進(jìn)內(nèi)核并保存,之后每次epoll_wait不拷貝 |
到此這篇關(guān)于C/C++中的select、poll和epoll的文章就介紹到這了,更多相關(guān)C++ select、poll和epoll內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C++中虛繼承時(shí)的構(gòu)造函數(shù)示例詳解
在虛繼承中,虛基類是由最終的派生類初始化的,換句話說,最終派生類的構(gòu)造函數(shù)必須要調(diào)用虛基類的構(gòu)造函數(shù),這跟普通繼承不同,在普通繼承中,派生類構(gòu)造函數(shù)中只能調(diào)用直接基類的構(gòu)造函數(shù),不能調(diào)用間接基類的,所以本文將通過代碼示例給大家介紹一下C++虛繼承構(gòu)造函數(shù)2023-09-09
C語言實(shí)現(xiàn)學(xué)生成績(jī)等級(jí)劃分的方法實(shí)例
這篇文章主要給大家介紹了關(guān)于C語言實(shí)現(xiàn)學(xué)生成績(jī)等級(jí)劃分的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-12-12
基于Matlab實(shí)現(xiàn)繪制3D足球的示例代碼
這篇文章主要為大家詳細(xì)介紹了如何利用Matlab實(shí)現(xiàn)繪制3D足球,文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)Matlab有一定幫助,需要的可以參考一下2022-11-11
Qt添加MSVC2017編譯器的實(shí)現(xiàn)方法
Qt添加MSVC2017編譯器是開發(fā)者在Windows平臺(tái)上進(jìn)行Qt應(yīng)用程序開發(fā)的重要步驟,本文詳細(xì)介紹了如何為Qt配置MSVC2017編譯器的具體步驟,感興趣的可以了解一下2023-09-09
三種獲取網(wǎng)頁源碼的方法(使用MFC/Socket實(shí)現(xiàn))
Windows下比較簡(jiǎn)單的獲取網(wǎng)頁源碼的方法:使用MFC、使用MFC、Socket實(shí)現(xiàn)2013-12-12
C++實(shí)現(xiàn)LeetCode(139.拆分詞句)
這篇文章主要介紹了C++實(shí)現(xiàn)LeetCode(139.拆分詞句),本篇文章通過簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-07-07

