APUE之I/O多路複用

I/O多路複用有3種具體實現模型:select、poll和epoll

/* int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout)
 * @nfds        要監聽的最大描述符編號加1,select支持的最大描述符編號爲FD_SETSIZE(通常是1024)
 * @readfds     要監聽可讀事件的描述符集合,NULL表示不監聽這類事件
 * @writefds    要監聽可寫事件的描述符集合,NULL表示不監聽這類事件
 * @exceptfds   要監聽異常事件的描述符集合,NULL表示不監聽這類事件
 * @timeout     超時時間,其中NULL表示永遠等待,全0表示立即返回(即使沒有事件觸發)
 * @返回值      如果因爲監聽的事件被觸發則返回關聯的描述符總數,同時會修改傳入的描述符集合,那些置1的描述符就是被觸發了的;
 *              如果因爲超時則返回0,同時所有的描述符集合都會被清0;
 *              如果出錯則返回-1。
 *
 * 備註:
 *      [1]. select返回-1時需要額外判斷errno,因爲信號會中斷select調用並也返回-1(同時將errno設置爲EINTR),這種情況下通常需要重新調用select
 *      [2]. fd_set這個數據類型用於表示描述符集合,其實現跟平臺相關,所以對描述符集合只能通過系統提供的4個接口進行操作:
 *                  FD_ZERO(fd_set *fdset)          將一個描述符集合的所有位清0
 *                  FD_SET(int fd,fd_set *fdset)    將一個描述符集合中fd對應的位置1
 *                  FD_CLR(int fd,fd_set *fdset)    將一個描述符集合中fd對應的位清0
 *                  FD_ISSET(int fd,fd_set *fdset)  判斷一個描述符集合中fd對應的位是否已置1
 *      [3]. 如果同一描述符的讀、寫事件都被觸發,那麼在select返回值中會對其記兩次數
 *      [4]. 由於select調用過程中可能會修改傳入的描述符集合和timeout,所以再次調用select時需要重新設置這幾個入參
 *      [5]. select異常事件主要包括網絡連接上有帶外數據到達等,需要注意的是異常事件不包括到達文件尾端和TCP連接斷開,這兩種情況下,select會認爲關聯的描述符可讀,然後調用read系列API,返回0
 *
 * select模型的主要缺陷:
 *          select會修改傳入的描述符集合和timeout,導致每次調用select都需要重新初始化這些入參,這對於需要反覆執行的場景顯然不太合理;
 *	        如果某個描述符監聽的條件被觸發,select僅僅會返回,但並不告訴用戶是哪個描述符,導致用戶只能逐個遍歷已加入監聽的描述符;
 *	        支持的描述符數量太少,缺省是 FD_SETSIZE = 1024
 */


/* int poll(struct pollfd *fds, nfds_t nfds, int timeout)
 * @fds     不同於select使用了3個位圖結構fd_set來設置要監聽的描述符集合,poll改用pollfd結構體數組實現
 * @nfds    指定了fds數組中元素(即pollfd結構體)的個數
 * @timeout 超時時間,單位ms,其中負值表示永遠等待,0表示立即返回(即使沒有事件觸發)
 * @返回值  如果成功則返回有事件或異常上報的描述符總數;
 *          如果超時則返回0;
 *          如果出錯則返回-1
 *
 * 備註:
 *      [1]. poll返回-1時的行爲和select類似
 *      [2]. pollfd結構體包含了要監聽描述符的相關信息,具體如下
 *                  struct pollfd{
 *                      int fd;         // 一個已經打開了的描述符
 *                      short events;   // 該描述符要監聽的事件集合,顯然由用戶設置
 *                      short revents;  // 該描述符監聽的事件集合中觸發了的事件集合,或者是POLLERR/POLLHUP/POLLNVAL之一,顯然是在poll返回時由內核設置
 *                  };
 *      [3]. poll支持的事件標誌主要有:
 *                  POLLIN/POLLRDNORM       有普通數據可讀(包括到達文件末尾和TCP連接對端正常關閉等)
 *                  POLLPRI                 有緊急數據可讀
 *                  POLLOUT/POLLWRNORM      可不阻塞地寫普通數據
 *                  POLLRDHUP               通常流式套接字(比如TCP)對端關閉連接時本端會觸發該事件
 *                  POLLERR                 (只會出現在返回的revents中)指定描述符上發生異常,通常會同時附加註冊時的POLLIN/POLLOUT事件
 *                  POLLNVAL                (只會出現在返回的revents中)指定描述符沒有打開
 *                  POLLHUP                 (只會出現在返回的revents中)指定描述符被掛斷,linux平臺上的典型例子就是關閉pipe的寫端,然後poll pipe的讀端,返回的通常就是POLLHUP
 *
 * poll模型相對select模型的主要改進:
 *          poll去掉了描述符數量的限制;
 *          poll不會修改傳入的pollfd結構體數組,所以在反覆執行poll的場景中不需要再重複進行初始化
 *
 * poll模型仍舊存在的主要缺陷:
 *          poll和select一樣都需要在返回後遍歷描述符來獲取已經觸發的事件,那麼隨着監聽的描述符數量增加,必然導致性能明顯下降(即便某一時刻只有很少部分處於就緒狀態)
 */


/* int epoll_create(int size)       // 創建一個epoll實例
 * @size    早期用於通知內核該新建的epoll實例可以監聽的描述符數量,linux 2.6.8後,這個參數被忽略,只是爲了保持兼容,所以需要傳入一個正數
 * @返回值  成功則返回一個指向新建epoll實例的描述符;出錯則返回-1
 *
 * int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)   // 在指定epoll實例中對一個描述符進行操作
 * @epfd    對應一個epoll實例(已經創建)的描述符
 * @op      有效的操作有3種:
 *              EPOLL_CTL_ADD   將一個描述符註冊到epoll實例中,同時附帶註冊了該描述符關聯的事件
 *              EPOLL_CTL_MOD   修改一個已經註冊的描述符關聯的事件
 *              EPOLL_CTL_DEL   註銷一個已經註冊的描述符
 * @fd      需要進行操作的描述符
 * @event   對應該描述符關聯的事件(linux 2.6.9後,傳入EPOLL_CTL_DEL時對應的event可以爲NULL)
 * @返回值  成功則返回0;出錯則返回-1
 *
 * int epoll_wait(int epfd,struct epoll_event *events,int maxevents,int timeout)    // 等待epoll上監聽的I/O事件觸發
 * @epfd        對應一個epoll實例(已經創建)的描述符
 * @events      返回時用於存放觸發事件的數組
 * @maxevents   events數組可以存放的事件數量上限
 * @timeout     超時時間,單位ms,其中-1表示永遠等待,0表示立即返回(即使沒有事件觸發)
 * @返回值      如果成功則返回有事件或異常上報的描述符總數;
 *              如果超時則返回0;
 *              如果出錯則返回-1
 *
 * 備註:
 *      [1]. epoll模型屬於linux平臺特有
 *      [2]. epoll_event結構體包含了要監聽描述符關聯的相關信息,具體如下
 *                  struct epoll_event{
 *                      uint32_t events;        // 在epoll_ctl中用於設置對應描述符要監聽的事件集合(顯然由用戶設置);在epoll_wait中用於返回對應描述符觸發的事件集合(顯然是由內核設置)
 *                      epoll_data_t data;      // 在epoll_ctl中用於設置對應描述符的自定義內容(顯然由用戶設置);在epoll_wait中用於返回對應描述符事先註冊的自定義內容(顯然是由內核設置)
 *                  };
 *                  typedef union epoll_data{
 *                      void *ptr;
 *                      int fd;
 *                      uint32_t u32;
 *                      uint64_t u64;
 *                  };
 *      [3]. epoll支持的事件標誌主要有:
 *                  EPOLLIN         類似POLLIN
 *                  EPOLLPRI        類似POLLPRI
 *                  EPOLLOUT        類似POLLOUT
 *                  EPOLLRDHUP      類似POLLRDHUP
 *                  EPOLLERR        類似POLLERR
 *                  EPOLLHUP        類似POLLHUP
 *                  EPOLLET         設置對應的描述符爲邊沿觸發(缺省是水平觸發)
 *                  EPOLLONESHOT    設置對應的描述符只做一次性監聽(缺省是持久生效的),意味着觸發過一次後就會失效
 *
 * epoll模型相對select模型和poll模型的主要改進:
 *          當某個描述符關聯的事件觸發時,epoll通過回調機制直接將對應的描述符通知用戶,從而去掉了遍歷操作,
 *          這種改進使得epoll模型中的I/O效率基本不會隨着監聽的描述符數量增加而下降
 *                    
 */


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章