《UNIX網絡編程》筆記 - select和poll

簡介

IO複用:讓進程等待一系列IO條件而不是一個IO條件數組

經過selectpoll函數咱們能夠同時監聽多個描述符,在描述符就緒時進行對應的操做。ide

select

定義:函數

//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

  1. 接收緩衝區數據字節數大於低水位(默認是1),這時讀取操做返回大於0
  2. 讀半關閉,也就是對端發來了FIN,這時返回0,也就是EOF
  3. 當前套接字是監聽套接字,並且已完成鏈接數不爲0,這時能夠進行accept操做
  4. 描述符上有套接字錯誤須要處理

描述符寫就緒條件進程

  1. 發送緩衝區數據字節數大於低水位(一般爲2048);
  2. 套接字已鏈接或不須要鏈接(UDP)
  3. 寫半關閉,這時若是再寫會收到SIGPIPE信號
  4. 使用非阻塞式connect的套接字已創建鏈接或者connect失敗
  5. 描述符上有套接字錯誤須要處理

shutdown&closeip

有兩個函數能夠關閉套接字:shutdownclose,它們的區別以下:

  1. close會將引用計數減1,當計數爲0時關閉套接字;shutdown能夠直接觸發關閉。
  2. 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

pollselect的功能相似,也支持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的就緒條件:

  • 全部正規TCP數據和全部UDP數據視爲普通數據
  • TCP帶外數據視爲優先級帶數據
  • 當TCP讀半關閉時(收到對端傳來的FIN),也視爲普通數據,隨後的讀操做將返回0
  • TCP鏈接存在錯誤既能夠視爲普通數據,也能夠視爲錯誤(POLLERR)。隨後的讀操做將返回-1,並設置全局的errno變量。
  • 監聽套接字上有新的鏈接既能夠視爲普通數據,也能夠視爲優先級數據。
  • 非阻塞式的connect的完成視爲使對應的套接字可寫。

總結

selectpoll都支持IO複用,其思路都是調用函數監聽多個描述符,當有描述符就緒或者超時的時候函數調用就會返回,對應的描述符集合狀態也會改變,這時候再遍歷描述符集合,處理其中就緒的部分便可。

這種方式在須要監聽的描述符比較小,或者是每次就緒的描述符不少的狀況下比較有效;但當描述符不少並且每次只有少數描述符就緒時,效率就比較低了。後面出現的epoll就避免了這種線性遍歷的問題。

另外select還受FD_SETSIZE的限制,只能處理較少的描述符,而poll則沒有這個限制。poll監聽的集合大小隻受進程能打開的文件數量(RLIMIT_NOFILE)的限制。

相關文章
相關標籤/搜索