网络编程 基于tcp/ip协议的C/S模型

目录

1.socket编程的一些概念

2.tcp/ip协议的基本知识​编辑

3.网络编程代码

        1.头文件

        2.WSAStartup()函数的使用

        3.SOCKET函数的使用

        1.socket介绍

         2.socket()函数

4.bind()函数使用

5.listen()函数的使用

6.accept函数使用

7.recv()函数使用

8.send()函数使用

9.connect()函数的使用

10.模型缺点

11.tcp/ip代码


1.socket编程的一些概念

        socket作用:运行在计算机中的两个程序通过socket建立起一个通道,数据在通道中传输。socket提供了流(stream),是基于TCP协议,是一个有序、可靠、双向字节流的通道,传输数据不会丢失、不会重复、顺序也不会错乱。

        TCP与UDP的区别:
1.TCP基于连接与UDP无连接
2.对系统资源的要求(TCP 较多,UDP 少)
3.UDP 程序结构较简单
4.流模式(TCP)与数据报模式(UDP)
        TCP 保证数据正确性,UDP可能丢包
        TCP 保证数据顺序,UDP 不保证

具体编程时的区别
1.socket的参数不同
2.UDP Server不需要调用 listen 和accept

3.UDP 收发数据用 sendto/recvfrom函数

4.TCp:地址信息在 connect/accept 时确定,UDP :在sendtorecvfrom函数中每次均 需指定地址信息5.UDP : shutdown 函数无效

        socket中的一些名词解释:

多重协议支持: 通过SPI接口支持其他协议;

多重命名空间: 根据服务与主机名选择协议

重叠I/0模式: 增强I/0香叶量与提高性能

分散与聚合:从多个缓冲区发送与接收数据

有条件接受:有选择性地决定是否接受连接

套接字共享:多个进程共享一个套接字句柄

socket简单的通信流程

2.tcp/ip协议的基本知识

3.网络编程代码

        1.头文件

c++网络编程有两个版本的头文件,第一版 <WinSock.h>,第二版 <WinSock2.h>,现在使用一般都是使用第二个版本,网络库也有两个版本,第一版 "wsock32.lib", 第二版 "ws2_32.lib",这两个版本都能用

#include<WinSock.h>//第一版
#include<WinSock2.h>//第二版
#pragma comment(lib,"wsock32.lib")//第一版 网络库
#pragma comment(lib,"ws2_32.lib")//第二版

        2.WSAStartup()函数的使用

        该函数的作用是用进程启动网络库(Winsock DLL),函数的原型

int WSAAPI WSAStartup(
  [in]  WORD      wVersionRequested,
  [out] LPWSADATA lpWSAData
);

        WORD类型实际上是一个 unsigned short 类型,参数wVersionRequested需要指定网络库的版本号,参数lqWSAData是一个结构体类型,返回的各种版本信息会存入结构体中的成员。

        函数如果执行成功会返回一个0,失败会返回错误信息,返回的错误信息有一些几类

错误代码含义
WSASYSNOTREADY基础网络子系统尚未准备好进行网络通信
WSAVERNOTSUPPORTED此特定 Windows 套接字实现不提供请求的 Windows 套接字支持版本。
WSAEINPROGRESS正在执行阻止 Windows 套接字 1.1 操作。
WSAEPROCLIM已达到 Windows 套接字实现支持的任务数的限制。
WSAEFAULTlpWSAData 参数不是有效的指针。

代码样例

#include<stdio.h>
#include<WinSock2.h> //网络头文件 window socket 第二版
#pragma comment(lib,"ws2_32.lib")//包含网络库的名字, ws2_32.lib是网络库 名字 //不区分大小写
//#pragma comment(lib, "wsock32.lib") //第一版的库

int main()
{
	//WSAstartup函数有两个参数,一个是 WORD 类型,一个是 LPWSADATA 结构体类型 在文档中 LP 开头的变量表示需要的地址变量
	WORD wdVersion = MAKEWORD(2, 2);//选定版本号 这里表示2.2版本
	//LPWSADATA和WSADATA*等价
	//LPWSADATA lwq = malloc(sizeof(WSADATA));//创建一个堆区
	WSADATA wdSockMsg;
	int nRes = WSAStartup(wdVersion, &wdSockMsg);//有返回值,启动成功返回0
	if (nRes != 0)
	{//这里是5个错误码
		switch (nRes)
		{
		case WSASYSNOTREADY:
			printf("基础网络子系统尚未准备好进行网络通信");
			break;
		case WSAVERNOTSUPPORTED:
			printf("此特定 Windows 套接字实现不提供请求的 Windows 套接字支持版本");
			break;
		case WSAEINPROGRESS:
			printf("正在执行阻止 Windows 套接字 1.1 操作。 ");
			break;
		case WSAEPROCLIM:
			printf("已达到 Windows 套接字实现支持的任务数的限制。");
			break;
		case WSAEFAULT:
			printf("lpWSAData 参数不是有效的指针");
			break;
		}
	}
	//校验版本
    //HIBYTE表示高位:是主版本,LOBYTE表示地位:是副版本
	if (2 != HIBYTE(wdSockMsg.wVersion) || 2 != LOBYTE(wdSockMsg.wVersion))
	{
		//版本不对
		WSACleanup();//关闭网络库函数
		return 0;
	}

	system("pause");
	return 0;
}

        3.SOCKET函数的使用

        1.socket介绍

         2.socket()函数

该函数创建一个绑定到特定传输服务提供者的套接字,函数原型

SOCKET WSAAPI socket(
  [in] int af,
  [in] int type,
  [in] int protocol
);

 参数1 af:是地址的类型参数

参数名称含义例子
AF_INET 2表示IPV4的地址,4个字节,32位的地址192.168.179.132
AF_INET6 23表示IPV6的地址,16个字节,128位地址fh40::4fd5:cgya:85b:84a5%20

AF_BTH 32

蓝牙地址系列6D:2D:BC:AA:8G:11

参数2 type:套接字类型

类型含义
SOCK_STREAM 1一种套接字类型,使用OOB数据传输机制提供有序、可靠、双向、基于连接的字节流。这种套接字类型为Internet地址族(AF_INET或AF_INET6)使用传输控制协议(TCP)。
SOCK_DGRAM 2一种支持数据报的套接字类型,数据报是固定(通常是小的)最大长度的无连接、不可靠的缓冲区。这种套接字类型使用用户数据报协议(UDP)作为Internet地址族(AF_INET或AF_INET6)。
SOCK_RAW 3一种套接字类型,提供一个原始套接字,允许应用程序操作下一个上层协议头。要操作IPv4报头,必须在套接字上设置IP_HDRINCL套接字选项。要操作IPv6报头,必须在套接字上设置IPV6_HDRINCL套接字选项。
SOCK_RDM 4

提供可靠消息数据报的套接字类型。这种类型的一个例子是Windows中的实用通用多播(Pragmatic General Multicast, PGM)多播协议实现,通常称为可靠的多播编程。


该类型值仅在安装可靠组播协议时被支持。

SOCK_SEQPACKET 5一种套接字类型,提供基于数据报的伪流包。

参数3 protocol:协议的类型

类型含义
IPPROTO_TCP 6传输控制协议(TCP)。当af参数为AF_INET或AF_INET6,类型参数为SOCK_STREAM时,这个值是可能的。
IPPROTO_UDP 17用户数据报协议(UDP)。当af参数为AF_INET或AF_INET6,类型参数为SOCK_DGRAM时,这个值是可能的。
IPPROTO_IGMP 2

Internet组管理协议(IGMP)。当af参数为AF_UNSPEC、AF_INET或AF_INET6且类型参数为SOCK_RAW或未指定时,这个值是可能的。

IPPROTO_ICMP 1Internet控制报文协议(ICMP)。当af参数为AF_UNSPEC、AF_INET或AF_INET6且类型参数为SOCK_RAW或未指定时,这个值是可能的。

        返回值

socket函数如果成功返回一个可用的socket,如果失败会返回 INVALID_SOCKET ,表示失败,可以调用函数 WSAGetLastError() 获取错误码。

注意:在最后一定要调用函数 closesocket() 函数销毁socket。

代码样例

	//SOCKET的使用
	SOCKET socketServer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);//三个参数 如果成功会返回一个可以的socket,
																	//在最后一定要销毁socket套接字
	if (INVALID_SOCKET == socketServer)
	{
		int a = WSAGetLastError();//当socket函数绑定传输协议套接字失败,会调用该函数,返回错误码。
		WSACleanup();
		return 0;
	}
	closesocket(socketServer);//销毁套接字的函数

4.bind()函数使用

该函数将本地地址与套接字相关联。函数原型

int WSAAPI bind(
  [in] SOCKET         s,
  [in] const sockaddr *name,
  [in] int            namelen
);

参数1 SOCKET:是socket()函数返回的值

参数2 name:是ip地址

参数3 namelen:空间大小

        参数2 name 是 sockaddr类型,这是网络库中内置的结构体类型,如下

typedef struct sockaddr_in {
  short          sin_family;
  u_short        sin_port;
  struct in_addr sin_addr;
  char           sin_zero[8];
} SOCKADDR_IN, *PSOCKADDR_IN, *LPSOCKADDR_IN;

sin_family是一个与socker函数中第一个参数一样的变量

sin_port表示指定的端口号

sin_addr表示指定的ip地址

        返回值

函数执行成功会返回0,失败会返回一个SOCKET_ERROR,实际上是一个-1

bind函数使用实例

//bing函数的使用
struct sockaddr_in si;//定义一个结构体
si.sin_family = AF_INET;//需要和socket函数中的参数一致
si.sin_port = htons("12345");//指定的端口号
si.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");//指定ip地址
bind(socketServer, (const struct sockaddr*)&si, sizeof(struct sockaddr_in));

5.listen()函数的使用

该函数将套接字置于侦听传入连接的状态(就是等待客户端连接),函数原型

int WSAAPI listen(
  [in] SOCKET s,
  [in] int    backlog
);

参数1 s:是socket函数的返回值

参数2 backlog:挂起的连接队列的最大长度

        返回值:连接成功返回0,失败返回SOCKET_ERROR

listen函数使用实例

//listen()函数的使用
//listen(socketServer, SOMAXCONN);
if (SOCKET_ERROR == listen(socketServer, SOMAXCONN))
{
	//出错了
	int a = WSAGetLastError();
	//释放
	closesocket(socketServer);
	//释放网络库
	WSACleanup();
}

6.accept函数使用

该函数允许在套接字上尝试传入连接,即把接收到的客户端信息创建成socket,函数原型

SOCKET WSAAPI accept(
  [in]      SOCKET   s,
  [out]     sockaddr *addr,
  [in, out] int      *addrlen
);

参数1 s:是socket函数的返回值

参数2 addr:是sockaddr定义的结构体对象

参数3 addrlen:第一个参数的空间长度

        返回值:连接成功返回给客户端包好的socket,失败返回 INVALID_SOCKET,表示无效

注意:该函数在没有客户端连接时,会一直阻塞,直到有客户端连接

accept函数代码样例

	//accept()函数的使用
	//创建客户端连接
	struct sockaddr_in clientMsg;//定义sockaddr_in的结构体类型
	int len = sizeof(clientMsg);//作为参数传入到accept函数
	//accept(socketServer,NULL,NULL);//可以这样
	SOCKET socketClient = accept(socketServer, (struct sockaddr*)&clientMsg, &len);//成功返回一个客户端socket
	if (INVALID_SOCKET == socketClient)
	{
		//出错了
		int a = WSAGetLastError();
		//释放
		closesocket(socketServer);
		//释放网络库
		WSACleanup();
	}

7.recv()函数使用

该函数从连接的套接字或绑定的无连接套接字接收数据,即得到指定客户端发来的消息,本质是由协议本身去做的,也就是socket底层实现,函数原型

int WSAAPI recv(
  [in]  SOCKET s,
  [out] char   *buf,
  [in]  int    len,
  [in]  int    flags
);

参数1 s:是收到信息的socket

参数2 buf:收到信息时,内容的保存空间

参数3 len:每次接收信息的字节大小

参数4 flags:一般为0,表示在系统缓冲区中读完就删除缓冲,如果传入参数 MSG_WAITALL 表示接收到的信息必须是参数3 len的大小才可以执行,否则会被阻塞

        返回值:如果执行没有错误,返回的是收到的字节个数,如果连接正常关闭返回值为0,如果出现错误返回值SOCKET_ERROR,使用WSAGetLastError()函数可以拿到错误码

recv函数代码实例

	//recv()函数使用
	char buf[1500] = { 0 };//作为收到信息的存储空间
	int res = recv(socketClient, buf, 1499, 0);//执行成功会返回接收到信息的字节大小
	if (res == 0)
	{
		printf("连接中断,客户端下线\n");
	}
	else if (SOCKET_ERROR == res)
	{
		//出错了
		int a = WSAGetLastError();//拿到错误码
		//需要根据实际情况处理
	}
	else
	{
		printf("%d		%s\n", res, buf);
	}

8.send()函数使用

该函数在连接的套接字上发送数据,即把要发送的数据发送给客户端,函数原型

int WSAAPI send(
  [in] SOCKET     s,
  [in] const char *buf,
  [in] int        len,
  [in] int        flags
);

参数1 s:客户端socket

参数2 buf:要发送的字符数组

参数3 len:一次发送的字节个数

参数4 flags:一般为0

        返回值:发送成功返回发送的字节个数,发送失败返回SOCKET_ERROR

send函数实例

	//send()函数使用
	if (SOCKET_ERROR == send(socketClient, "abcd\0qwer", sizeof("abcd\0qwer"), 0))//成功会返
回发送出去的字节个数,一次发送出去的字节个数应该在15000以内
	{
		//发送不成功
		printf("发送出错\n");
		int a = WSAGetLastError();//拿到错误码
		//需要根据实际情况处理
	}

9.connect()函数的使用

该函数在客户端中使用,建立与指定套接字的连接,即连接指定ip地址和端口号的服务器,函数原型

int WSAAPI connect(
  [in] SOCKET         s,
  [in] const sockaddr *name,
  [in] int            namelen
);

参数1 s:socket函数返回的值

参数2 name:struct sockaddr定义的结构体对象

参数3 namelen:第二个参数所占用的空间长度

        返回值:连接成功返回0,不成功返回SOCKET_ERROR,需要使用WSAGetLastError()获取错误码,在工具中的错误查找,可以找到具体原因

connect函数代码实例

	//连接到服务器
	struct sockaddr_in serverMsg;
	serverMsg.sin_family = AF_INET;
	serverMsg.sin_port = htons("12345");
	serverMsg.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
	if (SOCKET_ERROR == connect(socketClient, (struct sockaddr*)&serverMsg, sizeof(serverMsg)))
	{
		int a = WSAGetLastError();//获取错误码
		printf("连接出错\n");
		closesocket(socketClient);
		WSACleanup();
		return 0;
	}

10.模型缺点

        客户端与服务器连接后,数据的传送是你一发,我一发的,这样就就造成如果一方没有发送数据,另一方就会死等的情况,如果在等待数据的过程中又有其它的连接请求,会出现无法处理的情况。

11.tcp/ip代码

服务端代码

#define _CRT_SECURE_NO_WARNINGS
#define _WINSOCK_DEPRECATED_NO_WARNINGS

#include<stdio.h>
#include<stdlib.h>
#include<WinSock2.h> //网络头文件 window socket 第二版
#pragma comment(lib,"ws2_32.lib")//包含网络库的名字, ws2_32.lib是网络库 名字 //不区分大小写
//#pragma comment(lib, "wsock32.lib") //第一版的库

int main()
{
	//第一步打开网络库
	//WSAstartup函数有两个参数,一个是 WORD 类型,一个是 LPWSADATA 结构体类型 在文档中 LP 开头的变量表示需要的地址变量
	WORD wdVersion = MAKEWORD(2, 2);//选定版本号 这里表示2.2版本
	//LPWSADATA和WSADATA*等价
	//LPWSADATA lwq = malloc(sizeof(WSADATA));//创建一个堆区
	WSADATA wdSockMsg;
	int nRes = WSAStartup(wdVersion, &wdSockMsg);//有返回值,启动成功返回0
	if (nRes != 0)
	{//这里是5个错误码
		switch (nRes)
		{
		case WSASYSNOTREADY:
			printf("基础网络子系统尚未准备好进行网络通信");
			break;
		case WSAVERNOTSUPPORTED:
			printf("此特定 Windows 套接字实现不提供请求的 Windows 套接字支持版本");
			break;
		case WSAEINPROGRESS:
			printf("正在执行阻止 Windows 套接字 1.1 操作。 ");
			break;
		case WSAEPROCLIM:
			printf("已达到 Windows 套接字实现支持的任务数的限制。");
			break;
		case WSAEFAULT:
			printf("lpWSAData 参数不是有效的指针");
			break;
		}
		printf("打开网络库出错\n");
		return 0;
	}
	//第二步 校验版本
	if (2 != HIBYTE(wdSockMsg.wVersion) || 2 != LOBYTE(wdSockMsg.wVersion))//HIBYTE表示高位:表示主版本,LOBYTE表示地位:表示副版本
	{
		//版本不对
		WSACleanup();//关闭网络库函数
		printf("校验版本出错\n");
		return 0;
	}


	//第三步 创建SOCKET
	//SOCKET的使用
	SOCKET socketServer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);//三个参数 如果成功会返回一个可以的socket,
																	//在最后一定要销毁socket套接字
	if (INVALID_SOCKET == socketServer)
	{
		int a = WSAGetLastError();//当socket函数绑定传输协议套接字失败,会调用该函数,返回错误码。
		WSACleanup();
		printf("创建socket出错\n");
		return 0;
	}

	//struct sockaddr_in si;
	//si.sin_family = AF_INET;
	//si.sin_port = htons(27015);
	//struct in_addr s;//存放Ipv4地址的结构体
	//inet_pton(AF_INET, "127.0.0.1", (void*)&si.sin_addr.S_un.S_addr);//进行转换

	//第四步 绑定地址与端口号
	//bing函数的使用
	struct sockaddr_in si;//定义一个结构体
	si.sin_family = AF_INET;//需要和socket函数中的参数一致
	si.sin_port = htons(12332);//指定的端口号
	si.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");//指定ip地址
	//bind(socketServer, (const struct sockaddr*)&si, sizeof(struct sockaddr_in));
	//返回值,成功返回0,不成功返回SOCKET_ERROR实际上是一个-1
	if (SOCKET_ERROR == bind(socketServer, (const struct sockaddr*)&si, sizeof(struct sockaddr_in)))
	{
		//出错了
		int a = WSAGetLastError();
		//释放
		closesocket(socketServer);
		//释放网络库
		WSACleanup();
		printf("绑定ip和端口号出错\n");
		return 0;
	}

	//第五步 开始监听
	//listen()函数的使用
	//listen(socketServer, SOMAXCONN);
	if (SOCKET_ERROR == listen(socketServer, SOMAXCONN))
	{
		//出错了
		int a = WSAGetLastError();
		//释放
		closesocket(socketServer);
		//释放网络库
		WSACleanup();
		printf("监听函数出错\n");
		return 0;
	}

	//第六步 创建客户端socket/接受连接
	//accept()函数的使用
	//创建客户端连接
	struct sockaddr_in clientMsg;//定义sockaddr_in的结构体类型
	int len = sizeof(clientMsg);//作为参数传入到accept函数
	//accept(socketServer,NULL,NULL);//可以这样
	SOCKET socketClient = accept(socketServer, (struct sockaddr*)&clientMsg, &len);//成功返回一个客户端socket
	if (INVALID_SOCKET == socketClient)
	{
		//出错了
		int a = WSAGetLastError();
		//释放
		closesocket(socketServer);
		//释放网络库
		WSACleanup();
		printf("连接函数出错\n");
		return 0;
	}
	printf("服务器\n");

	char buf[1500] = { 0 };//作为收到信息的存储空间
	int res = recv(socketClient, buf, 1499, 0);//执行成功会返回接收到信息的字节大小
	if (res == 0)
	{
		printf("连接中断,客户端下线\n");
	}
	else if (SOCKET_ERROR == res)
	{
		//出错了
		int a = WSAGetLastError();//拿到错误码
		//需要根据实际情况处理
	}
	else
	{
		printf("%d	%s\n", res, buf);
	}
	if (SOCKET_ERROR == send(socketClient, "我是服务器,我收到了你的消息", sizeof("我是服务器,我收到了你的消息"), 0))//成功会返回发送出去的字节个数,一次发送出去的字节个数应该在15000以内
	{
		//发送不成功
		printf("发送出错\n");
		int a = WSAGetLastError();//拿到错误码
		//需要根据实际情况处理
	}

	//循环一下
	while (1)
	{
		//第七步 与客户端收发消息
	//recv()函数使用
		int res = recv(socketClient, buf, 1499, 0);//执行成功会返回接收到信息的字节大小
		if (res == 0)
		{
			printf("连接中断,客户端下线\n");
		}
		else if (SOCKET_ERROR == res)
		{
			//出错了
			int a = WSAGetLastError();//拿到错误码
			//需要根据实际情况处理
		}
		else
		{
			printf("%d	%s\n", res, buf);
		}

		scanf("%s", buf);
		//send()函数使用
		if (SOCKET_ERROR == send(socketClient, buf, strlen(buf), 0))//成功会返回发送出去的字节个数,一次发送出去的字节个数应该在15000以内
		{
			//发送不成功
			printf("发送出错\n");
			int a = WSAGetLastError();//拿到错误码
			//需要根据实际情况处理
		}
	}

	closesocket(socketServer);//销毁套接字的函数 释放成功返回0
	closesocket(socketClient);
	WSACleanup();//清理网络库 释放成功返回0

	system("pause");
	return 0;
}

客户端代码

#define _CRT_SECURE_NO_WARNINGS
#define _WINSOCK_DEPRECATED_NO_WARNINGS

#include<stdio.h>
#include<stdlib.h>
#include<WinSock2.h>
#pragma comment(lib, "ws2_32.lib")

int main()
{
	//第一步 打开网络库
	WORD wdVersion = MAKEWORD(2, 2);
	WSADATA wdSocketMsg;
	int nRes = WSAStartup(wdVersion, &wdSocketMsg);
	if (nRes != 0)
	{
		printf("打开失败");
		return 0;
	}
	//第二部 校验版本
	if (HIBYTE(wdSocketMsg.wVersion) != 2 || LOBYTE(wdSocketMsg.wVersion) != 2)
	{
		//版本不对
		WSACleanup();//关闭网络库
		printf("版本出错\n");
		return 0;
	}
	//第三部 创建socket
	SOCKET socketClient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);//还是服务器的socket
	if (socketClient == INVALID_SOCKET)
	{
		//创建了无效的socket
		int a = WSAGetLastError();//获取错误码
		printf("创建出错\n");
		WSACleanup();
		return 0;
	}
	//第四步  连接到服务器
	struct sockaddr_in serverMsg;
	serverMsg.sin_family = AF_INET;
	serverMsg.sin_port = htons(12332);
	serverMsg.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
	if (SOCKET_ERROR == connect(socketClient, (struct sockaddr*)&serverMsg, sizeof(serverMsg)))
	{
		int a = WSAGetLastError();//获取错误码
		printf("连接出错\n");
		closesocket(socketClient);
		WSACleanup();
		return 0;
	}
	printf("客户端\n");

	if (SOCKET_ERROR == send(socketClient, "我是客户端", sizeof("我是客户端"), 0))//发送成功会返回发送的字节个数
	{
		//发送失败
		printf("发送出错\n");
		int a = WSAGetLastError();//获取错误码
	}

	//循环一下
	while (1)
	{
		//第五步 与服务起收发消息
		char buf[1500] = { 0 };
		int res = recv(socketClient, buf, 1499, 0);
		if (res == 0)
		{
			printf("发送中断,服务器断开连接\n");
		}
		else if (res == SOCKET_ERROR)
		{
			//连接出错
			int a = WSAGetLastError();//获取错误码
			printf("接收出错\n");
		}
		else
		{
			printf("%d	%s\n", res, buf);
		}
		scanf("%s", buf);
		if (SOCKET_ERROR == send(socketClient, buf, strlen(buf), 0))//发送成功会返回发送的字节个数
		{
			//发送失败
			printf("发送出错\n");
			int a = WSAGetLastError();//获取错误码
		}
	}
	

	closesocket(socketClient);
	WSACleanup();

	system("pause");
	return 0;
}

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

爱笑的蛐蛐

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

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

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

打赏作者

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

抵扣说明:

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

余额充值