|
Select,Poll,Epoll是學(xué)習(xí)I/O多路復(fù)用必不可少的一節(jié),這章我將借用對(duì)這三個(gè)系統(tǒng)調(diào)用的講解,進(jìn)一步加深對(duì)于I/O多路復(fù)用的理解
首先我們要知道為什么要有I/O多路復(fù)用,可以通過(guò)一次系統(tǒng)調(diào)用,檢查多個(gè)文件描述符的狀態(tài)。這是 I/O 多路復(fù)用的主要優(yōu)點(diǎn),相比于非阻塞 I/O,在文件描述符較多的場(chǎng)景下,避免了頻繁的用戶(hù)態(tài)和內(nèi)核態(tài)的切換,減少了系統(tǒng)調(diào)用的開(kāi)銷(xiāo)。
I/O 多路復(fù)用相當(dāng)于將「遍歷所有文件描述符、通過(guò)非阻塞 I/O 查看其是否就緒」的過(guò)程從用戶(hù)線(xiàn)程移到了內(nèi)核中,由內(nèi)核來(lái)負(fù)責(zé)輪詢(xún)。
首先回憶一下,I/O多路復(fù)用是一種使用少數(shù)線(xiàn)程來(lái)監(jiān)聽(tīng)多數(shù)網(wǎng)絡(luò)Socket的一種I/O方法,那么我們?cè)趺词褂肐/O多路復(fù)用呢
Select
select使用一個(gè)固定大小的位圖來(lái)表示文件描述符fd的集合,調(diào)用select檢查這些fd的狀態(tài),每次調(diào)用select時(shí)都會(huì)重新構(gòu)建位圖,并將其傳遞給內(nèi)核,內(nèi)核來(lái)判斷是否有I/O已經(jīng)就緒
select的核心數(shù)據(jù)結(jié)構(gòu)是一個(gè)fd_set, 這是一個(gè)文件描述符集合,用來(lái)管理需要監(jiān)視的文件描述符
fd_set的核心是一個(gè)位圖(大小位1024),每一位對(duì)應(yīng)著文件描述符的狀態(tài),1表示該描述符需要監(jiān)視,0表示該描述符不需要監(jiān)視
typedef __kernel_fd_set fd_set;
#define __FD_SETSIZE 1024
typedef struct {
unsigned long fds_bits[__FD_SETSIZE / (8 * sizeof(long))];
} __kernel_fd_set;
select的函數(shù)原型如下
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
可以看到,有三種fd_set
● readfds -- 可讀事件,用來(lái)監(jiān)視事件是否可讀
● writefds -- 可寫(xiě)事件,用來(lái)監(jiān)視事件是否可寫(xiě)
● exceptfds -- 描述符是否有異常情況
● nfds -- 要監(jiān)視的文件描述符的最大值+1
● timeout -- 可以選擇的監(jiān)視時(shí)間 可以是 阻塞(NULL),立即返回(0),或者指定的等待時(shí)間
1. 在調(diào)用select的是否我們需要把需要監(jiān)視的事件通過(guò)函數(shù)加入到對(duì)應(yīng)的隊(duì)列中
2. 進(jìn)入內(nèi)核態(tài)進(jìn)行檢查,程序在調(diào)用select之后,內(nèi)核會(huì)歷遍對(duì)應(yīng)的fd查看是否符合對(duì)應(yīng)的狀態(tài)
a. 符合:如果符合就把事件加入到readfds當(dāng)中去
b. 不符合:如果所有的都不符合就根據(jù)timeout來(lái)選擇等待的方式和事件
3. 返回,最后會(huì)返回符合要求的fd的數(shù)量
while (1) {
fd_set rfds;
fd_set wfds;
int32_t maxfd = 0, res = 0;
struct timeval timeout;
timeout.tv_sec = 0;
timeout.tv_usec = 500;
FD_ZERO(&rfds);
FD_ZERO(&wfds);
FD_SET(socket1, &rfds);
FD_SET(socket2, &rfds);
maxfd = socket1 > socket2 ? socket1 : socket2;
res = select(maxfd + 1, &rfds, NULL, NULL, &timeout);
if (res < 0 && errno != EINTR && errno != 0) {
// log it
return;
}
if (FD_ISSET(socket1, &rfds)) {
// do something
}
if (FD_ISSET(socket2, &rfds)) {
// do something
}
}
上面大致講解了如何使用select,這里其實(shí)我們可以很明顯的看出一個(gè)缺點(diǎn)的,
1. 就是我們并不返回符合要求的fd,而是把所有的fd都返回,所以返回到用戶(hù)態(tài)之后我們要進(jìn)行fd的歷遍最終才能找到有相應(yīng)的fd,這顯然是比較耗費(fèi)事件的 |
|