本站原创:KKnD

Winsock提供了一个很有用的异步I/O模型,利用这个模型,应用程序可以在一个套接字上接

收以Windows消息为基础的网络事件通知。这个模型最开始出现在Winsock
1.1
版本中,是为

了帮助开发者面向一些早期的16位Windows平台而设计的。但是现在的应用程序仍然可以从

这种模型中得到好处,就连MFC中的CSocket类也采纳了这种模型。

由于该模型是基于Windows消息机制的,所以要想使用这种模型必须要Create一个窗口,这

个窗口将会被用来接收消息。接下来建立套接字,然后调用WSAAsyncSelect函数,打开窗口

消息通知,函数原型如下:

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

其中s就是我们想要的那个套接字;hWnd是接收消息通知那个窗口句柄;wMsg参数指定在

发生网络事件时要接受的消息,通常设成比WM_USER大的一个值,以避免消息冲突;


lEvent
指定了一个位掩码,对应一系列网络事件的组合,见下表:

Event
含义
FD_READ 程序想要接收有关是否可读的通知,以便读入数据
FD_WRITE 程序想要接收有关是否可写的通知,以便写入数据
FD_OOB 程序想要接收是否有OOB数据到达的通知
FD_ACCEPT 程序想要接收与进入连接有关的通知
FD_CONNECT 程序想要接收与一次连接或多点接入有关的通知
FD_CLOSE 程序想要接收与套接字关闭有关的通知
FD_QOS 程序想要接收套接字“服务质量(QoS)”发生变化的通知
FD_GROUP_QOS 暂时没用,属于保留事件
FD_ROUTING_INTERFACE_CHANGE 程序想要接收有关到指定地址的路由接口发生变化的通知
FD_ADDRESS_LIST_CHANGE 程序想要接收本地地址变化的通知

当程序在一个套接字上调用WSAAsyncSelect成功后,这个程序就会在与hWnd窗口句柄对

应的窗口例程中以Windows消息的形式接收网络事件通知。窗口例程通常定义成这个样子:

LRESULT
CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam,


         LPARAM
lParam)


其中wParam参数指定在其上面发生了一个网络事件的套接字,如果定义了多个套接字,这

个参数就显得很重要了。lParam参数则包含了两方面的重要信息,它的低位字指定了已经发

生的网络事件,而高位字包含了可能出现的错误代码。

简单的来说,这个模型的具体使用流程就是:

当网络消息抵达一个窗口例程后,程序要先检测lParam的高位字节,从而判断是否在套接字

上面发生了网络错误。现成的宏已经有在这里了 –> WSAGETSELECTERROR,可以用它返

回高字节包含的错误信息,如果没有发现任何的错误,接下来就是确定究竟是什么类型的网

络事件触发了这条Windows消息,这个操作也有现成的宏
–> WSAGETSELECTEVENT

下面就是源代码,其中部分很基本的代码我就省略掉了,编译平台为

Win2000
Server with SP2 + VC6.0 with SP5

#include
<windows.h>

#include <winsock2.h>


#define PORT 5150

#define DATA_BUFSIZE 8192

typedef
struct _SOCKET_INFORMATION {

 BOOL RecvPosted;

 CHAR Buffer[DATA_BUFSIZE];

 WSABUF DataBuf;

 SOCKET Socket;

 DWORD BytesSEND;

 DWORD BytesRECV;

 _SOCKET_INFORMATION *Next;

} SOCKET_INFORMATION, * LPSOCKET_INFORMATION;

#define
WM_SOCKET (WM_USER + 1)

void
CreateSocketInformation(SOCKET s, HWND);

LPSOCKET_INFORMATION GetSocketInformation(SOCKET s);

void FreeSocketInformation(SOCKET s);

LPSOCKET_INFORMATION
SocketInfoList;

LRESULT
CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;

int
APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,

    
LPSTR
lpCmdLine, int nCmdShow)

{

 DWORD Ret;

 SOCKET Listen;

 SOCKADDR_IN InternetAddr;

 WSADATA wsaData;

 static TCHAR szAppName[] = TEXT ("HelloWin") ;

 HWND hwnd ;

 MSG msg ;

 WNDCLASS wndclass ;


 // Prepare echo server

 wndclass.style
= CS_HREDRAW | CS_VREDRAW ;

 …

 …

 RegisterClass
(&wndclass);

 hwnd = CreateWindow (…) ; // creation parameters

 ShowWindow (hwnd, nCmdShow) ;

 UpdateWindow (hwnd) ;



 if ((Ret = WSAStartup(0×0202, &wsaData)) != 0)

 {

  MessageBox(hwnd, TEXT("Start socket failed"),
TEXT("error"), MB_OK);

  ExitProcess(1);


 }

 if
((Listen = socket (PF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET)


 {

  MessageBox(hwnd, TEXT("socket() failed"), TEXT("error"),
MB_OK);

  ExitProcess(1);


 
}

 WSAAsyncSelect(Listen,
hwnd, WM_SOCKET, FD_ACCEPT|FD_CLOSE);

 InternetAddr.sin_family
= AF_INET;


 InternetAddr.sin_addr.s_addr = htonl(INADDR_ANY);

 InternetAddr.sin_port = htons(PORT);


 if (bind(Listen, (PSOCKADDR) &InternetAddr, sizeof(InternetAddr))

    == SOCKET_ERROR)

 {

  MessageBox(hwnd, TEXT("bind() failed"), TEXT("error"),
MB_OK);

  ExitProcess(1);

 }

 if
(listen(Listen, 5))

 {

  MessageBox(hwnd, TEXT("listen() failed"), TEXT("error"),
MB_OK);

  ExitProcess(1);


 
}

 // Translate and dispatch window messages for the application
thread

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

 {

  TranslateMessage (&msg) ;

  DispatchMessage (&msg) ;

 }

 return msg.wParam ;

}

LRESULT
CALLBACK WndProc (HWND hwnd, UINT message,

        
WPARAM
wParam, LPARAM lParam)

{

 HDC hdc ;

 PAINTSTRUCT ps ;

 RECT rect ;

 SOCKET Accept;

 LPSOCKET_INFORMATION SocketInfo;

 DWORD RecvBytes, SendBytes;

 DWORD Flags;



 switch (message)

 {

  case WM_CREATE:

   return 0 ;



  case WM_PAINT:

   hdc = BeginPaint (hwnd, &ps) ;

   GetClientRect (hwnd, &rect) ;

   DrawText (hdc, TEXT ("Server Started!"), -1,
&rect,

   DT_SINGLELINE | DT_CENTER | DT_VCENTER) ;

   EndPaint (hwnd, &ps) ;

   return 0 ;



  case WM_DESTROY:

   PostQuitMessage (0) ;

   return 0 ;

  case
WM_SOCKET:


   if (WSAGETSELECTERROR(lParam))

   {

    MessageBox();

    FreeSocketInformation(wParam);

   }

   else

   {

    switch(WSAGETSELECTEVENT(lParam))

    {

     case FD_ACCEPT:

      if ((Accept = accept(wParam, NULL, NULL)) == INVALID_SOCKET)

      {

       MessageBox();

       break;

      }

      //
Create a socket information structure to associate with the


      // socket for processing I/O.

      CreateSocketInformation(Accept,
hwnd);


      WSAAsyncSelect(Accept,
hwnd, WM_SOCKET,

         FD_READ|FD_WRITE|FD_CLOSE);

      break;

     case
FD_READ:

      SocketInfo
= GetSocketInformation(wParam);

      //
Read data only if the receive buffer is empty.

      if
(SocketInfo->BytesRECV != 0)

      {

       SocketInfo->RecvPosted
= TRUE;

       return
0;

      }

      else

      {

       SocketInfo->DataBuf.buf
= SocketInfo->Buffer;

       SocketInfo->DataBuf.len
= DATA_BUFSIZE;


       Flags
= 0;

       if
(WSARecv(SocketInfo->Socket, &(SocketInfo->DataBuf),


           1,
&RecvBytes, &Flags, NULL, NULL) == SOCKET_ERROR)

       {

        if
(WSAGetLastError() != WSAEWOULDBLOCK)

        {

         MessageBox(…);

         FreeSocketInformation(wParam);

         return
0;

        }

       }


       else
// No error so update the byte count

        SocketInfo->BytesRECV
= RecvBytes;

      }

      //
DO NOT BREAK HERE SINCE WE GOT A SUCCESSFUL RECV.

      //
Go ahead and begin writing data to the client.

      case
FD_WRITE:

       SocketInfo
= GetSocketInformation(wParam);

       if
(SocketInfo->BytesRECV > SocketInfo->BytesSEND)

       {

        SocketInfo->DataBuf.buf
=

         SocketInfo->Buffer
+ SocketInfo->BytesSEND;

        SocketInfo->DataBuf.len
=

         SocketInfo->BytesRECV
– SocketInfo->BytesSEND;

        if
(WSASend(SocketInfo->Socket, &(SocketInfo->DataBuf),


            1,
&SendBytes, 0,NULL, NULL) == SOCKET_ERROR)

        {

         if
(WSAGetLastError() != WSAEWOULDBLOCK)

         {

          MessageBox(…);

          FreeSocketInformation(wParam);

          return
0;

         }

        }


        else
// No error so update the byte count

         SocketInfo->BytesSEND
+= SendBytes;

       }

       if
(SocketInfo->BytesSEND == SocketInfo->BytesRECV)

       {

        SocketInfo->BytesSEND
= 0;

        SocketInfo->BytesRECV
= 0;

        //
If a RECV occurred during our SENDs then we need to post

        //
an FD_READ notification on the socket.

        if
(SocketInfo->RecvPosted == TRUE)

        {

         SocketInfo->RecvPosted
= FALSE;

         PostMessage(hwnd,
WM_SOCKET, wParam, FD_READ);

        }

       }

       if(SocketInfo->DataBuf.buf
!= NULL)

        MessageBox(hwnd,
SocketInfo->DataBuf.buf,

          
TEXT("Received"), MB_OK);

       break;

      case
FD_CLOSE:

       FreeSocketInformation(wParam);

       break;

    }

   }

   return
0;

 }

 return
DefWindowProc(hwnd, message, wParam, lParam);

}



void CreateSocketInformation(SOCKET s, HWND hwnd)

{

 LPSOCKET_INFORMATION
SI;


 
if
((SI = (LPSOCKET_INFORMATION) GlobalAlloc(GPTR,

 sizeof(SOCKET_INFORMATION)))
== NULL)

 {

  MessageBox(…);

  return;

 }

 //
Prepare SocketInfo structure for use.

 SI->Socket
= s;

 SI->RecvPosted
= FALSE;

 SI->BytesSEND
= 0;

 SI->BytesRECV
= 0;

 SI->Next
= SocketInfoList;

 SocketInfoList
= SI;

}

LPSOCKET_INFORMATION
GetSocketInformation(SOCKET s)

{

 SOCKET_INFORMATION
*SI = SocketInfoList;

 
while(SI)

 {

  if (SI->Socket == s)

  return SI;

  SI
= SI->Next;

 }

 return
NULL;

}

void
FreeSocketInformation(SOCKET s)

{

 SOCKET_INFORMATION *SI = SocketInfoList;

 SOCKET_INFORMATION *PrevSI = NULL;

 while(SI)

 {

  if (SI->Socket == s)

  {

   if (PrevSI)

    PrevSI->Next = SI->Next;

   else

    SocketInfoList = SI->Next;

   closesocket(SI->Socket);

   GlobalFree(SI);

   return;

  }

  PrevSI
= SI;

  SI = SI->Next;

 }

}

服务器就这样建好了,只需要一个客户机就可以通信了,具体的代码我就不再贴了,各位可

以自己设计客户机,或者去下载区下载源程序。最后向大家推荐《Windows网络编程技术》,

真的不错。

本文中部分内容翻译自MSDN,客户机程序使用的是《Windows网络编程技术》中的例子


评论

该日志第一篇评论

发表评论

评论也有版权!