异步选择
异步选择(WSAAsyncSelect)模型是一个有用的异步 I/O 模型。利用这个模型,应用程序可在一个套接字上,接收以 Windows 消息为基础的网络事件通知。具体的做法是在建好一个套接字后,调用WSAAsyncSelect函数。
WSAAsyncSelect模型是Select模型的异步版本,在调用select()函数时,会发生阻塞现象。可以通过select()函数timeout参数,设置函数调用的阻塞时间。在设定的时间内,线程保持等待,直到其中一个或多个套接字满足可读可写的条件时,该函数返回。
该模型的核心即是WSAAsyncSelect函数,该函数是非阻塞的。
要想使用 WSAAsyncSelect 模型,在应用程序中,必须有一个窗口,且该窗口有一个窗口例程函数(WinProc)
与select模型比较
相同点:他们都可以对Windows套接字应用程序所使用的多个套接字进行有效的管理。
不同点: 1.WSAAsyncSelect模型是异步的。在应用程序中调用WSAAsyncSelect()函数,通知系统感兴趣的网络事件,该函数立即返回,应用程序继续执行;
2.发生网络事件时,应用程序得到的通知方式不同。Select()函数返回时,说明某个或者某些套接字满足可读可写的条件,应用程序需要使用FD_ISSET宏,判断套接字是否存在可读可写集合中。而对于WSAAsyncSelect模型来说,当网络事件发生时,系统向应用程序发送消息。
3.WSAAsyncSelect模型应用在基于消息的Windos环境下,使用该模型时必须创建窗口。而Select模型广泛应用在Unix系统和Windows系统,使用该模型不需要创建窗口。
4.应用程序调用WSAAsyncSelect()函数后,自动将套接字设置为非阻塞模式。而应用程序中调用select()函数后,并不能改变套接字的工作方式
WSAAsyncSelect 的函数原型
int WSAAsyncSelect( __in SOCKET s, __in HWND hWnd, __in unsigned int wMsg, __in long lEvent );
● s 参数指定的是我们感兴趣的那个套接字。
● hWnd 参数指定一个窗口句柄,它对应于网络事件发生之后,想要收到通知消息的那个窗口。
● wMsg 参数指定在发生网络事件时,打算接收的消息。该消息会投递到由hWnd窗口句柄指定的那个窗口。(通常,应用程序需要将这个消息设为比Windows的WM_USER大的一个值,避免网络窗口消息与系统预定义的标准窗口消息发生混淆与冲突) ● lEvent 参数指定一个位掩码,对应于一系列网络事件的组合,大多数应用程序通常感兴趣的网络事件类型包括: FD_READ、FD_WRITE、FD_ACCEPT、FD_CONNECT、FD_CLOSE。当然,到底使用FD_ACCEPT,还是使用FD_CONNECT类型,要取决于应用程序的身份是客户端,还是服务器。
如应用程序同时对多个网络事件有兴趣,只需对各种类型执行一次简单的按位OR(或)运算,然后将它们分配给lEvent就可以了,例如: WSAAsyncSeltct(s, hwnd, WM_SOCKET, FD_CONNECT | FD_READ | FD_WRITE | FD_CLOSE);
解释说明:我们的应用程序以后便可在套接字s上,接收到有关连接、发送、接收以及套接字关闭这一系列网络事件的通知。 FD_READ 应用程序想要接收有关是否可读的通知,以便读入数据
FD_WRITE 应用程序想要接收有关是否可写的通知,以便写入数据
FD_ACCEPT 应用程序想接收与进入连接有关的通知
FD_CONNECT 应用程序想接收与一次连接完成的通知
FD_CLOSE 应用程序想接收与套接字关闭的通知
注意 ①: 多个事件务必在套接字上一次注册! 另外还要注意的是,一旦在某个套接字上允许了事件通知,那么以后除非明确调用closesocket命令,或者由应用程序针对那个套接字调用了WSAAsyncSelect,从而更改了注册的网络事件类型,否则的话,事件通知会永远有效!若将lEvent参数设为0,效果相当于停止在套接字上进行的所有网络事件通知。
注意 ②: 若应用程序针对一个套接字调用了WSAAsyncSelect,那么套接字的模式会从“锁定”变成“非锁定”。这样一来,如果调用了像WSARecv这样的Winsock函数,但当时却并没有数据可用,那么必然会造成调用的失败,并返回WSAEWOULDBLOCK错误。为防止这一点,应用程序应依赖于由WSAAsyncSelect的uMsg参数指定的用户自定义窗口消息,来判断网络事件类型何时在套接字上发生;而不应盲目地进行调用。
注意 ③:应用程序如何对 FD_WRITE 事件通知进行处理。 只有在三种条件下,才会发出 FD_WRITE 通知:
■ 使用 connect 或 WSAConnect,一个套接字首次建立了连接。
■ 使用 accept 或 WSAAccept,套接字被接受以后。
■ 若 send、WSASend、sendto 或 WSASendTo 操作失败,返回WSAEWOULDBLOCK 错误,而且缓冲区的空间变得可用。 因此,作为一个应用程序,自收到首条 FD_WRITE 消息开始,便应认为自己必然能在一个套接字上发出数据,直至一个send、WSASend、sendto 或 WSASendTo 返回套接字错误 WSAEWOULDBLOCK。经过了这样的失败以后,要再用另一条 FD_WRITE 通知应用程序再次发送数据。
应用程序在一个套接字上成功调用了WSAAsyncSelect之后,会在与hWnd窗口句柄对应的窗口例程中,以Windows消息的形式,接收网络事件通知。
窗口例程通常定义如下: LRESULT CALLBACK WindowProc( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam );
● hWnd 参数指定一个窗口的句柄,对窗口例程的调用正是由那个窗口发出的。
● uMsg 参数指定需要对哪些消息进行处理。这里我们感兴趣的是WSAAsyncSelect调用中定义的消息。
● wParam 参数指定在其上面发生了一个网络事件的套接字。假若同时为这个窗口例程分配了多个套接字,这个参数的重要性便显示出来了。
● lParam参数中,包含了两方面重要的信息。其中, lParam的低字(低位字)指定了已经发生的网络事件,而lParam的高字(高位字)包含了可能出现的任何错误代码。
步骤:
网络事件消息抵达一个窗口例程后,应用程序首先应检查lParam的高字位,以判断是否在网络错误。
这里有一个特殊的宏: WSAGETSELECTERROR,可用它返回高字位包含的错误信息。 若应用程序发现套接字上没有产生任何错误,接着便应调查到底是哪个网络事件类型,具体的做法便是读取lParam低字位的内容。
此时可使用另一个特殊的宏:WSAGETSELECTEVENT,用它返回lParam的低字部分。
WSAAsyncSelect模型的优势和不足
优势:
1.该模型的使用方便了在基于消息的Windows环境下开发套接字的应用程序。开发人员可以像处理其他消息一样对网络事件消息进行处理。
2.该模型确保接收所有数据提供了很好的机制。通过注册FD_CLOSE网络事件,从容关闭服务器与客户端的连接保证了数据全部接收。
不足:
1.该模型局限在,他基于Windows的消息机制,必须在应用程序中创建窗口。当然,在开发中可以根据具体情况是否显示该窗口。MFC的CSocketWnd类就是用来创建一个不显示的窗口,并在该类中声明接收网络事件消息处理函数。
2.由于调用WSAAsyncSelect()函数后,自动将套接字设置为非阻塞状态。当应用程序为接收到网络事件调用相应函数时,未必能够成功返回。这无疑增加了开发人员使用该模型的难度。对于这一点可以从MFC CSocket类的Accept()、Receive()和Send()函数的实现得到验证。
代码示例
// MyWnd.cpp : 实现文件
//
#include "stdafx.h"
#include "0809Feiq.h"
#include "MyWnd.h"
// CMyWnd
IMPLEMENT_DYNCREATE(CMyWnd, CFrameWnd)
CMyWnd* CMyWnd::m_pWnd = new CMyWnd;
CMyWnd::CMyWnd()
{
}
CMyWnd::~CMyWnd()
{
}
BEGIN_MESSAGE_MAP(CMyWnd, CFrameWnd)
ON_MESSAGE(NM_READMSG,&CMyWnd::ReadMsg )
END_MESSAGE_MAP()
// CMyWnd 消息处理程序
//recvfrom
LRESULT CMyWnd::ReadMsg(WPARAM w,LPARAM l)
{
char szbuf[_DEF_SIZE] = {0};
sockaddr_in addrClient;
int nSize = sizeof(addrClient);
int nRelRecvNum;
//WPARAM -- 发生网络事件的socket
SOCKET sock = (SOCKET)w;
//LPARAM -- 低位发生的网络事件
switch (LOWORD(l))
{
case FD_READ:
{
nRelRecvNum = recvfrom(sock,szbuf,_DEF_SIZE,0,(sockaddr*)&addrClient,&nSize);
if(nRelRecvNum >0)
{
//交给中介者去处理
theApp.GetMediator()->DealData(szbuf,addrClient.sin_addr.S_un.S_addr);
}
}
break;
case FD_WRITE:
break;
default:
break;
}
return 0;
}
#include "stdafx.h"
#include "UDPNet.h"
UDPNet::UDPNet(IMediator *pMediator)
{
m_sockListen = NULL;
m_hThread = NULL;
m_bFlagQuit = true;
m_pMediator = pMediator;
}
UDPNet::~UDPNet()
{}
bool UDPNet::InitNetWork()
{
WORD wVersionRequested;
WSADATA wsaData;
int err;
/* Use the MAKEWORD(lowbyte, highbyte) macro declared in Windef.h */
wVersionRequested = MAKEWORD(2, 2);
err = WSAStartup(wVersionRequested, &wsaData);
if (err != 0) {
/* Tell the user that we could not find a usable */
/* Winsock DLL. */
return false;
}
if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) {
UnInitNetWork();
return false;
}
//2.雇个人 -- 创建套接字(与外界通信接口)-
m_sockListen = socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
m_sockbak = socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
if(m_sockListen == INVALID_SOCKET || INVALID_SOCKET == m_sockbak)
{
UnInitNetWork();
return false;
}
//改变socket 广播属性
BOOL bval = TRUE;
setsockopt(m_sockListen,SOL_SOCKET,SO_BROADCAST,(const char*)&bval,sizeof(bval));
//改变socket 属性
u_long argp = true;
ioctlsocket(m_sockListen,FIONBIO,&argp);
ioctlsocket(m_sockbak,FIONBIO,&argp);
//3.选个地方 -- 绑定--(IP,端口号)
sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(_DEF_PORT);
addr.sin_addr.S_un.S_addr =INADDR_ANY /*inet_addr("127.0.0.1")*/;
if(SOCKET_ERROR == bind(m_sockListen,(const sockaddr *)&addr,sizeof(addr)))
{
UnInitNetWork();
return false;
}
sockaddr_in addrbak;
addrbak.sin_family = AF_INET;
addrbak.sin_port = htons(1245);
addrbak.sin_addr.S_un.S_addr =INADDR_ANY /*inet_addr("127.0.0.1")*/;
if(SOCKET_ERROR == bind(m_sockbak,(const sockaddr *)&addrbak,sizeof(addrbak)))
{
UnInitNetWork();
return false;
}
//创建窗口 ---处理消息
if(!CMyWnd::GetMyWnd()->Create(NULL,"MyWnd"))
{
UnInitNetWork();
return false;
}
//向Windows注册
WSAAsyncSelect(m_sockListen,CMyWnd::GetMyWnd()->m_hWnd,NM_READMSG,FD_READ|FD_WRITE);
WSAAsyncSelect(m_sockbak,CMyWnd::GetMyWnd()->m_hWnd,NM_READMSG,FD_READ|FD_WRITE);
//recvfrom --创建线程
//m_hThread = (HANDLE)_beginthreadex(NULL,0,&ThreadProc,this,0,0);
return true;
}
unsigned __stdcall UDPNet::ThreadProc( void * lpvoid)
{
// UDPNet *pthis = (UDPNet*)lpvoid;
// char szbuf[_DEF_SIZE] = {0};
// sockaddr_in addrClient;
// int nSize = sizeof(addrClient);
// int nRelRecvNum;
// fd_set fdReadsets;
//timeval tv;
//tv.tv_sec = 0;
//tv.tv_usec = 100;
//
//
// while(pthis->m_bFlagQuit)
// {
// fdReadsets = pthis->fdsets;
// select(NULL,&fdReadsets,NULL,NULL,&tv);
// int i = 0;
// while(i < pthis->fdsets.fd_count)
// {
// if(FD_ISSET(pthis->fdsets.fd_array[i],&fdReadsets))
// {
// nRelRecvNum = recvfrom(pthis->fdsets.fd_array[i],szbuf,_DEF_SIZE,0,(sockaddr*)&addrClient,&nSize);
//
// if(nRelRecvNum >0)
// {
// //交给中介者去处理
// pthis->m_pMediator->DealData(szbuf,addrClient.sin_addr.S_un.S_addr);
// }
// }
// i++;
// }
//
//
//
//
//
// }
return 0;
}
//bool UDPNet::SelectSocket()
//{
//
//
//
// if(!FD_ISSET(sock,&fdsets))
// return false;
//
// return true;
//
//}
void UDPNet::UnInitNetWork()
{
m_bFlagQuit = false;
if(m_hThread)
{
if(WAIT_TIMEOUT == WaitForSingleObject(m_hThread,100))
TerminateThread(m_hThread,-1);
CloseHandle(m_hThread);
m_hThread = NULL;
}
WSACleanup();
if(m_sockListen)
{
closesocket(m_sockListen);
m_sockListen = NULL;
}
if(m_sockbak)
{
closesocket(m_sockbak);
m_sockbak = NULL;
}
CMyWnd::DeleteMyWnd();
}
bool UDPNet::SendData(long lSendIp,char *szbuf,int nlen)
{
sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(_DEF_PORT);
addr.sin_addr.S_un.S_addr = lSendIp;
if(!szbuf || nlen <=0)
return false;
if(sendto(m_sockListen,szbuf,nlen,0,(const sockaddr*)&addr,sizeof(addr))<=0)
return false;
return true;
}