【网络编程】Day4 TCP并发服务器

1. 广播,组播代码重新写一遍
2. 多进程通过模型自己写一遍
3. 多线程代码写一遍,能否将newfd定义成全局(不行,找原因)
4. 上传下载写完。

1. 广播、组播

        1.1 广播

        接收方:boardrRcv.c

#include <myhead.h>

/*--------------------广播的接收方 (类似于UDP 服务器端)---------------------*/
#define ERR_MSG(msg) do{\
				fprintf(stderr,"_%d_",__LINE__);\
				perror(msg);\
				return -1;\
			}while(0)

#define IP "192.168.0.255"  // ifconfig 广播IP地址
#define PORT 8888

int main(int argc, const char *argv[])
{
	//创建报式套接字
	int sfd = socket(AF_INET,SOCK_DGRAM,0);
	if(sfd < 0)
	{
		ERR_MSG("socket error");
	}
	printf("sfd = %d\n",sfd);

	//2. 填充服务器的地址信息结构体 AF_INET: man 7 ip
	struct sockaddr_in sin;
	sin.sin_family      = AF_INET;       //IPv4
	sin.sin_port        = htons(PORT);   //端口号
	sin.sin_addr.s_addr = inet_addr(IP); //广播IP


	//3. 将套接字和网络信息结构体绑定
	if(-1 == bind(sfd,(struct sockaddr*)&sin,sizeof(sin)))
	{
		ERR_MSG("bind error");
	}
	printf("bind success\n");

	//4.接收,发送收据
	struct sockaddr_in cin;  //用来存储客户端数据包
	socklen_t addrlen = sizeof(cin);
	char buf[128]="";
	while(1)
	{
		//接收收据
		memset(buf,0,sizeof(buf));
		int res = recvfrom(sfd,buf,sizeof(buf),0,(struct sockaddr *)&cin,&addrlen);  //注意没有新的文件描述符产生
		if(-1 == res)
		{
			ERR_MSG("recv error");
		}
		printf("接收到来自[%s : %d] :%s\n",inet_ntoa(cin.sin_addr),ntohs(cin.sin_port),buf);
		
	/*	//发送数据
		strcat(buf,"***");
		if(-1 == sendto(sfd,buf,sizeof(buf),0,(struct sockaddr *)&cin,sizeof(cin)))
		{
			ERR_MSG("sendto error");
		}
		printf("发送信息到[%s : %d] :%s\n",inet_ntoa(cin.sin_addr),ntohs(cin.sin_port),buf);	*/
	}
	
	//5.关闭套接字
	close(sfd);

	return 0;
}

         发送方:boardSend.c

#include <myhead.h>

/*--------------------广播的发送方 (类似于UDP 客户端)---------------------*/
#define ERR_MSG(msg) do{\
				fprintf(stderr,"_%d_",__LINE__);\
				perror(msg);\
				return -1;\
			}while(0)

#define IP "192.168.0.255"  //广播 IP地址 
#define PORT 8888   //服务器端 端口号

int main(int argc, const char *argv[])
{
	//创建报式套接字
	int sfd = socket(AF_INET,SOCK_DGRAM,0);   //UDP协议: SOCK_DGRAM
	if(sfd < 0)
	{
		ERR_MSG("socket error");
	}
	printf("sfd = %d\n",sfd);


	//设置允许广播  --> man 7 socket
	int board = 1;
	if(-1 == setsockopt(sfd,SOL_SOCKET,SO_BROADCAST,&board,sizeof(board)))
	{
		perror("setsockopt error");
		return -1;
	}
	printf("设置允许广播成功\n");


	//绑定客户端的地址信息--->非必须绑定
	//若不绑定则会自动绑定本机IP以及随机端口
	

	//2. 填充要发送的服务器的地址信息结构体,给下面的sendto函数使用
	struct sockaddr_in sin;
	sin.sin_family      = AF_INET;       //必须填IPv4
	sin.sin_port        = htons(PORT);   //服务器绑定的端口号(网络字节序)
	sin.sin_addr.s_addr = inet_addr(IP); //服务器绑定的IP地址(网络字节序)

	char buf[128]="";
	while(1)
	{	
		//发送数据
		memset(buf,0,sizeof(buf));  //缓存清0
		printf("请输入>>>");    	//通过终端输入
		fgets(buf,sizeof(buf),stdin);
		buf[strlen(buf)-1] = '\0';

		if(-1 == sendto(sfd,buf,sizeof(buf),0,(struct sockaddr *)&sin,sizeof(sin)))
		{
			ERR_MSG("sendto error");
		}
		printf("发送成功:%s\n",buf);	
	}
	
	//4.关闭套接字
	close(sfd);
	return 0;
}

        1.2 组播 

        接收方:memberRcv.c

#include <myhead.h>

/*--------------------组播的接收方 (类似于UDP 服务器端)---------------------*/
//加入多播组 setsockopt
//填充地址信息结构体,增加组播IP

#define ERR_MSG(msg) do{\
				fprintf(stderr,"_%d_",__LINE__);\
				perror(msg);\
				return -1;\
			}while(0)
#define GRP_IP  "224.8.8.8"  	//组播IP
#define IP      "192.168.0.107"  //本机IP地址,ifconfig
#define PORT 8888

int main(int argc, const char *argv[])
{
	//创建报式套接字
	int sfd = socket(AF_INET,SOCK_DGRAM,0);
	if(sfd < 0)
	{
		ERR_MSG("socket error");
	}
	printf("sfd = %d\n",sfd);
/*=====================加入多播组==================*/
	//加入多播组
	struct ip_mreqn mq;   //组播结构体 man 7 IPPROTO_IP
	mq.imr_multiaddr.s_addr = inet_addr(GRP_IP);  //组播IP地址
	mq.imr_address.s_addr   = inet_addr(IP);  //本机IP地址
	mq.imr_ifindex 			= 0;  //网络设备索引号,0表示自动, 网卡ens33的设备索引号,也可填2
	if(-1 == setsockopt(sfd,IPPROTO_IP,IP_ADD_MEMBERSHIP,&mq,sizeof(mq)))
	{
		ERR_MSG("setsockopt error");
	}
	printf("加入 %s 组播的 %d 端口成功\n",GRP_IP,PORT);

	//2. 填充服务器的地址信息结构体 AF_INET: man 7 ip
	struct sockaddr_in sin;
	sin.sin_family      = AF_INET;       //IPv4
	sin.sin_port        = htons(PORT);   //端口号
	sin.sin_addr.s_addr = inet_addr(GRP_IP); //组播IP


	//3. 将套接字和网络信息结构体绑定
	if(-1 == bind(sfd,(struct sockaddr*)&sin,sizeof(sin)))
	{
		ERR_MSG("bind error");
	}
	printf("bind success\n");

	//4.接收,发送收据
	struct sockaddr_in cin;  //用来存储客户端数据包
	socklen_t addrlen = sizeof(cin);
	char buf[128]="";
	while(1)
	{
		//接收收据
		memset(buf,0,sizeof(buf));
		int res = recvfrom(sfd,buf,sizeof(buf),0,(struct sockaddr *)&cin,&addrlen);  //注意没有新的文件描述符产生
		if(-1 == res)
		{
			ERR_MSG("recv error");
		}
		printf("接收到来自[%s : %d] :%s\n",inet_ntoa(cin.sin_addr),ntohs(cin.sin_port),buf);
	}
	
	//5.关闭套接字
	close(sfd);

	return 0;
}

         发送方:memberSend.c

#include <myhead.h>

/*--------------------组播的发送方 (类似于UDP 客户端)---------------------*/
//更改组播地址即可

#define ERR_MSG(msg) do{\
				fprintf(stderr,"_%d_",__LINE__);\
				perror(msg);\
				return -1;\
			}while(0)

#define IP "224.8.8.8"  //组播IP  224.0.0.0 - 239.255.255.255
#define PORT 8888   //端口号

int main(int argc, const char *argv[])
{
	//创建报式套接字
	int sfd = socket(AF_INET,SOCK_DGRAM,0);   //UDP协议: SOCK_DGRAM
	if(sfd < 0)
	{
		ERR_MSG("socket error");
	}
	printf("sfd = %d\n",sfd);


	//绑定客户端的地址信息--->非必须绑定
	//若不绑定则会自动绑定本机IP以及随机端口
	

	//2. 填充要发送的服务器的地址信息结构体,给下面的sendto函数使用
	struct sockaddr_in sin;
	sin.sin_family      = AF_INET;       //必须填IPv4
	sin.sin_port        = htons(PORT);   //绑定的端口号(网络字节序)
	sin.sin_addr.s_addr = inet_addr(IP); //组播IP(网络字节序) 224.0.0.0 - 239.255.255.255

	char buf[128]="";
	while(1)
	{	
		//发送数据
		memset(buf,0,sizeof(buf));  //缓存清0
		printf("请输入>>>");    	//通过终端输入
		fgets(buf,sizeof(buf),stdin);
		buf[strlen(buf)-1] = '\0';

		if(-1 == sendto(sfd,buf,sizeof(buf),0,(struct sockaddr *)&sin,sizeof(sin)))
		{
			ERR_MSG("sendto error");
		}
		printf("发送成功:%s\n",buf);	
	}
	
	//4.关闭套接字
	close(sfd);
	return 0;
}

2.多进程实现并发服务器

#include <myhead.h>

/*=======================TCP并发服务器 多进程实现========================*/
/*可以处理来自多个客户端的任务
//sfd = socket();
//bind();
//while(1){
	newfd = accept();
	if(fork() == 0){    //创建子进程
		close(sfd);
		while(1){
			recv();
			send();
		}
	}
}
*/

#define ERR_MSG(msg)  do{\
		fprintf(stderr," %d ",__LINE__);\
		perror(msg);\
		return -1; \
		}while(0)

#define IP "192.168.0.109"  //ifconfig查看本机IP
#define PORT 6768

int task_cli(int newfd,struct sockaddr_in cin); //处理客户端任务
void handler(int sig)
{
	while(waitpid(-1,NULL,WNOHANG) > 0);  //非阻塞方式回收资源
}


int main(int argc, const char *argv[])
{	
	//捕获17号信号SIGCHID
	//子进程退出时会给父进程发一个信号SIGCHID,
	//父进程收到信号后,再去给子进程回收资源,handler函数
	if(signal(SIGCHLD,handler) == SIG_ERR)
	{
		ERR_MSG("signal");
	}
	
	//1.创建流式套接字
	int sfd = socket(AF_INET,SOCK_STREAM,0);
	if(sfd < 0)
	{
		ERR_MSG("socket error");
	}

	//允许端口快速被重用
	int reuse = 1;
	if(setsockopt(sfd,SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(reuse)) < 0)
	{
		ERR_MSG("setsockopt error");
	}
	printf("允许端口快速被重用成功\n");


	//2.填充服务器的地址信息结构体
	struct sockaddr_in sin;
	sin.sin_family      = AF_INET;  	 //必须填AF_INET
	sin.sin_port        = htons(PORT);   //端口号的网络字节序
	sin.sin_addr.s_addr = inet_addr(IP); //本机IP的网络字节序

	//3.绑定服务器的IP和端口--绑定
	if(-1 == bind(sfd,(struct sockaddr*)&sin,sizeof(sin)))
	{
		ERR_MSG("bind error");
	}
	printf("bind success\n");


	//4.将套接字设置为被动监听状态,监听是否有客户端连接
	if(listen(sfd,128) <0 )
	{
		ERR_MSG("listen error");
	}
	printf("listen success\n");


	//5.有客户端连接后,创建一个新的通信套接字与客户端通信
	struct sockaddr_in cin;
	socklen_t addrlen =sizeof(cin);

	pid_t pid = 0;
	int newfd = -1;

	while(1)
	{
		printf("正在等待客户端连接...\n");

		//阻塞函数,阻塞等待客户端连接
		//产生新的文件描述符,才是与客户端通信的文件描述符
		int newfd = accept(sfd,(struct sockaddr*)&cin,&addrlen);
		if(newfd < 0)
		{
			ERR_MSG("accept error");
		}
		printf("[%s :%d] newfd = %d 客户端连接成功\n",\
			inet_ntoa(cin.sin_addr),ntohs(cin.sin_port),newfd);  //网络字节序转换成点分字节序
	


		pid = fork();  //创建进程
		if(pid == 0 )
		{
			//子进程处理与客户端交互:读写
			task_cli(newfd,cin);
			close(newfd);
			exit(0);   //子进程只负责与客户端交互,当客户端退出后,子进程也许退出
		}

		close(newfd);
	}

	//8.关闭文件描述符
	close(sfd);
	return 0;
}

int task_cli(int newfd,struct sockaddr_in cin)
{
	char buf[128] = "";
	ssize_t res = 0;
	while(1)
	{
		//1.接收数据
		memset(buf,0,sizeof(buf));
		res = recv(newfd,buf,sizeof(buf),0);
		if(res == -1)
		{
			ERR_MSG("recv error");
		}
		if(res == 0)    //客户端下线
		{
			printf("[%s :%d] newfd = %d 客户端已断开连接.....\n",\
			inet_ntoa(cin.sin_addr),ntohs(cin.sin_port),newfd);  //网络字节序转换成点分字节序

			break;
		}
		printf("newfd = %d: %s\n",newfd,buf);


		//2.发送数据
		strcat(buf,"*_*");    //可以修改为从终端获取
		if(-1 == send(newfd,buf,sizeof(buf),0))
		{
			ERR_MSG("send error");
		}
		printf("发送成功\n");
	}
}

3. 多线程实现并发服务器,能否将newfd定义成全局(不行,找原因)

#include <myhead.h>

/*=======================TCP并发服务器 多线程实现========================*/
/*可以处理来自多个客户端的任务
	sfd = socket();
	bind();
	while(1)
	{
		newfd = accept();
		pthread(&tid,NULL,task(),(void*)&msg)  //创建子线程
		pthread_detach(tid);
	}
	close(sfd);
void* task(void *arg)
{
  		while(1){
			recv();
			send();
			}
			close(newfd);
}
*/

#define ERR_MSG(msg)  do{\
		fprintf(stderr," %d ",__LINE__);\
		perror(msg);\
		return -1; \
		}while(0)

#define IP "192.168.0.109"  //ifconfig查看本机IP
#define PORT 6768

struct info
{
	int newfd;
	struct sockaddr_in cin;
};

void* task(void* arg); //处理客户端任务

int main(int argc, const char *argv[])
{	
	//1.创建流式套接字
	int sfd = socket(AF_INET,SOCK_STREAM,0);
	if(sfd < 0)
	{
		ERR_MSG("socket error");
	}

	//允许端口快速被重用
	int reuse = 1;
	if(setsockopt(sfd,SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(reuse)) < 0)
	{
		ERR_MSG("setsockopt error");
	}
	printf("允许端口快速被重用成功\n");


	//2.填充服务器的地址信息结构体
	struct sockaddr_in sin;
	sin.sin_family      = AF_INET;  	 //必须填AF_INET
	sin.sin_port        = htons(PORT);   //端口号的网络字节序
	sin.sin_addr.s_addr = inet_addr(IP); //本机IP的网络字节序

	//3.绑定服务器的IP和端口--绑定
	if(-1 == bind(sfd,(struct sockaddr*)&sin,sizeof(sin)))
	{
		ERR_MSG("bind error");
	}
	printf("bind success\n");


	//4.将套接字设置为被动监听状态,监听是否有客户端连接
	if(listen(sfd,128) <0 )
	{
		ERR_MSG("listen error");
	}
	printf("listen success\n");


	//5.有客户端连接后,创建一个新的通信套接字与客户端通信
	struct sockaddr_in cin;
	socklen_t addrlen =sizeof(cin);

	int newfd = -1;
	pthread_t tid;
	struct info msg;

	while(1)
	{
//主线程负责连接 accept
		printf("正在等待客户端连接...\n");
		//阻塞函数,阻塞等待客户端连接
		//产生新的文件描述符,才是与客户端通信的文件描述符
		int newfd = accept(sfd,(struct sockaddr*)&cin,&addrlen);
		if(newfd < 0)
		{
			ERR_MSG("accept error");
		}
		printf("[%s :%d] newfd = %d 客户端连接成功\n",\
			inet_ntoa(cin.sin_addr),ntohs(cin.sin_port),newfd);  //网络字节序转换成点分字节序
 //创建子线程
 		msg.newfd = newfd;
		msg.cin = cin;
		
		if(pthread_create(&tid,NULL,task,(void*)&msg) != 0)
		{
			fprintf(stderr,"子线程创建失败 __%d__\n",__LINE__);
			return -1;
		}
		pthread_detach(tid);  //设置成分离状态,回收子线程,自动回收
	}

	//8.关闭文件描述符
	close(sfd);
	return 0;
}

void* task(void* arg)
{
	int newfd = ((struct info*)arg) ->newfd;
	struct sockaddr_in cin = ((struct info*)arg) ->cin;

	char buf[128] = "";
	ssize_t res = 0;
	while(1)
	{
		//1.接收数据
		memset(buf,0,sizeof(buf));
		res = recv(newfd,buf,sizeof(buf),0);
		if(res == -1)
		{
			perror("recv error");
			break;
		}
		if(res == 0)    //客户端下线
		{
			printf("[%s :%d] newfd = %d 客户端已断开连接.....\n",\
			inet_ntoa(cin.sin_addr),ntohs(cin.sin_port),newfd);  //网络字节序转换成点分字节序

			break;
		}
		printf("newfd = %d: %s\n",newfd,buf);

		//2.发送数据
		strcat(buf,"*_*");    //可以修改为从终端获取
		if(-1 == send(newfd,buf,sizeof(buf),0))
		{
			perror("send error");
		}
		printf("发送成功\n");
	}
	close(newfd);
	pthread_exit(NULL);
}	

编译结果如下:

ubuntu@ubuntu:05tcp_pthread$ gcc pthread_tcpSer.c  -o ser.out -pthread
ubuntu@ubuntu:05tcp_pthread$ ./ser.out 
允许端口快速被重用成功
bind success
listen success
正在等待客户端连接...
[192.168.0.109 :47152] newfd = 4 客户端连接成功
正在等待客户端连接...
newfd = 4: hello
发送成功
newfd = 4: summer
发送成功

4.TFTP上传下载

#include <myhead.h>

/*===============================基于UDP 的tftp客户端===========================*/
#define ERR_MSG(msg) do{\
				fprintf(stderr,"_%d_",__LINE__);\
				perror(msg);\
				return -1;\
			}while(0)

#define IP "192.168.0.104"  //查看服务器的IP地址 windows下ipconfig
#define PORT 69   //tftp专用

int download(int sfd,struct sockaddr_in sin);
int upload(int sfd,struct sockaddr_in sin);

int main(int argc, const char *argv[])
{
	//创建报式套接字
	int sfd = socket(AF_INET,SOCK_DGRAM,0);
	if(sfd < 0)
	{
		ERR_MSG("socket error");
	}

	//绑定客户端的地址信息-->非必须绑定
	//若不绑定则会自动绑定本机IP以及随机端口
	

	//2. 填充服务器的地址信息结构体,给下面的sendto函数使用
	struct sockaddr_in sin;
	sin.sin_family      = AF_INET;       //
	sin.sin_port        = htons(PORT);   //端口号
	sin.sin_addr.s_addr = inet_addr(IP); //IP地址



	char c = 0;
	while(1)
	{
		printf("\n----------------------------------\n");
		printf("               1.上传             \n");
		printf("               2.下载             \n");
		printf("               3.退出             \n");
		printf("********************************* \n");
		printf("请选择>>> ");

		c = getchar();
		while(getchar() != 10);

		switch(c)
		{
			case '1':  //上传
				upload(sfd,sin);
				break;
			case '2':  //下载
				download(sfd,sin);
				break;
			case '3':  //退出
				goto END;
			default:
				printf("输入有误,请重新输入\n");
		}
	}

END:
	//5.关闭套接字
	close(sfd);

	return 0;
}

int download(int sfd,struct sockaddr_in sin)
{
	char filename[30] = "";
	printf("请输入要下载的文件名>>>");
	scanf("%s",filename);
	while(getchar() != 10);

//组下载请求协议包
	char buf[516] = "";

	unsigned short *p1 = (unsigned short*)buf;
	*p1 = htons(1);  //p1操作码
	
	char *p2 = buf+2;
	strcpy(p2,filename);

	char *p3 =p2+strlen(filename);
	*p3 = 0;
	
	char *p4 = p3+1;
	strcpy(p4,"octet");

	int size = 4+strlen(p2)+strlen(p4);

	//向服务器发送下载请求
	if(-1 == sendto(sfd,buf,size,0,(struct sockaddr*)&sin,sizeof(sin)))
	{
		ERR_MSG("sendto error");
	}
	printf("发送下载请求成功\n");

	unsigned short code=0;   //操作码
	unsigned short block=0;  //块编号
	char data[512]="";  //数据
	int count=0;
	int len=0;
	int fd;
	unsigned short *p = p1+1;   //指向块编号
	socklen_t addrlen = sizeof(sin);

	while(1)
	{
		//接收数据包
		memset(buf,0,sizeof(buf));
		if(-1 == (len=recvfrom(sfd,buf,sizeof(buf),0,(struct sockaddr*)&sin,&addrlen)))  //需接收新的端口号(临时端口号12345)
		{
			ERR_MSG("recv error");
		}
		//解析接收到的数据包
		code = ntohs(*p1);   	//解析操作码
		block = ntohs(*p);  	//解析块编号
		strcpy(data,buf+4);  	//解析数据

		if(code == 3 && block==count+1)   //下载数据校验
		{
			count++;
			if(count==1)
			{
				fd = open(filename,O_WRONLY | O_CREAT | O_TRUNC,0666);
				if(-1 == fd)
				{
					ERR_MSG("file open error");
				}
			}
			if(-1 == write(fd,data,len-4))
			{
				ERR_MSG("write error");
			}

		//回复ACK
			*p1 = htons(4);   //ACK操作码
			*p  = htons(block);   //ACK块编号
			if(-1 == sendto(sfd,buf,4,0,(struct sockaddr*)&sin,addrlen))
			{
				ERR_MSG("sendto error");
			}

		//判断数据是否小于512,若<512,则传输结束
			if(len-4 < 512)
			{
				close(fd);
				printf("已完成下载\n");
				break;
			}		
		}
		else if(code == 5)  //error
		{
			printf("error... %d:  %s\n",block,data);
			break;
		}
	}
}


int upload(int sfd,struct sockaddr_in sin)
{
	char filename[30] = "";
	printf("请输入要上传的文件名>>>");
	scanf("%s",filename);
	while(getchar() != 10);

	int fd = open(filename,O_RDONLY);
	if(fd<0)
	{
		ERR_MSG("file open error");
	}


	char buf[516] = "";
	unsigned short *p1 = (unsigned short*)buf;   // 写操作
	unsigned short *p2 = p1+1;   //指向块编号

//向服务器发送上传请求  //写操作
	int size = sprintf(buf,"%c%c%s%c%s%c",0,2,filename,0,"octet",0);  //组写请求协议包
	if(-1 == sendto(sfd,buf,size,0,(struct sockaddr*)&sin,sizeof(sin)))
	{
		ERR_MSG("sendto error");
	}
	printf("发送上传请求成功\n");

	unsigned short code=0;   //操作码
	unsigned short block=0;  //块编号
	char data[512]="";  //数据
	int count=0;
	int len=0;
	socklen_t addrlen = sizeof(sin);

	while(1)
	{
		//接收数据包
		memset(buf,0,sizeof(buf));
		if(-1 == (len=recvfrom(sfd,buf,sizeof(buf),0,(struct sockaddr*)&sin,&addrlen)))  
		{
			ERR_MSG("recv error");
		}
		//解析接收到的数据包
		//code = ntohs(*p1);   	//解析操作码
		//block = ntohs(*p2);  	//解析块编号
		code  = ntohs(*(unsigned short*)buf);
		block = ntohs(*(unsigned short*)(buf+2));
		
		if(code == 4)
		{
			if(block == count)
			{
				count++;
				*p1 = htons(3);
				*p2 = htons(block+1);
				//*(unsigned short*)(buf+2) = htons(block+1);
		
				//发送数据
				int res = read(fd, data, sizeof(data));
				if(res < 0)
				{
					ERR_MSG("read error");
				}
				else if(0 == res)
				{
					printf("文件上传成功\n");
					break;
				}

				//发送数据包
				if(sendto(sfd, buf, res+4, 0, (struct sockaddr*)&sin, sizeof(sin)) <0)
				{
					ERR_MSG("sendto error");
				}
	//		printf("code=%d : block=%d, data= %s\n", code,block,data);
			}
			else
			{
				printf(" 文上传失败 \n");
				break;
			}
		}
		else if(code == 5)
		{
			printf("error: %d,%s\n", block,data);
			break;
		}

	}
	return 0;
}

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值