8.2.2 WSAAsyncSelect
Wi n s o c k提供了一个有用的异步I / O模型。利用这个模型,应用程序可在一个套接字上,接收以Wi n d o w s消息为基础的网络事件通知。具体的做法是在建好一个套接字后,调用W S A A s y n c S e l e c t函数。该模型最早出现于Wi n s o c k的1 . 1版本中,用于帮助应用程序开发者面向一些早期的1 6位Wi n d o w s平台(如Windows for Wo r k g r o u p s),适应其“落后”的多任务消息环境。应用程序仍可从这种模型中得到好处,特别是它们用一个标准的Wi n d o w s例程(常
称为“ w i n p r o c”),对窗口消息进行管理的时候。该模型亦得到了Microsoft Foundation Class
(微软基本类,M F C)对象C S o c k e t的采纳。
消息通知
要想使用W S A A s y n c S e l e c t模型,在应用程序中,首先必须用C r e a t e Wi n d o w函数创建一个窗口,再为该窗口提供一个窗口例程支持函数( Wi n p r o c)。亦可使用一个对话框,为其提供一个对话例程,而非窗口例程,因为对话框本质也是“窗口”。考虑到我们的目的,我们打算用一个简单的窗口来演示这种模型,采用的是一个支持窗口例程。设置好窗口的框架后,便可开始创建套接字,并调用W S A A s y n c S e l e c t函数,打开窗口消息通知。该函数的定义如下:

int WSAAsyncSelect(
          SOCKET s,
          HWND hWnd,
          unsigned  int wMsg,
          long lEvent
         );

其中, s参数指定的是我们感兴趣的那个套接字。h W n d参数指定的是一个窗口句柄,它对应于网络事件发生之后,想要收到通知消息的那个窗口或对话框。w M s g参数指定在发生网络事件时,打算接收的消息。该消息会投递到由h W n d窗口句柄指定的那个窗口。通常,应用程序需要将这个消息设为比Wi n d o w s的W M _ U S E R大的一个值,避免网络窗口消息与预定义的标准窗口消息发生混淆与冲突。最后一个参数是l E v e n t,它指定的是一个位掩码,对应于一
系列网络事件的组合(请参考表8 - 3),应用程序感兴趣的便是这一系列事件。大多数应用程序通常感兴趣的网络事件类型包括: F D _ R E A D、F D _ W R I T E、F D _ A C C E P T、F D _ C O N N E C T和
F D _ C L O S E。当然,到底使用F D _ A C C E P T,还是使用F D _ C O N N E C T类型,要取决于应用程序的身份到底是一个客户机呢,还是一个服务器。如应用程序同时对多个网络事件有兴趣,只需对各种类型执行一次简单的按位O R(或)运算,然后将它们分配给l E v e n t就可以了。举个例子来说:

WSAAsyncSelect(s,hWnd,WM_SOCKET,FD_CONNECT|FD_READ|FD_WRITE|FD_CLOSE);

这样一来,我们的应用程序以后便可在套接字s上,接收到有关连接、发送、接收以及套
接字关闭这一系列网络事件的通知。特别要注意的是,多个事件务必在套接字上一次注册!
另外还要注意的是,一旦在某个套接字上允许了事件通知,那么以后除非明确调用c l o s e s o c k e t命令,或者由应用程序针对那个套接字调用了W S A A s y n c S e l e c t,从而更改了注册的网络事件类型,否则的话,事件通知会永远有效!若将l E v e n t参数设为0,效果相当于停止在套接字上进行的所有网络事件通知。

若应用程序针对一个套接字调用了W S A A s y n c S e l e c t,那么套接字的模式会从“锁定”自动变成“非锁定”,我们在前面已提到过这一点。这样一来,假如调用了像W S A R e c v这样的Wi n s o c k  I / O函数,但当时却并没有数据可用,那么必然会造成调用的失败,并返回W S A E W O U L D B L O C K
错误。为防止这一点,应用程序应依赖于由W S A A s y n c S e l e c t的u M s g参数指定的用户自定义窗口消息,来判断网络事件类型何时在套接字上发生;而不应盲目地进行调用。

表8-3 用于W S A A s y n c S e l e c t函数的网络事件类型

F D _ R E A D             应用程序想要接收有关是否可读的通知,以便读入数据
F D _ W R I T E          应用程序想要接收有关是否可写的通知,以便写入数据
F D _ O O B                应用程序想接收是否有带外( O O B)数据抵达的通知
F D _ A C C E P T       应用程序想接收与进入连接有关的通知
F D _ C O N N E C T    应用程序想接收与一次连接或者多点j o i n操作完成的通知
F D _ C L O S E             应用程序想接收与套接字关闭有关的通知
F D _ Q O S            应用程序想接收套接字“服务质量”(Q o S)发生更改的通知
F D _ G R O U P _ Q O S             应用程序想接收套接字组“服务质量”发生更改的通知(现在没什么用处,为未来套接字组的使用保留)
F D _ R O U T I N G _ I N T E R FA C E _ C H A N G E       应用程序想接收在指定的方向上,与路由接口发生变化的通知
F D _ A D D R E S S _ L I S T _ C H A N G E       应用程序想接收针对套接字的协议家族,本地地址列表发生变化的通知

应用程序在一个套接字上成功调用了W S A A s y n c S e l e c t之后,应用程序会在与h W n d窗口句柄参数对应的窗口例程中,以Windows消息的形式,接收网络事件通知。窗口例程通常定义如下:

LRESULT CALLBACK WindProc(
             HWND hWnd,
             UINT uMsg,
             WPARAM wParam,
             LPARAM lParam
             );
其中,h W n d参数指定一个窗口的句柄,对窗口例程的调用正是由那个窗口发出的。u M s g参数指定需要对哪些消息进行处理。就我们的情况来说,感兴趣的是W S A A s y n c S e l e c t调用中定义的消息。w P a r a m参数指定在其上面发生了一个网络事件的套接字。假若同时为这个窗口例程分配了多个套接字,这个参数的重要性便显示出来了。在l P a r a m参数中,包含了两方面重要的信息。其中, l P a r a m的低字(低位字)指定了已经发生的网络事件,而l P a r a m的高字
(高位字)包含了可能出现的任何错误代码。
网络事件消息抵达一个窗口例程后,应用程序首先应检查l P a r a m的高字位,以判断是否在套接字上发生了一个网络错误。这里有一个特殊的宏: W S A G E T S E L E C T E R R O R,可用它返回高字位包含的错误信息。若应用程序发现套接字上没有产生任何错误,接着便应调查到底是哪个网络事件类型,造成了这条Wi n d o w s消息的触发—具体的做法便是读取l P a r a m之低字位的内容。此时可使用另一个特殊的宏:W S A G E T S E L E C T E V E N T,用它返回l P a r a m的低字部分。
在程序清单8 - 5中,我们向大家演示了如何使用W S A A s y n c S e l e c t这种I / O模型,来实现窗口消息的管理。在源程序中,我们着重强调的是开发一个基本服务器应用要涉及到的基本步骤,忽略了开发一个完整的Wi n d o w s应用需要涉及到的大量编程细节。

程序清单8-5 WSAAsyncSelect服务器示范代码

#define WM_SOCKET  WM_USER+1
#inlude <windows.h>

int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR  lpCmdline,int nCmdShow)
{
 SOCKET listen;
 HWND window;
 //create a window  and assign the serverWinProc blew to it
 window = CreateWindow();
 //start winsock and create a socket
 
 WSAStarup(...);
 listen = Socket();
 //Bind the socket to port 5150
 // and begin listening for connection
 
 InternetAddr.sin_family = AF_INET;
 InternetAddr.sin_addr.s_addr=htonl(INADDR_ANY);
 InternetAddr.sin_port = htons(5150);
 bind(listen,(PSOCKETADDR)&InternetAddr,sizeof(InternetAddr));
 
 //set up window message notification on the new socket using the WM_SOCKET define above
 WSAAsyncSelect(listen,window,WM_SOCKET,FD_ACCEPT|FD_CLOSE);
 
 listen(listen,5);
 
 //translate and dispatch window messages
 //until the appliation terminates
}             


BOOL CALLBACK ServerWinProc(HWND hDlg,WORD wMsg,WORD wParam,WORD lParam)
{
 SOCKET accept;
 switch(wMsg)
 {
  case WM_PAINT:
     break;
  case WM_SOCKET:
     //determine whether an error occured on the socket
     //by using the WSAGETSELECTERROR() macro
     
     if(WSAGETSELECTERROR(lWparam))
     {
      //display the error and close the socket
      closesocket(wParam);
      break;
     }
     //determine what event occured on the socket
     
     switch(WSAGETSELECTEVENT(lParam)
     {
      case FD_ACCEPT:
         //ACCEPT an incoming connection
         Accept = accept(wParam,NULL,NULL);
         
         //prepare accepted socket for read
         //write,and close notifation
         
         WSAAsyncSelect(Accept,hWnd,WM_SOCKET,FD_READ|FD_WRITE|FD_CLOSE);
      case FD_READ:
         //RECEIVE data from the socket in wParam
         break;
      case FD_WRITE:
         //THE socket in wParam is ready for sending data
         
         break;
      case FD_CLOSE:
         //THE connection is now closed
         closesocket(wParam);
         break;
         
     }
     break;
 }
 return TRUE;
}

最后一个特别有价值的问题是应用程序如何对F D _ W R I T E事件通知进行处理。只有在三
种条件下,才会发出F D _ W R I T E通知:
■ 使用c o n n e c t或W S A C o n n e c t,一个套接字首次建立了连接。
■ 使用a c c e p t或W S A A c c e p t,套接字被接受以后。
■ 若s e n d、W S A S e n d、s e n d t o或W S A S e n d To操作失败,返回了W S A E W O U L D B L O C K错
误,而且缓冲区的空间变得可用因此,作为一个应用程序,自收到首条F D _ W R I T E消息开始,便应认为自己必然能在一个套接字上发出数据,直至一个s e n d、W S A S e n d、s e n d t o或W S A S e n d To返回套接字错误W S A E W O U L D B L O C K。经过了这样的失败以后,要再用另一条F D _ W R I T E通知应用程序再次发送数据。
            

Posted on 2006-09-12 16:52 艾凡赫 阅读(917) 评论(0)  编辑 收藏 引用 所属分类: 网络编程

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理