重叠IO

#include <winsock2.h>
#include <windows.h>
#define PORT 6000
#define MSGSIZE 1024
#pragma comment (lib, "Ws2_32.lib")
BOOL WinSockInit()
{
WSADATA data = { 0 };
if (WSAStartup(MAKEWORD(2, 2), &data))
return FALSE;
if (LOBYTE(data.wVersion) != 2 || HIBYTE(data.wVersion) != 2){
WSACleanup();
return FALSE;
}
return TRUE;
}

typedef struct
{
WSAOVERLAPPED overlap;  //第一个成员。。。重叠IO,必须要用OVERLAPPED
WSABUF Buffer;  //发送长度或者接受的时候允许的最大长度
char szMessage[MSGSIZE];  
DWORD NumberOfBytesRecvd;  //保存接收到的字节数
DWORD Flags;  //保存操作类型 收 OR 发
}PER_IO_OPERATION_DATA, *LPPER_IO_OPERATION_DATA;

int g_iTotalConn = 0;
SOCKET g_CliSocketArr[MAXIMUM_WAIT_OBJECTS];
WSAEVENT g_CliEventArr[MAXIMUM_WAIT_OBJECTS];
LPPER_IO_OPERATION_DATA g_pPerIODataArr[MAXIMUM_WAIT_OBJECTS];


DWORD WINAPI WorkerThread(LPVOID);
void Cleanup(int);

int _tmain()
{
SOCKET sListen, sClient;
SOCKADDR_IN local, client;
DWORD dwThreadId;
int iaddrSize = sizeof(SOCKADDR_IN);
// 初始化windows socket库
WinSockInit();
// 创建监听socket
sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); //默认设置 WSA_FLAG_OVERLAPPED
// 绑定
local.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
local.sin_family = AF_INET;
local.sin_port = htons(PORT);
bind(sListen, (struct sockaddr *)&local, sizeof(SOCKADDR_IN));
// 监听
listen(sListen, 3);
// 创建工作者线程
CreateThread(NULL, 0, WorkerThread, NULL, 0, &dwThreadId);
while (TRUE)
{
// 接受连接
sClient = accept(sListen, (struct sockaddr *)&client, &iaddrSize);
printf("Accepted client:%s:%d\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));
g_CliSocketArr[g_iTotalConn] = sClient;

// 分配一个单io操作数据结构
g_pPerIODataArr[g_iTotalConn] = (LPPER_IO_OPERATION_DATA)HeapAlloc(
GetProcessHeap(),
HEAP_ZERO_MEMORY,
sizeof(PER_IO_OPERATION_DATA));

g_pPerIODataArr[g_iTotalConn]->Buffer.len = MSGSIZE;
g_pPerIODataArr[g_iTotalConn]->Buffer.buf = g_pPerIODataArr[g_iTotalConn]->szMessage;
g_CliEventArr[g_iTotalConn] = g_pPerIODataArr[g_iTotalConn]->overlap.hEvent = WSACreateEvent();
// 开始一个异步操作
WSARecv(
g_CliSocketArr[g_iTotalConn],
&g_pPerIODataArr[g_iTotalConn]->Buffer,
1,
&g_pPerIODataArr[g_iTotalConn]->NumberOfBytesRecvd,
&g_pPerIODataArr[g_iTotalConn]->Flags, 
&g_pPerIODataArr[g_iTotalConn]->overlap,
NULL);
//WSARecv的参数中都有一个 Overlapped 参数,我们可以假设是把我们的WSARecv这样的操作“绑定”到这个重叠结构上,
//提交一个请求,而不是将操作立即完成,其他的事情就交给重叠结构去做,而其中重叠结构又要与Windows的事件对象“绑定”在一起,
// 这样我们调用完 WSARecv 以后就可以“坐享其成”,等到重叠操作完成以后,自然会有与之对应的事件来通知我们操作完成,
//然后我们就可以来根据重叠操作的结果取得我们想要的数据了。

g_iTotalConn++;
}

closesocket(sListen);
WSACleanup();
return 0;
}
DWORD WINAPI WorkerThread(LPVOID lpParam)
{
int ret, index;
DWORD cbTransferred;
while (TRUE)
{
//判断出一个重叠 I/O 调用是否完成
ret = WSAWaitForMultipleEvents(g_iTotalConn, g_CliEventArr, FALSE, 1000, FALSE);
if (ret == WSA_WAIT_FAILED || ret == WSA_WAIT_TIMEOUT)
continue;
index = ret - WSA_WAIT_EVENT_0;
WSAResetEvent(g_CliEventArr[index]);//手动设置为 未传信
WSAGetOverlappedResult(
g_CliSocketArr[index],
&g_pPerIODataArr[index]->overlap,
&cbTransferred,
TRUE,
&g_pPerIODataArr[g_iTotalConn]->Flags);//判断该重叠调用到底是成功,还是失败
if (cbTransferred == 0)
Cleanup(index);//客户端连接关闭
else
{
// g_pPerIODataArr[index]->szMessage保存了接受到的数据
g_pPerIODataArr[index]->szMessage[cbTransferred] = '\0';
send(g_CliSocketArr[index], g_pPerIODataArr[index]->szMessage, cbTransferred, 0);
// 进行另一个异步操作
WSARecv(g_CliSocketArr[index],
&g_pPerIODataArr[index]->Buffer,
1,
&g_pPerIODataArr[index]->NumberOfBytesRecvd,
&g_pPerIODataArr[index]->Flags,
&g_pPerIODataArr[index]->overlap,
NULL);
}
}
return 0;
}
void Cleanup(int index)
{
closesocket(g_CliSocketArr[index]);
WSACloseEvent(g_CliEventArr[index]);
HeapFree(GetProcessHeap(), 0, g_pPerIODataArr[index]);
if (index < g_iTotalConn - 1)
{
g_CliSocketArr[index] = g_CliSocketArr[g_iTotalConn - 1];
g_CliEventArr[index] = g_CliEventArr[g_iTotalConn - 1];
g_pPerIODataArr[index] = g_pPerIODataArr[g_iTotalConn - 1];
}
g_pPerIODataArr[--g_iTotalConn] = NULL;
}

首先需要初始化网络环境,然后创建监听socket,绑定,监听,然后死循环,accpet,接受连接,代码中g_CliSocketArr是一个SOCKET数组,用来保存客户端连接的socket,g_iTotalConn是用来保存连接的数量,接下来我们创建了一个结构体LPPER_IO_OPERATION_DATA,简单看一下结构体的成员,第一个参数是WSAOVERLAPPED,这个是重叠IO的核心,必须要使用,因为其结构中的WSAEVENT会在后面使用到,第二个参数是发送长度或者接受的时候允许的最大长度,第三个是保存的字符szBuff,第四个是保存接收到的字节数,第五个是一个标签,用来表明此IO操作时接收还是发送。
然后HeapAlloc在栈上申请一片内存,其次把结构体中的成员进行赋值,这里要注意,g_CliEventArr是一个WSAEVENT,参见EventSelect
,然后需要把g_pPerIODataArr中的overlap.hEvent这个成员事件初始化,也就是WSACreatEvent这个函数,把信号量变成无。
接下来是一个异步操作,类似图中所说,我只需要接听,然后剩下的事情都交给“秘书”操办即可,我只用接电话就可以了,这里的“秘书”就是操作系统
这里写图片描述

这里要使用一个WSARecv函数,这个函数并不是阻塞函数,他会立即执行然后继续完成下面的代码,这个函数的第一个参数是SOCKET,客户端的Socket,第二个是一个LPWSABuff的参数,传入g_pPerIODataArr,第三个参数是有多少个WSABuff,给个1即可,第四个参数是接收到的Buffer长度,传入g_pPerIODataArr的NumberOfBytesRecvd这个成员,第五个参数是Flags标签,第六个很重要是一个LPWSAOVERLAPPED类型,我们需要传入overlap,第七个传入NULL,然后g_iTotalConn++,开始下一个循环。
我们在循环之前创建了一个工作线程,那么工作线程中 第一个函数WSAWaitForMultipleEvents,等待信号,当主线程收到信号后,这里的就会接收到,然后返回数组中是哪一个位置发生的信号,这个时候需要手动将信号设为无信号,也就是WSAResetEvent,如果不设置,会导致下一次接受的时候是有信号的,出现BUG,然后我们使用WSAGetOverlapperdResult这个函数,返回网络事件的结果,第一个参数是SOCKET,第二个是LPWSAOVERLAPPED,传入overlap,第三个是接收到的长度,第四个参数是BOOL型,TRUE不返回,如果FALSE,可以用WASGetLastError返回错误,第五个是标签,没什么用。
然后判断一下,如果接收到的数据长度为0,关闭客户端连接,否则,这里 g_pPerIODataArr[index]->szMessage[cbTransferred] 保存了接收到的数据,在这个数据最后加一个’\0’,然后你可以做你想做的事情,比如发送数据包给子客户端,这里不需要在使用异步操作,直接使用Send发送即可,最后还需要使用一个WSARecv函数,其中的参数和主线程一样,这里必须要是进行一次异步操作,不然会导致主线程的异步操作出错,最后Cleanup,清空处理。
总体来说重叠IO的模型已经将能简化的简化到最低底线了。就相当于老板和小秘,老板只需要接电话打电话告诉秘书,至于做的事情就是LPPER_IO_OPERATION_DATA ,告诉了小秘那么要做的事情就直接让小秘去做,就可以了。

重叠IO的缺点:
工作者线程与监听者线程之间的互斥。
工作者线程与工作者线程之间的同步。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值