Windows Sockets網路程式設計(3)WSAEventSelect模型開發
摘要:WSAEventSelect模型是非阻塞的,該模型允許在一個或者多個套接字上接收以事件為基礎的網路事件通知。Windows Sockets應用程式在建立套接字後,呼叫WSAEventSelect()函式,將一個事件物件與網路事件集合關聯在一起。當網路事件發生時,應用程式以事件的形式接收網路事件通知。使用這個模型的基本思路是為感興趣的一組網路事件建立一個事件物件,再呼叫WSAEventSelect()函式將網路事件和事件物件關聯起來。當網路事件發生時,Winsock使相應的事件物件受信,在事件物件上的等待函式就會返回。WSAEventSelect模型簡單易用,也不需要視窗環境。該模型唯一的缺點是有最多等待64個事件物件的限制,當套接字連線數量增加時,就必須建立多個執行緒來處理I/O,也就是所謂的執行緒池。
目錄:
-------------------------------
- 建立TCP連結
- WSACreateEvent函式
- WSAWaitForMultipleEvents函式
- WSAEnumNetworkEvents函式
- 實踐1:WASEventSelect模型
- 實踐2:TCP Client
1.建立TCP連結
這裡不再贅述了,能來到本文的,相信基本功已經不用多講了。實在不明白的可以閱讀《Windows Sockets網路程式設計(0)TCP In Action》一文,該文詳細的敘述了TCP建立的整個過程。
SOCKET socket_listener = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(8086);
sin.sin_addr.S_un.S_addr = INADDR_ANY;
if (bind(socket_listener, (sockaddr*)&sin, sizeof(sin)) == SOCKET_ERROR){
closesocket(socket_listener);
return;
}
listen(socket_listener, SOMAXCONN);//SOMAXCONN = 5
2.WSACreateEvent函式
在呼叫WSAEventSelect函式之間,必須要先建立事件,否則沒法監聽。Windows事件物件有兩個狀態“已觸發”和“未觸發”,而事件物件的工作模式也有兩種“人工重設”以及“自動重設”。WSACreateEvent建立的事件——原始狀態是“未觸發態”,當事件到來時系統會將其置為已觸發態,工作模式是“人工重設”。
WSAEVENT eventArray[WSA_MAXIMUM_WAIT_EVENTS];//WSA_MAXIMUM_WAIT_EVENTS = 64
SOCKET sockArray[WSA_MAXIMUM_WAIT_EVENTS];
int nEventTotal = 0;
WSAEVENT event = WSACreateEvent();
WSAEventSelect(socket_listener, event, FD_ACCEPT | FD_CLOSE);
eventArray[nEventTotal] = event;
sockArray[nEventTotal] = socket_listener;
++nEventTotal;
另外,需要注意的是WSAResetEvent是將事件從“已觸發”態重置為“未觸發”狀態。而WSACloseEvent主要用來釋放事件物件所佔用的資源。
3.WSAWaitForMultipleEvents函式
該函式主要用來等待一個或者所有事件變為“已觸發”態。
WSAWaitForMultipleEvents(
_In_ DWORD
cEvents,
_In_reads_(cEvents)
const WSAEVENT FAR * lphEvents,
_In_ BOOL
fWaitAll,
_In_ DWORD
dwTimeout,
_In_ BOOL
fAlertable
);
lphEvents:事件控制程式碼存放處;
fWaitAll:有兩種狀態,TRUE時表示等待所有事件被觸發、FALSE時表示只要一個事件觸發,就繼續執行;
dwTimeout:超時時間,WSA_INFINITE表示無限等待;
fAlertable:該引數主要用於重疊I/O,此處總是設定為FALSE即可。
int nIndex = WSAWaitForMultipleEvents(nEventTotal, eventArray,FALSE,WSA_INFINITE,FALSE);
WSAWaitForMultipleEvents函式的返回值會指出——到底哪一個事件被觸發了(在設定為單一觸發的情況下)。這裡牽涉到一個巨集WSA_WAIT_EVENT_0(該巨集在winnt.h中其實是被定義為0的),一般來說where =
nIndex - WSA_WAIT_EVENT_0;使用其返回值減去WSA_WAIT_EVENT_0所得的值,則表示事件陣列中,具體被觸發的那個事件下標。當然,如果fWaitAll設定為TRUE的話,根本不需要計算返回值,因為該情況下,總是全部被觸發。
4.WSAEnumNetworkEvents函式
事件發生了,也知道具體是哪個Socket被觸發了。但是,為何被觸發了?這就需要該函式來確定了。
WSAEnumNetworkEvents(
_In_ SOCKET s,
_In_ WSAEVENT
hEventObject,
_Out_ LPWSANETWORKEVENTS lpNetworkEvents
);
s :被觸發的套接字控制程式碼;
hEventObject :需要被重置的Event(WSACreateEvent是“人工重置”的);
lpNetworkEvents:指向_WSANETWORKEVENTS 結構體指標,包含了到底是發生了何種網路事件,或者網路錯誤。
typedef struct
_WSANETWORKEVENTS {
long lNetworkEvents;
int iErrorCode[FD_MAX_EVENTS];
} WSANETWORKEVENTS,
FAR * LPWSANETWORKEVENTS;
lNetworkEvents:指示發生的網路事件。當一個事件物件成為“已觸發”狀態時,可能同時發生了多個網路事件。
iErrorCode:網路事件陣列。所以事件需要使用一個陣列來存放。
WSANETWORKEVENTSevent;
WSAEnumNetworkEvents(sockArray[nIndex], eventArray[nIndex], &event);
那麼,具體要如何判斷髮生了什麼自己關注的事件呢?
if (event.lNetworkEvents &FD_ACCEPT)
{
if (event.iErrorCode[FD_ACCEPT_BIT] == 0)
{
}
}
識別符號命名是有規則的,如果需要檢查某一事件是否發生了錯誤,在對應事件末尾追加“_BIT”即可。比如FD_ACCEPT網路事件所對應的錯誤位置即為FD_ACCEPT_BIT。假若iErrorCode[FD_ACCEPT_BIT] == 0,則表示未發生錯誤。
5.實踐1:WASEventSelect模型
#include <WinSock2.h>
#pragma comment(lib,"ws2_32.lib")
#include <stdio.h>
/*
Date |Change
-----------------------------
2017-7-24 |WSAEventSelect模型
*/
WSAEVENT eventArray[WSA_MAXIMUM_WAIT_EVENTS];//WSA_MAXIMUM_WAIT_EVENTS = 64
SOCKET sockArray[WSA_MAXIMUM_WAIT_EVENTS];
int nEventTotal = 0;
void wsa_event_select()
{
SOCKET sAccept = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(8086);
sin.sin_addr.S_un.S_addr = INADDR_ANY;
if (bind(sAccept, (sockaddr*)&sin, sizeof(sin)) == SOCKET_ERROR){
closesocket(sAccept);
return;
}
listen(sAccept, SOMAXCONN);//SOMAXCONN = 5
WSAEVENT event = WSACreateEvent();
WSAEventSelect(sAccept, event, FD_ACCEPT | FD_CLOSE);
eventArray[nEventTotal] = event;
sockArray[nEventTotal] = sAccept;
++nEventTotal;
while (true)
{
int nIndex = WSAWaitForMultipleEvents(nEventTotal, eventArray, FALSE, WSA_INFINITE, FALSE);
nIndex = nIndex - WSA_WAIT_EVENT_0;
if (nIndex == WSA_WAIT_FAILED || nIndex == WSA_WAIT_TIMEOUT)
{
continue;
}
else
{
WSANETWORKEVENTS event;
WSAEnumNetworkEvents(sockArray[nIndex], eventArray[nIndex], &event);
if (event.lNetworkEvents & FD_ACCEPT)
{
if (event.iErrorCode[FD_ACCEPT_BIT] == 0)
{
if (nEventTotal > WSA_MAXIMUM_WAIT_EVENTS)
{
continue;
}
SOCKET sClient = accept(sockArray[nIndex], NULL, NULL);
WSAEVENT event = WSACreateEvent();
WSAEventSelect(sClient, event, FD_READ | FD_CLOSE | FD_WRITE);
eventArray[nEventTotal] = event;
sockArray[nEventTotal] = sClient;
++nEventTotal;
printf("accept.\n");
}
}
else if (event.lNetworkEvents & FD_READ) // 處理FD_READ通知訊息
{
if (event.iErrorCode[FD_READ_BIT] == 0)
{
char data[1024];
int ret = recv(sockArray[nIndex], data, strlen(data), 0);
if (ret > 0)
{
data[ret] = '\0';
printf("recv: %s.\n",data);
send(sockArray[nIndex], data, strlen(data), 0);
}
}
}
else if (event.lNetworkEvents & FD_CLOSE)
{
if (event.iErrorCode[FD_CLOSE_BIT] == 0)
{
printf("close.\n");
closesocket(sockArray[nIndex]);
for (int j = nIndex; j < nEventTotal - 1; j++)
{
eventArray[j] = eventArray[j + 1];
sockArray[j] = sockArray[j + 1];
}
--nEventTotal;
}
}
else if (event.lNetworkEvents & FD_WRITE)
{
printf("send.\n");
char* data = "qingdujun";
send(sockArray[nIndex], data, sizeof(data)/sizeof(char), 0);
}
}
}
}
int main(int argc, char* argv[])
{
WSADATA wsa;
WSAStartup(MAKEWORD(2, 2), &wsa);
wsa_event_select();
WSACleanup();
return 0;
}
6.實踐2:TCP Client
#include <WinSock2.h>
#pragma comment(lib,"ws2_32.lib")
#include <stdio.h>
/*
Date |Change
-----------------------------------------
2017-7-24 |WSAEventSelect模型,TCP測試客戶端
*/
void tcp_client()
{
SOCKET sClient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(8086);
sin.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
if (connect(sClient, (sockaddr *)&sin, sizeof(sin)) == SOCKET_ERROR){
closesocket(sClient);
return;
}
char buffer[1024] = "abcdef";
send(sClient, buffer, strlen(buffer), 0);
int ret = recv(sClient, buffer, sizeof(buffer), 0);
buffer[ret] = '\0';
printf("%s.\n",buffer);
closesocket(sClient);
}
int main(int argc, char* argv[])
{
WSADATA wsa;
WSAStartup(MAKEWORD(2, 2), &wsa);
int i = 5;
while (i--){
tcp_client();
Sleep(2000);
}
WSACleanup();
return 0;
}
參考文獻:
[1] 孫海民. 精通Windows Sockets網路開發——基於Visual C++實現[M]. 北京:人民郵電出版社, 2008. 238-245
2017-7-25 in Xi'An
相關文章
- Java網路程式設計和NIO詳解3:IO模型與Java網路程式設計模型Java程式設計模型
- Windows Sockets錯誤碼Windows
- 網路程式設計-OSI模型程式設計模型
- 併發程式設計---JMM模型程式設計模型
- 網路程式設計之IO模型程式設計模型
- TCP/IP網路程式設計模型TCP程式設計模型
- 一文徹底搞定(阻塞/非阻塞/同步/非同步)網路IO、併發程式設計模型、非同步程式設計模型的愛恨情仇非同步程式設計模型
- 併發程式設計模型小結程式設計模型
- JVM併發程式設計模型覽JVM程式設計模型
- 【譯】闖入遊戲開發 #3:程式設計遊戲開發程式設計
- 自由程式設計師的3個開發技巧程式設計師
- Java程式設計開發之資料圖表分析模型Java程式設計模型
- kestrel網路程式設計--開發Fiddler程式設計
- 《Linux網路開發必學教程》6_Window 下的網路程式設計Linux程式設計
- 網路通訊程式設計程式設計
- 網路協程程式設計程式設計
- Socket 程式設計 (網路篇)程式設計
- py網路工具程式設計程式設計
- 計算機網路(一) --網路模型計算機網路模型
- 遊戲開發新手入門之Windows程式設計(轉)遊戲開發Windows程式設計
- windows程式設計師開發linux程式的頭一個月Windows程式設計師Linux
- 併發程式設計——IO模型詳解程式設計模型
- python3網路爬蟲開發實戰_Python 3開發網路爬蟲(一)Python爬蟲
- Windows的驅動開發模型Windows模型
- 《Linux網路開發必學教程》1_網路程式設計核心概念與模式Linux程式設計模式
- Java 程式設計開發Java程式設計
- 網路程式設計-Netty-Reactor模型程式設計NettyReact模型
- Linux網路程式設計之IO模型Linux程式設計模型
- Python網路Socket程式設計Python程式設計
- python 網路篇(網路程式設計)Python程式設計
- Socket程式設計模型程式設計模型
- 3-Windows程式設計 -視窗與訊息Windows程式設計
- kestrel網路程式設計--開發redis伺服器程式設計Redis伺服器
- iOS開發那些事-iOS網路程式設計同步GET方法請求程式設計iOS程式設計
- 基於.NetCore開發部落格專案 StarBlog - (3) 模型設計NetCore模型
- 系統程式設計-網路-tcp客戶端伺服器程式設計模型(續)、連線斷開、獲取連線狀態場景程式設計TCP客戶端伺服器模型
- Unrecognized Windows Sockets error: 0: JVM_Bind異常ZedWindowsErrorJVM
- windows核心程式設計--程式Windows程式設計