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 dwErrorWSAGetOverlappedResult 的错误码就是回调函数产生的错误码
DWORD cbTransferredLPDWORD lpcbTransfer
DWORD dwFlagsLPDWORD lpdwFlags
LPWSAOVERLAPPED lpOverlappedLPWSAOVERLAPPED 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;
}
重叠IO模型之OverLapped完成例程模型WSACompletionRoutineServer VS2010 基础入门 客户端与服务器端 客户端向服务器端发送数据 可接收多个客户端 #include #include #pragma comment (lib, "ws2_32.lib") #define PORT 8088 #define MSG_SIZE 1024 SOCKET g_sConnect; bool g_bConnect = false; typedef struct { WSAOVERLAPPED overLap; WSABUF wsaBuf; char chMsg[MSG_SIZE]; DWORD nRecvNum; DWORD nFlags; SOCKET sClient; }PRE_IO_OPERATION_DATA, *LP_PER_IO_OPERATION_DATA; void CALLBACK CompletionRoutine(DWORD dwError, DWORD dwTrans, LPWSAOVERLAPPED lpOverlap, DWORD nFlags); DWORD WINAPI workThread(LPVOID lp) { LP_PER_IO_OPERATION_DATA lpData; while(TRUE) { if (g_bConnect) // 有新的连接 { // 为lpData分配空间并初始化 lpData = (LP_PER_IO_OPERATION_DATA)HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(PRE_IO_OPERATION_DATA)); lpData->wsaBuf.len = MSG_SIZE; lpData->wsaBuf.buf = lpData->chMsg; lpData->sClient = g_sConnect; WSARecv(lpData->sClient, &lpData->wsaBuf, 1, &lpData->nRecvNum, &lpData->nFlags, &lpData->overLap, CompletionRoutine); g_bConnect = false; // 处理完毕 } SleepEx(1000, TRUE); } return 0; } // 系统在WSARecv收到信息后,自动调用此函数,并传入参数--回调函数 void CALLBACK CompletionRoutine(DWORD dwError, DWORD dwTrans, LPWSAOVERLAPPED lpOverlap, DWORD nFlags) { LP_PER_IO_OPERATION_DATA lpData = (LP_PER_IO_OPERATION_DATA)lpOverlap; if (0 != dwError) // 接收失败 { printf("Socket %d Close!\n", lpData->sClient); closesocket(lpData->sClient); HeapFree(GetProcessHeap(), 0, lpData); } else // 接收成功 { lpData->chMsg[dwTrans] = '\0'; send(lpData->sClient, lpData->chMsg, dwTrans, 0); printf("Socket:%d MSG: %s \n", lpData->sClient, lpData->chMsg); memset(&lpData->overLap, 0, sizeof(WSAOVERLAPPED)); lpData->wsaBuf.len = MSG_SIZE; lpData->wsaBuf.buf = lpData->chMsg; // 继续接收来自客户端的数据 实现 WSARecv与CompletionRoutine循环 WSARecv(lpData->sClient, &lpData->wsaBuf,1, &lpData->nRecvNum, &lpData->nFlags, &lpData->overLap, CompletionRoutine); } } int main() { WSADATA wsaData; WSAStartup(0x0202, &wsaData); SOCKET sListen; sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); sockaddr_in addrListen; addrListen.sin_family = AF_INET; addrListen.sin_port = htons(PORT); addrListen.sin_addr.S_un.S_addr = htonl(ADDR_ANY); int nErrorCode = 0; nErrorCode = bind(sListen, (sockaddr*)&addrListen, sizeof(sockaddr)); nErrorCode = listen(sListen, 5); DWORD nThreadID; CreateThread(NULL, 0, workThread, NULL, 0, &nThreadID); sockaddr_in addrConnect; int nAddrLen = sizeof(sockaddr_in); printf("Server Started!\n"); while(TRUE) { g_sConnect= accept(sListen, (sockaddr*)&addrConnect, &nAddrLen); if (INVALID_SOCKET == g_sConnect) { return -1; } g_bConnect = true; // 连接成功 printf("Accept Client :%s -- PORT:%d\n", inet_ntoa(addrConnect.sin_addr), htons(addrConnect.sin_port)); } return 0; }
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

oldmao_2000

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值