【网络程序设计】WSAASYNCSELECT I / O模型

84 阅读5分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第7天,点击查看活动详情


相关原理

1.基于windows消息为基础的网络事件io模型。因此我们必须要在窗口程序中使用该模型。该模型中的核心是调用WSAAsyncSelect函数实现异步I/O。

2.若应用程序针对一个套接字调用了WSAAsyncSelect,那么套接字的模式会从阻塞自动变成非阻塞,这样一来,假如调用了像WSARecv这样的Winsock I/O函数,但当时却并没有数据可用,那么必然会造成调用的失败,并返回WSAEWOULDBLOCK错误。为防止这一点,应用程序应依赖于由WSAAsyncSelect的uMsg参数指定的用户自定义窗口消息,来判断网络事件类型何时在套接字上发生;而不应盲目地进行调用。

WSAAsyncSelect函数:注册网络事件函数  
int WSAAsyncSelect(  
    SOCKET s,//  
    HWND hWnd,//  
    unsigned int wMsg,//注意,该消息值应该大于WM_USER(1024)  
    long lEvent,//网络事件  
    );  

3.网络事件:
(1)FD_READ:读数据
(2)FD_WRITE:写数据
(3)FD_ACCEPT:接收连接
(4)FD_CONNECT:连接
(5)FD_CLOSE:关闭
各个事件可以进行或运算。特别要注意的是,多个事件务必在套接字上一次注册!另外还要注意的是,一旦在某个套接字上允许了事件通知,那么以后除非明确调用closesocket命令,或者由应用程序针对那个套接字调用了WSAAsyncSelect,从而更改了注册套接字的网络事件类型,否则的话,事件通知会永远有效!若将lEvent参数设为0,效果相当于停止在套接字上进行的所有网络事件通知。

4.在异步选择模型中,还需要自定义窗口消息处理函数。来对各种网络事件进行处理。该函数定义和win32窗口中的窗口消息处理函数相同。原型如下:

LRESULT CALLBACK WindowProc(  
    HWND hWnd,//  
    UINT uMsg,//  
    WPARAM wParam,//  
    LPARAM lParam  
    )

参数lParam的低字(低位字)指定了已经发生的网络事件,而lParam的高字(高位字)包含了可能出现的任何错误代码。
网络事件消息抵达一个窗口例程后,应用程序首先应检查lParam的高字位,以判断是否在套接字上发生了一个网络错误。这里有一个特殊的宏WSAGETSELECTERROR,可用它返回高字位包含的错误信息。若套接字上没有产生任何错误,接着便应判断到底是哪个网络事件发生。具体的做法便是读取lParam低字节的内容。此时可使用另一个特殊的宏:WSAGETSELECTEVENT,用它返回lParam低字节的内容。

5.最后一个特别有价值的问题是应用程序如何对FD_WRITE事件进行处理。只有在三
种条件下,才会发出FD_WRITE通知:
(1)使用connect或WSAConnect,一个套接字首次建立了连接。
(2)使用accept或WSAAccept,套接字被接受以后。
(3)若send、WSASend、sendto或WSASendto操作失败,返回了WSAWOULDBLOCK错误,而且缓冲区的空间变得可用
因此,作为一个应用程序,自收到首条FD_WRITE消息开始,便应认为自己必然能在一个套接字上发出数据,直至一个send、WSASend、sendto或WSASendto返回套接字错误WSAWOULDBLOCK。经过了这样的失败以后,要再用另一条FD_WRITE通知应用程序再次发送数据。

6.WSAAsyncSelect模型

允许应用程序以windows 消息的形式接受网络事件通知,这个模型是为了适应windows的消息驱动环境而设置的,限制许多对性能要求不高的网络应用程序都采用WSAAsyncSelect 模型,MFC中的CSocket也是使用它

WSAAsyncSelect  函数自动把套接字设为 非阻塞模式,并且为套接字绑定一个窗口句柄,当有网络事件发生时,便向这个窗口发送消息

image.png

LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

int main()

{

char szClassName[] = "MainWClass";

WNDCLASSEX wndclass;

// 用描述主窗口的参数填充WNDCLASSEX结构

wndclass.cbSize = sizeof(wndclass);

wndclass.style = CS_HREDRAW|CS_VREDRAW;

wndclass.lpfnWndProc = WindowProc;

wndclass.cbClsExtra = 0;

wndclass.cbWndExtra = 0;

wndclass.hInstance = NULL;

wndclass.hIcon = ::LoadIcon(NULL, IDI_APPLICATION);

wndclass.hCursor = ::LoadCursor(NULL, IDC_ARROW);

wndclass.hbrBackground = (HBRUSH)::GetStockObject(WHITE_BRUSH);

wndclass.lpszMenuName = NULL;

wndclass.lpszClassName = szClassName ;

wndclass.hIconSm = NULL;

::RegisterClassEx(&wndclass);

// 创建主窗口

HWND hWnd = ::CreateWindowEx(

0,

szClassName,

"",

WS_OVERLAPPEDWINDOW,

CW_USEDEFAULT,

CW_USEDEFAULT,

CW_USEDEFAULT,

CW_USEDEFAULT,

NULL,

NULL,

NULL,

NULL);

if(hWnd == NULL)

{

::MessageBox(NULL, "创建窗口出错!", "error", MB_OK);

return -1;

}

 

USHORT nPort = 4567; // 此服务器监听的端口号

 

// 创建监听套节字

SOCKET sListen = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

sockaddr_in sin;

sin.sin_family = AF_INET;

sin.sin_port = htons(nPort);

sin.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");

// 绑定套节字到本地机器

if(::bind(sListen, (sockaddr*)&sin, sizeof(sin)) == SOCKET_ERROR)

{

printf(" Failed bind() \n");

return -1;

}

 

// 将套接字设为窗口通知消息类型。

::WSAAsyncSelect(sListen, hWnd, WM_SOCKET, FD_ACCEPT|FD_CLOSE);

 

// 进入监听模式

::listen(sListen, 5);

 

// 从消息队列中取出消息

MSG msg;

while(::GetMessage(&msg, NULL, 0, 0))

{

// 转化键盘消息

::TranslateMessage(&msg);

// 将消息发送到相应的窗口函数

::DispatchMessage(&msg);

}

// 当GetMessage返回0时程序结束

return msg.wParam;

}

 

 

LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)

{

switch (uMsg)

{

case WM_SOCKET:

{

// 取得有事件发生的套节字句柄

SOCKET s = wParam;

int Netevent=WSAGETSELECTEVENT(lParam);

printf("发生的消息为:%d\n", uMsg);

printf("发生网络事件的套接字为:%d\n", s);

printf("发生的网络事件为:%d\n", Netevent);

// 查看是否出错

if(WSAGETSELECTERROR(lParam))

{

::closesocket(s);

return 0;

}

// 处理发生的事件

switch(WSAGETSELECTEVENT(lParam))

{  

case FD_ACCEPT: // 监听中的套接字检测到有连接进入

{

//int Netevent;

//Netevent=WSAGETSELECTEVENT(lParam);

//printf("发生的网络事件为:%d\n", Netevent);

SOCKET client = ::accept(s, NULL, NULL);

::WSAAsyncSelect(client, hWnd, WM_SOCKET, FD_READ|FD_WRITE|FD_CLOSE);

}

break;

case FD_WRITE:

{

 

}

break;

case FD_READ:

{

char szText[1024] = { 0 };

if(::recv(s, szText, 1024, 0) == -1)

::closesocket(s);

else

printf("接收数据:%s\n", szText);

}

break;

case FD_CLOSE:

{

::closesocket(s);

}

break;

}

}

return 0;

case WM_DESTROY:

::PostQuitMessage(0) ;

return 0 ;

}

 

// 将我们不处理的消息交给系统做默认处理

return ::DefWindowProc(hWnd, uMsg, wParam, lParam);

}