天天看點

windows下的套接字IO模型

一般情況下,IO操作的行為受兩種因素的影響:

  • IO操作對象的類型(阻塞還是非阻塞)
  • 擷取IO操作結果的方式(同步還是異步).

同步就是指操作的發起和操作結果的擷取由調用者完成.

異步指操作發起由調用方完成,操作結果由服務方主動告知.

IO操作一般可以分為4種:

  • 同步阻塞IO :調用方調用IO函數,并阻塞在這個函數上面.
  • 同步非阻塞IO:調用方不斷的調用IO函數,直到有”明确”的傳回值.
  • 異步阻塞IO:調用方發起IO操作後,阻塞在接收IO通知的API上.
  • 異步非阻塞IO:調用方發起IO操作後繼續其他工作,由核心負責回調或者發出IO通知信号.
windows下的套接字IO模型

阻塞IO對象在調用期間會阻塞IO函數,函數傳回的時候,操作結果是明确的,是以不需要配合其他API來擷取改操作的結果.

非阻塞IO對象在發生IO調用的時候,總是立即傳回(傳回的IO請求的結果,不是IO的執行結果),但執行結果不能馬上得知,調用者可能需要使用配套的一系列API來擷取IO結果(在什麼時機,什麼地點使用由調用方自己決定).

現在我們将IO對象放在套接字上,那麼套接字有阻塞型套接字(Bolocking Socket)和非阻塞型套接字.再将目光放在windows作業系統上:

windows下套接字模型可以分為:
  • Blocking Mode               阻塞型
  • Non-blocking Mode    非阻塞型
windows下套接字IO模型:
  • The blocking Model                
  • The select Model                        
  • The WSAAsyncSelect Model
  • The WSAEventSelect Model
  • The Overlapped Model
  • The Completion Port Model
備注:
  • 上面六種基本上可以認為:按照從上到下的順序,上面的使用最簡單程度,依次遞減,性能依次遞增.
  • 隻要調用WSAEventSelect 或者WSAAsyncSelect, 套接字都會被自動設定為Non-Blocking.
  • windows下典型的套接字API就是WSAXxxx家族函數,比如WSASend,WSARecv.因為WSAXxxx能夠處理所有傳統套接字API(如send,recv)的所有功能,我們隻讨論前者.

Windwos下的WSAXxxx系列函數被設計成一套适合各種場景的API,看上去它非常的”聰明”,根據不同的場景有不同的語義:

  1. 如果操作的對象是一個阻塞型套接字,那麼它的行為就跟傳統的套接字API一樣,調用方被阻塞在這個API上,直到IO操作傳回結果.這種方式的最大好處是處理流程簡單.
  2. 如果操作的對象是一個非阻塞套接字,它會立即傳回結果(一般是WSAEWOULDBLOCK),這種方式的好處就是可以很友善的控制IO逾時.

    WSAEWOULDBLOCK其實算不上一種嚴重的錯誤,在不同場景有不同的語義:

    Function Name Description
    WSAAccept and accept The application has not received a connection request. Call again to check for a connection.
    closesocket In most cases, this means that setsockopt was called with the SO_LINGER option and a nonzero timeout was set.
    WSAConnect and connect The connection is initiated. Call again to check for completion.
    WSARecv, recv, WSARecvFrom, and recvfrom No data has been received. Check again later.
    WSASend, send, WSASendTo, and sendto No buffer space available for outgoing data. Try again later.
  3. 當操作的對象是一個重疊IO的套接字,情況就稍微複雜一些了,以WSASend為例子,先看其函數原型:
    1: int WSASend(      
    2:   __in          SOCKET s,      
    3:   __in          LPWSABUF lpBuffers,      
    4:   __in          DWORD dwBufferCount,      
    5:   __out         LPDWORD lpNumberOfBytesSent,      
    6:   __in          DWORD dwFlags,      
    7:   __in          LPWSAOVERLAPPED lpOverlapped,      
    8:   __in          LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine      
    9: );      

    在發起IO操作時,lpOverlapped->hEvent 和lpCompletionRoutine 的有效性組合關系可有下面幾種:

    A. lpCompletionRoutine 有效,hEvent 會被忽略.核心會在IO事件完成時調用該回調函數(lpCompletionRoutine ).

    B.lpCompletionRoutine 為NULL,hEvent 有效,核心會在IO事件完成時為這個hEvent 設定信号.應用層可以用WSAWaitForMultipleEvents等方式等待信号.

    C. lpCompletionRoutine 和lpOverlapped都為NULL(hEvent 自然也是無效的),則視為非重疊IO套接字對待.

如果套接字句柄已經綁定到某個完成端口句柄上,回調函數必須設定為NULL,否則将得到10022(參數錯誤).如果IO結構體中的hEvent有效,核心仍然會為這個句柄設定信号.

重疊IO套接字上的操作通常都會立即傳回,如果該操作可以立即完成則會傳回位元組數,否則傳回(WSA_IO_PENDING),表示操作結果不能立即取得,從這個時候開始一直到完成事件到達,送出給核心的記憶體緩沖區将被鎖定,調用方需要保證這期間該記憶體一直有效.因為在這種模式下,核心的政策是IO傳輸的資料直接緩沖到調用方的這塊記憶體位址是,而不是套接字自身的緩沖區.這就為實作一個零拷貝(Zero Copy)的IO架構提供了可能性,試想一下,整個過程中不需要任何的memcpy,是不是很誘人?

此外,伺服器也可能是一個CPU密集型服務,這個時候隻需要改變一下套接字API(WSASend/WSARecv)的用法,很容易就從proactor模式切換為reactor模式,竅門就是使用

零緩沖WSASend/WSARecv .

最後,一個常用的流程圖:

windows下的套接字IO模型

圖中的FIOBNIO=TRUE表示套接字設定是非阻塞,反之為阻塞套接字.

文章資訊

作者:

J.Cheen

出處:

www.cnblogs.com/cheen

繼續閱讀