UDP.6.重叠IO模型:完成例程
Posted oldmao_2001
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了UDP.6.重叠IO模型:完成例程相关的知识,希望对你有一定的参考价值。
https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-socket
基于UDP的网络编程还有5种模型:
SELECT模型
事件选择模型
异步选择模型
重叠IO模型
完成端口模型
这节讲基于UDP的重叠IO模型:完成例程。
理论部分不啰嗦了,可以看TCP的完成例程的内容
直接上逻辑及代码:
重叠IO模型:完成例程代码逻辑
和之前重叠IO模型:事件通知的服务器端代码前面部分是一样的:
1、包含网络头文件网络库
2、打开网络库
3、校验版本
4、创建WSASocket
5、绑定地址与端口
6、创建重叠IO事件对象
f1、PostRecvFrom
7、循环
7.1、等待信号WSAWaitForMultipleEvents
WSAWaitForMultipleEvents的最后一个参数必须是TRUE
这里对于完成例程而言成功会返回WSA_WAIT_IO_COMPLETION
7.2、有信号,获取重叠结构上的信息(WSAGetOverlappedResult)
7.3、信号置空
7.4、处理
f1.PostRecvFrom
f1.1、对客户端套接字投递WSARecvFrom,指定回调函数OnRecv
f1.2、立即完成,转f1
f1.3、异步完成,返回
f2.PostSendTo
f2.1、对客户端套接字投递WSASendTo,指定回调函数OnSend
f2.2、立即完成,返回
f2.3、异步完成,返回
回调函数介绍
既然伪代码差不多,因此具体代码实现和上一节差不多,这里只看改什么东西。
下看WSARecvFrom
int WSAAPI WSARecv(
SOCKET s,
LPWSABUF lpBuffers,
DWORD dwBufferCount,
LPDWORD lpNumberOfBytesRecvd,
LPDWORD lpFlags,
LPWSAOVERLAPPED lpOverlapped,
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
);
最后一个参数,在事件通知中设置的是NULL,这里完成例程中要设置为要绑定的回调函数。
回调函数的定义为:
typedef
void
(CALLBACK * LPWSAOVERLAPPED_COMPLETION_ROUTINE)(
DWORD dwError,
DWORD cbTransferred,
LPWSAOVERLAPPED lpOverlapped,
DWORD dwFlags
);
从名字上可以推断这是一个回调函数指针。具体可以看这里:
https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nc-winsock2-lpwsaoverlapped_completion_routine
稍微解释一下:
void:代表没有返回值
CALLBACK:代表这个函数是一个回调函数(具体的调用约定可以转到定义自己看),后面接一个自己起的函数名字。
参数1:错误码,UDP不用额外判断10054
参数2:发送或者接收到的字节数,UDP没有字节数为0的情况
参数3:重叠结构
参数4:函数执行方式,这个和WSARecvFrom、WSASendTo的参数5意思是一样的。
需要说一下,这个回调函数是由系统自动调用的,是执行完下面这个函数自动调用:
BOOL WSAAPI WSAGetOverlappedResult(
SOCKET s,
LPWSAOVERLAPPED lpOverlapped,
LPDWORD lpcbTransfer,
BOOL fWait,
LPDWORD lpdwFlags
);
因此二者有很多联系,注意看下面代码中参数的对应关系。
回调函数 | WSAGetOverlappedResult |
---|---|
DWORD dwError | WSAGetOverlappedResult 的错误码就是回调函数产生的错误码 |
DWORD cbTransferred | LPDWORD lpcbTransfer |
DWORD dwFlags | LPDWORD lpdwFlags |
LPWSAOVERLAPPED lpOverlapped | LPWSAOVERLAPPED lpOverlapped |
在回调函数中的处理流程和之前的接收到信号后的流程差不多。
回调函数的实现
下面看收数据的回调函数:
void CALLBACK OnRecv(DWORD dwError, DWORD cbTransferred, LPWSAOVERLAPPED lpOverlapped, DWORD dwFlags)
{
WSAResetEvent(lpOverlapped->hEvent);
if (0 != dwError)
{
printf("OnRecv有错误码:\\n%d", dwError);
return;
}
if (cbTransferred > 0)//有收到数据
{
printf("收到:%s\\n", recvBuff);
recvBuff[0] = 0;
//struct sockaddr_in* temp = (struct sockaddr_in*)&gsa;
printf("recvport:%d\\n", ntohs(gsi.sin_port));
PostSendTo(&gsi);
printf("recvportraw:%d\\n", gsi.sin_port);
int x = PostRecvFrom();
printf("xxx:%d\\n", x);
}
}
整体代码
注意注释中的标注与代码逻辑的对应关系
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <stdio.h>
#include <tchar.h>
//1、包含网络头文件网络库
# include <WinSock2.h>
# pragma comment(lib, "Ws2_32.lib")
SOCKET socketServer;
WSAOVERLAPPED wolServer;
char recvBuff[548];
struct sockaddr_in gsi;
int PostRecvFrom();
int PostSendTo(struct sockaddr_in* psi);
//处理强制关闭事件
BOOL WINAPI CtrlFun(DWORD dwType)
{
switch (dwType)
{
case CTRL_CLOSE_EVENT:
//关闭服务器事件句柄
WSACloseEvent(wolServer.hEvent);
//关闭服务器socket
closesocket(socketServer);
//关闭网络库
WSACleanup();
break;
}
return FALSE;
}
int main()
{
//投递关闭事件
SetConsoleCtrlHandler(CtrlFun, TRUE);
WORD wVersionRequested = MAKEWORD(2, 2);//版本
WSADATA wsaDATA;
//2、打开网络库
int iret = WSAStartup(wVersionRequested, &wsaDATA);
if (iret != 0)
{
//有错
switch (iret)
{
case WSASYSNOTREADY:
printf("解决方案:重启。。。");
break;
case WSAVERNOTSUPPORTED:
printf("解决方案:更新网络库");
break;
case WSAEINPROGRESS:
printf("解决方案:重启。。。");
break;
case WSAEPROCLIM:
printf("解决方案:网络连接达到上限或阻塞,关闭不必要软件");
break;
case WSAEFAULT:
printf("解决方案:程序有误");
break;
}
return 0;
}
printf("WSAStartup成功\\n");
//3、校验版本,只要有一个不是2,说明系统不支持我们要的2.2版本
if (2 != HIBYTE(wsaDATA.wVersion) || 2 != LOBYTE(wsaDATA.wVersion))
{
printf("版本有问题!");
WSACleanup();//关闭网络库
return 0;
}
printf("校验版本成功\\n");
// 4、创建WSASocket
socketServer = WSASocket(AF_INET, SOCK_DGRAM, IPPROTO_UDP, NULL, 0, WSA_FLAG_OVERLAPPED);
if (INVALID_SOCKET == socketServer)
{
//清理网络库,不关闭句柄
WSACleanup();
return 0;
}
printf("创建SOCKET成功\\n");
//5、绑定地址与端口
struct sockaddr_in lsi;
lsi.sin_family = AF_INET;//这里要和创建SOCKET句柄的参数1类型一样
lsi.sin_port = htons(9527);//用htons宏将整型转为端口号的无符号整型
lsi.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
printf("lsiport:%d\\n", ntohs(lsi.sin_port));
if (bind(socketServer, (struct sockaddr*)&lsi, sizeof(gsi)) == SOCKET_ERROR)
{
int err = WSAGetLastError();//取错误码
printf("服务器bind失败错误码为:%d\\n", err);
closesocket(socketServer);//释放
WSACleanup();//清理网络库
}
printf("绑定SOCKET成功\\n");
//6.创建事件对象
wolServer.hEvent = WSACreateEvent();
if (WSA_INVALID_EVENT == wolServer.hEvent)
{
int err = WSAGetLastError();//取错误码
printf("创建事件对象失败错误码为:%d\\n", err);
closesocket(socketServer);//与4、创建SOCKET对应,如果有创建客户端SOCKET句柄,也要关闭
//清理网络库,不关闭句柄
WSACleanup();
return 0;
}
printf("创建事件对象成功\\n");
//f1.投递WSARecvFrom
if (0 == PostRecvFrom())
{
int err = WSAGetLastError();//取错误码
printf("PostRecvFrom失败错误码为:%d\\n", err);
WSACloseEvent(wolServer.hEvent);//与6.创建事件对象对应
closesocket(socketServer);//与4、创建SOCKET对应,如果有创建客户端SOCKET句柄,也要关闭
WSACleanup();
system("pause");
return 0;
}
printf("投递WSARecvFrom成功\\n");
while (1)//循环
{
printf("等信号\\n");
int nRet = WSAWaitForMultipleEvents(1, &wolServer.hEvent, FALSE, WSA_INFINITE, TRUE);
printf("有信号...\\n");
if (WSA_WAIT_IO_COMPLETION == nRet)//成功
{
printf("WSAWaitForMultipleEvents成功\\n");
continue;
}
if (WSA_WAIT_FAILED == nRet)//失败
{
printf("WSAWaitForMultipleEventss失败\\n");
break;
}
}
WSACloseEvent(wolServer.hEvent);//与6.创建事件对象对应
closesocket(socketServer);//与4、创建SOCKET对应,如果有创建客户端SOCKET句柄,也要关闭
//关闭网络库
WSACleanup();
system("pause");
return 0;
}
void CALLBACK OnRecv(DWORD dwError, DWORD cbTransferred, LPWSAOVERLAPPED lpOverlapped, DWORD dwFlags)
{
WSAResetEvent(lpOverlapped->hEvent);
if (0 != dwError)
{
printf("OnRecv有错误码:\\n%d", dwError);
return;
}
if (cbTransferred > 0)//有收到数据
{
printf("收到:%s\\n", recvBuff);
recvBuff[0] = 0;
//struct sockaddr_in* temp = (struct sockaddr_in*)&gsa;
printf("recvport:%d\\n", ntohs(gsi.sin_port));
PostSendTo(&gsi);
printf("recvportraw:%d\\n", gsi.sin_port);
int x = PostRecvFrom();
printf("xxx:%d\\n", x);
}
}
//f1.投递WSARecvFrom
int PostRecvFrom()
{
WSABUF wbsBuff;
wbsBuff.buf = recvBuff;
wbsBuff.len = 548;//留出/0的位置
DWORD dwRecvCont = 0;//成功接收到的字节数
DWORD flag = 0;
int nlen = sizeof(gsi);
int nRet = WSARecvFrom(socketServer, &wbsBuff, 1, &dwRecvCont, &flag, (struct sockaddr*)&gsi, &nlen, &wolServer, OnRecv);
struct sockaddr_in* temp1 = (struct sockaddr_in*)&gsi;
printf("clientport:%d\\n", ntohs(temp1->sin_port));
printf("clientportraw:%d\\n", temp1->sin_port);
if (0 == nRet)
{
printf("%s\\n", recvBuff);
PostSendTo(&gsi);
//memset(recvBuff,0,548);//mark法1:清空接收缓存,逐位设置为0
recvBuff[0] = 0;//mark法2:为第一位设置特殊值
PostRecvFrom();//io3.1.1有客户端消息,系统空闲,立即完成,跳io3
}
else
{
int wasrecverr = WSAGetLastError();
if (ERROR_IO_PENDING == wasrecverr)//io3.2延迟完成,跳io3.3
{
printf("WSARecvFrom延迟完成\\n");
return 1;
}
}
return 0;
}
void CALLBACK OnSend(DWORD dwError, DWORD cbTransferred, LPWSAOVERLAPPED lpOverlapped, DWORD dwFlags)
{
printf("CALLBACK OnSend完成\\n");
}
//f2.根据需求对客户端套接字投递WSASend
int PostSendTo(struct sockaddr_in* psi)
{
WSABUF wbsBuff;
wbsBuff.buf = const_cast < char*> ("服务器调用PostSentTo发送给客户端\\n");
wbsBuff.len = 548;//留出/0的位置
DWORD dwRecvCont = 0;//成功接发送的字节数
int nlen = sizeof(struct sockaddr);
int nRet = WSASendTo(socketServer, &wbsBuff, 1, &dwRecvCont, 0, (const struct sockaddr*)psi, nlen, &wolServer, OnSend);
if (0 == nRet)
{
printf("WSASendTo执行完毕\\n");//io4.1有消息要发送,系统空闲,立即完成,跳io4
}
else
{
int wassendtoerr = WSAGetLastError();
if (ERROR_IO_PENDING == wassendtoerr)//io4.2延迟完成跳3.3
{
//printf("WSASendTo延迟完成\\n");
return 1;
}
}
return 0;
}
以上是关于UDP.6.重叠IO模型:完成例程的主要内容,如果未能解决你的问题,请参考以下文章