Linux_IO多路轉接:select/poll/epoll

一、select 模型

1. int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
  • nfds:最大的描述符的值+1 – 提高內核中監控效率
  • readfds:可讀事件的監控合集,用戶若對描述符關心可讀事件,就將描述符添加到這個集合中
  • writefds:可寫事件的監控合集,用戶若對描述符關心可寫事件,就將描述符添加到這個集合中
  • exceptfds:異常事件的監控合集,用戶若對描述符關心異常事件,就將描述符添加到這個集合中
  • timeout:timeval{ tv_sev, tv_usec} select 超時等待時間,select默認當timeout爲NULL時,永久阻塞,直到有描述符就緒/出錯
  • 返回值:>0:就緒的描述符個數,==0:超時等待,沒有描述符就緒,<0:監控出錯
2. 實現原理
  • 用戶自己定義相應事件(可讀/可寫/異常)描述符集合 fd_set;
    • 用戶若是關心可讀事件就定義可讀事件集合/若是關心可寫事件就定義可寫事件集合
    • 向集合中添加描述符的時候,若這個描述符關心的是可讀事件,則將描述符加到可讀事件集合中…
  • select 將集合拷貝到內核中進行遍歷輪詢,判斷有沒有描述符就緒了相應的事件
  • 若集合中有描述符就緒,先將集合遍歷完畢,然後將集合中沒有就緒的描述符從集合中移除,這個時候調用返回,通知進程有多少個描述符就緒了(集合中保存的就是就緒的描述符)
  • 進程通過輪詢遍歷描述符是否在集合中,找出就緒的描述符(集合中的描述符就是就緒的描述符),進而對這個描述符進行相應的處理
  • fd_set 這是一個位圖,用於存儲要監控的描述符,將描述符添加到集合中,起始就是將描述符這個數字在位圖中對應的位置置1,這個描述符集合的大小取決於一個宏,_FD_SETSIZE = 1024
3. 接口介紹

void FD_CLR(int fd, fd_set *set); // 將指定的描述符從集合中移除
int FD_ISSET(int fd, fd_set *set); // 判斷指定的描述符是否在集合中
void FD_SET(int fd, fd_set *set); // 將指定的描述符添加到集合中
void FD_ZERO(fd_set *set); // 清空描述符集合

4. selct 優缺點分析
  • 缺點:
  1. select 能夠監控的描述符有上限限制 – 1024 FD_SETSIZE
  2. select 實現監控原理時在內核中對描述符進行遍歷輪詢判斷,性能隨着描述符增多而下降
  3. select 並不會告訴用戶具體哪一個描述符就緒(只給了一個就緒集合),因此需要用戶自己通過判斷哪一個描述符在集合中,而得到就緒的描述符,這個判斷是一個遍歷的過程,性能隨着描述符增多而下降,並且代碼複雜度更高
  4. select 會修改描述符集合(有描述符就緒後,集合中保存的就是就緒的描述符),因此每次監控都需要用戶重新向監控集合中添加描述符(性能下降,代碼複雜度增加)
  5. 每次都需要重新將監控集合拷貝到內核進行監控
  • 優點:
  1. 遵循posix標準,可以跨平臺使用
  2. 監控的超時等待時間可以精細到微秒

二、poll模型

1. int poll(struct pollfd *fds, nfds_t nfds, int timeout);
  • struct pollfd { int fd; short events; short revents;};
    • fd:用戶監聽的描述符
    • events:描述符關心的事件 POLLIN / PILLOUT
    • revents:描述符實際就緒的事件
  • fds:描述符事件結構數組
  • nfds:要監控的事件個數
  • timeout:超時等待時間-毫秒
2. 實現原理
  1. 用戶定義描述符事件數組,向數組中添加監控的描述符以及相應事件
  2. pollfd事件數組,拷貝到內核中進行遍歷輪詢監控,判斷是夠就緒了關心的事件events
  3. 當有描述符就緒,poll將描述符實際就緒的事件信息,標記到revents
  4. poll返回,用戶遍歷pollfd事件數組,通過revent判斷描述符就緒了什麼事件,進而進行相應的操作
3. poll優缺點分析
  • 優點:
  1. poll 採用事件結構形式對描述符關心的事件進行監控,簡化了select三種集合操作的流程
  2. poll 沒有描述符上限的設置
  • 缺點:
  1. poll 只能用於linux下,不能跨平臺,並且相較於select性能並沒有多大提升(幾乎被淘汰)
  2. 在內核中進行輪詢遍歷判斷就緒,性能隨着描述符事件增多而下降
  3. 也不會告訴用戶具體哪一個描述符就緒,需要用戶輪詢遍歷判斷事件中的revents來決定描述符應該進行哪些相應的事件操作
  4. 需要每次都將事件結構信息拷貝到內核才能進行監控
  5. 監控的超時等待時間只能精細到毫秒

三、epoll 模型

  • linux 下性能最高的多路轉接模型
int epoll_create(int size);
  • 功能: 創建 epoll,在內核中創建eventpoll結構體
  • size: 決定epoll最多監控多少個描述符,在linux2.6.8之後被忽略,但是必須>0
  • 返回值: 返回一個文件描述符,作爲epoll的操作句柄
  • struct eventpoll { … struct rb_root rbr(紅黑樹) }
int epoll_ctl(int epfd, int op, int fd, struct epoll_event * event);
  • 功能: 對內核中的eventpoll結構體進行操作:epoll採用事件結構方式對描述符進行事件監控
  • 用戶定義struct epoll_event描述符事件結構信息,將事件信息可以拷貝到內核添加到eventpoll結構體中的紅黑樹中
  • epfd:epoll操作句柄
  • op:對內核eventpoll進行的操作
    • EPOLL_CTL_ADD:向紅黑樹中添加描述符的監控事件結構信息event
    • EPOLL_CTL_DEL:從紅黑樹中移除描述符的監控事件結構信息event
    • EPOLL_CTL_ADD:修改描述符fd在紅黑樹中的對應事件結構信息event
  • fd:用戶需要監控的描述符
  • event:描述符對應的事件結構信息
> struct epoll_event {
> 		uint32_t events; //用戶對描述符進行監控的事件(EPOLLIN/EPOLLOUT)
> 		union {
> 			int fd;
> 			void *ptr;
> 		}
> }
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
  • epfd:epoll操作句柄
  • events:epoll_event事件結構信息數組
  • maxevents:epoll_event事件結構信息數組的節點數量
  • timeout:epoll_wait監控的等待超時時間
  • 返回值:<0 監控出錯,==0 監控超時,>0 就緒的描述符個數
  • epoll_wait 會將就緒的描述符對應事件結構信息拷貝到events結構數組中,相當於直接告訴用戶哪個描述符就緒,用戶直接從epoll_events結構體數組中取出信息,對描述符直接進行相應事件操作

epoll 監控流程

  • epoll 對描述符的事件監控是一個異步操作,epoll_wait 發起調用,讓操作系統對描述符進行相應事件監控,操作系統對每個要監控的描述符都定義了就緒事件回調函數,當描述符相應事件就緒的時候觸發事件,調用回調函數(將描述符事件結構信息指針添加到eventpoll的雙向鏈表中)
  • 但是epoll_wait 並沒有直接返回(是一個阻塞操作),每隔一會兒看一下eventpoll中雙向鏈表是否爲空,從而判斷是否有描述符就緒,若爲空,則沒有描述符就緒,等待一會兒重新查看;若不爲空空,表示有描述符就緒,則將這個描述符對應的事件結構信息,拷貝到epoll_wait傳入的事件結構數組中後調用返回
epoll 事件觸發方式(默認水平觸發)
  • 水平觸發方式:
    • 可讀事件就緒:接收緩衝區中數據大小,大於低水位標記(默認1字節)
    • 可寫事件就緒:發送緩衝區中空閒空間大小,大於低水位標記(默認1字節)
    • 只要接受/發送緩衝區中數據/剩餘空間大小大於低水位標記就會一直觸發事件
  • 邊緣觸發方式:
    • 可讀事件就緒:接受緩衝區中,只有新數據到來的時候纔會觸發一次
    • 可寫事件就緒:發送緩衝區中,只有新數據到來或者空間大小從0變爲>0纔會觸發一次
  • 邊緣觸發方式注意事項:
    • 邊緣觸發方式中,只有新的數據到來的時候,可讀事件纔會被觸發一次
    • 需要用戶在這一次事件觸發中將緩衝區中的數據全部讀取完畢(循環讀直到不能讀爲止)
    • 但是套接字默認recv沒有數據的時候會阻塞,爲了避免循環讀取數據導致程序流程因爲阻塞而無法繼續推進,因此需要將描述符設置爲非阻塞
epoll 優缺點分析
  • 優點:
    • epoll 採用事件結構方式對描述符進行監控,簡化了select集合操作的流程
    • epoll 描述符監控數量無上限
    • 每個epoll監控的描述符事件信息,只需要向內核拷貝一次
    • epoll_wait 使用異步阻塞操作在內核中完成時間監控
      • 事件監控是操作系統通過事件回調的方式將就緒描述符事件信息添加到雙向鏈表中
      • 而epoll_wait只是每隔一段時間看一下雙向鏈表是否爲空判斷是否有描述符就緒(並非輪詢遍歷)性能不會隨着描述符增多而降低
    • epoll 直接通過epoll_wait傳入的事件結構數組向用戶返回就緒的事件信息,可以直接告訴用戶哪些描述符就緒,不用用戶進行空遍歷查找
  • 缺點:
    • 無法跨平臺,只有在linux下才能用
    • 等待超時時間微秒級別

IO 多路轉接模型的適用場景

  • 對大量描述符進行監控,但是同一時間只有少量描述符活躍的場景
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章