天天看點

[轉][網摘收藏]Socket的阻塞模式和非阻塞模式

阻塞模式

  Windows套接字在阻塞和非阻塞兩種模式下執行I/O操作。在阻塞模式下,在I/O操作完成前,執行的操作函數一直等候而不會立即傳回,該函數所在的線程會阻塞在這裡。相反,在非阻塞模式下,套接字函數會立即傳回,而不管I/O是否完成,該函數所在的線程會繼續運作。

在阻塞模式的套接字上,調用任何一個Windows Sockets API都會耗費不确定的等待時間。圖所示,在調用recv()函數時,發生在核心中等待資料和複制資料的過程。

當調用recv()函數時,系統首先查是否有準備好的資料。如果資料沒有準備好,那麼系統就處于等待狀态。當資料準備好後,将資料從系統緩沖區複制到使用者空間,然後該函數傳回。在套接應用程式中,當調用recv()函數時,未必使用者空間就已經存在資料,那麼此時recv()函數就會處于等待狀态。

[轉][網摘收藏]Socket的阻塞模式和非阻塞模式

Windows套接字程式使用“生産者-消費者”模式來解決上述問題。在程式中,“生産者”讀入資料,“消費者”根據需求對讀入資料進行處理。通常“生産者”和“消費者”存在于兩個線程中,當“生産者”完成讀入資料時,使用線程同步機制,例如設定一個事件通知“消費者”,“消費者”接收到這個事件後對讀入的資料進行處理。

  當使用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連接配接總會等待至少到伺服器的一次往返時間。

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

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

非阻塞模式

把套接字設定為非阻塞模式,即通知系統核心:在調用Windows Sockets

API時,不要讓線程睡眠,而應該讓函數立即傳回。在傳回時,該函數傳回一個錯誤代碼。圖所示,一個非阻塞模式套接字多次調用recv()函數的過程。前三次調用recv()函數時,核心資料還沒有準備好。是以,該函數立即傳回WSAEWOULDBLOCK錯誤代碼。第四次調用recv()函數時,資料已經準備好,被複制到應用程式的緩沖區中,recv()函數傳回成功訓示,應用程式開始處理資料。

[轉][網摘收藏]Socket的阻塞模式和非阻塞模式

  當使用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模型”,它有助于應用程式通過異步方式,同時對一個或多個套接字的通信加以管理。

贈人玫瑰

手留餘香