网络编程 重叠IO模型

目录

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;
		}
	}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

爱笑的蛐蛐

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

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

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

打赏作者

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

抵扣说明:

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

余额充值