linux---select,poll,epoll的原理以及優缺點

多路轉接IO(也叫IO多路複用)是一種處理高併發的IO事件監控,同時對大量的描述符進行時間監控,監控是否具備IO條件。
就緒:包括了讀就緒事件(就是有數據到來的時候),寫就緒事件(緩衝區有空閒的空間),異常事件(發生異常)。對於服務器來說,很多時候我們都是監控的讀事件,對於寫事件和異常事件都只會在特定的情況下使用。

  1. select
    select模型:通過對幾個事件集合中的描述符進行事件監控,當集合中有就緒事件的時候就返回。
    select其實是在內核中開闢一塊數組空間用來存儲用戶空間傳遞的文件描述符進行監控,監控這件事也是內核做的,我們只需要傳遞文件描述符,在用戶空間進行檢測,當有了就緒事件就拷貝到用戶空間進行處理。
	   #include <sys/time.h>
       #include <sys/types.h>
       #include <unistd.h>
       int select(int nfds, fd_set *readfds, fd_set *writefds,
                  fd_set *exceptfds, struct timeval *timeout);

nfds:監控的描述符中最大描述符+1。
fd_set:描述符集合(實際上是一個位圖),位圖的大小取決於FD_SETSIZE=1024.所以我們最多監控的文件描述符最大隻能是1024。
readfd,writefds,exceptfds:分別是讀就緒事件,寫就緒事件,異常就緒事件的集合。

FD_ZERO(fd_set *fdset):清空fdset與所有文件句柄的聯繫。 
FD_SET(int fd, fd_set *fdset):建立文件句柄fd與fdset的聯繫。 
FD_CLR(int fd, fd_set *fdset):清除文件句柄fd與fdset的聯繫。 
FD_ISSET(int fd, fdset *fdset):檢查fdset聯繫的文件句柄fd是否可讀寫,>0表示可讀寫。

返回之前會將集合中沒有就緒的描述符清理掉。
timeout:等待超時事件
返回值:>0,返回就緒事件的個數。=0:監控事件超時。<0監控出錯

流程
1)定義一個描述符集合:fd_set(位圖,最大值爲1024)
2)將集合拷貝到內核中進行監控,監控的原理就是對所有描述符輪詢編譯狀態
3)當有描述符就緒的時候,在返回前,將沒有就緒的描述符剔除掉
4)用戶操作,對所有描述符進行遍歷,看哪一個還在集合中,那麼這個就和就是已經就緒的
5)重新fd_set拷貝到內核中進行監控
注意:監控的fd_set,但是我們不能讓他傳遞到內核中去,不然傳遞回來的時候已經改變,所以我們需要定義一箇中間變量拷貝到內核中,當傳遞回來的時候中間變量中就是已經準備好的文件描述符,然而我們監控的文件描述符就可以繼續定義變量監控。

select優缺點

  • 優點:遵循POSIX標準,可以跨平臺,監控的超市可以精確到微秒
  • 缺點:
    1)所有監控的文件描述符是由上線的,默認是1024,取決於FD_SETSIZE的大小
    2)select實現的監控原理是在內核中輪詢遍歷狀態,因此會隨着描述符增多性能下降
    3)select每次監控的時候都會修改監控集合中的值,都需要重新向內核拷貝監控描述符集合
    4)select要監控的集合中的描述符每次都要向內核拷貝數據。
  1. poll
    poll實際上和select類似,都是在內核中開闢一個空間,但是和select不是監控每種事件,poll監控的是事件結構化的事件集合。
struct pollfd{
	int fd;
	short events;//監控的事件
	short revents;//就緒的事件
}

常用的事件就是POLLIN和POLLOUT

 #include <poll.h>
 int poll(struct pollfd *fds, nfds_t nfds, int timeout);

原理:
1)用戶定義事件數組,對描述符添加關心事件
2)poll實現監控也是將數據拷貝到內核中,然後進行輪詢遍歷監控,性能隨着文件描述符增多而下降
3)用戶根據返回的revents判斷哪一個事件就緒然後重新操作
4)poll也不會告訴用戶哪一個描述符就緒,只是告訴用戶有就緒事件,需要用戶遍歷查找。

poll優缺點:

  • 優點
    1)採用事件結構的方式對描述符進行監控,簡化了多個事件集合的監控方式
    2)沒有描述符的具體上限
  • 缺點
    1)不能跨平臺
    2)隨着描述符增多性能下降

poll已經被淘汰了,因爲他的功能可以使用其他模型替代,而且性能高,比如我們下面講的epoll。select比poll的優點就是poll不能跨平臺。但是比起epoll來說poll一無是處。

  1. epoll模型

epoll模型linux下性能最高的IO多路轉接
在epoll下就緒事件變得不同。對於可讀事件就緒就是接受緩衝區的數據大小大於低水位標記(一般爲一字節)。對於可寫事件就緒就是可寫緩衝區的空閒空間大小大於低水位標記(1B)。
epoll在內核中是一紅黑樹進行節點的刪除和添加,還有一個rdlist雙向鏈表,用來存儲就緒事件的鏈表。
epoll也是採用事件結構來進行監控的。

  • 創建一個epoll結點
       #include <sys/epoll.h>
       int epoll_create(int size);

功能:在內核中創建一個結構體,eventpoll{};在結構體中我們只關心兩個參數,一個是紅黑樹,一個就是雙向鏈表rdlist.
size:能監控的描述符上限,但是在linux2.6.8版本之後就被忽略了,但是參數必須大於0。
返回值:返回一個描述符,epoll的操作句柄。

  • 向epoll樹上添加結點,刪除結點,修改結點
       #include <sys/epoll.h>
       int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

功能:在內核的eventpoll結構中添加移除,修改所監控的事件結構。
epfd:epoll_create返回的eventpoll結構體的操作句柄
op:用戶要進行的操作

EPOLL_CTL_ADD:向內核中eventpoll添加要監控的事件結構
EPOLL_CTL_DEL:向內核中eventpoll刪除要監控的事件結構
EPOLL_CTL_MOD:向內核中eventpoll修改要監控的事件結構
  • 開始監控
       #include <sys/epoll.h>
       int epoll_wait(int epfd, struct epoll_event *events,
                      int maxevents, int timeout);

功能:開始監控eventpoll中的紅黑樹上保存的時間結構節點,如果有就緒事件就拷貝到當前的雙向鏈表rdlist中。
epfd:eventpoll操作句柄
events:就緒事件的結構體集合
maxevents:用於確定最後一次最多獲取的就緒事件個數
timeout:超時時間,只能到ms。

步驟:
1)第一步告訴內核要開始對文件描述符進行監控了
2)操作系統對描述符進行監控,採用的是事件觸發方式進行監控。爲每一個要監控的描述符都定義了一個事件,並且對這個事件都有回調函數(ep_poll_callback())。
3)這個事件回調函數要做的就是將就緒的這個描述符所對應的epoll_event事件結構添加到雙向鏈表中(將事件結構地址添加到雙向鏈表中)
4)epoll_wait並沒有立即返回,是每隔一會兒來看看雙向鏈表中是否爲空,進而判斷是否有描述符就緒。
5)若鏈表不爲空的話就表示有文件描述符就緒,則epoll_wait直接返回。在返回之前,將就緒的描述符對應事件結構向用戶太拷貝一份,
6)epoll會將就緒描述符對應的事件拷貝到用戶態,直接告訴用戶有那些描述符就緒,進而用戶可以直接操作就緒的描述符。

epoll的優缺點

  • 優點
    1)epoll沒有監控的上線
    2)採用事件結構簡化了select監控集合的監控流程
    3)epoll是一個異步阻塞操作,發起調用,讓操作系統進行文件描述符的監控,使用事件回調函數對描述符進行監控,避免了select的遍歷輪詢,性能不會隨着文件描述符增多而下降
    4)epoll發起調用進行等待,循環判斷內核中epoll就緒時間鏈表是不是爲空來確定是否有就緒事件,若有就緒事件,則將對應的事件拷貝到用戶態,直接告訴了用戶那些描述符就緒了,不需要循環判斷。
    5)epoll描述符的事件結構,只需要向內核中拷貝一次,不需要每次都拷貝

  • 缺點
    1)不能跨平臺
    2)延時時間只能精確到毫秒

epoll的兩種觸發方式

  • 水平觸發:只要緩衝區中的數據大小大於低水位標記的話,就會觸發可讀或者可寫事件。
  • 邊緣觸發方式
    1)對於可讀事件,只要每次有新的時間按到來的時候纔會觸發一次可讀事件,
    2)對於可寫事件:每次只要緩衝區空閒空間從0大於低水位標記的時候纔會觸發可是事件。

通常來說,讀寫事件混合監控的時候,對於可寫事件就會使用邊緣觸發方式(防止可寫事件每次不寫入數據但是有空閒的時候都會觸發事件)。
若是可讀事件被設置爲邊緣觸發,需要用戶一次性將數據讀取完畢,但是因爲不知道數據有多少,因此只能循環從緩衝區中讀取數據,當循環讀取但是緩衝區中沒有數據的時候recv就會阻塞。

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