zoukankan      html  css  js  c++  java
  • WSAAsyncSelect 消息模型

    select 模型虽然可以管理多个socket,但是它涉及到一个时机的问题,select模型会针对所管理的数组中的每一个socket循环检测它管理是否在对应的数组中,从时间复杂度上来说它是O(n^2)的,而且还有可能发生数组中没有socket处于待决状态而导致本轮循环做无用功的情况,针对这些问题,winsock中有了新的模型——WSAAsyncSelect 消息模型

    消息模型的核心是基于Windows窗口消息获得网络事件的通知,Windows窗口是用来与用户交互的,而它并不知道用户什么时候会操作窗口,所以Windows窗口本身就是基于消息的异步通知,网络事件本身也是一个通知消息,将二者结合起来可以很好的使socket通知像消息那样当触发通知时调用窗口过程。这样就解决了select中的时机问题和里面两层循环的问题
    WSAAsyncSelect函数原型如下:

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

    第一个参数是绑定的socket,第二个参数是消息所对应的窗口句柄,第三个参数是对应的消息,这个消息需要自己定义,第4个参数是我们所关心的事件,当在s这个socket发生lEvent这个事件发生时会向hWnd对应的窗口发送wMsg消息。
    在消息附带的两个参数wParam和lParam中,lParam的高位16位表示当前的错误码,低16位表示当前socket上发生的事件。其中事件的取值如下:

    1. FD_WRITE : 当socket上可写时触发该事件,FD_WRITE的触发与调用send没有必然的联系,FD_WRITE只是表示socket已经为发送准备好了必要的条件,其实调用时可以不必理会这个事件,只需要在想发送数据的场合调用send,一般来说FD_WRITE只在这些条件下触发:a) 调用connect函数成功连接到服务器 b) 调用accept接受连接成功后(该条件是绑定在accept返回的那个与客户端通讯的socket上) c)调用send,sendto 失败并返回WSAWOULDBLOCK(由于是异步操作,可能同时客户端也在发数据, 此时可能导致send失败)
      为了方便我们处理这些参数,WinSock 提供了两个宏来解析它的高16位和低16位,分别是WSAGETSELECTERROR和WSAGETSELECTEVENT
      而lParam则保存了当前触发事件的socket句柄

    如果对一个句柄调用了WSAAsyncSelect 并成功后,对应的socket会自动编程非阻塞模式。它就不像前面的select模型那样需要显示调用ioctrlsocket将socekt设置为非阻塞。
    另外不需要每个socket都定义一个消息ID,通常一个ID已经足够处理所有的socket事件。
    下面是一个具体的例子

    int _tmain(int argc, TCHAR *argv[])
    {
        WSADATA wd = {0};
        WSAStartup(MAKEWORD(2, 2), &wd);
        SOCKADDR_IN SrvAddr = {AF_INET};
        SrvAddr.sin_addr.s_addr = htonl(INADDR_ANY);
        SrvAddr.sin_port = htons(SERVER_PORT);
    
        SOCKET skServer = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
        if (INVALID_SOCKET == skServer)
        {
        	 printf("初始化socket失败,错误码为:%08x
    ", WSAGetLastError());
        	 goto __CLEAR_UP;
        }
    
        if (0 != bind(skServer, (SOCKADDR*)&SrvAddr, sizeof(SOCKADDR)))
        {
        	 printf("绑定失败,错误码为:%08x
    ", WSAGetLastError());
        	 goto __CLEAR_UP;
        }
    
        if (0 != listen(skServer, 5))
        {
          	printf("监听失败,错误码为:%08x
    ", WSAGetLastError());
          	goto __CLEAR_UP;
        }
    
        RegisterWindow();
        CreateAndShowWnd();
    
        g_uSockMsgID = RegisterWindowMessage(SOCKNOTIFY_MESSAGE);
        WSAAsyncSelect(skServer, g_hMainWnd, g_uSockMsgID, FD_ACCEPT | FD_CLOSE);
    
        MessageLoop();
    __CLEAR_UP:
        if (INVALID_SOCKET != skServer)
        {
            closesocket(skServer);
        }
        WSACleanup();
        return 0;
    }
    
    LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
    {
      	LRESULT lRes = 0;
      	switch (uMsg)
      	{
      	case WM_CLOSE:
      	   {
      		     CloseWindow(hwnd);
      			   DestroyWindow(hwnd);
      		 }   
      		break;
      	case WM_PAINT:
      		{
        			PAINTSTRUCT ps = {0};
        			BeginPaint(hwnd, &ps);
        			EndPaint(hwnd, &ps);
      		}
      		break;
      	case WM_DESTROY:
      		  PostQuitMessage(0);
      		  break;
      	default:
        		if (uMsg == g_uSockMsgID)
        		{
        			   lRes = ParseNotifyMessage(wParam, lParam);
        		}
        		lRes = DefWindowProc(hwnd, uMsg, wParam, lParam);
      	}
    
      	return lRes;
    }
    
    LRESULT ParseNotifyMessage(WPARAM wParam, LPARAM lParam)
    {
      	WORD wNotify = WSAGETSELECTEVENT(lParam);
      	WORD wError = WSAGETSELECTERROR(lParam);
    
      	if (wNotify == FD_ACCEPT)
      	{
      		  return OnAcceptMsg((SOCKET)wParam, lParam);
      	}else if (wNotify == FD_READ)
      	{
      		  return OnReadMsg((SOCKET)wParam, lParam);
      	}
    
      	return 1;
    }
    
    LRESULT OnAcceptMsg(SOCKET s, LPARAM lParam)
    {
      	SOCKADDR_IN AddrClient = {0};
      	int nAddrSize = sizeof(SOCKADDR);
      	SOCKET sClient = accept(s, (SOCKADDR*)&AddrClient, &nAddrSize);
      	printf("有客户端连接进来[%s:%u]
    ", inet_ntoa(AddrClient.sin_addr), ntohs(AddrClient.sin_port));
      	return WSAAsyncSelect(sClient, g_hMainWnd, g_uSockMsgID, FD_WRITE | FD_READ | FD_CLOSE);
    }
    
    LRESULT OnReadMsg(SOCKET s, LPARAM lParam)
    {
      	char *pszBuf = (char*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, 1024);
      	ZeroMemory(pszBuf, 1024);
      	int nTotalSize = 0;
      	int i = 1;
    
      	while (TRUE)
      	{
      		  i++;
        		int nReadSize = recv(s, pszBuf + nTotalSize, 1024, 0);
        		if (nReadSize < 1024)
        		{
        			nTotalSize += nReadSize;
        			break;
        		}
    
        		nTotalSize += nReadSize;
        		HeapReAlloc(GetProcessHeap(), 0, pszBuf, 1024 * i);
      	}
    
      	if (strcmp(pszBuf, "exit") == 0)
      	{
        		shutdown(s, SD_BOTH);
        		closesocket(s);
      	}
    
      	send(s, pszBuf, nTotalSize, 0);
      	HeapFree(GetProcessHeap(), 0, pszBuf);
    
      	return 0;
    }
    

    在上面的代码中我们在main函数中创建了窗口程序,而常规的都是在WinMain中创建,其实从本质上讲控制台程序和窗口程序都是一个进程,至于以main作为入口还是以WinMain作为入口只是习惯上这样,但是并没有硬性规定。 在创建窗口之后我们将监听socket也绑定到窗口消息中,然后在对应的消息中判断FD_ACCEPT事件,如果是则调用accept进行连接。并将对生成的socket进行绑定。 在接下来的socket消息中主要处理FD_READ事件,当发生READ事件时调用read接收数据,然后调用send将数据原封不动的发送出去。

    从上面的代码上看,该模型相对于select来说省去了查看socket是否在对应数组中的操作,减少了循环。而且可以很好的把握什么调用时机问题。
    主要的缺点是它需要一个窗口,这样在服务程序中基本就排除掉了这个模型,它基本上只会出现在客户端程序中。
    另外如果在一个窗口中需要管理成千上万个句柄时,它的性能会急剧下降,因此它的伸缩性较差。但是在客户端中基本不存在这个问题,所以如果要在客户端中想要减少编程难度,它是一个不二的选择

  • 相关阅读:
    TF400916错误修复办法
    架构优化
    .net 动态代理的泛型方法支持问题
    解决Kafka-1194问题
    SOA服务总线设计
    SOA初探
    重构心法——提高代码复用率
    重构心法——拆分大对象
    重构心法——你应该这样写方法
    反爬虫那些事儿
  • 原文地址:https://www.cnblogs.com/lanuage/p/9128278.html
Copyright © 2011-2022 走看看