(Linux) 高級IO

典型的五種IO模型:阻塞IO / 非阻塞IO / 信號驅動IO / 異步IO / 多路轉接IO

IO完成的過程

  • 1、等待IO就緒(滿足IO條件)
  • 2、進行數據拷貝

阻塞IO:發起IO調用,若IO條件不具備,則一直等待。

  • 優點:流程非常簡單,代碼操作簡單,任務順序操作;
  • 缺點:無法充分利用資源,任務處理效率比較低。

非阻塞IO:發起IO調用,若IO條件不具備,則立即報錯返回,可以乾點其他事情,完畢後循環回來重新發起IO請求。

  • 優點:相較於阻塞IO,任務處理效率高,利用IO等待時間幹其他的事情;
  • 缺點:例程相較於阻塞IO更復雜,需要循環處理。

信號驅動IO:定義IO就緒信號處理方式,收到信號則認爲IO就緒,發起IO調用;在處理方式中進行IO請求,進程可以一直幹其他事情,等收到IO就緒信號的時候,會打斷進程當前操作去處理進行IO;

  • 優點:相較於非阻塞IO,更加實時,資源利用更加充分;
  • 缺點:流程更加複雜,需要定義信號處理,既有主控流程又有信號處理流程,涉及到信號是否可靠的問題;

異步IO:IO順序不確定,IO過程(等待+數據拷貝),由系統完成,不自己進行;
定義IO完成信號處理方式自定義,發起異步IO調用,告訴系統要完成什麼功能,剩下的IO功能完全由系統完成,完成後通過信號通知進程。

  • 優點:對資源的利用最爲充分,以最高效率進行任務處理;
  • 缺點:資源消耗比較高,流程最爲複雜;

這四種IO,其實是處理效率的逐步增加,對資源的利用更加充分,流程越來越複雜的進化過程;

概念理解

  • 阻塞:爲了完成一個功能,發起調用,若不具備完成的條件,則調用一直等待;
  • 非阻塞:爲了完成一個功能,發起調用,若不具備完成功能的條件,則立即報錯返回;
  • 阻塞和非阻塞的區別:常用於討論調用函數是否阻塞,表示這個函數無法立即完成功能時是否立即返回;
  • 同步:功能完成的流程通常是順序化的,並且功能是進程自身完成的;
  • 異步:功能完成的流程通常是不確定的,並且功能不是由進程自身完成的,由系統完成;
  • 異步的種類:1、異步阻塞- - -等着別人完成功能; / 2、異步非阻塞- - -不等待別人完成功能;
  • 同步與異步的區別:通常用於討論功能的完成方式,表示一個功能是否順序化且由自己來完成的;
  • 同步好還是異步好?:同步處理流程簡單,同一時間佔用資源少,但是異步處理效率高,同一時間佔用資源多;

多路轉接IO

主要用於進行大量的IO就緒事件監控,能夠讓我們的進程只針對就緒了指定事件的IO進行IO操作。
就緒事件:IO事件的就緒;
可讀事件:一個描述符當前是否有數據可讀;
可寫事件:一個描述符當前是否可以寫入數據;
異常事件:一個描述符是否發生了某些異常;

只對就緒的描述符進行IO操作有什麼好處呢?- - -避免阻塞,並且提高效率
1、在默認的socket中,例如tcp一個服務端只能與一個客戶端的socket通信一次,因爲我們不知道哪個客戶端新建的socket有數據到來或者監聽socket有新連接,有可能就會對沒有新連接到來的監聽socket進行accept操作而阻塞或者對沒有數據到來的普通socket進行recv阻塞;
2、在tcp服務端中,將所有的socket設置爲非阻塞,若沒有數據到來,則立即報錯返回,進行下一個描述符的操作,這個操作中,有一個不好的地方,就是對沒有就緒事件的描述符進行操作,降低了處理效率;

如何實現多路轉接IO:操作系統提供了三種模型:select模型、poll模型、epoll模型;

select模型

使用流程以及接口介紹和原理理解

  • 1、用戶定義想要監控的事件的描述集合(就緒事件有三種,但是並不是每一個描述符都要監控所有事件),例如,定義可讀事件的描述符集合,將哪些描述符添加到這個集合中,就表示要對這個描述符監控可讀事件,因此是想要監控什麼事件就定義什麼集合;
  • (1)定義指定事件的集合 / (2)初始化集合 / (3)將需要監控這個事件的描述符添加到這個集合中;
    集合:fd_set 結構體,結構體中只有一個成員,就是一個數組- - -作爲位圖進行使用,向集合中添加一個描述符,描述符就是一個數字,添加描述符其實就是這個數字對應的比特位置爲1,表示這個描述符被添加到集合中;
    這個數組中有多少個比特位或者說select最多能監控多少描述符,取決於宏 _FD_SETSIZE,默認爲1024;
    fd_set rfds;
    void FD_ZERO(fd_set *set); - - -清空指定的描述符集合;
    void FD_SET(int fd, fd_set *set); - - -將fd描述符添加到set集合中;
  • 2、發起調用,將集合中數據拷貝到內核中進行監控,監控採用輪詢遍歷判斷方式進行;
    int select( int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
    nfds:所有集合中最大的那個描述符的數值 + 1,爲了減少內核中遍歷次數;
    readfds:可讀事件集合;
    writefds:可寫事件集合;
    excptfds:異常事件集合- - -對不同集合中的描述符監控不同是事件;
    timeout:select默認是一個阻塞操作(struct timeval{tv_sec; tv_usec}),若timeout = NULL表示永久阻塞直到有描述符就緒再返回;若timeout中的數據爲0,則表示非阻塞,沒有就緒則立即返回;若timeout 有數據,若指定事件內沒有描述符就緒則超時返回;
    返回值:返回值小於0,表示監控出錯;返回值等於0,表示沒有描述符就緒;返回值大於0,表示就緒是描述符個數;
    select會在返回之前,會將所有集合中沒有就緒的描述符都給從集合中移除出去(調用返回後,集合中保存的就是就緒的描述符),返回給程序員一個就緒的描述符集合;
  • 3、select調用返回後,進程遍歷哪些描述符還在哪些集合中,確定哪個描述符就緒哪個事件,進而進行相應操作。
  • 其他操作:
    void FD_CLR(int fd, fd_set *set)- - -從set集合中移除fd描述符;
    int FD_ISSET(int fd, fd_set *set) - - -判斷fd描述符是否在set集合中;
    封裝select,封裝一個select類,每一個實例化的對象都是一個監控對象,向外提供簡單的接口,可以監控大量的描述符,並且直接能夠在外部獲取到就緒的描述符(不需要在外部進行復雜的select操作);
class Select{
public:
	Select(){};  //初始化操作
	bool Add(TcpSocket &sock); //將sock中的描述符添加到保存集合中
	bool Del(TcpSocket &sock);  //從保存集合中移除這個描述符,不再監控這個描述符
	bool Wait(std::vector<TcpSocket> *list, int outtime);  //進行監控,並且直接向外提供就緒的TcpSocket
private:
	fd_set_rfds; //可讀事件集合---保存要監控可讀事件的描述符,每次監控使用的集合都是這個集合的複製版(selec會修改集合)
	int _maxfd;
};

select總結,優缺點分析
優點
1、select遵循posix標準,可以跨平臺移植;
2、select的超時等待時間設置,可以精細到微秒;

缺點
1、select所能夠監控的描述符數量有最大上限,取決於宏 _FD_SETSIZE,默認是1024;
2、select進行監控的原理,是內核中進行輪詢遍歷判斷,性能會隨着描述符的增多而下降;
3、select返回時移除集合中未就緒的描述符,每次監控都要重新添加描述符,重新拷貝到內核;
4、select只能返回就緒的描述符集合,無法直接返回就緒的描述符,需要用戶進行遍歷判斷哪個描述符還在集合中才能確定是否就緒;

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