<a target="_blank" href="http://blog.csdn.net/hguisu/article/details/7453390">socket阻塞與非阻塞,同步與異步</a>
1. 概念了解
在進行網絡程式設計時,我們常常見到同步(sync)/異步(async),阻塞(block)/非阻塞(unblock)四種調用方式:
同步:
所謂同步,就是在發出一個功能調用時,在沒有得到結果之前,該調用就不傳回。也就是必須一件一件事做,等前一件做完了才能做下一件事。
例如普通b/s模式(同步):送出請求->等待伺服器處理->處理完畢傳回 這個期間用戶端浏覽器不能幹任何事
異步:
異步的概念和同步相對。當一個異步過程調用發出後,調用者不能立刻得到結果。實際處理這個調用的部件在完成後,通過狀态、通知和回調來通知調用者。
例如
ajax請求(異步): 請求通過事件觸發->伺服器處理(這是浏覽器仍然可以作其他事情)->處理完畢
阻塞
阻塞調用是指調用結果傳回之前,目前線程會被挂起(線程進入非可執行狀态,在這個狀态下,cpu不會給線程配置設定時間片,即線程暫停運作)。函數隻有在得到結果之後才會傳回。
有人也許會把阻塞調用和同步調用等同起來,實際上他是不同的。對于同步調用來說,很多時候目前線程還是激活的,隻是從邏輯上目前函數沒有傳回而已。 例如,我們在socket中調用recv函數,如果緩沖區中沒有資料,這個函數就會一直等待,直到有資料才傳回。而此時,目前線程還會繼續處理各種各樣的消息。
非阻塞
非阻塞和阻塞的概念相對應,指在不能立刻得到結果之前,該函數不會阻塞目前線程,而會立刻傳回。
對象的阻塞模式和阻塞函數調用
對象是否處于阻塞模式和函數是不是阻塞調用有很強的相關性,但是并不是一一對應的。阻塞對象上可以有非阻塞的調用方式,我們可以通過一定的api去輪詢狀 态,在适當的時候調用阻塞函數,就可以避免阻塞。而對于非阻塞對象,調用特殊的函數也可以進入阻塞調用。函數select就是這樣的一個例子。
1. 同步,就是我調用一個功能,該功能沒有結束前,我死等結果。
2. 異步,就是我調用一個功能,不需要知道該功能結果,該功能有結果後通知我(回調通知)
3. 阻塞, 就是調用我(函數),我(函數)沒有接收完資料或者沒有得到結果之前,我不會傳回。
4. 非阻塞, 就是調用我(函數),我(函數)立即傳回,通過select通知調用者
同步io和異步io的差別就在于:資料拷貝的時候程序是否阻塞!
阻塞io和非阻塞io的差別就在于:應用程式的調用是否立即傳回!
對于舉個簡單c/s 模式:
同步:送出請求->等待伺服器處理->處理完畢傳回這個期間用戶端浏覽器不能幹任何事
異步:請求通過事件觸發->伺服器處理(這是浏覽器仍然可以作其他事情)->處理完畢
同步和異步都隻針對于本機socket而言的。
同步和異步,阻塞和非阻塞,有些混用,其實它們完全不是一回事,而且它們修飾的對象也不相同。
阻塞和非阻塞是指當程序通路的資料如果尚未就緒,程序是否需要等待,簡單說這相當于函數内部的實作差別,也就是未就緒時是直接傳回還是等待就緒;
而同步和異步是指通路資料的機制,同步一般指主動請求并等待i/o操作完畢的方式,當資料就緒後在讀寫的時候必須阻塞(差別就緒與讀寫二個階段,同步的讀寫必須阻塞),異步則指主動請求資料後便可以繼續處理其它任務,随後等待i/o,操作完畢的通知,這可以使程序在資料讀寫時也不阻塞。(等待"通知")
1. 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。
簡介:程序會一直阻塞,直到資料拷貝完成
應用程式調用一個io函數,導緻應用程式阻塞,等待資料準備好。 如果資料沒有準備好,一直等待….資料準備好了,從核心拷貝到使用者空間,io函數傳回成功訓示。
阻塞i/o模型圖:在調用recv()/recvfrom()函數時,發生在核心中等待資料和複制資料的過程。

當調用recv()函數時,系統首先查是否有準備好的資料。如果資料沒有準備好,那麼系統就處于等待狀态。當資料準備好後,将資料從系統緩沖區複制到使用者空間,然後該函數傳回。在套接應用程式中,當調用recv()函數時,未必使用者空間就已經存在資料,那麼此時recv()函數就會處于等待狀态。
當使用socket()函數和wsasocket()函數建立套接字時,預設的套接字都是阻塞的。這意味着當調用windows sockets api不能立即完成時,線程處于等待狀态,直到操作完成。
并不是所有windows sockets api以阻塞套接字為參數調用都會發生阻塞。例如,以阻塞模式的套接字為參數調用bind()、listen()函數時,函數會立即傳回。将可能阻塞套接字的windows sockets api調用分為以下四種:
1.輸入操作: recv()、recvfrom()、wsarecv()和wsarecvfrom()函數。以阻塞套接字為參數調用該函數接收資料。如果此時套接字緩沖區内沒有資料可讀,則調用線程在資料到來前一直睡眠。
2.輸出操作: send()、sendto()、wsasend()和wsasendto()函數。以阻塞套接字為參數調用該函數發送資料。如果套接字緩沖區沒有可用空間,線程會一直睡眠,直到有空間。
3.接受連接配接:accept()和wsaacept()函數。以阻塞套接字為參數調用該函數,等待接受對方的連接配接請求。如果此時沒有連接配接請求,線程就會進入睡眠狀态。
4.外出連接配接:connect()和wsaconnect()函數。對于tcp連接配接,用戶端以阻塞套接字為參數,調用該函數向伺服器發起連接配接。該函數在收到伺服器的應答前,不會傳回。這意味着tcp連接配接總會等待至少到伺服器的一次往返時間。
使用阻塞模式的套接字,開發網絡程式比較簡單,容易實作。當希望能夠立即發送和接收資料,且處理的套接字數量比較少的情況下,使用阻塞模式來開發網絡程式比較合适。
阻塞模式套接字的不足表現為,在大量建立好的套接字線程之間進行通信時比較困難。當使用“生産者-消費者”模型開發網絡程式時,為每個套接字都分别配置設定一個讀線程、一個處理資料線程和一個用于同步的事件,那麼這樣無疑加大系統的開銷。其最大的缺點是當希望同時處理大量套接字時,将無從下手,其擴充性很差
簡介:非阻塞io通過程序反複調用io函數(多次系統調用,并馬上傳回);在資料拷貝的過程中,程序是阻塞的;
我們把一個socket接口設定為非阻塞就是告訴核心,當所請求的i/o操作無法完成時,不要将程序睡眠,而是傳回一個錯誤。這樣我們的i/o操作函數将不斷的測試資料是否已經準備好,如果沒有準備好,繼續測試,直到資料準備好為止。在這個不斷測試的過程中,會大量的占用cpu的時間。
把socket設定為非阻塞模式,即通知系統核心:在調用windows sockets api時,不要讓線程睡眠,而應該讓函數立即傳回。在傳回時,該函數傳回一個錯誤代碼。圖所示,一個非阻塞模式套接字多次調用recv()函數的過程。前三次調用recv()函數時,核心資料還沒有準備好。是以,該函數立即傳回wsaewouldblock錯誤代碼。第四次調用recv()函數時,資料已經準備好,被複制到應用程式的緩沖區中,recv()函數傳回成功訓示,應用程式開始處理資料。
當使用socket()函數和wsasocket()函數建立套接字時,預設都是阻塞的。在建立套接字之後,通過調用ioctlsocket()函數,将該套接字設定為非阻塞模式。linux下的函數是:fcntl().
套接字設定為非阻塞模式後,在調用windows sockets api函數時,調用函數會立即傳回。大多數情況下,這些函數調用都會調用“失敗”,并傳回wsaewouldblock錯誤代碼。說明請求的操作在調用期間内沒有時間完成。通常,應用程式需要重複調用該函數,直到獲得成功傳回代碼。
需要說明的是并非所有的windows sockets api在非阻塞模式下調用,都會傳回wsaewouldblock錯誤。例如,以非阻塞模式的套接字為參數調用bind()函數時,就不會傳回該錯誤代碼。當然,在調用wsastartup()函數時更不會傳回該錯誤代碼,因為該函數是應用程式第一調用的函數,當然不會傳回這樣的錯誤代碼。
要将套接字設定為非阻塞模式,除了使用ioctlsocket()函數之外,還可以使用wsaasyncselect()和wsaeventselect()函數。當調用該函數時,套接字會自動地設定為非阻塞方式。
由于使用非阻塞套接字在調用函數時,會經常傳回wsaewouldblock錯誤。是以在任何時候,都應仔細檢查傳回代碼并作好對“失敗”的準備。應用程式連續不斷地調用這個函數,直到它傳回成功訓示為止。上面的程式清單中,在while循環體内不斷地調用recv()函數,以讀入1024個位元組的資料。這種做法很浪費系統資源。
要完成這樣的操作,有人使用msg_peek标志調用recv()函數檢視緩沖區中是否有資料可讀。同樣,這種方法也不好。因為該做法對系統造成的開銷是很大的,并且應用程式至少要調用recv()函數兩次,才能實際地讀入資料。較好的做法是,使用套接字的“i/o模型”來判斷非阻塞套接字是否可讀可寫。
非阻塞模式套接字與阻塞模式套接字相比,不容易使用。使用非阻塞模式套接字,需要編寫更多的代碼,以便在每個windows sockets api函數調用中,對收到的wsaewouldblock錯誤進行處理。是以,非阻塞套接字便顯得有些難于使用。
但是,非阻塞套接字在控制建立的多個連接配接,在資料的收發量不均,時間不定時,明顯具有優勢。這種套接字在使用上存在一定難度,但隻要排除了這些困難,它在功能上還是非常強大的。通常情況下,可考慮使用套接字的“i/o模型”,它有助于應用程式通過異步方式,同時對一個或多個套接字的通信加以管理。
簡介:主要是select和epoll;對一個io端口,兩次調用,兩次傳回,比阻塞io并沒有什麼優越性;關鍵是能實作同時對多個io端口進行監聽;
i/o複用模型會用到select、poll、epoll函數,這幾個函數也會使程序阻塞,但是和阻塞i/o所不同的的,這兩個函數可以同時阻塞多個i/o操作。而且可以同時對多個讀操作,多個寫操作的i/o函數進行檢測,直到有資料可讀或可寫時,才真正調用i/o操作函數。
簡介:兩次調用,兩次傳回;
首先我們允許套接口進行信号驅動i/o,并安裝一個信号處理函數,程序繼續運作并不阻塞。當資料準備好時,程序會收到一個sigio信号,可以在信号處理函數中調用i/o操作函數處理資料。
簡介:資料拷貝的時候程序無需阻塞。
當一個異步過程調用發出後,調用者不能立刻得到結果。實際處理這個調用的部件在完成後,通過狀态、通知和回調來通知調用者的輸入輸出操作
同步io引起程序阻塞,直至io操作完成。
異步io不會引起程序阻塞。
io複用是先通過select調用阻塞。
1. select、poll、epoll簡介
epoll跟select都能提供多路i/o複用的解決方案。在現在的linux核心裡有都能夠支援,其中epoll是linux所特有,而select則應該是posix所規定,一般作業系統均有實作
select:
select本質上是通過設定或者檢查存放fd标志位的資料結構來進行下一步處理。這樣所帶來的缺點是:
1、 單個程序可監視的fd數量被限制,即能監聽端口的大小有限。
一般來說這個數目和系統記憶體關系很大,具體數目可以cat /proc/sys/fs/file-max察看。32位機預設是1024個。64位機預設是2048.
2、 對socket進行掃描時是線性掃描,即采用輪詢的方法,效率較低:
當套接字比較多的時候,每次select()都要通過周遊fd_setsize個socket來完成排程,不管哪個socket是活躍的,都周遊一遍。這會浪費很多cpu時間。如果能給套接字注冊某個回調函數,當他們活躍時,自動完成相關操作,那就避免了輪詢,這正是epoll與kqueue做的。
3、需要維護一個用來存放大量fd的資料結構,這樣會使得使用者空間和核心空間在傳遞該結構時複制開銷大
poll:
poll本質上和select沒有差別,它将使用者傳入的數組拷貝到核心空間,然後查詢每個fd對應的裝置狀态,如果裝置就緒則在裝置等待隊列中加入一項并繼續周遊,如果周遊完所有fd後沒有發現就緒裝置,則挂起目前程序,直到裝置就緒或者主動逾時,被喚醒後它又要再次周遊fd。這個過程經曆了多次無謂的周遊。
它沒有最大連接配接數的限制,原因是它是基于連結清單來存儲的,但是同樣有一個缺點:
1、大量的fd的數組被整體複制于使用者态和核心位址空間之間,而不管這樣的複制是不是有意義。 2、poll還有一個特點是“水準觸發”,如果報告了fd後,沒有被處理,那麼下次poll時會再次報告該fd。
epoll:
epoll支援水準觸發和邊緣觸發,最大的特點在于邊緣觸發,它隻告訴程序哪些fd剛剛變為就需态,并且隻會通知一次。還有一個特點是,epoll使用“事件”的就緒通知方式,通過epoll_ctl注冊fd,一旦該fd就緒,核心就會采用類似callback的回調機制來激活該fd,epoll_wait便可以收到通知
epoll的優點:
1、沒有最大并發連接配接的限制,能打開的fd的上限遠大于1024(1g的記憶體上能監聽約10萬個端口);
2、效率提升,不是輪詢的方式,不會随着fd數目的增加效率下降。隻有活躍可用的fd才會調用callback函數;
即epoll最大的優點就在于它隻管你“活躍”的連接配接,而跟連接配接總數無關,是以在實際的網絡環境中,epoll的效率就會遠遠高于select和poll。
3、 記憶體拷貝,利用mmap()檔案映射記憶體加速與核心空間的消息傳遞;即epoll使用mmap減少複制開銷。
select、poll、epoll 差別總結:
1、支援一個程序所能打開的最大連接配接數
select
單個程序所能打開的最大連接配接數有fd_setsize宏定義,其大小是32個整數的大小(在32位的機器上,大小就是32*32,同理64位機器上fd_setsize為32*64),當然我們可以對進行修改,然後重新編譯核心,但是性能可能會受到影響,這需要進一步的測試。
poll
poll本質上和select沒有差別,但是它沒有最大連接配接數的限制,原因是它是基于連結清單來存儲的
epoll
雖然連接配接數有上限,但是很大,1g記憶體的機器上可以打開10萬左右的連接配接,2g記憶體的機器可以打開20萬左右的連接配接
2、fd劇增後帶來的io效率問題
因為每次調用時都會對連接配接進行線性周遊,是以随着fd的增加會造成周遊速度慢的“線性下降性能問題”。
同上
因為epoll核心中實作是根據每個fd上的callback函數來實作的,隻有活躍的socket才會主動調用callback,是以在活躍socket較少的情況下,使用epoll沒有前面兩者的線性下降的性能問題,但是所有socket都很活躍的情況下,可能會有性能問題。
3、 消息傳遞方式
核心需要将消息傳遞到使用者空間,都需要核心拷貝動作
epoll通過核心和使用者空間共享一塊記憶體來實作的。
總結:
綜上,在選擇select,poll,epoll時要根據具體的使用場合以及這三種方式的自身特點。
1、表面上看epoll的性能最好,但是在連接配接數少并且連接配接都十分活躍的情況下,select和poll的性能可能比epoll好,畢竟epoll的通知機制需要很多函數回調。
2、select低效是因為每次它都需要輪詢。但低效也是相對的,視情況而定,也可通過良好的設計改善