天天看點

(一)Windows網絡模型之select模型和事件選擇模型

1 Select模型

  1. select模型解決的問題是基本的C/S模型中,accept函數和recv函數傻等阻塞問題;
  2. 另外還使伺服器可以與多個用戶端連接配接,與多個用戶端分别通信
  3. 除了傻等阻塞還有一種阻塞為執行阻塞,例如send()、recv()等函數執行的過程是執行阻塞的,但是這不是select模型解決的問題
  • 基本的原理是:
    • 首先:伺服器這邊有兩種SOCKET;一種是我們自己主動建立的SOCKET,暫稱為伺服器SOCKET,另一種是接收到用戶端來連接配接之後又accept()函數傳回的SOCKET,暫稱為用戶端通信SOCKET
    • 通過将伺服器SOCKET和用戶端通信SOCKET通通裝進一個資料結構(數組)中,然後調用select函數,周遊資料結構中的SOCKET,當某個Socket有響應,再做相應的處理
    • 如果是伺服器SOCKET---->調用accept
    • 如果是用戶端通信SOCKET---->調用recv或send
  • 上面所說的資料結構其實就是fd_set
    typedef struct fd_set {
            u_int fd_count;               	/* how many are SET? */
            SOCKET  fd_array[FD_SETSIZE];   /* an array of SOCKETs */
    } fd_set;
               
伺服器:(前4步與基本C/S模式一樣)
  1. 打開網絡庫并校驗
  2. 建立伺服器SOCKET
  3. 綁定伺服器的IP和PORT
  4. 啟動監聽
  5. 調用select()函數
    • 一般可以放在一個循環之中,循環調用select函數
    int WSAAPI select
    (
      int           nfds,		// 忽略,直接填0
      fd_set        *readfds,	// 這是一個雙向參數,将fd_set的副本傳址進去,函數執行結束後,該參數代表的資料中僅包含有可讀響應的socket(如需要執行recv或accept)
      fd_set        *writefds,	// 同上,傳回結果表示有可寫響應的socket(進行send),可以填NULL
      fd_set        *exceptfds,	// 通常,表示有異常的socket
      const timeval *timeout	// 最長等待時間,NULL表示完全阻塞(等到用戶端有響應才會傳回)
    );
    // 傳回值:		
    //	0 	表示用戶端在等待時間内沒有響應
    //	>0	表示用戶端有有響應或請求
    // 	SOCKET_ERROR 表示出錯
               

結合select()函數的處理邏輯如下(僞代碼):

while(1){
	int res = select(...);
    if(res == 0) {
        // 表示用戶端無響應
        continue;
    } else if (res > 0) {
        // 表示有響應
 		// 周遊響應select函數第二個參數的fd_set
        for(int i = 0; i < fdset.count; i++){
            // 如果是伺服器socket  調用accept 與新的用戶端建立連接配接
            if (fdset.array[i] == socketSever){
                SOCKET newsocket = accept(socketSever, NULL, 0);
                //将新的Socket裝進全局的fdset
                FD_SET(newsocket, &allfdset);
            }
            // 如果是用戶端通信socket,接收資料recv
            else {
                int nRes = recv(fdset.array[i], buffer, len, 0);
                printf(buffer);
            	// 如果檢測到用戶端正常下線則從全局fdset中删除以及應關閉全局fdset中的對應socket
            }
        }
    } else {
        // 出錯處理
        // 在winsock中 強制下線也是錯誤的一種
    }
}
           
用戶端:同基本C/S一樣
  1. 打開網絡庫并校驗
  2. 建立SOCKET
  3. 連接配接到伺服器
  4. 與伺服器進行通信
總結:
  • 所謂網絡模型的不斷優化,其本質就是在解決函數調用過程中的阻塞問題(傻等阻塞與執行阻塞)
  • select模型解決的是基本C/S模型中的傻等阻塞(accept和recv)
    • accept函數在沒有用戶端前來連接配接的時候會一直阻塞等待,也就是傻等阻塞
    • recv函數進行通信,在對端沒有發來資料的時候也就一直阻塞等待,也就是傻等阻塞
  • 但是select函數本身也是阻塞的,這屬于執行阻塞,recv和send在執行的時候造成的阻塞也是執行阻塞,比如資料有點多,在發送和接收資料時就會執行阻塞
  • 通常在實際應用中用的最多的就是select函數的第2個參數,也即可讀的socket響應

場景:

  • ​ 小使用者量通路,幾十、幾百,簡單友善。

如何回答對select模型的認識?

首先select模型是用在伺服器端的,用戶端代碼并沒有什麼改變。

而最基本的伺服器端的代碼邏輯為

①建立socket(socket())

②綁定本地IP和端口号(bind)

③啟動監聽(Listen)

④接收來自用戶端的連接配接(accept)

⑥連接配接建立成功後使用recv、send函數接收、發送資料(Unix系統上是read和write函數)

但存在一個問題:也即是accept函數以及recv函數都是阻塞的,而且是傻傻等待的那種,隻要對方沒有連接配接請求或發送資料,程式就卡死了

為了解決這個阻塞的問題,select模型就出現了:

  • 通過

    ft_set

    以及相應的宏去操作

    fd_set

    ,傳遞給select函數(參數2:關心可讀socket,參數3:關心可寫socket,參數4:關心異常socket)
  • 可以通過循環反複調用select函數,根據select的傳回參數,确定是否存在需要處理的socket,如果有就進行相應的處理。
  • 如此就解決了accept函數與recv/read函數等存在的阻塞傻等問題,優化了程式

2 事件選擇模型(基于事件機制–核心是事件集合)

什麼是事件機制?

答:

  • Windows處理使用者行為的兩種方式之一
  • 事件機制的核心是

    事件集合

    ,程式員通過為某些行為或動作綁定一個事件(本質可能就是一個ID),我們通過調用相應的API函數建立事件;
  • 然後将事件集合投遞給作業系統,作業系統會幫我們監測這些事件,當事件發生時就将其設定為

    有信号

    ;事件被響應後設定為

    無信号

  • 事件的響應是無序的,是以不一定會嚴格按照事件發生的順序響應,有些最先發生的事件不一定最早被處理;最晚發生的事情也可能更早的被處理。

Windows下的事件選擇模型最具有代表性的API函數是

WSAEventSelect()

,可以簡單了解為

select()

函數的更新版!

事件選擇模型的邏輯:

  1. 建立事件

    WSACreateEvent()

    ,目前建立的事件沒有綁定任何操作或動作的,是一個單純的核心對象
  2. 使用

    WSAEventSelect()

    綁定

    socket

    上的

    某個行為或操作

    并投遞給作業系統監管,此時我們的程式可以去做其他的事情。因為這是一個異步的操作,就解決了select模型中

    select()

    函數本身執行阻塞的情況
    通過綁定某個

    socket[參數1]

    某個動作[參數3]

    某個事件[參數2]

    上,将事件、行為與socket聯系起來
    通常會建立一個結構如下:
    struct fd_es_set
    {
    	unsigned short count;						// 表示事件數目
    	SOCKET sockall[WSA_MAXIMUM_WAIT_EVENTS];	// 表示socket數組
    	WSAEVENT evnetall[WSA_MAXIMUM_WAIT_EVENTS];	// 表示event數組,與socket數組中相同下表處的元素一一對應
    };
               
    伺服器如果接收了新的連接配接,就會産生新的

    用戶端通信Socket

    ,然後建立新的事件并綁定到該socket之後投遞給系統

    然後再添加到

    fd_es_set

    為什麼一定要建立

    fd_es_set

    呢?

    回答:為了擷取有信号的事件,并進一步處理相應的socket,然後做出正确的動作

    int WSAAPI WSAEventSelect
    (
      SOCKET   s,				// 某個socket
      WSAEVENT hEventObject,	// 某個事件對象
      long     lNetworkEvents	// 某個動作  如:ACCEPT  READ  WRITE
    );
               
  3. 查詢事件是否有信号—

    WSAWaitForMultipleEvents()

    一般的用法是循環的去詢問~

    函數傳回值指明有信号的事件在事件清單中的索引(其實是

    傳回值 - WSA_WAIT_EVENT_0

    事件清單是參數2

    DWORD WSAAPI WSAWaitForMultipleEvents(
      DWORD          cEvents,		// 有效事件數目
      const WSAEVENT *lphEvents,	// 事件清單
      BOOL           fWaitAll,		// true表示等待所有的事件有信号再傳回  false表示任意一個事件有信号就傳回
      DWORD          dwTimeout,		// 逾時時間或者一直等待或者立即傳回
      BOOL           fAlertable		// 重疊I/O模型填TRUE 否則填FALSE
    );
               
  4. 有信号時就分類處理

    WSAEnumNetworkEvents()

    注意:

    WSAWaitForMultipleEvents()

    隻會傳回一個事件索引,如果同時有多個事件有信号,會傳回索引最小的事件

    獲得有信号的事件以及對應的

    socket

    後,還需要知道是什麼操作觸發了信号,是以要使用

    WSAEnumNetworkEvents()

    函數

    該函數通過參數3

    lpNetworkEvents

    傳回出信号的事件類型,也即具體操作
    int WSAAPI WSAEnumNetworkEvents(
      SOCKET             s,					// 有信号的事件綁定的socket
      WSAEVENT           hEventObject,		// 有信号的事件
      LPWSANETWORKEVENTS lpNetworkEvents	// 這是一個指針,本質是一個傳出參數,其中傳回了觸發信号的具體操作
    );
               
    之後就是基于

    NetWorkEvents

    的分類邏輯處理

關于核心對象:

①核心對象由作業系統在核心中申請,由作業系統進行通路,也就是說所有對核心對象的操作都需要經過系統調用

②建立和釋放都需要調用相應的函數,如果沒有釋放,就會造成核心資源洩露,隻能重新開機電腦才能解決

③常見的windows核心對象,

socket

event

file

thread

Mutex

信号量

如何回答對事件選擇模型的了解?

事件選擇模型是基于事件機制的,事件機制的核心是事件集合,該模型是簡單的select模型的更新版,解決了select模型中

select()

執行阻塞的問題。

通過将

套接字socket

事件event

以及

相應的行為

如,有用戶端連接配接、socket可寫可讀等綁定在一起并投遞給作業系統,讓作業系統進行監測,如果有相應的行為或事件發生,該事件就會被置為有信号狀态。

之後通過相應的API函數,擷取到有信号的事件以及對應的socket。然後再調用API函數進一步獲知觸發事件的具體行為。

最後根據這些行為進行對應的業務處理,是該accept接收連接配接還是發送或接收資料等。

其中最關鍵的是将事件交給作業系統監測與程式執行本身是異步的,這就解決了select模型中select函數執行阻塞的問題。

(一)Windows網絡模型之select模型和事件選擇模型

推薦閱讀:(二)Windows網絡模型之異步選擇模型(基于消息機制)