阻塞和非阻塞
側重狀態。
阻塞調用是指調用后對方一直沒有給你回復,你一直等着,什么事都不能干。
非阻塞調用指在調用后一直沒有給你回復,你每一段時間就問一次,你在這期間可以干別的。
同步和異步
側重方式。
同步:甲方請求一次,乙方應答一次”這樣的有序序列處理業務,只有當“一次請求一次應答”的過程結束才可以發生下一次的“一次請求一次應答”,那么就說他們采用的是同步。
異步:如果甲方只要有需要,就會發送請求,不管上次請求有沒有得到乙方應答。而乙方只要甲方有請求就會接受,不是等這次請求處理完畢再接受甲方新請求。這樣請求應答分開的序列,就可以認為是異步。異步情況下,請求和應答不需要一致進行,可能甲方后請求的業務,卻先得到乙方的應答。同步是線性的,而異步可以認為是並發的。
每一種方式肯定對應着相應的狀態。
如果請求量大, 數據量大,那么同步阻塞肯定會影響性能。
unix網絡編程中將IO模型分為5類:阻塞IO,非阻塞IO,IO復用,信號驅動,異步IO。
- 阻塞IO:就是那種recv, read,一直等,等到有了數據才返回;
- 非阻塞IO:就是立即返回,設置描述符為非阻塞,但是要進程自己一直檢查是否可讀;
- IO復用其實也是阻塞的,不過可以用來等很多描述符,比起阻塞有了進步,可以算有點異步了,但需要阻塞着檢查是否可讀。對同一個描述符的IO操作也是有序的。
- 信號驅動采用信號機制等待,有了更多的進步,不用監視描述符了,而且不用阻塞着等待數據到來,被動等待信號通知,由信號處理程序處理。但對同一個描述符的IO操作還是有序的。
- 異步IO,發送IO請求后,不用等了,也不再需要發送IO請求獲取結果了。等到通知后,其實是系統幫你把數據讀取好了的,你等到的通知也不再是要求你去讀寫IO了,而是告訴你IO請求過程已經結束了。你要做的就是可以處理數據了。且同一個描述符上可能同時存在很多請求。
以上io模型的案例簡單描述
- 阻塞I/O模型
老李去火車站買票,排隊三天買到一張退票。
耗費:在車站一直排隊,沒法干別的事。 - 非阻塞I/O模型
老李去火車站買票,火車站說沒有,每隔12小時去火車站問有沒有退票,三天后買到一張票。
耗費:往返車站路上耗費時間,其他時間可以做別的事。 - I/O復用模型
- select/poll
老李去火車站買票,委托黃牛(內核中),然后每隔6小時電話黃牛詢問,黃牛三天內買到票,然后老李去火車站交錢領票。
耗費:打電話 - epoll
老李去火車站買票,委托黃牛,黃牛買到后即通知老李去領,然后老李去火車站交錢領票。
耗費:無需打電話
-
信號驅動I/O模型
老李去火車站買票,給售票員留下電話,有票后,售票員電話通知老李,然后老李去火車站交錢領票。
耗費:無需打電話 -
異步I/O模型
老李去火車站買票,給售票員留下電話,有票后,售票員電話通知老李並快遞送票上門。
耗費:無需打電話
I/O多路復用的形成原因
如果一個I/O流進來,我們就開啟一個進程處理這個I/O流。那么假設現在有一百萬個I/O流進來,那我們就需要開啟一百萬個進程一一對應處理這些I/O流(——這就是傳統意義下的多進程並發處理)。思考一下,一百萬個進程,你的CPU占有率會多高,這個實現方式及其的不合理。所以人們提出了I/O多路復用這個模型,一個線程,通過記錄I/O流的狀態來同時管理多個I/O,可以提高服務器的吞吐能力。
fd是什么
操作文件的描述符。
fd的類型為int, < 0 為非法值, >=0 為合法值。在linux中,一個進程默認可以打開的文件數為1024個,fd的范圍為0~1023。
select
調用過程:
select函數的調用過程
a. 從用戶空間將fd_set拷貝到內核空間
b. 注冊回調函數
c. 調用其對應的poll方法
d. poll方法會返回一個描述讀寫是否就緒的mask掩碼,根據這個mask掩碼給fd_set賦值。
e. 如果遍歷完所有的fd都沒有返回一個可讀寫的mask掩碼,就會讓select的進程進入休眠模式,直到發現可讀寫的資源后,重新喚醒等待隊列上休眠的進程。如果在規定時間內都沒有喚醒休眠進程,那么進程會被喚醒重新獲得CPU,再去遍歷一次fd。
f. 將fd_set從內核空間拷貝到用戶空間
缺點:
兩次拷貝耗時(從用戶空間將fd_set拷貝到內核空間, 將fd_set從內核空間拷貝到用戶空間)
輪詢所有fd耗時(遍歷完所有的fd查看是否返回一個可讀寫的mask掩碼)
支持的文件描述符太小(1024)
優點:
跨平台支持
poll
優點:
連接數(也就是文件描述符)沒有限制(鏈表存儲)
poll函數的調用過程與select完全一致
缺點:
大量拷貝,水平觸發(當報告了fd沒有被處理,會重復報告,很耗性能)
epoll
epoll的ET與LT模式
LT:延遲處理,當檢測到描述符事件通知應用程序,應用程序不立即處理該事件。那么下次會再次通知應用程序此事件。
ET:立即處理,當檢測到描述符事件通知應用程序,應用程序會立即處理。
ET模式減少了epoll被重復觸發的次數,效率比LT高。我們在使用ET的時候,必須采用非阻塞套接口,避免某文件句柄在阻塞讀或阻塞寫的時候將其他文件描述符的任務餓死。
epoll的函數調用流程
a. 當調用epoll_wait函數的時候,系統會創建一個epoll對象,每個對象有一個evenpoll類型的結構體與之對應,結構體成員結構如下。
rbn,代表將要通過epoll_ctl向epll對象中添加的事件。這些事情都是掛載在紅黑樹中。
rdlist,里面存放的是將要發生的事件。
b. 文件的fd狀態發生改變,就會觸發fd上的回調函數
c. 回調函數將相應的fd加入到rdlist,導致rdlist不空,進程被喚醒,epoll_wait繼續執行。
d. 有一個事件轉移函數——ep_events_transfer,它會將rdlist的數據拷貝到txlist上,並將rdlist的數據清空。
e. ep_send_events函數,它掃描txlist的每個數據,調用關聯fd對應的poll方法去取fd中較新的事件,將取得的事件和對應的fd發送到用戶空間。如果fd是LT模式的話,會被txlist的該數據重新放回rdlist,等待下一次繼續觸發調用。
epoll的優點
沒有最大並發連接的限制
只有活躍可用的fd才會調用callback函數
內存拷貝是利用mmap()文件映射內存的方式加速與內核空間的消息傳遞,減少復制開銷。(內核與用戶空間共享一塊內存)
只有存在大量的空閑連接和不活躍的連接的時候,使用epoll的效率才會比select/poll高。
所以epoll相比select和poll,相當的利用最大連接數並節省內存
