/* 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效率基本不會隨着監聽的描述符數量增加而下降
*
*/
APUE之I/O多路複用
I/O多路複用有3種具體實現模型:select、poll和epoll
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.