初探重叠IO

本文介绍了Windows环境下基于事件的网络异步模型,重点讲解了重叠IO结构、事件对象的创建与等待、重置事件以及异步接收请求的实现。通过创建事件、绑定到重叠结构,利用WSAWaitForMultipleEvents函数等待多个事件,并在事件触发后进行数据处理。同时,展示了服务器端如何处理客户端连接和数据收发,以及客户端的简单实现。
摘要由CSDN通过智能技术生成

个人学习总结笔记 对于这一块也不算很通透,如果大家发现错误,一定要及时告诉我

1.基于重叠IO的网络异步模型

有两个写法,一个是基于完成例程的,一个是基于事件的,用完成例程的复杂而且效率不如完成端口,所以在此只介绍基于事件的。

2.windows下的事件

就是你创建一个事件对象,一般来说初始都是将该事件对象设置为无信号的,然后在该事件对象上绑定上你感兴趣的一个操作,然后调用事件等待函数等待该事件的发生,等待过程中线程是被挂起的(也就是说阻塞在了等待处),不耗费CPU的资源,当感兴趣的操作发生的时候,会将事件设置为有信号,内核会解除等待线程的阻塞,使其继续往下运行。

3.重叠结构

windows下所有的异步操作,都需要基于该重叠结构
在这里插入图片描述
前四个字段我们不用进行关心,起码初学者现在用不到,最后一个字段event,就是要绑定的事件。
每一个异步操作,都需要一个重叠结构,而每一个重叠结构都需要绑定一个事件。
当绑定了一个事件后,就需要等待该事件的发生。

4.等待事件发生函数

在这里插入图片描述

该函数的第一个参数是等待的事件个数,第二个参数是事件数组,一个线程最多等待的事件个数是64个,等需要监听的事件个数多的时候,需要多开线程,如果用户量达到上万的话,那么就需要很多线程,反而损失了效率,建议用完成端口,如果是就几百个的话,这个就足够用了
第三个参数是是否等待全部事件完成之后,才解除阻塞,填写false,不等待
第四个参数是超时时间,一般不要设置为永久等待,因为永久等待可能会造成死锁,因为如果后续有新的socket进来的话,你的等待事件个数还是上次的,后续的就只能等前面的事件发生时候,才能解除阻塞
第五个参数是完成例程使用的,这里直接设为false

5.事件 重新设为有信号

WSAResetEvent();

当WSAWaitForMultipleEvents函数解除阻塞之后,此时事件是有信号的状态,此时需要将该事件设置为无信号,以便进行下一次的等待。

6.获取等待的结果

在这里插入图片描述
第一个参数是等待的那个socket
第二个参数是为socket创建的那个重叠结构
第三个参数返回接受到的数据字节数
第四个参数设为true,等待该函数完成之后再返回
第五个参数是获取该结果的flag

7.异步的接收请求

在这里插入图片描述
第一个参数是在哪个socket上进行接收
第二个参数是WSABUF,是一个缓冲区结构体
第三个参数是缓冲区个数
第四个参数是要接收的字节数
第五个标志一般给0
第六个参数是给定一个重叠IO结构
第七个参数是完成端口使用的不关心,给NULL

8.封装重叠结构

根据上述异步接收请求来封装

typedef struct

{

     WSAOVERLAPPED overlap; //每一个socket连接需要关联一个WSAOVERLAPPED对象

     WSABUF Buffer; //与WSAOVERLAPPED对象绑定的缓冲区

     char szMessage[BUF_SIZE]; //初始化buffer的缓冲区

     DWORD NumberOfBytesRecvd; //指定接收到的字符的数目

     DWORD Flags;

}MY_WSAOVERLAPPED, * LPMY_WSAOVERLAPPED;

9.需要的数组

int g_TotalConnCount = 0; //总共的连接计数
sockaddr_in ClientArr[WSA_MAXIMUM_WAIT_EVENTS]; //客户信息的保存
LPMY_WSAOVERLAPPED g_OverLappedArr[WSA_MAXIMUM_WAIT_EVENTS];//重叠结构数组
HANDLE g_EventArr[WSA_MAXIMUM_WAIT_EVENTS];//事件数组
SOCKET g_SockArr[WSA_MAXIMUM_WAIT_EVENTS];//套接字数组

①一个socket的连接计数,记录当前有多少个客户连接到socket上
②全局客户socket信息数组,记录连接而来的客户信息和端口
③重叠结构数组,每来一个客户socket就需要一个重叠结构
④事件数组,每一个重叠结构就需要一个事件
⑤socket数组,记录socket
就是socket-事件-重叠 三个绑在一起

10服务器实现代码

#include <iostream>
#include <ws2tcpip.h>
#include <windows.h>

using namespace std;

#define MY_PORT 5050

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

#define BUF_SIZE 255

void Cleanup(int index);

typedef struct

{

     WSAOVERLAPPED overlap; //每一个socket连接需要关联一个WSAOVERLAPPED对象

     WSABUF Buffer; //与WSAOVERLAPPED对象绑定的缓冲区

     char szMessage[BUF_SIZE]; //初始化buffer的缓冲区

     DWORD NumberOfBytesRecvd; //指定接收到的字符的数目

     DWORD Flags;

}MY_WSAOVERLAPPED, * LPMY_WSAOVERLAPPED;

//重叠IO

//每一个套接字都需要一个重叠结构 每个重叠结构需要绑定一个事件
//事件要加入到事件数组中 因为WSAWaitForMultipleEvents

int g_TotalConnCount = 0; //总共的连接计数
sockaddr_in ClientArr[WSA_MAXIMUM_WAIT_EVENTS]; //客户信息的保存
LPMY_WSAOVERLAPPED g_OverLappedArr[WSA_MAXIMUM_WAIT_EVENTS];//重叠结构数组
HANDLE g_EventArr[WSA_MAXIMUM_WAIT_EVENTS];//事件数组
SOCKET g_SockArr[WSA_MAXIMUM_WAIT_EVENTS];//套接字数组




DWORD WINAPI ThreadProc(LPVOID lpParameter);

int main()
{
     SOCKET ListenSock;
     WSADATA wd;
     if (0 != WSAStartup(MAKEWORD(2, 2), &wd))
     {
          perror("WSAStartup: ");
          exit(-1);
     }
     
     
     

     if (INVALID_SOCKET == (ListenSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)))
     {
          perror("socket: ");
          exit(-1);
     }

     sockaddr_in SockAddr;
     SockAddr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
     SockAddr.sin_port = htons(MY_PORT);
     SockAddr.sin_family = AF_INET;

     if (SOCKET_ERROR == bind(ListenSock,(sockaddr *)&SockAddr, sizeof(sockaddr_in)))
     {
          perror("bind: ");
          exit(-1);
     }

     if (SOCKET_ERROR == listen(ListenSock, 5))
     {
          perror("listen: ");
          exit(-1);
     }
     
     int addrLen = sizeof(sockaddr_in);

     //创建工作线程
     CreateThread(NULL, 0, ThreadProc, NULL, NULL, NULL);

     while (1)
     {
          char szBuf[30];

          
          SOCKET NewSock = 
          accept(ListenSock, (sockaddr*)&ClientArr[g_TotalConnCount], &addrLen);
          if (INVALID_SOCKET == NewSock)
          {
               continue;
          }


          

          g_SockArr[g_TotalConnCount] = NewSock; //保存套接字

          //分配新的overlapped对象
          g_OverLappedArr[g_TotalConnCount] = 
          	(LPMY_WSAOVERLAPPED)malloc(sizeof(MY_WSAOVERLAPPED));
          g_OverLappedArr[g_TotalConnCount]->Flags = 0;
          g_OverLappedArr[g_TotalConnCount]->Buffer.len = BUF_SIZE;
          g_OverLappedArr[g_TotalConnCount]->Buffer.buf = 
          	g_OverLappedArr[g_TotalConnCount]->szMessage;

          //创建事件对象 将重叠对象 与 事件进行绑定
          g_EventArr[g_TotalConnCount] = 
          	g_OverLappedArr[g_TotalConnCount]->overlap.hEvent 
          		= WSACreateEvent();


          //将套接字 重叠对象进行绑定 并进行投递
          WSARecv(NewSock, &g_OverLappedArr[g_TotalConnCount]->Buffer, 1, 
          &g_OverLappedArr[g_TotalConnCount]->NumberOfBytesRecvd, 
          &g_OverLappedArr[g_TotalConnCount]->Flags, 
          &g_OverLappedArr[g_TotalConnCount]->overlap, NULL);

          

          

          inet_ntop(AF_INET, &(ClientArr[g_TotalConnCount].sin_addr), szBuf, 30);
          cout << "Client IP : " << szBuf <<" Port: "
          	<<ntohs(ClientArr[g_TotalConnCount].sin_port) << endl;

          g_TotalConnCount++;
     }

     closesocket(ListenSock);
     WSACleanup();

     return 0;
}


DWORD WINAPI ThreadProc(LPVOID lpParameter)
{
     int ret, Index;
     DWORD cbTransferred;

     while (1)
     {
          
          ret = WSAWaitForMultipleEvents(g_TotalConnCount, g_EventArr, FALSE, 100, FALSE);
          if (WSA_WAIT_FAILED == ret || WSA_WAIT_TIMEOUT == ret)
               continue;

          //获取下标
          Index = ret - WSA_WAIT_EVENT_0;

          //重新设置 无信号
          WSAResetEvent(g_EventArr[Index]);
         
          WSAGetOverlappedResult(g_SockArr[Index], &g_OverLappedArr[Index]->overlap, 
          &cbTransferred, TRUE, &g_OverLappedArr[Index]->Flags);
          if (0 == cbTransferred)
          {
               Cleanup(Index);//关闭客户端连接
          }
          else
          {

               //这里直接就转发回去了
               send(g_SockArr[Index], g_OverLappedArr[Index]->szMessage, cbTransferred, 0);
               // 进行另一个异步操作
               WSARecv(g_SockArr[Index],
                    &g_OverLappedArr[Index]->Buffer,
                    1,
                    &g_OverLappedArr[Index]->NumberOfBytesRecvd,
                    &g_OverLappedArr[Index]->Flags,
                    &g_OverLappedArr[Index]->overlap, NULL);
          }
     
     }



     return 0;
}


void Cleanup(int index)
{
     //EnterCriticalSection(&g_Data);
     char szBuf[30];
     inet_ntop(AF_INET, &(ClientArr[index].sin_addr), szBuf, 30);
     cout << "Client IP : " << szBuf << " Port: " 
     << ntohs(ClientArr[index].sin_port) << "Exit " << endl;


     //进行套接字关闭
     closesocket(g_SockArr[index]);
     //对事件的对象的关闭
     WSACloseEvent(g_EventArr[index]);
     free(g_OverLappedArr[index]);

     if (index < g_TotalConnCount - 1)
     {
          g_SockArr[index] = g_SockArr[g_TotalConnCount - 1];
          g_EventArr[index] = g_EventArr[g_TotalConnCount - 1];
          g_OverLappedArr[index] = g_OverLappedArr[g_TotalConnCount - 1];
     }

     g_OverLappedArr[--g_TotalConnCount] = NULL;


}

11 客户端实现代码

#include <iostream>
#include <windows.h>
using namespace std;

#define MY_PORT 5050

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



int main()
{
     WSADATA wd;
     WSAStartup(MAKEWORD(2, 2), &wd);

     SOCKET ConnectSock;

     ConnectSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
     if (INVALID_SOCKET == ConnectSock)
     {
          perror("socket: ");
          exit(-1);
     }

     sockaddr_in SockAddr;
     SockAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
     SockAddr.sin_family = AF_INET;
     SockAddr.sin_port = htons(MY_PORT);

     if (SOCKET_ERROR == connect(ConnectSock, (sockaddr*)&SockAddr, sizeof(SockAddr)))
     {
          perror("connect: ");
          exit(-1);
     }
     

     while (1)
     {
          char szBuf[255];
          cin >> szBuf;
          send(ConnectSock, szBuf, strlen(szBuf)+1, 0);

          //recv(ConnectSock, szBuf, 255, 0);
          //cout << szBuf << endl;
     }


     WSACleanup();

     return 0;
}
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值