【Linux】多路轉接之select、poll、epoll模型

多路轉接模型

多路轉接IO:對大量的描述符進行就緒事件監控–讓進程能夠僅僅對就緒的描述符執行操作 不僅僅提高效率,而且避免阻塞。 只要是存在對描述符進行監控的需求,都可以使用多路轉接模型進行事件的監控

多路轉接模型使用場景:只要對描述符有(可讀,可寫,異常)事件的監控需求都可以使用多路轉接模型。也使用與對大量描述符進行監控,但是同一時間只有少量的描述符活躍的場景(因爲三種模型都是併發處理的)。
當然udp可以使用多路轉接模型,udp只有一個描述符,阻塞操作也可以,所以可以用也可以不用,使用了select模型或者epoll模型也沒有差別。

select模型

多路轉接IO模型: 用於對描述符事件進行監控

多路轉接模型:大多數只用在服務端,客戶端只有一個描述符,直接阻塞操作就行了。只要需要對描述符進行監控的場景下都可以使用多路轉接模型

select模型的操作流程

  1. 定義某個事件的描述符集合(可讀事件描述符集合,可寫事件描述符集合,異常事件描述符集合),初始化清空集合。對哪個描述符關心什麼事件,就把這個描述符添加到相應事件的描述符集合中

  2. 將集合拷貝到內核中進行監控(只有內核才能知道你的文件描述符是否就緒),監控的原理是輪詢遍歷判斷。(若超時等待,有描述符就緒事件則調用返回)

    可讀事件的就緒:接收緩衝區中的數據大小低於低水位標記(量化標準–通常默認爲1個字節)
    可寫時間的就緒:發送緩衝區中剩餘空間的大小低於低水位標記(量化標準–通常默認爲1個字節) 有一個字節的空間,代表都可以寫入
    異常事件的就緒:描述符是否產生了某個異常。

  3. 監控調用的返回,表示監控出錯/ 有描述符就緒 / 監控等待超時了 並且調用返回的時候,將事件監控的描述符集合中未就緒的描述符集合從集合中移除(只保留就緒的描述符)
    注意:因爲調用返回的時候修改了集合,因此下次監控的時候,就需要重新向集合中添加描述符。

  4. 輪詢判斷哪個描述符仍然在哪個集合中,就確定了這個描述符是否就緒了某個事件,然後進行對應事件的操作即可。
    注意:select並不會直接返回給用戶就緒的描述符直接操作,而是返回了就緒的描述符集合,因此需要我們進行輪詢判斷

select模型的代碼操作

代碼操作:

  1. 定義集合
struct fd_set 
成員中只有一個數組--當做二進制位圖使用 -- 添加描述符就是將描述符的值對應的比特位置1
因此select能夠監控的描述符數量,取決於二進制位圖中的比特位的多少,而比特位多少取決於宏 -    
_FD_SETSIZE,默認等於1024
128byte * 8 = 1024個bit
  1. 初始化清空集合
void FD_ZERO(fd_set* set);  
  1. 將fd描述符添加到set集合中
void FD_SET(int fd, fd_set* set) -- 將fd描述符添加到set集合中
  1. 調用select
int select(int nfds, fd_set* readfds, fd_set* writefds, fd_set* exceptfds, struct timeval* timeout);在內核中輪詢遍歷
nfds : 當前監控集合中最大描述符+1,減少遍歷次數,提高效率
readfds/writefds/exceptfds/ 可讀/可寫/異常三種事件的描述符集合
timeout: struct timeval{tv_ sec, tv_ usec;} 時間結構體,通過時間決定select阻塞,非阻塞,限制超時的阻塞
若timeout爲NULL,則表示阻塞監控,直到有描述符就緒,或者監控出錯纔會返回
若timeout中的成員數據爲0,則表示非阻塞,監控的時候若沒有描述符就緒,則立即超時返回
若timeout中的成員數據不爲0,則在指定的時間內,沒有就緒則超時返回,
返回值:
返回值  >0 表示就緒的描述符個數
= 0 表示沒有描述符就緒,超時返回
< 0 監控出錯,一個描述符關閉了,你還在監控就會出錯。


  1. 調用返回
    作用:返回給我們就緒的描述符集合,遍歷哪一個描述符還在哪個集合中,就是就緒了哪個事件
int FD_ISSET(int fd, fd_set* set) -- 判斷fd描述符是否在集合中

注意:因爲select返回時會修改集合,因此每一次監控的時候都要重新添加描述符

  1. 若對描述符不想進行監控了,則從集合中移除描述符
void FD_CLR(int fd, fd_set* set) -- 從set集合中刪除描述符

select模型的優缺點分析:

缺點:

  1. select對描述符進行監控有最大數量上限,取決於宏 _FD_SETSIZE,默認大小是1024
  2. 在內核中進行監控,是通過輪詢遍歷的,性能會隨着描述符的增多而下降
  3. 只能返回就緒的集合,需要進程進行輪詢的遍歷判斷才能得知哪個描述符就緒了哪個事件
  4. 每一次監控都要重新添加描述符到集合中,每一次監控都需要將集合重新拷貝到內核中。

優點:
遵循posix標準,跨平臺移植性比較好。

POLL模型

poll模型的操作流程:

  1. 定義監控的描述符事件結構體數組,將需要的描述符以及事件標識信息,添加到數組的各個節點中
  2. 發起調用,開始監控,將描述符事件結構的數組,拷貝到內核中進行輪詢遍歷判斷,若有就緒或等待超時則調用返回,並且在每一個描述符對應的事件結構體中,標識當前就緒的事件
  3. 進程輪詢遍歷數組,判斷數組中每個節點中的就緒事件是哪個事件,決定是否就緒了以及如何對描述符進行操作。

poll模型的代碼操作

poll模型雖然只有一個接口,但是參數中帶有對監控事件描述的結構體

int poll(struct pollfd* array_fds,nfds_t nfds, int timeout);
poll 監控採用事件結構體的形式
struct pollfd結構體:
struct pollfd 
{
int fd; //要監控的描述符
short evects; // 要監控的事件 POLLIN(輸入)/POLLOUT(輸出) 多個事件
short revents; //  調用返回時,填充的就緒事件
}
array_fds: 事件的結構體數組。填充要監控的描述符以及事件信息
nfds:數組中的有效節點個數(數組有可能很大,但是需要監控的節點只有前nfds個)
timeout: 監控的超時等待時間 - 單位 : 毫秒
返回值:
返回值 > 0標識就緒的這個描述符事件的個數
返回值 等於 0表示等待超時
返回值<0表示監控出錯。

poll模型優缺點分析:

優點:

  1. 使用事件結構體進行監控,簡化了select三種集合的操作流程
  2. 監控的描述符數量,不做最大數量的限制
  3. 不需要每次重新定義節點,不需要每一次添加描述符信息

缺點:

  1. 跨平臺移植性差
  2. 每一次監控依然向內核中拷貝監控數據
  3. 在內核中監控採取輪詢遍歷的方式,性能會隨描述符的增多而下降。

epoll模型

定位:Linux下最好用的,性能最高的多路轉接模型

epoll模型的操作流程:

操作流程:

  1. 發起調用在內核中創建epoll句柄epollevent結構體 (這個結構體包含很多的信息,紅黑樹+雙向鏈表)
  2. 發起調用對內核中的epoolevent結構添加/刪除/修改所監控的描述符監控信息
  3. 發起調用開始監控,採用異步阻塞操作實現監控,等待超時/有描述符就緒了時間返回,返回給用戶就緒描述符的事件結構信息
  4. 進程直接對就緒的事件結構體中的描述符成員進行操作即可。

epoll模型的代碼操作

  1. 創建epoll句柄
int epoll_create(int size)  -- 創建epoll句柄
size: 在linux2.6.2之後就被忽略掉了,大於0即可, 最大的epoll數量
返回值: 文件描述符--epoll的操作句柄
  1. 對epoll的文件描述符進行各種操作
int epoll_ctl(int epfd, int cmd, int fd, struct epoll_event* ev)
epfd:epoll_create返回的操作句柄
cmd:針對fd描述符的監控信息進行操作,添加,刪除,修改 , EPOLL_CTL_ADD/EPOLL_CTL_DEL/EPOLL_CTL_MOD
fd: 要監控操作的描述符
ev: fd事件描述符對應的結構體信息

struct epoll_event
{
uint32_t events; // 對fd描述符監控的事件 - EPOLLIN(寫入事件)/EPOLLOUT(讀出事件)
union
{
int fd;
void * ptr;
}data;  //就緒後,要填充的描述符信息
}

注意:一旦epoll開始監控,描述符若時間就緒,則就會給用戶返回我們所對應添加的對應時間結構體信息,通過時間結構體信息中包含的描述符進行操作–因此第三個參數的fd與結構體中的fd描述符通常是同一個描述符。

  1. 開始監控
int epoll_wait(int epfd, struct epoll_event* evs, int max_event, int timeout)
epfd:epoll的操作句柄
evs:struct epoll_evect結構體數組的首地址,用於接收就緒描述符對應的事件結構體信息
max_event: 本次監控想要獲取就緒事件的最大數量,不大於evs數組中節點的個數,防止越界
timeout: 等待超時時間,毫秒
返回值: >0 就緒事件的個數  = 0 超時等待  <0 監控出錯

epoll的監控流程: 異步阻塞操作

監控有系統完成,用戶添加描述符以及對應的時間結構體到內核中的eventpoll結構體中的紅黑樹中,一旦開始發起調用監控,則操作系統爲每一個描述符的事件做了一個回調函數,當描述符就緒了關心的事件,則將描述符對應的事件結構添加到雙向鏈表中,進程只需要每隔一段時間,判斷雙向鏈表是否爲NULL,決定是否就緒。

  1. 創建句柄
  2. 添加監控的描述符,以及對應事件的結構體信息到內核(用紅黑樹存儲)
  3. 開始異步阻塞監控,系統將就緒描述符的對應事件結構體信息添加到雙向鏈表中.
  4. 進程通過判斷雙向鏈表是否爲空,判斷是否有就緒,有講就緒的事件結構返回給進程
  5. 進程只需要根據就緒事件的結構體中的事件信息決定對事件結構中的fd描述符進行那個相應操作即可。
注意:
  1. epoll只需要判斷雙向鏈表是否爲空就知道有沒有就緒的事件,而且雙向鏈表中放置的就是已就緒的事件,所以,返回的時候只需要把這些事件拷貝到用戶態就可以了,其實這個拷貝過程是通過地址映射完成的。不需要向select,poll一樣需要做遍歷纔可以知道是否有就緒的事件,只需要判斷是鏈表中是否爲空,不爲空,代表有就緒的事件需要處理。
  2. 雙向鏈表中是不分什麼事件的,因爲我們在添加的時候,我們就說了我們監控的是什麼事件的操作,操作系統就針對這個事件進行回調函數。事件在操作系統內核中根據事件判斷你有沒有就緒的事件,若是有就緒的事件就加在雙向鏈表中。

epoll 模型優缺點分析:

優點

  1. 沒有描述符監控數量的上線
  2. 監控信息只需要向內核中添加一次
  3. 監控使用異步阻塞操作完成,性能不會隨着描述符的增多而下降(只需要判斷雙向鏈表是否爲空)
  4. 直接向用戶返回就緒事件信息(包含描述符在內),進程直接對描述符以及事件進行操作,不需要判斷是否就緒。

缺點

  1. 跨平臺移植性差

多路轉接模型與多線程/多進程的不同之處

多路轉接的併發是用戶層面上的併發,是我們自己實現的併發。
多線程的併發是基於操作系統時間片均衡的併發,cpu資源足夠多的情況下可以並行。
多路轉接可以和線程池搭配使用。

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