一、WSAAsyncSelect模型介绍

利用WSAAsyncSelect模型结合windows窗口消息循环,应用程序可以在一个套接字上接收以Windows消息为基础的网络事件通知。要想使用WSAAsyncSelect模型,首先必须创建一个Windows窗口,并为该窗口提供一个窗口过程支持函数。

1
2
3
4
5
6
int WSAAsyncSelect(
_In_ SOCKET s,
_In_ HWND hWnd,
_In_ unsigned int wMsg,
_In_ long lEvent
);

s 指定我们感兴趣的套接字。
hWnd 指定一个窗口句柄,它表示网络事件发生之后,收到消息通知的那个窗口。
wMsg 表示在网络事件发生时,窗口收到的消息。
lEvent 代表一个掩码,指定应用程序感兴趣的网络事件的组合。事件类型包括:FD_READ,FD_WRITE,FD_CLOSE,FD_CONNECT,FD_ACCEPT。对于FD_CONNECT,FD_ACCEPT,服务端一般使用FD_ACCEPT,客户端一般使用FD_CONNECT。若将lEvent设置为0,则表示停止接收该套接字上的所有网络事件通知。

一旦调用WSAAsyncSelect在套接字上启用了事件通知,除非明确调用closesocket,或者再次调用WSAAsyncSelect重新设置网络事件类型,否则,事件通知总是有效的。

WSAAsyncSelect模型需要结合windows窗口来使用,不适用于没有界面的服务程序。建议使用WSAEventSelect模型来代替。

二、示例

因为重点不在界面编程上面,所以示例中使用TraceMsgA打印信息到visual studio的输出窗口或debugview。
InitWindow函数用于创建一个简单的、空白的窗口,将句柄存储在全局变量g_hwnd中。
WndProc为窗口的处理过程函数。

服务端在有新的客户端连接上时,给客户端发送“hello, I’m server.”消息,在服务端退出时,关闭所有客户端连接。
客户端连接上服务端之后,接收服务端的消息。

2.1 服务端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
#include <winsock2.h>
#include <vector>
#include <strsafe.h>

#pragma comment(lib, "Ws2_32.lib")

const u_short kPort = 10001;
const std::string kHelloServer = "hello, I'm server.";

#define WUM_SOCKET (WM_USER+1)

HWND g_hwnd;

SOCKET socket_srv = INVALID_SOCKET;
std::vector<SOCKET> clients;


void TraceMsgA(const char *lpFormat, ...) {
if (!lpFormat)
return;

char *pMsgBuffer = NULL;
unsigned int iMsgBufCount = 0;

va_list arglist;
va_start(arglist, lpFormat);
HRESULT hr = STRSAFE_E_INSUFFICIENT_BUFFER;
while (hr == STRSAFE_E_INSUFFICIENT_BUFFER) {
iMsgBufCount += 1024;
if (pMsgBuffer) {
free(pMsgBuffer);
pMsgBuffer = NULL;
}
pMsgBuffer = (char*)malloc(iMsgBufCount * sizeof(char));
if (!pMsgBuffer) {
break;
}

hr = StringCchVPrintfA(pMsgBuffer, iMsgBufCount, lpFormat, arglist);
}
va_end(arglist);
if (hr == S_OK) {
OutputDebugStringA(pMsgBuffer);
}

if (pMsgBuffer) {
free(pMsgBuffer);
pMsgBuffer = NULL;
}
}


LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
int err;
SOCKET s;
char buf[100] = { 0 };

switch (message) {
// (5)
case WUM_SOCKET:
if (WSAGETSELECTERROR(lParam)) {
closesocket((SOCKET)wParam);
break;
}

switch (WSAGETSELECTEVENT(lParam))
{
case FD_ACCEPT:
s = accept((SOCKET)wParam, NULL, NULL);

clients.push_back(s);

WSAAsyncSelect(s, g_hwnd, WUM_SOCKET, FD_READ | FD_WRITE | FD_CLOSE);

TraceMsgA("new connection\n");

err = send(s, (const char*)kHelloServer.c_str(), kHelloServer.length(), 0);
if (err == SOCKET_ERROR) {
TraceMsgA("send failed, GLE: %d\n", WSAGetLastError());
break;
}
break;
case FD_READ:
err = recv((SOCKET)wParam, buf, 100, 0);
if (err > 0) {
TraceMsgA("recv: %s\n", buf);
}
else if (err == 0) {
TraceMsgA("connection closed\n");
closesocket((SOCKET)wParam);
}
else {
TraceMsgA("recv failed, GLE: %d\n", WSAGetLastError());
closesocket((SOCKET)wParam);
}
break;
case FD_WRITE:
break;
case FD_CLOSE:
closesocket((SOCKET)wParam);

for (std::vector<SOCKET>::iterator it = clients.begin(); it != clients.end(); it++) {
if (*it == (SOCKET)wParam) {
clients.erase(it);
break;
}
}

break;
}

break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}


void InitWindow(HINSTANCE hInstance) {
WCHAR szWindowClass[100] = L"Test";

WNDCLASSEX wcex = { sizeof(WNDCLASSEX) };

wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.lpszClassName = szWindowClass;

RegisterClassExW(&wcex);

g_hwnd = CreateWindowW(szWindowClass, L"server", WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);

if (g_hwnd) {
ShowWindow(g_hwnd, SW_SHOW);
UpdateWindow(g_hwnd);
}
}

int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPWSTR lpCmdLine,
_In_ int nCmdShow)
{
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);

InitWindow(hInstance);

WSADATA wsaData;
WORD wVersionRequested = MAKEWORD(2, 2);
WSAStartup(wVersionRequested, &wsaData);


do
{
// (1)
socket_srv = ::socket(AF_INET, SOCK_STREAM, 0);
if (socket_srv == INVALID_SOCKET) {
TraceMsgA("create socket failed, GLE: %d\n", WSAGetLastError());
break;
}

// (2)
struct sockaddr_in addr = { 0 };
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons(kPort);
if (bind(socket_srv, reinterpret_cast<const sockaddr*>(&addr), sizeof(addr)) == SOCKET_ERROR) {
TraceMsgA("bind failed, GLE: %d\n", WSAGetLastError());
break;
}

// (3)
WSAAsyncSelect(socket_srv, g_hwnd, WUM_SOCKET, FD_ACCEPT | FD_READ | FD_WRITE | FD_CLOSE);

// (4)
if (listen(socket_srv, 5) == SOCKET_ERROR) {
TraceMsgA("listen failed, GLE: %d\n", WSAGetLastError());
break;
}
TraceMsgA("listen on port: %d\n", kPort);

} while (false);


MSG msg;
while (GetMessage(&msg, nullptr, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}

for (std::vector<SOCKET>::iterator it = clients.begin(); it != clients.end(); it++) {
closesocket(*it);
}

// (6)
closesocket(socket_srv);

WSACleanup();
return 0;
}

2.2 客户端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
#include <winsock2.h>
#include <vector>
#include <strsafe.h>

#pragma comment(lib, "Ws2_32.lib")

const std::string kIP = "127.0.0.1";
const u_short kPort = 10001;


#define WUM_SOCKET (WM_USER+1)

HWND g_hwnd;

void TraceMsgA(const char *lpFormat, ...) {
if (!lpFormat)
return;

char *pMsgBuffer = NULL;
unsigned int iMsgBufCount = 0;

va_list arglist;
va_start(arglist, lpFormat);
HRESULT hr = STRSAFE_E_INSUFFICIENT_BUFFER;
while (hr == STRSAFE_E_INSUFFICIENT_BUFFER) {
iMsgBufCount += 1024;
if (pMsgBuffer) {
free(pMsgBuffer);
pMsgBuffer = NULL;
}
pMsgBuffer = (char*)malloc(iMsgBufCount * sizeof(char));
if (!pMsgBuffer) {
break;
}

hr = StringCchVPrintfA(pMsgBuffer, iMsgBufCount, lpFormat, arglist);
}
va_end(arglist);
if (hr == S_OK) {
OutputDebugStringA(pMsgBuffer);
}

if (pMsgBuffer) {
free(pMsgBuffer);
pMsgBuffer = NULL;
}
}


LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
int err;
SOCKET s;
char buf[100] = { 0 };

switch (message) {
// (4)
case WUM_SOCKET:
if (WSAGETSELECTERROR(lParam)) {
closesocket((SOCKET)wParam);
TraceMsgA("connect failed, %d\n", WSAGETSELECTERROR(lParam));
break;
}

switch (WSAGETSELECTEVENT(lParam))
{
case FD_CONNECT:
TraceMsgA("connection to server\n");
break;
case FD_READ:
err = recv((SOCKET)wParam, buf, 100, 0);
if (err > 0) {
TraceMsgA("recv: %s\n", buf);
}
else if (err == 0) {
closesocket((SOCKET)wParam);
TraceMsgA("connection closed\n");
}
else {
closesocket((SOCKET)wParam);
TraceMsgA("recv failed, GLE: %d\n", WSAGetLastError());
}
break;
case FD_WRITE:
break;
case FD_CLOSE:
closesocket((SOCKET)wParam);
TraceMsgA("connection closed\n");
break;
}
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}


void InitWindow(HINSTANCE hInstance) {
WCHAR szWindowClass[100] = L"Test";

WNDCLASSEX wcex = { sizeof(WNDCLASSEX) };

wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.lpszClassName = szWindowClass;

RegisterClassExW(&wcex);

g_hwnd = CreateWindowW(szWindowClass, L"client", WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);

if (g_hwnd) {
ShowWindow(g_hwnd, SW_SHOW);
UpdateWindow(g_hwnd);
}
}

int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPWSTR lpCmdLine,
_In_ int nCmdShow)
{
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);

InitWindow(hInstance);

WSADATA wsaData;
WORD wVersionRequested = MAKEWORD(2, 2);
WSAStartup(wVersionRequested, &wsaData);

SOCKET socket_ = INVALID_SOCKET;

do
{
// (1)
socket_ = ::socket(AF_INET, SOCK_STREAM, 0);
if (socket_ == INVALID_SOCKET) {
TraceMsgA("create socket failed, GLE: %d\n", WSAGetLastError());
break;
}

// (2)
WSAAsyncSelect(socket_, g_hwnd, WUM_SOCKET, FD_CONNECT | FD_READ | FD_WRITE | FD_CLOSE);

// (3)
struct sockaddr_in addr = { 0 };
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr(kIP.c_str());
addr.sin_port = htons(kPort);
if (connect(socket_, reinterpret_cast<const sockaddr*>(&addr), sizeof(addr)) == SOCKET_ERROR) {
int gle_err = WSAGetLastError();
if (gle_err != WSAEWOULDBLOCK) {
TraceMsgA("connect failed, GLE: %d\n", WSAGetLastError());
break;
}
}
} while (false);


MSG msg;
while (GetMessage(&msg, nullptr, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}

// (5)
closesocket(socket_);

WSACleanup();
return 0;
}

代码下载地址: https://github.com/winsoft666/CodeSnippet/tree/main/WSAAsyncSelect-Sample