zoukankan      html  css  js  c++  java
  • WinSock WSAEventSelect模型

    概念:WSAEventSelect模型是Windows Sockets提供的一个有用异步I/O模型。该模型允许在一个或者多个套接字上接收以事件为基础的网络事件通知。Windows Sockets应用程序在创建套接字后,调用WSAEventSelect()函数,将一个事件对象与网络事件集合关联在一起。当网络事件发生时,应用程序以事件的形式接收网络事件通知。

    基本流程 :

    1. 初始化网络环境,创建一个监听的socket,然后进行connect操作。接下来WSACreateEvent()创建一个网络事件对象,其声明如下:
      WSAEVENT WSACreateEvent(void); //返回一个手工重置的事件对象句柄
    2. 再调用WSAEventSelect,来将监听的socket与该事件进行一个关联,其声明如下:
      int WSAEventSelect(    
        SOCKET s,                 //套接字  
        WSAEVENT hEventObject,    //网络事件对象  
        long lNetworkEvents       //需要关注的事件  
      ); 

      我们客户端只关心FD_READ和FD_CLOSE操作,所以第三个参数传FD_READ | FD_CLOSE。

    3. 启动一个线程调用WSAWaitForMultipleEvents等待1中的event事件,其声明如下:
      复制代码
      DWORD WSAWaitForMultipleEvents(    
        DWORD cEvents,                  //指定了事件对象数组里边的个数,最大值为64  
        const WSAEVENT FAR *lphEvents,  //事件对象数组  
        BOOL fWaitAll,                  //等待类型,TRUE表示要数组里全部有信号才返回,FALSE表示至少有一个就返回,这里必须为FALSE  
        DWORD dwTimeout,                //等待的超时时间  
        BOOL fAlertable                 //当系统的执行队列有I/O例程要执行时,是否返回,TRUE执行例程返回,FALSE不返回不执行,这里为FALSE  
      );  
      复制代码

      由于我们是客户端,所以只等待一个事件。

    4. 当事件发生,我们需要调用WSAEnumNetworkEvents,来检测指定的socket上的网络事件。其声明如下:
      复制代码
      int WSAEnumNetworkEvents  
      (    
        SOCKET s,                             //指定的socket  
        WSAEVENT hEventObject,                //事件对象  
        LPWSANETWORKEVENTS lpNetworkEvents    //WSANETWORKEVENTS<span >结构地址</span>  
      );  
      复制代码

      当我们调用这个函数成功后,它会将我们指定的socket和事件对象所关联的网络事件的信息保存到WSANETWORKEVENTS这个结构体里边去,我们来看下这个结构体的声明:

      typedef struct _WSANETWORKEVENTS {  
        long     lNetworkEvents;<span style="white-space:pre">          </span>//指定了哪个已经发生的网络事件  
        int      iErrorCodes[FD_MAX_EVENTS];<span style="white-space:pre">      </span>//错误码  
      } WSANETWORKEVENTS, *LPWSANETWORKEVENTS;  

      根据这个结构体我们就可以判断是否是我们所关注的网络事件已经发生了。如果是我们的读的网络事件发生了,那么我们就调用recv函数进行操作。若是关闭的事件发生了,就调用closesocket将socket关掉,在数组里将其置零等操作。

    5. 整个模型的流程图如下:整个模型的流程图如下:
    6. 代码

      #pragma once
      #include "stdafx.h"
      #include <WinSock2.h>
      #include <Windows.h>
      
      // 释放指针的宏
      #define RELEASE(x)            {if(x != NULL) {delete x; x = NULL;}}
      // 释放句柄的宏
      #define RELEASE_HANDLE(x)    {if(x != NULL && x != INVALID_HANDLE_VALUE) { CloseHandle(x); x = INVALID_HANDLE_VALUE; }}
      // 释放Socket的宏
      #define RELEASE_SOCKET(x)    {if(x != INVALID_SOCKET) { closesocket(x); x = INVALID_SOCKET; }}
      
      class ClientBase
      {
      public:
          ClientBase();
          ~ClientBase();
      
          // 启动通信
          BOOL Start(const char *IPAddress, USHORT port);
          // 关闭通信
          BOOL Stop();
          // 发送数据
          BOOL Send(const BYTE* buffer, int len);
          // 是否已启动
          BOOL HasStarted();
      
          // 事件通知函数(派生类重载此族函数)
          // 连接关闭
          virtual void OnConnectionClosed() = 0;
          // 连接上发生错误
          virtual void OnConnectionError() = 0;
          // 读操作完成
          virtual void OnRecvCompleted(BYTE* buffer, int len) = 0;
          // 写操作完成
          virtual void OnSendCompleted() = 0;
      
      private:
          // 接收线程函数
          static DWORD WINAPI RecvThreadProc(LPVOID lpParam);
          // socket是否存活
          BOOL IsSocketAlive(SOCKET sock);
          SOCKET clientSock;
          WSAEVENT socketEvent;
          HANDLE stopEvent;
          HANDLE thread;
      };
      #include "ClientBase.h"
      #include <WS2tcpip.h>
      
      #pragma comment(lib, "WS2_32.lib")
      
      ClientBase::ClientBase()
      {
          WSADATA wsaData;
          WSAStartup(MAKEWORD(2, 2), &wsaData);
      }
      
      
      ClientBase::~ClientBase()
      {
          WSACleanup();
      }
      
      BOOL ClientBase::Start(const char *IPAddress, USHORT port)
      {
          clientSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
          if (clientSock == INVALID_SOCKET)
              return false;
          socketEvent = WSACreateEvent();
          stopEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
      
          sockaddr_in serAddr;
          serAddr.sin_family = AF_INET;
          serAddr.sin_port = htons(port);
          inet_pton(AF_INET, IPAddress, &serAddr.sin_addr);
          //serAddr.sin_addr.S_un.S_addr = inet_addr(IPAddress);
          if (connect(clientSock, (sockaddr *)&serAddr, sizeof(serAddr)) == SOCKET_ERROR)
          {  //连接失败
              closesocket(clientSock);
              return false;
          }
          if (0 != WSAEventSelect(clientSock, socketEvent, FD_READ | FD_CLOSE))
              return false;
      
          thread = CreateThread(0, 0, RecvThreadProc, (void *)this, 0, 0);
          return true;
      }
      
      BOOL ClientBase::Stop()
      {
          SetEvent(stopEvent);
          WaitForSingleObject(thread, INFINITE);
          RELEASE_SOCKET(clientSock);
          WSACloseEvent(socketEvent);
          RELEASE_HANDLE(stopEvent);
          return true;
      }
      
      BOOL ClientBase::Send(const BYTE * buffer, int len)
      {
          if (SOCKET_ERROR == send(clientSock, (char*)buffer, len, 0))
          {
              return false;
          }
          return true;
      }
      
      BOOL ClientBase::HasStarted()
      {
          return 0;
      }
      
      DWORD ClientBase::RecvThreadProc(LPVOID lpParam)
      {
          if (lpParam == NULL)
              return 0;
      
          ClientBase *client = (ClientBase *)lpParam;
          DWORD ret = 0;
          int index = 0;
          WSANETWORKEVENTS networkEvent;
          HANDLE events[2];
          events[0] = client->socketEvent;
          events[1] = client->stopEvent;
      
          while (true)
          {
              ret = WSAWaitForMultipleEvents(2, events, FALSE, INFINITE, FALSE);
              if (ret == WSA_WAIT_FAILED || ret == WSA_WAIT_TIMEOUT)
                  continue;
              index = ret - WSA_WAIT_EVENT_0;
              if (index == 0)
              {
                  WSAEnumNetworkEvents(client->clientSock, events[0], &networkEvent);
                  if (networkEvent.lNetworkEvents & FD_READ)
                  {
                      if (networkEvent.iErrorCode[FD_READ_BIT != 0])
                      {
                          //Error
                          continue;
                      }
                      char *buff = (char*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, 4096);
                      ret = recv(client->clientSock, buff, 4096, 0);
                      if (ret == 0 || (ret == SOCKET_ERROR && WSAGetLastError() == WSAECONNRESET))
                      {
                          client->OnConnectionClosed();
                          break;        //错误
                      }
                      client->OnRecvCompleted((BYTE*)buff, ret);
                  }
                  if (networkEvent.lNetworkEvents & FD_CLOSE)
                  {
      
                      client->OnConnectionClosed();
                      break;    //关闭
                  }
              }
              else
              {
                  client->OnConnectionClosed();
                  break;    // 退出
              }
      
          }
          return 1;
      }
      
      BOOL ClientBase::IsSocketAlive(SOCKET sock)
      {
          return 0;
      }
      #include "ClientBase.h"
      #include <stdio.h>
      
      class Client : public ClientBase
      {
      public:
          // 连接关闭
          virtual void OnConnectionClosed()
          {
              printf("   Close
      ");
          }
          // 连接上发生错误
          virtual void OnConnectionError()
          {
              printf("   Error
      ");
          }
          // 读操作完成
          virtual void OnRecvCompleted(BYTE* buffer, int len)
          {
              printf("recv[%d]:%s
      ", len, (char*)buffer);
          }
          // 写操作完成
          virtual void OnSendCompleted()
          {
              printf("*Send success
      ");
          }
      
      };
      
      int main()
      {
          Client client;
          if (!client.Start("127.0.0.1", 10240))
          {
              printf("   start error
      ");
          }
      
          int i = 0;
          while (true)
          {
              char buff[128];
              //scanf_s("%s", &buff, 128);
      
      
              sprintf_s(buff, 128, "第%d条Msg", i++);
              Sleep(500);
              client.Send((BYTE*)buff, strlen(buff)+1);
          }
      }

      注意:上面代码在编译的时候如果出现大量重复定义错误时,原因是Windows.h文件中本身包含了Winsock.h头文件,而WinSock2.h是WinSock.h的升级版,两个同时引用的时候出现重复定义问题。决思路就是要让Winsock.h和WinSock2.h两个当中只能用一个进行编译,这样的话解决的方法有:

      方法一 不要同时包含Windows.h或者WinSock2.h,这对于一些不需要用到硬件接口的程序可行,但是对于需要用于硬件操作的程序必须包含Windows.h,这时这个方法会失效。
      
      方法二 将WinSock2.h放在Windows.h前面,这是一个最方便,最简单的方法。
      
      方法三 添加自定义宏,打开VS2010中的Project的属性,然后C/C++->Preprocessor->Preprocessor Definitions中添加如下宏:WIN32_LEAN_AND_MEAN
    7. 转载:https://www.cnblogs.com/tanguoying/p/8506821.html

    111
  • 相关阅读:
    乐字节Java编程语言发展,面向对象和类
    乐字节Java编程之方法、调用、重载、递归
    乐字节Java循环:循环控制和嵌套循环
    乐字节Java反射之四:反射相关操作
    乐字节Java反射之三:方法、数组、类加载器和类的生命周期
    乐字节Java反射之二:实例化对象、接口与父类、修饰符和属性
    乐字节Java反射之一:反射概念与获取反射源头class
    Java变量与数据类型之三:数据类型与转义字符
    数论 N是完全平方数 充分必要条件 N有奇数个约数
    动态规划专题 01背包问题详解 HDU 2546 饭卡
  • 原文地址:https://www.cnblogs.com/zwj-199306231519/p/13174526.html
Copyright © 2011-2022 走看看