IO複用:讓進程等待一系列IO條件而不是一個IO條件數組
經過select
和poll
函數咱們能夠同時監聽多個描述符,在描述符就緒時進行對應的操做。ide
定義:函數
//maxfdpl: 待測試的描述符個數
//返回就緒描述符的個數,若超時則爲0, 若出錯則爲-1
int select(int maxfdpl, fd_set *readset, fd_set *writeset, fd_set *exceptset, struct timeval *timeout);
//超時選項
//NULL:wait forever;0:don't wait
struct timeval {
long tv_sec; /* seconds */
long tv_usec; /* and microseconds */
};
//每一個fds_bit的每一位對應一個描述符
typedef struct fd_set {
int fds_bits[FD_SETSIZE/sizeof(int)/NBBY]; /* NBBY=bits in a byte ; usually 8*/
} fd_set;
#define FD_SETSIZE 1024 /* fd_set中描述符的總數 */
void FD_ZERO(fd_set *fdset); /* clear all bits in fdset */
void FD_SET(int fd, fd_set *fdset); /* turn on the bit for fd in fdset */
void FD_CLR(int fd, fd_set *fdset); /* turn off the bit for fd in fdset */
void FD_ISSET(int fd, fd_set *fdset); /* is the bit for fd on in fdset ? */
複製代碼
select的使用方法:測試
int fds[FD_SETSIZE]; 保存當前全部描述符
fd_set rset, wset, eset; //定義讀、寫、異常對應的fd_set
//初始化fd_set,很是重要且不能省略,由於若是不初始化可能會影響FD_ISSET的調用結果
FD_ZERO(&rset);
FD_ZERO(&wset);
FD_ZERO(&eset);
for (;;) {
//在循環中調用select
select(FD_SETSIZE, &rset, &wset, &eset, NULL);
//遍歷當前全部的fd,處理就緒的fd
for (int i = 0; i < FD_SETSIZE; i++)
{
if (FD_ISSET(fds[i], &rset))
{
//handle read
}
if (FD_ISSET(fds[i], &wset))
{
//handle write
}
if (FD_ISSET(fds[i], &eset))
{
//handle exception
}
}
}
複製代碼
fd_set的限制spa
在很早以前就看到網上的介紹說select在描述符個數上是有限制的,如今終於知道這個限制是從哪來的了,這實際上跟fd_set的實現機制有關。rest
fd_set
中使用int數組中的各個位來保存多個描述符的狀態,這個數組稱爲描述符集
,好比數組的第一個數有32位,那麼第一個數的每一位就表示第0~31個描述符的狀態,這樣一來當咱們調用FD_ISSET
來判斷某一個描述符狀態時,咱們只須要找到其對應的位判斷其是0或者1就好了;同理當咱們須要設置某個描述符狀態時,只須要設置對應的位的狀態便可。而fd_set
中數組的大小是經過FD_SETSIZE
這個值算出來的,FD_SETSIZE
是一個宏定義,一般它的默認值比較小,在個人mac上查看其默認值是1024,也就是說在個人mac上select可以支持的最大的描述符數量是1024個。固然FD_SETSIZE
也能夠從新定義,但若是要調整須要從新編譯內核。code
描述符讀就緒條件orm
描述符寫就緒條件進程
SIGPIPE
信號connect
的套接字已創建鏈接或者connect
失敗shutdown&closeip
有兩個函數能夠關閉套接字:shutdown
和close
,它們的區別以下:
close
會將引用計數減1,當計數爲0時關閉套接字;shutdown
能夠直接觸發關閉。close
會終止讀和寫兩個方向;shutdown
能夠經過參數howto
指定關閉某個方向int close(int fd);
int shutdown(int fd, int howto);
/* * howto arguments for shutdown(2), specified by Posix.1g. */
#define SHUT_RD 0 /* shut down the reading side */
#define SHUT_WR 1 /* shut down the writing side */
#define SHUT_RDWR 2 /* shut down both sides */
複製代碼
poll
和select
的功能相似,也支持IO複用,可是poll
沒有使用描述符集,而是使用pollfd
這種結構來表示描述符的狀態。
//nfds:array的長度,受進程能打開的最大文件數限制
//返回就緒描述符的個數,若超時則爲0, 若出錯則爲-1
int poll(struct pollfd *fdarray, unsigned long nfds, int timeout);
struct pollfd {
int fd; /* descriptor to check */
short events; /* events of intrests on fd */
short revents; /* events that occurred on fd */
};
複製代碼
poll
的使用方法:
struct pollfd pollfds[OPEN_MAX]; //定義pollfd數組,將須要監聽的描述符保存起來
for (;;)
{
//在循環中調用poll
poll(pollfds, OPEN_MAX, INFTIM);
for (int i = 0; i < OPEN_MAX; i++)
{
//遍歷pollfd數組處理就緒的描述符
struct pollfd pfd = pollfds[i];
if (pfd.revents & POLLIN) {
//handle read
}
if (pfd.revents & POLLOUT) {
//handle write
}
}
}
複製代碼
poll
識別的數據類型:普通(normal)、優先級帶(priority band)、高優先級(high priority); 這些術語來自基於流的實現。(沒太明白,先標記下)
events常量列舉:
常量 | 出如今events | 出如今revents | 說明 |
---|---|---|---|
POLLIN | y | y | 普通或優先級帶數據可讀 |
POLLRDNORM | y | y | 普通數據可讀 |
POLLRDBAND | y | y | 優先級帶數據可讀 |
POLLPRI | y | y | 高優先級帶數據可讀 |
POLLOUT | y | y | 普通數據可寫 |
POLLWRNORM | y | y | 普通數據可寫 |
POLLWRBAND | y | y | 優先級帶數據可寫 |
POLLERR | n | y | 發生錯誤 |
POLLHUP | n | y | 發生掛起 |
POLLNVAL | n | y | 描述符不是一個打開的文件 |
poll的就緒條件:
POLLERR
)。隨後的讀操做將返回-1,並設置全局的errno
變量。connect
的完成視爲使對應的套接字可寫。select
和poll
都支持IO複用,其思路都是調用函數監聽多個描述符,當有描述符就緒或者超時的時候函數調用就會返回,對應的描述符集合狀態也會改變,這時候再遍歷描述符集合,處理其中就緒的部分便可。
這種方式在須要監聽的描述符比較小,或者是每次就緒的描述符不少的狀況下比較有效;但當描述符不少並且每次只有少數描述符就緒時,效率就比較低了。後面出現的epoll
就避免了這種線性遍歷的問題。
另外select
還受FD_SETSIZE
的限制,只能處理較少的描述符,而poll
則沒有這個限制。poll
監聽的集合大小隻受進程能打開的文件數量(RLIMIT_NOFILE
)的限制。