事件选择模型(select模型)

这是我参与11月更文挑战的第二十四天,活动详情查看:2021最后一次更文挑战

事件选择模型

目的和要求

1.    了解事件选择模型的应用场景;

2.    掌握事件选择模型的通讯过程;

3.    掌握事件选择模型的代码实现;

4.    了解事件选择模型的改进方法:有序及增加客户端数量。

服务器端:

1、包含网络头文件网络库

#include <WinSock2.h>

#include <stdio.h>

#pragma comment(lib, "Ws2_32.lib")

#pragma warning(disable:4996);
复制代码

2、打开网络库

int nRes = WSAStartup(wdVersion, &wdScokMsg);

    if (0 != nRes)

    {

         switch (nRes)

         {

         case WSASYSNOTREADY:

             printf("解决方案:重启。。。\n");

             break;

         case WSAVERNOTSUPPORTED:

             break;

         case WSAEINPROGRESS:

             break;

         case WSAEPROCLIM:

             break;

         case WSAEFAULT:

             break;

         }

         return 0;
    }
复制代码

3、校验版本

if (2 != HIBYTE(wdScokMsg.wVersion) || 2 != LOBYTE(wdScokMsg.wVersion))

    {

         printf("版本有问题!\n");

         WSACleanup();

         return 0;

    }
复制代码

4、创建SOCKET

SOCKET socketServer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (INVALID_SOCKET == socketServer)

    {

         int err = WSAGetLastError();

         //清理网络库,不关闭句柄

         WSACleanup();

         return 0;

    }

    struct sockaddr_in si;

    si.sin_family = AF_INET;

    si.sin_port = htons(12345);//用htons宏将整型转为端口号的无符号整型

    si.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); 
复制代码

5、绑定地址与端口

if (SOCKET_ERROR == bind(socketServer, (const struct sockaddr*)&si, sizeof(si)))

    {

         int err = WSAGetLastError();//取错误码

         printf("服务器bind失败错误码为:%d\n", err);

         closesocket(socketServer);//释放

         WSACleanup();//清理网络库

 

         return 0;

    }

    printf("服务器端bind成功!\n");
复制代码

6、开始监听

if (SOCKET_ERROR == listen(socketServer, SOMAXCONN))
    {
         int err = WSAGetLastError();//取错误码

         printf("服务器监听失败错误码为:%d\n", err);

         closesocket(socketServer);//释放

         WSACleanup();//清理网络库
         return 0;
    }
    printf("服务器端监听成功!\n");
复制代码

7、事件选择模型

7.1定义fd_sockevent_set结构体

WSAEVENT eventServer = WSACreateEvent();
    if (eventServer == WSA_INVALID_EVENT)
    {
         int createerr = WSAGetLastError();

         closesocket(socketServer);

         WSACleanup();
         return 0;
}
复制代码

7.2为服务器创建事件句柄

if (WSAEventSelect(socketServer, eventServer, FD_ACCEPT) == SOCKET_ERROR)

    {

         int selecterr = WSAGetLastError();

         WSACloseEvent(eventServer);
         
         closesocket(socketServer);

         WSACleanup();

         return 0;

    }
复制代码

7.3为服务器SOCEKT和事件句柄绑定事件码:FD_ACCEPT,并放入

sockevent_set

sockevent_set.evnetall[sockevent_set.count] = eventServer;

sockevent_set.sockall[sockevent_set.count] = socketServer;

sockevent_set.count++;
复制代码

7.4循环查询事件状态是否有信号,没有信号则重新查询

7.4.1使用WSAWaitForMultipleEvents查询有信号的事件对应socket句柄下标
DWORD soindex = retSignal - WSA_WAIT_EVENT_0;
复制代码
7.4.2使用WSAEnumNetworkEvents获取事件操作码
7.4.3如果是FD_ACCEPT操作码
if (NetworkEvents.lNetworkEvents & FD_ACCEPT)

         {

             //判断FD_ACCEPT错误码对应位是否有值

             if (NetworkEvents.iErrorCode[FD_ACCEPT_BIT] == 0)

             {

                  //正常处理,创建客户端

                  SOCKET socketClient = accept(sockevent_set.sockall[soindex], NULL, NULL);

                  //创建失败则跳过

                  if (socketClient == INVALID_SOCKET)

                  {

                      continue;

                  }

                  //创建成功则为该SOCKET创建事件对象

                  WSAEVENT wsaClientEvent = WSACreateEvent();

                  //失败则关闭SOCKET句柄

                  if (wsaClientEvent == WSA_INVALID_EVENT)

                  {

                      closesocket(socketClient);

                      continue;

                  }

                  //绑定,投递客户端事件对象

                  //客户端事件码通常有三种

                  if (WSAEventSelect(socketClient, wsaClientEvent, FD_READ | FD_WRITE | FD_CLOSE) == SOCKET_ERROR)

                  {

                      //出错关闭句柄,关闭事件对象

                      closesocket(socketClient);

                      WSACloseEvent(wsaClientEvent);

                      //获取错误码略

                      continue;

                  }

                  //绑定投递成功后将客户端事件和SOCKET放到sockevent_set里面

                  sockevent_set.evnetall[sockevent_set.count] = wsaClientEvent;
                  sockevent_set.sockall[sockevent_set.count] = socketClient;
                  sockevent_set.count++;
             }
             else
             {
                  //出现异常不影响其他处理
                  continue;

             }
         }
复制代码
7.4.4如果是FD_WRITE操作码
if (NetworkEvents.lNetworkEvents & FD_WRITE)

         {

             //判断错误码对应位是否有值,没有说明SOCKET没有错误

             if (NetworkEvents.iErrorCode[FD_WRITE_BIT] == 0)

             {

                  if (send(sockevent_set.sockall[soindex], "连接成功~", sizeof("连接成功~"), 0) == SOCKET_ERROR)

                  {

                      int FD_WRITEsenderr = WSAGetLastError();

                      printf("得到FD_WRITE操作send函数执行的错误码为:%d\n", FD_WRITEsenderr);

                      continue;

                  }

             }

             else

             {

                  printf("得到FD_WRITE操作的错误码为:%d\n", NetworkEvents.iErrorCode[FD_WRITE_BIT]);

                  continue;

             }

         }

##### 7.4.5如果是FD_READ操作码

if (NetworkEvents.lNetworkEvents & FD_READ)

         {

             //判断错误码对应位是否有值,没有说明SOCKET没有错误

             if (NetworkEvents.iErrorCode[FD_READ_BIT] == 0)

             {

                  char strRecv[1500] = { 0 };

                  if (recv(sockevent_set.sockall[soindex], strRecv, sizeof(strRecv), 0) == SOCKET_ERROR)

                  {

                      int FD_READrecverr = WSAGetLastError();

                      printf("得到FD_READ操作recv函数执行的错误码为:%d\n", FD_READrecverr);

                      continue;

                  }

                  //打印接收的信息

                  printf("接收的消息为:%s\n", strRecv);

             }

             else

             {

                  printf("得到FD_READ操作的错误码为:%d\n", NetworkEvents.iErrorCode[FD_READ_BIT]);

                  continue;

             }

         }
复制代码

7.4.5如果是FD_CLOSE操作码

if (NetworkEvents.lNetworkEvents & FD_CLOSE)

         {

             printf("FD_CLOSE操作\n");

             printf("得到FD_CLOSE操作的错误码为:%d\n", NetworkEvents.iErrorCode[FD_CLOSE_BIT]);

 

             //清理下线的客户端套接字

             closesocket(sockevent_set.sockall[soindex]);

             sockevent_set.sockall[soindex] = sockevent_set.sockall[sockevent_set.count - 1];

             //清理下线的客户端事件

             WSACloseEvent(sockevent_set.evnetall[soindex]);

             sockevent_set.evnetall[soindex] = sockevent_set.evnetall[sockevent_set.count - 1];

 

             sockevent_set.count--;

 

         }
//释放fd_sockevent_set中的事件和SOCKET句柄

for (int i = 0; i < sockevent_set.count; i++)

    {

         WSACloseEvent(sockevent_set.evnetall[i]);

         closesocket(sockevent_set.sockall[i]);

    }
    WSACleanup();

    system("pause");
复制代码

客户端

可以查看以前文章Select(TCP)模型

运行结果

image.png

事件选择模型相对于SELECT模型有什么不同?

Select(选择)模型是Winsock中最常见的I/O模型。之所以称其为“Select模型”,是由于它的“中心思想”便是利用select函数,实现对I/O的管理。最初设计该模型时,主要面向的是某些使用UNIX操作系统的计算机,它们采用的是Berkeley套接字方案。Select模型已集成到Winsock 1.1中,它使那些想避免在套接字调用过程中被无辜“锁定”的应用程序,采取一种有序的方式,同时进行对多个套接字的管理。由于Winsock 1.1向后兼容于Berkeley套接字实施方案,所以假如有一个Berkeley套接字应用使用了select函数,那么从理论角度讲,毋需对其进行任何修改,便可正常运行。

事件选择模型是Winsock提供的另一个有用的异步I/O模型。和WSAAsyncSelect模型类似的是,它也允许应用程序在一个或多个套接字上,接收以事件为基础的网络事件通知。由WSAAsyncSelect模型采用的网络事件来说,它们均可原封不动地移植到新模型。在用新模型开发的应用程序中,也能接收和处理所有那些事件。该模型最主要的差别在于网络事件会投递至一个事件对象句柄,而非投递至一个窗口例程。

おすすめ

転載: juejin.im/post/7034902221909131295