目录
1.概念
2.代码详解
事件通知实现逻辑
1.WSASocket函数
2.AcceptEx函数
3.WSARecv函数
4.WSAGetOverlappedTesult函数
5.WSAResetEvent函数
6.WSASend函数
##重叠IO模型事件通知整体代码
完成例程实现逻辑编辑
##重叠IO模型完成例程的整体代码
1.概念
重叠IO模型是对C/S模型的直接优化,使用到的函数和概念如下图:
重叠IO的流程图
2.代码详解
事件通知实现逻辑
重叠IO模型中的代码与select模型,事件选择模型以及异步选择模型差别很大,使用的函数也不一样。
1.WSASocket函数
该函数创建绑定到特定传输服务提供程序的套接字,函数原型
SOCKET WSAAPI WSASocketA(
[in] int af,
[in] int type,
[in] int protocol,
[in] LPWSAPROTOCOL_INFOA lpProtocolInfo,
[in] GROUP g,
[in] DWORD dwFlags
);
前三个参数和socket函数的三个参数是一样的
参数4 lpProtocolInfo:设置套接字详细属性,是一个指向 WSAPROTOCOL_INFO 结构体的指针,一般填写NULL
参数5 g:该参数填写0
参数6 dwFlags:指定套接字属性,填写 WSA_FLAG_OVERLAPPED
返回值和socket函数一样
##代码样例
SOCKET socketServer = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED);
if (socketServer == INVALID_SOCKET)
{
//创建了无效的socket
int a = WSAGetLastError();//获取错误码
printf("创建出错\n");
WSACleanup();
return 0;
}
2.AcceptEx函数
该函数接受新连接,返回本地和远程地址,并接收客户端应用程序发送的第一个数据块,函数原型
BOOL AcceptEx(
[in] SOCKET sListenSocket,
[in] SOCKET sAcceptSocket,
[in] PVOID lpOutputBuffer,
[in] DWORD dwReceiveDataLength,
[in] DWORD dwLocalAddressLength,
[in] DWORD dwRemoteAddressLength,
[out] LPDWORD lpdwBytesReceived,
[in] LPOVERLAPPED lpOverlapped
);
参数1:填服务器socket
参数2:需要手动创建一个socket,并且填入,函数会把客户端发来的IP地址和端口号绑定在该socket上
参数3:填字符串数组,会接收到新连接上发来的第一个数据
参数4:一般设置成0,表示取消参数3的功能
参数5:填写字节长度,要比本地的最大传输协议地址长度大至少16个字节,即sizeof(struct sockaddr_in)+16
参数6:填写字节长度,要比远程的最大传输协议地址长度大至少16个字节,即sizeof(struct sockaddr_in)+16
参数7:接收新连接第一次发来的数据,配合参数3和4使用,填DWORD变量的地址
参数8:填重叠IO结构,填的是服务器对应的IO事件
返回值:如果返回TRUE,表示立即完成即同步,如果返回FALSE,错误码为 ERROR_IO_PENDING 表示异步等待,否则出错。
##代码样例
g_allsock[g_count] = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED);//手动创建一个socket,用来绑定客户端的ip和端口号
g_allOlp[g_count].hEvent = WSACreateEvent();//绑定事件
char str[1024] = { 0 };
DWORD dwRecvcount;
//返回值 返回TRUE表示立即完成,返回FALES,如果错误码是ERROR_IO_PENDING表示异步等待,否则出错
BOOL bRes = AcceptEx(g_allsock[0], g_allsock[g_count], str, 0, sizeof(struct sockaddr_in) + 16,
sizeof(struct sockaddr_in) + 16, &dwRecvcount, g_allOlp[g_count]);//接收连接
3.WSARecv函数
该函数投递异步接收消息,函数原型
int WSAAPI WSARecv(
[in] SOCKET s,
[in, out] LPWSABUF lpBuffers,
[in] DWORD dwBufferCount,
[out] LPDWORD lpNumberOfBytesRecvd,
[in, out] LPDWORD lpFlags,
[in] LPWSAOVERLAPPED lpOverlapped,
[in] LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
);
参数1:客户端socket
参数2:是WSABUF类型的结构体对象,把接收到的消息存放在结构体buf中
参数3:是WSABUF对象的个数
参数5:接收消息成功,会把字节数返回到该参数
参数5:用于修改WSARecv函数调用行为的标志的指针
参数6:重叠IO结构
参数7:回调函数,在这里填NULL
返回值:立即发生,会返回0,否则发生错误,如果错误码是 ERROR_IO_PENDING ,表示延迟处理。
##代码样例
int PostRecv(int index)
{
WSABUF wsabuf;//参数2
wsabuf.buf = g_strRecv;
wsabuf.len = MAX_RECV_COUNT;
DWORD dwRecvCount;//参数4
DWORD dwFlag = 0;//参数5
int wRes = WSARecv(g_allsock[index], &wsabuf, 1, &dwRecvCount, &dwFlag, &g_allOlp[index], NULL);
if (wRes == 0)
{
//立即完成
//输出信息
printf("%s\n", wsabuf.buf);
memset(g_strRecv, 0, MAX_RECV_COUNT);//把数组重新置0
//根据情况投递send
//继续对自己投递接收
PostRecv(index);
return 0;
}
else
{
int a = WSAGetLastError();
if (ERROR_IO_PENDING == a)
{
//延迟处理
}
else
{
//出错
}
}
}
4.WSAGetOverlappedTesult函数
该函数检索指定套接字上重叠操作的结果,即获取对应socket上的具体情况,函数原型
BOOL WSAAPI WSAGetOverlappedResult(
[in] SOCKET s,
[in] LPWSAOVERLAPPED lpOverlapped,
[out] LPDWORD lpcbTransfer,
[in] BOOL fWait,
[out] LPDWORD lpdwFlags
);
参数1:有发生信号的客户端socket
参数2:socket所对应的重叠结构
参数3:获取到发生或者接收到的实际字节数,返回0表示下线
参数4:填TRUE
参数5:装填WSARecv函数的参数5
返回值:成功返回TRUE,失败返回FALSE
##代码样例
WORD dwState;
WORD dwFlag;
BOOL bFlag = WSAGetOverlappedResult(g_allsock[i], &g_allOlp[i], &dwState, TRUE, &dwFlag);
5.WSAResetEvent函数
该函数重置指定事件对象的状态为无信号。函数原型
BOOL WSAAPI WSAResetEvent(
[in] WSAEVENT hEvent
);
参数传递需要重置的事件
返回值:成功返回TRUE,失败返回FALSE
6.WSASend函数
该函数在连接的套接字上发送数据,函数原型
int WSAAPI WSASend(
[in] SOCKET s,
[in] LPWSABUF lpBuffers,
[in] DWORD dwBufferCount,
[out] LPDWORD lpNumberOfBytesSent,
[in] DWORD dwFlags,
[in] LPWSAOVERLAPPED lpOverlapped,
[in] LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
);
参数和WSARecv的参数是一样的,在这里参数5就不需要取地址
返回值也是一样的
##代码样例
int Postsend(int index)
{
WSABUF wsabuf;//参数2
wsabuf.buf = "你好";
wsabuf.len = MAX_RECV_COUNT;
DWORD dwSendCount;//参数4
DWORD dwFlag = 0;//参数5
int wRes = WSASend(g_allsock[index], &wsabuf, 1, &dwSendCount, dwFlag, &g_allOlp[index], NULL);
if (wRes == 0)
{
//立即完成
printf("send succee\n");
return 0;
}
else
{
int a = WSAGetLastError();
if (ERROR_IO_PENDING == a)
{
//延迟处理
return 0;
}
else
{
//出错
return 0;
}
}
}
##重叠IO模型事件通知整体代码
#define _CRT_SECURE_NO_WARNINGS
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include<stdio.h>
#include<Winsock2.h>
#include<mswsock.h>//需要放在Winsock2.h下面
#pragma comment(lib, "ws2_32.lib")
#pragma comment(lib, "Mswsock.lib")
#define MAX_COUNT 1024
#define MAX_RECV_COUNT 1024
SOCKET g_allsock[MAX_COUNT];//socket数组
OVERLAPPED g_allOlp[MAX_COUNT];//事件数组
int g_count;
char g_strRecv[MAX_RECV_COUNT];
int PostAccept();//接收连接的函数
int PostRecv(int index);//接收消息
int Postsend(int index);//发送消息
//清理函数
void Clear()
{
for (int i = 0; i < g_count; i++)
{
closesocket(g_allsock[i]);
WSACloseEvent(g_allOlp[i].hEvent);
}
}
BOOL WINAPI fun(DWORD dwCtrlType)
{
switch (dwCtrlType)
{
case CTRL_CLOSE_EVENT:
//释放所有soket和事件
Clear();
break;
}
return TRUE;
}
int main()
{
SetConsoleCtrlHandler(fun, TRUE);
//第一步 打开网络库并校验版本
WORD wdVersion = MAKEWORD(2, 2);
WSADATA wdsockMsg;
int nRes = WSAStartup(wdVersion, &wdsockMsg);
if (nRes != 0)
{
printf("打开网络库失败\n");
return 0;
}
if (2 != HIBYTE(wdsockMsg.wVersion) || 2 != LOBYTE(wdsockMsg.wVersion))
{
printf("版本不对\n");
WSACleanup();
return 0;
}
//第二步 创建socket
SOCKET socketServer = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED);
if (socketServer == INVALID_SOCKET)
{
//创建了无效的socket
int a = WSAGetLastError();//获取错误码
printf("创建出错\n");
WSACleanup();
return 0;
}
//第三步 绑定ip地址和端口号
struct sockaddr_in si;
si.sin_family = AF_INET;
si.sin_port = htons(12332);
si.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
bind(socketServer, (const struct sockaddr*)&si, sizeof(si));
//第四步 开始监听
if (SOCKET_ERROR == listen(socketServer, SOCK_STREAM))
{
printf("监听失败\n");
closesocket(socketServer);
WSACleanup();
return 0;
}
//服务器socket绑定事件,并且放入到数组中
g_allsock[g_count] = socketServer;
g_allOlp[g_count].hEvent = WSACreateEvent();
g_count++;
if (0 != PostAccept())
{
Clear();
WSACleanup();
return 0;
}
while (1)
{
for (int i = 0; i < g_count; i++)
{
int wRes = WSAWaitForMultipleEvents(1, &(g_allOlp[i].hEvent), FALSE, 0, FALSE);//获取第i个事件的信号
if (wRes == WSA_WAIT_FAILED || wRes == WSA_WAIT_TIMEOUT)//出错或者超时
{
continue;
}
//有信号
WORD dwState;
WORD dwFlag;
BOOL bFlag = WSAGetOverlappedResult(g_allsock[i], &g_allOlp[i], &dwState, TRUE, &dwFlag);//获取socket上的对应情况
WSAResetEvent(g_allOlp[i].hEvent);//把信号置空
if (bFlag == FALSE)//出错
{
int a = WSAGetLastError();
if (a == 10054)
{
printf("force close\n");
//关闭
closesocket(g_allsock[i]);
WSACloseEvent(g_allOlp[i].hEvent);
//从数组中删掉
g_allsock[i] = g_allsock[g_count - 1];
g_allOlp[i] = g_allOlp[g_count - 1];
//循环控制变量-1
i--;
//下标减一
g_count--;
}
continue;
}
if (0 == i)
{
Postsend(g_count);
printf("accept\n");
//完成连接
//投递recv
PostRecv(g_count);
//根据情况投递send
//客户端适量++
g_count++;
//投递accept
PostAccept();
continue;
}
if (0 == dwState)
{
//客户端下线
printf("close\n");
//关闭
closesocket(g_allsock[i]);
WSACloseEvent(g_allOlp[i].hEvent);
//从数组中删掉
g_allsock[i] = g_allsock[g_count - 1];
g_allOlp[i] = g_allOlp[g_count - 1];
//循环控制变量-1
i--;
//下标减一
g_count--;
continue;
}
if (0 != dwState)
{
//表示发送或者接收成功
if (g_strRecv[0] != 0)//判断第一个元素不为空表示接收到消息
{
//输出信息
printf("%s\n", g_strRecv);
memset(g_strRecv, 0, MAX_RECV_COUNT);//把数组重新置0
//继续对自己投递接收
PostRecv(i);
}
}
}
}
closesocket(socketServer);
Clear();
WSACleanup();
return 0;
}
int PostAccept()
{
//手动创建一个socket,用来绑定客户端的ip和端口号
g_allsock[g_count] = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED);
g_allOlp[g_count].hEvent = WSACreateEvent();//绑定事件
char str[1024] = { 0 };
DWORD dwRecvcount;
//返回值 返回TRUE表示立即完成,返回FALES,如果错误码是ERROR_IO_PENDING表示异步等待,否则出错
BOOL bRes = AcceptEx(g_allsock[0], g_allsock[g_count], str, 0, sizeof(struct sockaddr_in) + 16,
sizeof(struct sockaddr_in) + 16, &dwRecvcount, &g_allOlp[0]);//接收连接
if (bRes == TRUE)
{
//立即完成
//投递recv
PostRecv(g_count);
//根据情况投递send
//客户端适量++
g_count++;
//投递accept
PostAccept();//递归完成
return 0;
}
else
{
int a = WSAGetLastError();
if (a == ERROR_IO_PENDING)
{
//异步等待
return 0;
}
else
{
//出错
return 0;
}
}
}
int PostRecv(int index)
{
WSABUF wsabuf;//参数2
wsabuf.buf = g_strRecv;
wsabuf.len = MAX_RECV_COUNT;
DWORD dwRecvCount;//参数4
DWORD dwFlag = 0;//参数5
int wRes = WSARecv(g_allsock[index], &wsabuf, 1, &dwRecvCount, &dwFlag, &g_allOlp[index], NULL);
if (wRes == 0)
{
//立即完成
//输出信息
printf("%s\n", wsabuf.buf);
memset(g_strRecv, 0, MAX_RECV_COUNT);//把数组重新置0
//根据情况投递send
//继续对自己投递接收
PostRecv(index);
return 0;
}
else
{
int a = WSAGetLastError();
if (ERROR_IO_PENDING == a)
{
//延迟处理
return 0;
}
else
{
//出错
return 0;
}
}
}
int Postsend(int index)
{
WSABUF wsabuf;//参数2
wsabuf.buf = "你好";
wsabuf.len = MAX_RECV_COUNT;
DWORD dwSendCount;//参数4
DWORD dwFlag = 0;//参数5
int wRes = WSASend(g_allsock[index], &wsabuf, 1, &dwSendCount, dwFlag, &g_allOlp[index], NULL);
if (wRes == 0)
{
//立即完成
printf("send succee\n");
return 0;
}
else
{
int a = WSAGetLastError();
if (ERROR_IO_PENDING == a)
{
//延迟处理
return 0;
}
else
{
//出错
return 0;
}
}
}
完成例程实现逻辑
在这个模型中的WSAWaitFormultipleEvents函数中的参数6需要改为TRUE,意义:将等待事件函数与完成例程有机制结合在一起,实现等待事件函数与完成例程函数的异步执行,执行完并给等待事件函数信号,即 WSAWaitFormultipleEvents函数不仅能获取事件的信号通知,还能获取完成例程的执行通知。函数WSARecv和函数WSASend都需要使用回调函数
回调函数需要使用系统给定的函数,函数原型
void
(CALLBACK * LPWSAOVERLAPPED_COMPLETION_ROUTINE)(
IN DWORD dwError,
IN DWORD cbTransferred,
IN LPWSAOVERLAPPED lpOverlapped,
IN DWORD dwFlags
);
参数1:获取到客户端socke的错误码
参数2:获取到客户端的发收字节数,为0表示客户端退出
参数3:获取到重叠IO的地址
参数4:表示函数执行的方式
##重叠IO模型完成例程的整体代码
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<WinSock2.h>
#include<mswsock.h>
#pragma comment(lib, "ws2_32.lib")
#pragma comment(lib, "Mswsock.lib")
#define MAX_COUNT 1024
#define MAX_RECV_COUNT 1024
SOCKET g_allsock[MAX_COUNT];
OVERLAPPED g_allIOp[MAX_COUNT];
int g_count;
char g_recvbuf[MAX_RECV_COUNT];//用来接收信息
int PostAccept();//投递连接
int PostRecv(int index);//投递接收信息
int PostSend(int index);
void Clear()
{
for (int i = 0; i < g_count; i++)
{
closesocket(g_allsock[i]);
WSACloseEvent(g_allIOp[i].hEvent);
}
}
int main()
{
//第一步 打开网络库并校验版本
WORD wdVersion = MAKEWORD(2, 2);
WSADATA wdSockMsg;
int nRes = WSAStartup(wdVersion, &wdSockMsg);
if (nRes != 0)
{
printf("打开网络库失败\n");
return 0;
}
if (HIBYTE(wdSockMsg.wVersion) != 2 || LOBYTE(wdSockMsg.wVersion) != 2)
{
printf("版本不对\n");
WSACleanup();
return 0;
}
//第二步 创建socket
SOCKET socketServer = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED);
if (socketServer == INVALID_SOCKET)
{
printf("创建socket失败\n");
WSACleanup();
return 0;
}
//第三步 绑定ip地址和端口号
struct sockaddr_in si;
si.sin_family = AF_INET;
si.sin_port = htons(12332);
si.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
if (SOCKET_ERROR == bind(socketServer, (const struct sockaddr*)&si, sizeof(si)))
{
printf("绑定出错\n");
closesocket(socketServer);
WSACleanup();
return 0;
}
//第四步 开始监听
if (SOCKET_ERROR == listen(socketServer, SOCK_STREAM))
{
printf("监听失败\n");
closesocket(socketServer);
WSACleanup();
return 0;
}
//第五步 重叠IO
//把socket和对应事件放入到数组中
g_allsock[g_count] = socketServer;
g_allIOp[g_count].hEvent = WSACreateEvent();
g_count++;
//投递连接
if (0 != PostAccept())
{
Clear();
WSACleanup();
return 0;
}
//循环处理每一个socket
while (1)
{
//接收消息
int wRes = WSAWaitForMultipleEvents(1, &(g_allIOp[0].hEvent), FALSE, WSA_INFINITE, TRUE);//获取信号
if (wRes == WSA_WAIT_FAILED || wRes == WSA_WAIT_IO_COMPLETION)
{
//获取信号错误
continue;
}
WSAResetEvent(g_allIOp[0].hEvent);//把信号置空
//PostSend(g_count);
//完成连接
printf("accept\n");
//投递Recv
PostRecv(g_count);
//数量增加
g_count++;
//继续投递
PostAccept();
}
Clear();
WSACleanup();
return 0;
}
int PostAccept()
{
while (1)
{
//手动创建socket
g_allsock[g_count] = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED);
g_allIOp[g_count].hEvent = WSACreateEvent();//绑定事件
if (g_allsock[g_count] == SOCKET_ERROR)//创建失败
{
return 0;
}
char str[1024] = { 0 };
DWORD dwRecvcount;
BOOL aRes = AcceptEx(g_allsock[0], g_allsock[g_count], str, 0, sizeof(struct sockaddr) + 16,
sizeof(struct sockaddr) + 16, &dwRecvcount, &g_allIOp[0]);
if (aRes == TRUE)//表示立即完成
{
//接收信息
//投递RECV
PostRecv(g_count);
//数量增加
g_count++;
//循环该客户端
//PostAccept();
continue;
}
else
{
int a = WSAGetLastError();
if (a == ERROR_IO_PENDING)
{
//异步等待
break;
}
else
{
//出错
break;
}
}
}
return 0;
}
void CALLBACK RecvCall(DWORD dwError, DWORD cbTransferred, LPWSAOVERLAPPED lpOverlapped, DWORD dwFlags)
{
int i = (int)(lpOverlapped - &g_allIOp[0]);//地址相减拿到下标
if (dwError == 10054 || cbTransferred == 0)//客户端退出
{
//关闭
printf("close\n");
closesocket(g_allsock[i]);
WSACloseEvent(g_allIOp[i].hEvent);
g_allsock[i] = g_allsock[g_count - 1];
g_allIOp[i] = g_allIOp[g_count - 1];
g_count--;
}
else
{
printf("%s\n", g_recvbuf);
memset(g_recvbuf, 0, MAX_RECV_COUNT);
PostRecv(i);
}
}
int PostRecv(int index)
{
WSABUF wsabuf;
wsabuf.buf = g_recvbuf;
wsabuf.len = MAX_RECV_COUNT;
DWORD dwRecvCount;
DWORD dwFlag = 0;
int wRes = WSARecv(g_allsock[index], &wsabuf, 1, &dwRecvCount, &dwFlag, &g_allIOp[index], RecvCall);
if (wRes == 0)
{
//立即完成
printf("%s\n", wsabuf.buf);
//把内存置为0
memset(g_recvbuf, 0, MAX_RECV_COUNT);
//继续投递
PostRecv(index);
return 0;
}
else
{
int a = WSAGetLastError();
if (a == WSA_IO_PENDING)
{
//已成功启动重叠的操作,延迟完成
return 0;
}
else
{
//出现错误
return 0;
}
}
}
void CALLBACK SendCall(DWORD dwError, DWORD cbTransferred, LPWSAOVERLAPPED lpOverlapped, DWORD dwFlags)
{
printf("send succee\n");
}
int PostSend(int index)
{
WSABUF wsabuf;
wsabuf.buf = "你好";
wsabuf.len = MAX_RECV_COUNT;
DWORD dwSendcount;
DWORD dFlag = 0;
int wRes = WSASend(g_allsock[index], &wsabuf, 1, &dwSendcount, dFlag, &g_allIOp[index], SendCall);
if (wRes == 0)
{
//立即完成发送
printf("send succee\n");
return 0;
}
else
{
int a = WSAGetLastError();
if (a == ERROR_IO_PENDING)
{
//延迟处理,异步等待
return 0;
}
else
{
//出现错误
return 0;
}
}
}