1 Select模型
- select模型解決的問題是基本的C/S模型中,accept函數和recv函數傻等阻塞問題;
- 另外還使伺服器可以與多個用戶端連接配接,與多個用戶端分别通信
- 除了傻等阻塞還有一種阻塞為執行阻塞,例如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模式一樣)
- 打開網絡庫并校驗
- 建立伺服器SOCKET
- 綁定伺服器的IP和PORT
- 啟動監聽
- 調用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一樣
- 打開網絡庫并校驗
- 建立SOCKET
- 連接配接到伺服器
- 與伺服器進行通信
總結:
- 所謂網絡模型的不斷優化,其本質就是在解決函數調用過程中的阻塞問題(傻等阻塞與執行阻塞)
- 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
,傳遞給select函數(參數2:關心可讀socket,參數3:關心可寫socket,參數4:關心異常socket)fd_set
- 可以通過循環反複調用select函數,根據select的傳回參數,确定是否存在需要處理的socket,如果有就進行相應的處理。
- 如此就解決了accept函數與recv/read函數等存在的阻塞傻等問題,優化了程式
2 事件選擇模型(基于事件機制–核心是事件集合)
什麼是事件機制?
答:
- Windows處理使用者行為的兩種方式之一
- 事件機制的核心是
,程式員通過為某些行為或動作綁定一個事件(本質可能就是一個ID),我們通過調用相應的API函數建立事件;事件集合
- 然後将事件集合投遞給作業系統,作業系統會幫我們監測這些事件,當事件發生時就将其設定為
;事件被響應後設定為有信号
;無信号
- 事件的響應是無序的,是以不一定會嚴格按照事件發生的順序響應,有些最先發生的事件不一定最早被處理;最晚發生的事情也可能更早的被處理。
Windows下的事件選擇模型最具有代表性的API函數是
WSAEventSelect()
,可以簡單了解為
select()
函數的更新版!
事件選擇模型的邏輯:
- 建立事件
,目前建立的事件沒有綁定任何操作或動作的,是一個單純的核心對象WSACreateEvent()
- 使用
綁定WSAEventSelect()
上的socket
并投遞給作業系統監管,此時我們的程式可以去做其他的事情。因為這是一個異步的操作,就解決了select模型中某個行為或操作
函數本身執行阻塞的情況select()
通過綁定某個
通常會建立一個結構如下:
的socket[參數1]
到某個動作[參數3]
上,将事件、行為與socket聯系起來某個事件[參數2]
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 );
- 查詢事件是否有信号—
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 );
- 有信号時就分類處理
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網絡模型之異步選擇模型(基于消息機制)