4.3.2异步选择模型
异步选择WSAAsyncSelect是Select模型的异步版本。在Select模型中,调用select()函数会发生阻塞;而WSAAsyncSelect模型在调用WSAAsyncSelect()函数时,它会通知系统感兴趣的网络事件,然后立即返回。
在前面,我们在windows下创建的都是控制台程序;本小节的代码则是windows应用程序。使用WSAAsyncSelect模型,必须在应用程序中创建一个窗口,并为窗口提供回调函数(窗口处理函数)。
//异步选择:TCP服务器端代码
#include <iostream>
#include <winsock.h>
#include <tchar.h>
using namespace std;
#pragma comment(lib, "ws2_32.lib")
#define WM_SOCKET WM_USER+1
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
//windows应用程序的入口函数:WinMain,其参数必须和声明保持一致;返回0表示正常退出
//参数1:当前实例的句柄;参数2:前一个实例的句柄;参数3:命令行参数;参数4:窗体显示形式(最大化、最小化)
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
{
static TCHAR szAppName[] = _T("AsyncSelect Model");
//步骤1:窗口类定义
WNDCLASS wndclass;//WNDCLASS结构体用来存储窗口信息
wndclass.style = CS_HREDRAW | CS_VREDRAW;//窗口的样式
wndclass.lpfnWndProc = WndProc;//定义窗口处理函数
wndclass.cbClsExtra = 0;
wndclass.cbWndExtra = 0;
wndclass.hInstance = hInstance;//当前实例句柄,由windows自动分发
wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);//窗口的最小化图标
wndclass.hCursor = LoadCursor(NULL, IDC_ARROW); // 窗口光标:采用箭头
wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); //窗口背景:白色
wndclass.lpszMenuName = NULL; //窗口无菜单
wndclass.lpszClassName = szAppName; //窗口类名
//步骤2:注册窗口
if (!RegisterClass(&wndclass))
{
MessageBox(NULL, TEXT("Registration Window Failed!"), szAppName, MB_ICONERROR);
return 0;
}
//步骤3:创建窗口
HWND hwnd;
hwnd = CreateWindow(szAppName, //窗口类名称
TEXT("AsyncSelect"), //窗口标题
WS_OVERLAPPEDWINDOW, //窗口风格,或称窗口格式
CW_USEDEFAULT, //窗口相对于父级的X坐标
CW_USEDEFAULT, //窗口相对于父级的Y坐标
CW_USEDEFAULT, //窗口的宽度
CW_USEDEFAULT, //窗口的高度
NULL, //没有父窗口,为NULL
NULL, //没有菜单,为NULL
hInstance, //当前应用程序的实例句柄
NULL); //没有附加数据,为NULL
//步骤4:显示窗口
ShowWindow(hwnd, iCmdShow);
//步骤5:更新窗口
UpdateWindow(hwnd);
//步骤6:从消息队列中,取出系统向应用程序发出的消息
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))//消息循环
{
TranslateMessage(&msg);//将消息转换为WM_CHAR消息
DispatchMessage(&msg);//把消息传到WindowProc
}
return 0;
}
//参数1:窗口句柄;参数2:消息ID;参数3/4:消息参数
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
static SOCKET sockListen;
SOCKET sockClient;
SOCKADDR_IN addrServer, addrClient;
int len = sizeof(addrClient);
char Buf[1024] = "\0";
int ret;
//步骤7:消息处理
switch (message)
{
//步骤7.1
case WM_CREATE://创建窗口时,发送WM_CREATE消息
WSADATA wsaData;
WSAStartup(0x0202, &wsaData);
sockListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
addrServer.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
addrServer.sin_family = AF_INET;
addrServer.sin_port = htons(6000);
bind(sockListen, (SOCKADDR *)&addrServer, sizeof(addrServer));
listen(sockListen, 3);
WSAAsyncSelect(sockListen, hwnd, WM_SOCKET, FD_ACCEPT);
return 0;
case WM_DESTROY://关闭应用程序
closesocket(sockListen);
WSACleanup();
PostQuitMessage(0);//提交WM_QUIT消息,GetMessage得到后返回0,因此退出消息循环
return 0;
case WM_SOCKET:
if (WSAGETSELECTERROR(lParam))
{
closesocket(wParam);
break;
}
switch (WSAGETSELECTEVENT(lParam))
{
//步骤7.2
case FD_ACCEPT://服务器接收连接的通知
sockClient = accept(wParam, (struct sockaddr *)&addrClient, &len);
WSAAsyncSelect(sockClient, hwnd, WM_SOCKET, FD_READ | FD_CLOSE);
break;
//步骤7.3
case FD_READ://套接字可读通知
ret = recv(wParam, Buf, 1024, 0);
//客户端和服务器端断开连接:ret == 0
if (ret == 0 || ret == SOCKET_ERROR)
{
closesocket(wParam);
}
else
{
//二次开发
cout << Buf << endl;
strcat(Buf, ":Server Received");
send(wParam, Buf, strlen(Buf)+1, 0);
}
break;
//步骤7.4
case FD_CLOSE://套接字关闭通知
closesocket(wParam);
break;
}
return 0;
}
//步骤8:缺省消息处理函数
return DefWindowProc(hwnd, message, wParam, lParam);
}
//TCP客户端
#include <iostream>
#include <winsock2.h>
#include <windows.h>
#pragma comment(lib,"ws2_32.lib")//引用库文件
using namespace std;
int main(int argc, char ** argv)
{
//步骤1:当前应用程序和相应的socket库绑定
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD(2, 2);
err = WSAStartup(wVersionRequested, &wsaData);
if (err != 0)
{
cout << "WSAStartup Failed!" << endl;
return -1;
}
//步骤2:创建TCP网络套接字
SOCKET sock = socket(AF_INET, SOCK_STREAM, 0);
//步骤3:设置服务器端地址结构SOCKADDR_IN
SOCKADDR_IN addr_server;
//3.1 初始化SOCKADDR_IN
memset(&addr_server, 0, sizeof(addr_server));
//3.2 设置地址协议族
addr_server.sin_family = AF_INET;
//3.3 设置服务器端的IP
addr_server.sin_addr.s_addr = inet_addr("192.168.1.101");
//3.4 设置服务器端的端口号
addr_server.sin_port = htons(6000);//不能使用公认端口,即端口>= 1024
//步骤4:客户端连接服务器
int result = connect(sock, (SOCKADDR*)&addr_server, sizeof(addr_server));
if (result == -1)
return -1;
cout << "Connect Seccessed!" << endl;
//步骤5:向服务器端发送数据
char Buf[1024] = "\0";
cin.getline(Buf, 1024);
send(sock, Buf, 1024, 0);
recv(sock, Buf, 1024, 0);
printf("%s\n", Buf);
//步骤6:关闭套接字
closesocket(sock);
//步骤7:将应用程序和socket库解除绑定
WSACleanup();
return 0;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam):
参数hwnd表示窗口的句柄;对于此函数的调用,正是由那个窗口发出的。
参数message表示需要对哪些消息进行处理。
参数wParam表示一个网络事件的套接字,若客户端发出连接请求,那它就表示服务器端的监听套接字;若客户端传输数据,它就表示客户端的套接字。即相当于选择模式中的FD_SET。
参数lParam包含两方面信息,高字节包含代码的错误信息,可以使用WSAGETSELECTERROR来获取;低字节表示已经发生的网络事件,可以用WSAGETSELECTEVENT来获取。
int PASCAL FAR WSAAsyncSelect(_In_ SOCKET s,_In_ HWND hWnd,_In_ u_int wMsg,_In_ long lEvent):
参数s表示是否有数据传输的套接字,立即返回。
参数hWnd指定一个窗口句柄,它对应于网络事件发生之后,想要收到通知消息的那个窗口。
参数wMsg指定在发生网络事件时,打算接收的消息,该消息会投递到由hWnd窗口句柄指定的那个窗口。
参数lEvent指定一个位掩码,对应于一系列网络事件的组合,大多数应用程序通常感兴趣的网络事件类型包括:FD_READ、FD_WRITE、FD_ACCEPT、FD_CONNECT、FD_CLOSE。