目录
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){//创建了无效的socketint 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;//参数2wsabuf.buf = g_strRecv;wsabuf.len = MAX_RECV_COUNT;DWORD dwRecvCount;//参数4DWORD dwFlag = 0;//参数5int 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;//参数2wsabuf.buf = "你好";wsabuf.len = MAX_RECV_COUNT;DWORD dwSendCount;//参数4DWORD dwFlag = 0;//参数5int 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;}//第二步 创建socketSOCKET socketServer = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED);if (socketServer == INVALID_SOCKET){//创建了无效的socketint 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];//循环控制变量-1i--;//下标减一g_count--;}continue;}if (0 == i){Postsend(g_count);printf("accept\n");//完成连接//投递recvPostRecv(g_count);//根据情况投递send//客户端适量++g_count++;//投递acceptPostAccept();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];//循环控制变量-1i--;//下标减一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){//立即完成//投递recvPostRecv(g_count);//根据情况投递send//客户端适量++g_count++;//投递acceptPostAccept();//递归完成return 0;}else{int a = WSAGetLastError();if (a == ERROR_IO_PENDING){//异步等待return 0;}else{//出错return 0;}}
}int PostRecv(int index)
{WSABUF wsabuf;//参数2wsabuf.buf = g_strRecv;wsabuf.len = MAX_RECV_COUNT;DWORD dwRecvCount;//参数4DWORD dwFlag = 0;//参数5int 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;//参数2wsabuf.buf = "你好";wsabuf.len = MAX_RECV_COUNT;DWORD dwSendCount;//参数4DWORD dwFlag = 0;//参数5int 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 1024SOCKET 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;}//第二步 创建socketSOCKET 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;}//循环处理每一个socketwhile (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");//投递RecvPostRecv(g_count);//数量增加g_count++;//继续投递PostAccept();}Clear();WSACleanup();return 0;
}int PostAccept()
{while (1){//手动创建socketg_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)//表示立即完成{//接收信息//投递RECVPostRecv(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);//把内存置为0memset(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;}}
}