面試考察的是如何把書本上的知識轉化爲本身的理解git
前先後後也面試了大小快二十場面試,總的來看感受面試的過程其實沒有想象的那麼難,不少知識是本身平時遇到的問題,要麼是沒有用心去理解,要麼是理解了,內心知道是什麼意思,可是沒法表達清楚,抓不住關鍵點。github
因此打算之後學習一個知識,先仔細閱讀理解,而後不看書本,用本身的話寫下來總結成一篇博客。web
套接字就是IP:端口號
,是用於 TCP 鏈接的端點面試
一個輸入操做大概就是分兩步:應用程序請求並等待數據到達;從內核向進程複製數據。而對於套接字上的輸入操做,第一步相似,就是等待數據從網絡中到達,數據到達之後爲了防止數據丟失,會先複製到內核中的某個緩衝區;第二步就是將內核緩衝區中的數據複製到應用程序緩衝區。編程
Unix 下有五種 I/O 模型:數組
具體過程就是首先應用進程發起系統調用,會進入等待,一直等到數據報就緒,這時候數據仍是在內核緩衝區中,須要將數據報返回給應用進程緩衝區。網絡
須要注意的是,阻塞式 I/O 不是意味着系統進入阻塞,而僅僅是當前應用程序阻塞,其餘應用程序仍是能夠繼續運行的,所以不消耗 CPU 時間,執行效率較高多線程
應用進程執行系統調用之後,不一樣於阻塞式進程直接進入阻塞,而是若是數據沒有準備好,就會返回一個錯誤碼,應用進程能夠繼續執行,可是須要不斷進行系統調用來得到 I/O 是否完成,這種方式稱爲輪詢(polling)異步
我這裏的理解就是其實非阻塞式 I/O 和阻塞式 I/O 其實是相同的,都須要等待數據,只不過一個是被動進入阻塞,另外一個是主動請求看數據有沒有好,就好比是高中學習,一個是在想學習新知識過程當中等待老師上課講,在老師沒有上課講以前就一直等待,而另一個同窗是不停去辦公室問,老師上不上課,何時上課。socket
I/O 複用其實流程和阻塞式有很大相同之處,只不過 I/O 複用會先調用 select,這個時候系統會監聽全部 select 負責的數據報,一旦有某個數據準備就緒,就會將其返回,而後進行 recvfrom 系統調用,執行同阻塞式 I/O 相同的處理。對比阻塞式 I/O,這裏須要調用兩個系統調用,因此效率確定不如前者,可是最大的特色就是能夠同時處理多個 connection。
多說一句。因此,若是處理的鏈接數不是很高的話,使用 select/epoll 的 web server 不必定比使用 multi-threading + blocking IO 的 web server 性能更好,可能延遲還更大。select/epoll 的優點並非對於單個鏈接能處理得更快,而是在於能處理更多的鏈接。
不經常使用,基本不會涉及到
進行 aio_read 系統調用會當即返回, 應用進程繼續執行, 不會被阻塞, 內核會在全部操做完成以後嚮應用進程發送信號。
異步 I/O 與信號驅動 I/O 的區別在於, 異步 I/O 的信號是通知應用進程 I/O 完成,而信號驅動 I/O 的信號是通知應用進程能夠開始 I/O。
簡單說就是,當用戶應用進程請求數據時,不會進入阻塞,而是繼續執行其餘任務,等到該應用進程數據處理完畢,那麼相應的系統會給用戶進程傳回一個信號,表示應用進程已經執行完畢,這是和信號驅動最大的不一樣,這個返回的信號是通知進程已經執行完畢,而信號驅動返回信號是通知進程能夠開始執行。
阻塞式和非阻塞式區別?同步異步區別?
先說簡單的,阻塞式和非阻塞式最大的區別就是阻塞式在等待數據階段會進入阻塞,而非阻塞式不會,可是對於非阻塞式,在得到數據存在內核緩衝區後,將內核緩衝區中數據複製到應用程序緩衝區這個階段是阻塞的。
在說同步異步區別以前先要了解什麼叫同步,什麼叫異步?區別就是在進行 I/O 操做時候會將進程阻塞,根據這個定義就知道,阻塞式、非阻塞式、信號驅動式、I/O 複用式都屬於同步,爲何非阻塞式也是呢?這就涉及到前面說的,雖然在開始是沒有阻塞,可是後面將數據從內核到應用程序是阻塞的。
引用博主中很好的一個例子來理解:
最後,再舉幾個不是很恰當的例子來講明這四個IO Model: 有A,B,C,D四我的在釣魚:
A用的是最老式的魚竿,因此呢,得一直守着,等到魚上鉤了再拉桿;
B的魚竿有個功能,可以顯示是否有魚上鉤,因此呢,B就和旁邊的MM聊天,隔會再看看有沒有魚上鉤,有的話就迅速拉桿;
C用的魚竿和B差很少,但他想了一個好辦法,就是同時放好幾根魚竿,而後守在旁邊,一旦有顯示說魚上鉤了,它就將對應的魚竿拉起來;
D是個有錢人,乾脆僱了一我的幫他釣魚,一旦那我的把魚釣上來了,就給D發個短信
select/poll/epoll 都是 I/O 多路複用的具體實現, select 出現的最先, 以後是 poll, 再是 epoll。
有三種類型的描述符類型: readset、 writeset、 exceptset, 分別對應讀、 寫、 異常 條件的描述符集合。 fd_set 使用數組實現, 數組大小使用 FD_SETSIZE 定義。
timeout 爲超時參數, 調用 select 會一直阻塞直到有描述符的事件到達或者等待的 時間超過 timeout。
成功調用返回結果大於 0, 出錯返回結果爲 -1, 超時返回結果爲 0。
關鍵代碼以下:
fd_set fd_in, fd_out;
struct timeval tv;
// Reset the sets
FD_ZERO( &fd_in );
FD_ZERO( &fd_out );
// Monitor sock1 for input events
FD_SET( sock1, &fd_in );
// Monitor sock2 for output events
FD_SET( sock2, &fd_out );
// Find out which socket has the largest numeric value as select requires it
int largest_sock = sock1 > sock2 ? sock1 : sock2;
// Wait up to 10 seconds
tv.tv_sec = 10;
tv.tv_usec = 0;
// Call the select
int ret = select( largest_sock + 1, &fd_in, &fd_out, NULL, &tv );
// Check if select actually succeed
if ( ret == -1 )
// report error and abort
else if ( ret == 0 )
// timeout; no event detected
else
{
if ( FD_ISSET( sock1, &fd_in ) )
// input event on sock1
if ( FD_ISSET( sock2, &fd_out ) )
// output event on sock2
}
複製代碼
select 詳細過程:
當用戶 process 調用 select 的時候,select 會將須要監控的 readfds 集合拷貝到內核空間(假設監控的僅僅是 socket 可讀),而後遍歷本身監控的 socket sk,挨個調用 sk 的 poll 邏輯以便檢查該 sk 是否有可讀事件,遍歷完全部的 sk 後,若是沒有任何一個 sk 可讀,那 select 會調用 schedule_timeout 進入 schedule 循環,使得 process 進入睡眠。若是在 timeout 時間內某個 sk 上有數據可讀了,或者等待 timeout 了,則調用 select 的 process 會被喚醒,接下來 select 就是遍歷監控的 sk 集合,挨個收集可讀事件並返回給用戶了
到這裏,咱們有三個問題須要解決:
(1)被監控的fds集合限制爲1024,1024過小了,咱們但願可以有個比較大的可監控fds集合
(2)fds集合須要從用戶空間拷貝到內核空間的問題,咱們但願不須要拷貝
(3)當被監控的fds中某些有數據可讀的時候,咱們但願通知更加精細一點,就是咱們但願可以從通知中獲得有可讀事件的fds列表,而不是須要遍歷整個fds來收集。
poll 的機制與 select 相似,與 select 在本質上沒有多大差異,管理多個描述符也是進行輪詢,根據描述符的狀態進行處理,可是 poll 沒有最大文件描述符數量的限制。poll改變了fds集合的描述方式,使用了pollfd結構而不是select的fd_set結構,使得poll支持的fds集合限制遠大於select的1024。其中 pollfd 使用鏈表實現
int poll(struct pollfd *fds, unsigned int nfds, int timeout);
複製代碼
實現大體相同,可是一些細節仍是存在不一樣:
速度都很慢
select 出現比較早,因此基本上全部的系統都支持,而只有比較新的系統才支持 poll
epoll_ctl() 用於向內核註冊新的描述符或者是改變某個文件描述符的狀態。 已註冊的描述符在內核中會被維護在一棵紅黑樹上, 經過回調函數內核會將 I/O 準備好的描述符加入到一個鏈表中管理, 進程調用 epoll_wait() 即可以獲得事件完成的描述符。
從上面的描述能夠看出, epoll 只須要將描述符從進程緩衝區向內核緩衝區拷貝一次, 而且進程不須要經過輪詢來得到事件完成的描述符。
epoll 僅適用於 Linux OS。
epoll 比 select 和 poll 更加靈活並且沒有描述符數量限制。
epoll 對多線程編程更有友好, 一個線程調用了 epoll_wait() 另外一個線程關閉了同一個描述符也不會產生像 select 和 poll 的不肯定狀況。
epoll 的描述符事件有兩種觸發模式: LT( level trigger) 和 ET( edge trigger)
當 epoll_wait() 檢測到描述符事件到達時,將此時間通知進程,進程能夠不當即處理該事件,下次調用 epoll_wait() 時會再次通知進程,這是默認一種模式,而且同時支持阻塞和非阻塞
和 LT 模式不一樣的是,通知以後必須當即處理事件,下次再調用 epoll_wait() 不會再獲得時間到達的通知。
減小了 epoll 事件被重複觸發的次數,所以效率比 LT 高,只支持非阻塞式,以免因爲一個文件句柄的阻塞讀/阻塞寫操做把處理多個文件描述符的任務餓死。
經過上面的對比,很容易理解是既然 epoll 這麼強大,那麼都使用 epoll 不就夠了?實際上不是這樣的,其實都各自有各自的使用場景
只須要運行在 Linux 平臺,而且有很是大量的描述符須要同時輪詢,並且這些鏈接最好是長鏈接