首先,我想说明一下,这篇文章是我在阅读《精通Windows Sockets 网络开发——基于Visual C++》(孙海民 编著,人民邮电出版社出版)这本书的时候,基于这本书归纳总结的一些关于网络编程中WSAAsyncSelect模型的知识。个人觉得这是一本很好的关于Windows Sockets 网络编程的书籍,对网络编程感兴趣或者有项目需要参考书的话,这本书是一个不错的选择。特别说明:本文中的大部分内容均摘录自原书,版权归原作者! 转载请注明出处!
PS:这是本人第一次在CSND上写博客,由于水平有限,还请各位大神批评指正。
一、WSAAsyncSelect模型介绍
WSAAsyncSelect模型是Windows Sockets的一个异步I/O模型。应用程序可以用它在一个套接字上接收以Windows消息为基础的网络事件。应用程序创建套接字后,调用WSAAsyncSelect()函数注册感兴趣的网络事件,当事件发生时,windows窗口接收到消息,然后程序就可以对收到的网络事件进行处理。
Select模型是阻塞的,而WSAAsyncSelect模型是非阻塞的。应用程序在调用recv()函数接收数据之前,调用WSAAsyncSelect()函数注册网络事件。WSAAsyncSelect()函数立即返回,线程继续运行。当系统中数据准备好时,向应用程序发送消息。程序接收到消息后调用recv()函数接收数据。
二、WSAAsyncSelect()函数
函数原型:int WSAAsyncSelect ( SOCKET s, HWND hWnd, u_int wMsg, long lEvent)
参数说明:s: 需要事件通知的套接字
hWnd:当网络事件发生时接收消息的窗口句柄
wMsg:当网络事件发生时窗口收到的消息
lEvent:应用程序感兴趣的网络事件集合
使用说明:当应用程序中调用该函数后,自动将套接字设置为非阻塞模式。通常,应用程序声明的消息要比Windows的WM_USER值大,以避免该消息与Windows预定义的消息发生混淆。
网络事件的种类和含义如下
网络事件类型 | |
种类 | 含义 |
FD_READ | 欲接收可读的通知 |
FD_WRITE | 欲接收可写的通知 |
FD_ACCEPT | 欲接收等待接受连接的通知 |
FD_CONNECT | 欲接收一次连接或者多点jion操作完成的通知 |
FD_OOB | 欲接收有带外(OOB)数据到达的通知 |
FD_CLOSE | 欲接收套接字关闭的通知 |
FD_QOS | 欲接收套接字服务质量发生变化的通知 |
FD_GROUP_QOS | 欲接收套接字组服务质量发生变化的通知 |
FD_ROUTING_INTERFACE_CHANGE | 欲在指定方向上,与路由接口发生变化的通知 |
FD_ADDRESS_LIST_CHANGE | 欲接收针对套接字的协议族,本地地址列表发生变化的通知 |
可根据需要同时注册多个网络事件,这时要把网络事件类型执行按位或(OR)运算,然后将它们分配给LEvent参数。例如:应用程序希望在套接字上接收连接完成、数据可读和套接字关闭的网络事件,则调用如下函数:
WSAAsyncSelect ( s, hwnd, WM_SOCKET, FD_CONNECT | FD_READ | FD_CLOSE);
当该套接字连接完成、有数据可读或者套接字关闭的网络事件发生时,就会有WM_SOCKET消息发送给窗口句柄为hwnd的窗口。
三、调用WSAAsyncSelect()函数需要注意的问题
1.接收不到网络事件
第一种情况是由于在同一个套接字同一个自定义消息上,多次调用WSAAsyncSelect()函数注册不同的网络事件,最后一次函数调用取消了前面注册的网络事件。
如:WSAAsyncSelect ( s, hwnd, WM_SOCKET, FD_READ);
WSAAsyncSelect ( s, hwnd, WM_SOCKET, FD_WRITE);
此时应用程序只能接收到FD_WRITE网络事件。
如果要取消所有的网络事件通知,告知windows sockets实现不再为该套接字发送任何网络事件相关的消息,要以参数lEvent值为0调用函数,即WSAAsyncSelect ( s, hwnd, 0, 0)。尽管应用程序调用上述函数取消了网络事件通知,但是在应用程序消息队列中,可能还有网络消息在排队。所以调用上述函数取消网络事件消息后,应用程序还应该继续准备接收网络事件。
第二种情况是在同一个套接字上,多次调用WSAAsyncSelect()函数,为不同网络事件定义了不同的消息,最后一次该函数调用将取消前面注册的网络事件。
如:WSAAsyncSelect ( s, hwnd, wMsg1, FD_READ);
WSAAsyncSelect ( s, hwnd, wMsg2, FD_WRITE);
第二次函数调用将会取消第一次函数调用的作用,只有FD_WRITE网络事件通过wMsg2通知到窗口。
2. accept()函数
因为调用accept()函数接受的套接字和监听套接字具有同样的属性,所以任何为监听套接字设置的网络事件对接受的套接字同样起作用。如果一个监听套接字请求FD_READ和FD_WRITE网络事件,则在该监听套接字上接受的任何套接字也会请求FD_READ和FD_WRITE网络事件,以及发送同样的消息。
3.关于FD_READ网络事件
不要为一个FD_READ网络事件多次调用recv()函数。如果为一个FD_READ网络事件调用了多个recv()函数,会使得该应用程序接收到多个FD_READ网络事件。
如果在一次接收FD_READ网络事件时需要调用多次recv()函数,应用程序应该在调用recv()函数之前关闭FD_READ消息。
4.如何判断套接字已经关闭
要使用FD_CLOSE网络事件来判断套接字是否已经关闭。
接收FD_CLOSE网络事件时,错误代码指示出套接字是从容关闭还是硬关闭。如果错误代码为0,则为从容关闭;若错误代码为WSAECONNRESET,则套接字是硬关闭。
5.发送数据失败
一个应用程序当接收到第一个FD_WRITE网络事件后,便认为在该套接字上可以发送数据。当调用输出函数发送数据时,会收到WSAEWOULDBLOCKE错误。经过这样的失败后,要在下一次接收到FD_WRITE网络事件后,再次发送数据,才能够将数据成功发送。
四、WSAAsyncSelect模型的优缺点
1.优点
该模型的使用方便了在基于消息的Windows环境下开发套接字应用程序。开发人员可以像处理其他消息一样对网络事件消息进行处理。该模型为确保接收所有数据提供了很好的机制,通过注册FD_CLOSE网络事件,从容关闭服务器与客户端的连接,保证了数据的全部接收。
2.缺点
该模型的局限在于它基于Windows的消息机制,必须在应用程序中创建窗口。当然,在开发中可以根据具体情况确定是否显示该窗口。
由于调用WSAAsyncSelect()函数后,自动将套接字设置为非阻塞状态。当应用程序为接收到网络事件调用相应的函数时,未必能够成功返回。这无疑增加了开发人员使用该模型的难度。对于这一点,可以从MFC CSocket类的accept()、receive()、和send()函数的实现中得到验证。