天天看點

Linux程式設計的socket阻塞與非阻塞,同步與異步、I/O模型

轉自 http://blog.csdn.net/hguisu/article/details/7453390

在進行網絡程式設計時,我們常常見到同步(Sync)/異步(Async),阻塞(Block)/非阻塞(Unblock)四種調用方式:

同步/異步主要針對C端: 

同步:

      所謂同步,就是在c端發出一個功能調用時,在沒有得到結果之前,該調用就不傳回。也就是必須一件一件事做,等前一件做完了才能做下一件事。

例如普通B/S模式(同步):送出請求->等待伺服器處理->處理完畢傳回 這個期間用戶端浏覽器不能幹任何事

異步:

      異步的概念和同步相對。當c端一個異步過程調用發出後,調用者不能立刻得到結果。實際處理這個調用的部件在完成後,通過狀态、通知和回調來通知調用者。

     例如 ajax請求(異步): 請求通過事件觸發->伺服器處理(這是浏覽器仍然可以作其他事情)->處理完畢

阻塞/非阻塞主要針對S端:

阻塞

     阻塞調用是指調用結果傳回之前,目前線程會被挂起(線程進入非可執行狀态,在這個狀态下,cpu不會給線程配置設定時間片,即線程暫停運作)。函數隻有在得到結果之後才會傳回。

     有人也許會把阻塞調用和同步調用等同起來,實際上他是不同的。對于同步調用來說,很多時候目前線程還是激活的,隻是從邏輯上目前函數沒有傳回而已。 例如,我們在socket中調用recv函數,如果緩沖區中沒有資料,這個函數就會一直等待,直到有資料才傳回。而此時,目前線程還會繼續處理各種各樣的消息。

   快遞的例子:比如到你某個時候到A樓一層(假如是核心緩沖區)取快遞,但是你不知道快遞什麼時候過來,你又不能幹别的事,隻能死等着。但你可以睡覺(程序處于休眠狀态),因為你知道快遞把貨送來時一定會給你打個電話(假定一定能叫醒你)。

非阻塞

      非阻塞和阻塞的概念相對應,指在不能立刻得到結果之前,該函數不會阻塞目前線程,而會立刻傳回。

     還是等快遞的例子:如果用忙輪詢的方法,每隔5分鐘到A樓一層(核心緩沖區)去看快遞來了沒有。如果沒來,立即傳回。而快遞來了,就放在A樓一層,等你去取。

對象的阻塞模式和阻塞函數調用

對象是否處于阻塞模式和函數是不是阻塞調用有很強的相關性,但是并不是一一對應的。阻塞對象上可以有非阻塞的調用方式,我們可以通過一定的API去輪詢狀 态,在适當的時候調用阻塞函數,就可以避免阻塞。而對于非阻塞對象,調用特殊的函數也可以進入阻塞調用。函數select就是這樣的一個例子。

1. 同步,就是我用戶端(c端調用者)調用一個功能,該功能沒有結束前,我(c端調用者)死等結果。

2. 異步,就是我(c端調用者)調用一個功能,不需要知道該功能結果,該功能有結果後通知我(c端調用者)即回調通知。

同步/異步主要針對C端, 但是跟S端不是完全沒有關系,同步/異步機制必須S端配合才能實作.同步/異步是由c端自己控制,但是S端是否阻塞/非阻塞, C端完全不需要關心.

3. 阻塞,      就是調用我(s端被調用者,函數),我(s端被調用者,函數)沒有接收完資料或者沒有得到結果之前,我不會傳回。

4. 非阻塞,  就是調用我(s端被調用者,函數),我(s端被調用者,函數)立即傳回,通過select通知調用者

同步IO和異步IO的差別就在于:資料通路的時候程序是否阻塞!

阻塞IO和非阻塞IO的差別就在于:應用程式的調用是否立即傳回!

同步和異步都隻針對于本機SOCKET而言的。

同步和異步,阻塞和非阻塞,有些混用,其實它們完全不是一回事,而且它們修飾的對象也不相同。

阻塞和非阻塞是指當server端的程序通路的資料如果尚未就緒,程序是否需要等待,簡單說這相當于函數内部的實作差別,也就是未就緒時是直接傳回還是等待就緒;

而同步和異步是指client端通路資料的機制,同步一般指主動請求并等待I/O操作完畢的方式,當資料就緒後在讀寫的時候必須阻塞(差別就緒與讀寫二個階段,同步的讀寫必須阻塞),異步則指主動請求資料後便可以繼續處理其它任務,随後等待I/O,操作完畢的通知,這可以使程序在資料讀寫時也不阻塞。(等待"通知")

2. Linux下的五種I/O模型

1)阻塞I/O(blocking I/O)

2)非阻塞I/O (nonblocking I/O)

3) I/O複用(select 和poll) (I/O multiplexing)

4)信号驅動I/O (signal driven I/O (SIGIO))

5)異步I/O (asynchronous I/O (the POSIX aio_functions))

前四種都是同步,隻有最後一種才是異步IO。

阻塞I/O模型:

        簡介:程序會一直阻塞,直到資料拷貝完成

     應用程式調用一個IO函數,導緻應用程式阻塞,等待資料準備好。 如果資料沒有準備好,一直等待….資料準備好了,從核心拷貝到使用者空間,IO函數傳回成功訓示。我們 第一次接觸到的網絡程式設計都是從 listen()、send()、recv()等接口開始的。使用這些接口可以很友善的建構伺服器 /客戶機的模型。

使用阻塞模式的套接字,開發網絡程式比較簡單,容易實作。當希望能夠立即發送和接收資料,且處理的套接字數量比較少的情況下,使用阻塞模式來開發網絡程式比較合适。

  阻塞模式套接字的不足表現為,在大量建立好的套接字線程之間進行通信時比較困難。當使用“生産者-消費者”模型開發網絡程式時,為每個套接字都分别配置設定一個讀線程、一個處理資料線程和一個用于同步的事件,那麼這樣無疑加大系統的開銷。其最大的缺點是當希望同時處理大量套接字時,将無從下手,其擴充性很差.

      阻塞模式給網絡程式設計帶來了一個很大的問題,如在調用 send()的同時,線程将被阻塞,在此期間,線程将無法執行任何運算或響應任何的網絡請求。這給多客戶機、多業務邏輯的網絡程式設計帶來了挑戰。這時,我們可能會選擇多線程的方式來解決這個問題。

       應對多客戶機的網絡應用,最簡單的解決方式是在伺服器端使用多線程(或多程序)。多線程(或多程序)的目的是讓每個連接配接都擁有獨立的線程(或程序),這樣任何一個連接配接的阻塞都不會影響其他的連接配接。

       具體使用多程序還是多線程,并沒有一個特定的模式。傳統意義上,程序的開銷要遠遠大于線程,是以,如果需要同時為較多的客戶機提供服務,則不推薦使用多程序;如果單個服務執行體需要消耗較多的 CPU 資源,譬如需要進行大規模或長時間的資料運算或檔案通路,則程序較為安全。通常,使用 pthread_create () 建立新線程,fork() 建立新程序。

上述多線程的伺服器模型似乎完美的解決了為多個客戶機提供問答服務的要求,但其實并不盡然。如果要同時響應成百上千路的連接配接請求,則無論多線程還是多程序都會嚴重占據系統資源,降低系統對外界響應效率,而線程與程序本身也更容易進入假死狀态。

       由此可能會考慮使用“線程池”或“連接配接池”。“線程池”旨在減少建立和銷毀線程的頻率,其維持一定合理數量的線程,并讓空閑的線程重新承擔新的執行任務。“連接配接池”維持連接配接的緩存池,盡量重用已有的連接配接、減少建立和關閉連接配接的頻率。這兩種技術都可以很好的降低系統開銷,都被廣泛應用很多大型系統,如apache,MySQL資料庫等。

      但是,“線程池”和“連接配接池”技術也隻是在一定程度上緩解了頻繁調用 IO 接口帶來的資源占用。而且,所謂“池”始終有其上限,當請求大大超過上限時,“池”構成的系統對外界的響應并不比沒有池的時候效果好多少。是以使用“池”必須考慮其面臨的響應規模,并根據響應規模調整“池”的大小

非阻塞IO模型 :

        簡介:非阻塞IO通過程序反複調用IO函數( 多次系統調用,并馬上傳回 ); 在資料拷貝的過程中,程序是阻塞的 ;

       我們把一個SOCKET接口設定為非阻塞就是告訴核心,當所請求的I/O操作無法完成時,不要将程序睡眠,而是傳回一個錯誤。這樣我們的I/O操作函數将不斷的測試資料是否已經準備好,如果沒有準備好,繼續測試,直到資料準備好為止。在這個不斷測試的過程中,會大量的占用CPU的時間。

  把SOCKET設定為非阻塞模式,即通知系統核心:在調用Windows Sockets API時,不要讓線程睡眠,而應該讓函數立即傳回。在傳回時,該函數傳回一個錯誤代碼。

IO複用模型:

       簡介:主要是select和epoll;對一個IO端口,兩次調用,兩次傳回,比阻塞IO并沒有什麼優越性;關鍵是能實作同時對多個IO端口進行監聽;

      I/O複用模型會用到select、poll、epoll函數,這幾個函數也會使程序阻塞,但是和阻塞I/O所不同的的,這兩個函數可以同時阻塞多個I/O操作。而且可以同時對多個讀操作,多個寫操作的I/O函數進行檢測,直到有資料可讀或可寫時,才真正調用I/O操作函數。

信号驅動IO

    簡介:兩次調用,兩次傳回;

    首先我們允許套接口進行信号驅動I/O,并安裝一個信号處理函數,程序繼續運作并不阻塞。當資料準備好時,程序會收到一個SIGIO信号,可以在信号處理函數中調用I/O操作函數處理資料。

異步IO模型

         簡介:資料拷貝的時候程序無需阻塞。

     當一個異步過程調用發出後,調用者不能立刻得到結果。實際處理這個調用的部件在完成後,通過狀态、通知和回調來通知調用者的輸入輸出操作

繼續閱讀