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,發起調用,如果當前不具備IO條件,則立即返回,但是需要循環發起IO。
優點:流程相對復雜一點,對資源的利用更加充分。
缺點:IO操作不夠實時。
信號驅動IO
定義一個IO信號處理方式,在處理方式中進行IO操作,IO就緒時信號通知進程,進程在IO就緒的時候去進行IO操作。
優點:IO操作更加實時,對資源的利用更加高效。
缺點:操作復雜。
異步IO
例如:排隊買票的例子。
假期出去游玩的人比較多,因此異步IO的操作,就類似于,你讓別人幫你排隊,并且幫你把票買了,最終將票給你就可以了。
因此異步IO的本質就是,其所有的IO操作都是讓別人幫忙完成,自身只是發起一個調用,告訴別人需要從哪里開始拷貝數據,以及拷貝多少數據即可。IO的等待與操作都是由別人完成。
優點:對資源利用更加充分
缺點:流程更加負責
同步與互斥
同步
這里的同步與之前進程中的同步不同,這里的同步指的是處理流程中,順序處理,一個完成之后再完成下一個,并且所有的功能都是由進程自身完成的。
異步
處理流程順序不定,主要的功能都是由別人完成或者由操作系統完成。
- 異步阻塞:功能是由別人完成,但是在調用的時候是等著別人完成。
- 異步非阻塞:功能由別人完成,調用之后立即返回,不用等待。
高級IO中的同步與互斥的主要區別:
判斷主要的功能是不是自己完成的,完成的順序是否確定。
多路轉接IO
對大量的描述符進行IO事件監控,可以告訴我們,當前有哪些描述符已經就緒了哪些事件,進程就可以針對就緒了指定事件的進程進行響應操作,避免了對沒有就緒的描述符進行操作導致的效率降低。
IO事件
可讀事件、可寫事件、異常事件
多路轉接IO模型:
- 作用:用于對描述符事件進行監控的。
- 實現方式:select、poll、epoll
select模型
操作流程:
- 定義某個事件的描述符集合 (可讀、可寫、異常事件集合),初始化集合。
- 對哪個描述符關心,就把這個描述符添加到指定的相應事件中。
- 將集合拷貝到內核中進行監控。監控的原理是輪詢監控。(輪詢遍歷判斷)
可讀事件的就緒:接收緩沖區中的數據大小大于低水位標記,通常默認為一個字節。
可寫事件的就緒:發送緩沖區中剩余空間的大小大于低水位標記,通常默認為一個字節。
異常事件的就緒:描述符是否產生了某個異常。 - 監控調用的返回,表示監控出錯、描述符已經就緒、監控超時。在調用返回的時候,將事件監控的描述符集合中的未就緒的描述符從集合中移除,在集合中僅僅保留就緒的描述符。
- 輪詢判斷哪個描述符處于哪個集合中,就確定這個描述符是否就緒了某個事件,然后進行對應事件的操作。(如果一個描述符處于可讀集合中,表述當前描述符已經滿足可讀事件,然后進行具體的操作)
注意
在select中,并不會直接返回給用戶就緒的描述符進行操作,而是返回的就緒描述符的集合,因此需要我們自己進行判斷。
因為,select在返回的時候,將沒有就緒的描述符已經移除,因此在下次監控的時候,就需要重新向集合中添加描述符。
代碼操作:
- 定義集合(struct fd_set),該成員只有一個數組,在該數組中是以二進制比特位進行使用的,添加描述符的就是將對應的二進制比特位置一。默認大小為1024個描述符。
- 初始化清空集合:
void FD_ZERO(fd_set *set)
- 添加描述符到指定集合中。
void FD_SET(int fd,fd_set* set)
,將fd描述符添加到set集合中。 - 開發發起監控調用。
int select(int nfds,fd_set * readfds,fd_set* writefds,fd_set *exceptfds,struct timeval *timeout)
;
- nfds:當前監控集合中最大的描述符+1。因為集合默認監控的描述符大小為1024個,避免遍歷沒用的描述符,減少遍歷的次數。
- readfds:可讀事件描述符集合,如果不需要則置空。
- writedfs:可寫事件描述符集合,如果不需要則置空。
- exceptfds:異常事件描述符集合,如果不需要則置空。
- timeout:時間結構體,通過時間決定select阻塞、非阻塞、限制超時的阻塞,如果timeout為空,則表示阻塞監控,直到有描述符就緒或者監控出錯才會返回。 如果timeout中的成員數據為0,則表示非阻塞,監控的時候如果沒有描述符就緒,則會立即超時返回。如果timeout中的成員數據不為0,則在指定的時間之內,沒有就緒則超時返回。
返回值:返回值大于0,表示就緒描述符個數;返回值如果為0,表示沒有描述符就緒,超時返回;返回值小于0,表示監控出錯。
-
調用返回,返回就緒的描述符,遍歷判斷哪個描述符還在集合中,就是哪個事件已經就緒。
int FD_ISSET(int fd,fd_set *set)
,判斷fd描述符是否在集合中。本質就是對二進制比特位查看的封裝。
-
如果對描述符不進行監控,則移除描述符。
void FD_CLR(int fd,fd_set* set)
在set集合中刪除fd描述符
int main()
{fd_set redfs;while(1){cout<<"開始監控"<<endl;struct timeval tv;tv.tv_sec = 3;tv.tv_usec = 0;FD_ZERO(&redfs)FD_SET(0,&redfs); int res = select(1,redfs,NULL,NULL,&tv); if(res<0){perror("select error")return -1;}else if(res == 0){cout<<"監控超時"<<endl;continue;}if(FD_ISSET(0,&redfs)){cout<<"監控成功,0號事件已經響應"<<endl;}}
}
優缺點:
- 跨平臺移植性比較好。
- select對描述符的監控由最大數量上限。FD_SETSIZE 。
- 在內核中進行監控數通過輪詢遍歷實現的,因此性能會隨著描述符的增多而下降。
- 只能返回就緒的集合,需要進程進行輪詢判斷才能知道哪個描述符就緒了哪個事件。
- 每次監控都需要將描述符添加到集合中,每次監控都需要將集合拷貝到內核中。
poll模型
操作流程:
- 定義監控的描述符時間的結構體數組,將需要監控的描述符以及事件標識信息,添加到數組的各個節點中。
- 發起調用開始監控,將監控事件的描述符結構體數組,拷貝到內核中進行輪詢遍歷判斷,如果有就緒/等待超時則調用返回,并且在每個描述符對應的事件結構體中,標識當前就緒的事件。
- 進程輪詢遍歷數組,判斷數組中的每個節點中的就緒事件是哪個事件,再決定對描述符如何進行操作。
接口:
int poll (struct pollfd* arry,nfds_t nfds,int timeout)
監控采用事件結構體。
struct pollfd {int fd; shor events; shor revents}
fd要監控的描述符、events要監控的事件(POLLIN可讀事件、POLLOUT可寫事件)、revents哪個監控事件就緒了就將其放在其中。
nfds:數組中有效節點的個數
timeout:監控的超時等待時間:毫秒
返回值:>0表示監控的就緒事件的個數;返回值==0表示等待超時,返回值<0監控出錯。
優缺點:
- 使用事件結構體進行監控,簡化了select中三種事件集合的操作流程。
- 監控數量不做限制。
- 不需要每次重新定義事件節點。
- 跨平臺移植性差。
- 每次監控依然需要向內核中拷貝數據。
- 在內核中監控,依然采用輪詢遍歷。
epoll模型
操作流程:
- 在內核中創建epoll句柄(epollevent結構體。紅黑樹+雙向鏈表)
- 對內核中的epollevent結構添加、刪除、修改
- 開始監控,發起調用,在內核中采用異步阻塞實現監控,等待超時或者描述符就緒了事件后調用返回,返回給用戶就緒描述符事件的結構信息。
- 進行直接對就緒的事件結構體中的描述符成員進行操作即可
接口信息:
int epoll_create(int size)
創建epoll句柄,size只要大于0即可。
返回值:一個文件描述符,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:描述符對應的事件結構體信息。當epoll開始監控的時候,描述符如果就緒了進程關心的事件,則會給用戶返回我們添加對應事件結構體信息,通過事件結構體信息中包含的描述符進行操作,所以fd與ev結構體中的fd是同一個描述符。int epoll_wait(int epfd,struct epoll_events *evs,int max_event,int timeout)
epfd:epoll操作句柄
evs:struct epoll_events結構體數組的首地址,用于接受就緒的描述符對應的事件結構體信息。
max_event:本次監控想要獲取就緒事件的最大數量,不能大于evs數組大小。
timeout:超時等待時間,單位毫秒。
返回值:>0表示就緒事件的個數 =0表示等待超時,<0監控出錯
監控原理
異步阻塞操作。
監控由系統完成,用戶添加監控的描述符以及對應事件結構體會被添加到內核的eventpoll結構體中的紅黑樹中,一旦進程發起調用,操作系統為每個描述符的事件進行回調。當描述符就緒了哪個事件,則將描述符對應的事件結構體添加到雙向鏈表中。
進程自身,每隔一段事件,判斷雙向鏈表是否為NULL,決定是否有事件就緒。