【网络篇】socket编程——TCP(史上最全)

这篇具有很好参考价值的文章主要介绍了【网络篇】socket编程——TCP(史上最全)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

目录

一、初始TCP

1.TCP协议特点

2.TCP头:

 3.确认应答机制

4.超时重传机制

5.流量控制

6.拥塞控制

(1)TCP 的拥塞控制方法

慢开始

拥塞避免

快重传

快恢复

二、建立连接——三次握手

 三、断开连接——四次挥手

四、socket编程

##客户端API函数

##服务端API函数


一、初始TCP

1.TCP协议特点

(1)TCP 是面向连接的运输层协议。应用程序在使用 TCP 协议之前,必须先建立 TCP 连接。在传送数据完毕后,必须释放已经建立的 TCP 连接

(2)每一条 TCP 连接只能有两个端点,每一条 TCP 连接只能是点对点的(一对一)

(3)TCP 提供可靠交付(确认机制、拥塞控制、流量控制、超时重传的服务。通过 TCP 连接传送的数据,无差错、不丢失、不重复,并且按序到达

(4)TCP 提供全双工通信。TCP 允许通信双方的应用进程在任何时候都能发送数据。TCP 连接的两端都设有发送缓存和接受缓存,用来临时存放双向通信的数据

(5)面向字节流。TCP 中的“流”指的是流入到进程或从进程流出的字节序列

2.TCP头:

【网络篇】socket编程——TCP(史上最全)

 3.确认应答机制

确认应答机制是TCP可靠性中核心之一

接收方回复收到的一个应答报文(ACK),表示已经收到

例如:你叫朋友一起出去玩,TCP中的确认应答机制如下所示。针对发送的请求进行编号,应答时也针对相应的编号应答,这样就能保证数据传输的可靠性。

【网络篇】socket编程——TCP(史上最全)

4.超时重传机制

超时重传也是TCP可靠性保证的必要条件之一

确认应答是比较理想的情况,但数据在传输过程中,可能是会丢包的

我们以上面叫朋友去玩举例:A 给 B 发消息,你在家嘛?等了很久,A 也没收到 B 的消息,此时,存在以下几种情况:
(1) B 不想回 A 的消息

TCP 抱着一种 “悲观的态度”,当一次丢包重传之后,TCP 就觉得大概率后面的重传也没用,所以就隔一个更长的时间,节省带宽

(2)B 没收到 A 的消息 (丢包情况: 发的请求丢失)
(3)B 回复了消息,但 A 没收到 (丢包情况:应答的 ACK 丢失,重传就意味着接收到相同数据)

(2)(3)情况:丢包的两种情况,对于发送方来说无法确定是哪种情况,因此,进行统一处理:当发送了一条数据之后,TCP 内部就会自动启动一个定时器,达到一定时间也没收到 ACK,定时器就会自动触发重传消息的动作 —— 超时重传

5.流量控制

流量控制(flow control):让发送方的发送速率不要太快,要让接收方来得及接收

利用滑动窗口机制可以很方便地在 TCP 连接上实现对发送方的流量控制,实质就是TCP在发送数据的时候将数据放到发送缓冲区,将接收的数据放到接收缓冲区。而流量控制要做的事情就是通过接收缓冲区的大小,控制发送端的发送,如果对方的接收缓冲区满了,就不能继续发送。为了控制发送端的速率,接收端在进行ACK确认时会携带自身的窗口(rwnd)大小,就会告知发送端自己缓冲区的大小,进行相应的流量控制。

6.拥塞控制

拥塞控制就是防止过多的数据注入到网络中,这样可以使网络中的路由器或链路不致过载。拥塞控制所要做的都是一个前提,就是网络能够承受现有的网络负荷

(1)TCP 的拥塞控制方法

TCP 进行拥塞控制的算法有四种,即慢开始(slow-start)、拥塞避免(congestion avoidance)、快重传(fast retransmit)和快恢复(fast recovery)

慢开始

当主机开始发送数据时,由于并不清楚网络的负荷情况,如果立即把大量数据字节注入到网络,就有可能引起网络发生拥塞。经验证明,较好的方法是先探测一下,即由小到大逐渐增大发送窗口,也就是说,由小到大逐渐增大拥塞窗口数值。

在执行慢开始算法时,发送方每收到一个队新报文段的确认 ACK,就把拥塞窗口值加1,然后开始下一轮的传输。因此拥塞窗口 cwnd 随着传输轮次按指数规律增长。当拥塞窗口 cwnd 增长到慢开始门限值 ssthresh 时,就改成执行拥塞避免算法,拥塞窗口按线性规律增长

ssthresh:慢开始门限,一般会有一个初始值

拥塞避免

让拥塞窗口 cwnd 缓慢地增大,即每经过一个往返时间 RTT 就把发送方的拥塞窗口 cwnd 加1,而不是像慢开始阶段那样加倍增加。拥塞窗口 cwnd 按线性规律缓慢增长,比慢开始算法的拥塞窗口增长速率缓慢得多

“拥塞避免”并非完全能够避免拥塞,而是把拥塞窗口控制为按线性规律增长,使网络比较不容易出现拥塞

快重传

采用快重传算法可以让发送方尽早知道发生了个别报文段的丢失。快重传算法首先要求接收方不要等待自己发送数据时才进行捎带确认,而是要立即发送确认,即使收到了失序的报文段也要立即发出对已收到的报文段的重复确认

快恢复

发送方知道当前只是丢失了个别的报文段。于是不启动慢开始,而是执行快恢复算法。这时,发送方调整门限值 ssthresh = cwnd / 2 ,同时设置拥塞窗口 cwnd = ssthresh ,并开始执行拥塞避免算法

二、建立连接——三次握手

建立TCP连接共有三步,即三次握手

先简单的给大家举个例子,我们可以把他类比于平时打电话,如下:

 【网络篇】socket编程——TCP(史上最全)

第一次A不知道B能否听到自己的声音,A对B说“喂,你能听到吗?”,第二次B听到后还需要知道A是否也能听到自己的声音,就是B对A的回话“我能听到,你呢”,第三次A的回复确认双方都能听到声音,也就是A对B的回复“我也能”,由上三次就能保证通话的正常,类似于网络建立连接时的三次握手。

两次握手可以吗??
不可以
两次握手只能保证单向连接是通畅的,TCP 协议是双向的,第三次握手是为了使得sever知道客户端答应了连接的请求。其中两次握手只能确定从客户端到服务端的网络是可达的,但却无法保证从服务端到客户端的网络是可达的。所以我们一定要保证双向的可达。

【网络篇】socket编程——TCP(史上最全)

 如上图所示,没有第三次握手则不知道A的听筒是否正常,导致不能正常通话

TCP三次握手:

【网络篇】socket编程——TCP(史上最全)

第一次握手:

TCP客户端打算建立连接,向服务器发送连接请求报文,客户端请求连接,客户端进行同步发送状态(SYN-SEND),在连接请求报文中,把SYN=1,表示这是一个请求连接报文,把序号字段seq=x(x就是一个初始值),作为客户端的初始序号

第二次握手:

TCP服务端在收到客户端的请求后,如果体连接,则会向客户端发送确认请求报文,服务器进入同步接收状态,在确认报文中,把同步位SYN和确认位ACK置为1 ,表示这是一个请求确认报文。把序号seq设置为一个初始值y,作为服务器的初始序号。把确认字段ack=x+1作为对客户端的确认

第三次握手:

TCP客户端在收到了服务器的确认信号后,还要向服务器发送一个确认报文,并进入连接已建立(ESTABLISHED),发送针对服务器确认的确认报文。

确认位ACK=1,表示这是一个确认报文

序号seq=x+1,表示第一次是x,第二次发送x+1

确认号ack=y+1,则是对服务器的确认

服务器收到后,进入连接已建立

 三、断开连接——四次挥手

   断开TCP连接共有四步,即四次挥手

【网络篇】socket编程——TCP(史上最全)

第一次挥手:

TCP客户端主动关闭TCP连接,TCP客户端发送连接释放(断开)报文给服务端,并进入终止等待态1,

在连接断开释放报文中:

(1)终止位FIN和确认位ACK要设置为1,表示是一个TCP连接释放报文,同时对之前的报文做确认

(2)序号seq设置为u(表示特定的值),等于之前已经传送过去的数据最后一个字节+1

(3)确认号ack设置为v,等于之前收到的数据最后一个字节序号+1

第二次挥手:

TCP服务器收到了TCP连接断开请求报文,会发送一个确认报文给客户端,并进入关闭等待状态,客户端收到确认会进入终止等待态2

在断开确认报文中:

  1. 确认号ACK=1,表示是一个确认报文
  2. 序号seq=v,等于之前服务器发送的最后一个字节+1,与次一次挥手客户端的确认号做匹配

继续把当前没有传输完成的数据传输完毕

第三次挥手:

TCP服务端给客户端发送连接释放报文,并进入最后确认状态

在服务端连接释放报文中:

  1. 终止位FIN和确认位ACK设置为1,表示这是一个TCP连接释放,同时对之前的数据做确认
  2. 序号seq=w,这时服务器属于半关闭状态(断开是双向的)
  3. 确认号ack=u+1,释放的重复确认

第四次挥手:

TCP客户端在收到服务端的连接释放,发送确认报文,并进入时间等待状态

确认位ACK=1,表示是一个确认报文

序号seq=u+1(上一次是u,再发一次),表示是一个释放报文

确认号ack=w+1,表示是一个确认

 TCP四次挥手为什么要等待2MSL?

一个MSL表示报文存活的最大时间,不管是A发送到B的报文,还是B发送到A的报文,都是最大可存活1MSL,那么等待2MSL,也就是报文一来一回。

四、socket编程

客户端:类比于打电话

类比

功能

函数

有手机

创建套接字(有对应的TCP协议)

socket()

有手机号码

绑定套接字(有自己的网络信息)

bind()

拨打对方电话号码

请求服务器连接(与服务器建立连接)

connect()

进行通话

收发数据(进行通信)

read()、write()、recv()、send()、

挂断电话

结束通信(关闭套接字)

close()

##客户端API函数

1.创建套接字

#include <sys/types.h>

#include <sys/socket.h>

//创建套接字文件,为进程添加一个对应的网络通信协议(文件),返回值就是创建号的文件描述符(socket描述符就代表一套协议---套接字)

int socket(int domain, int type, int protocol);

参数1:

int domain:地址族,选用那种网络层协议地址

AF_INET------IPV4
AF_INET6-----IPV6

参数2:

int type:套接字类型

SOCK_STREAM--------TCP

SOCK_DGRAM---------UDP

SOCK_RAW:原始套接字(没有传输层协议)

参数3:

int protocol:套接字协议

 0:套接字默认协议

返回值:

int:整数---------文件描述符(套接字文件)

成功:返回套接字描述符 >= 0

失败:返回-1

2.绑定套接字

#include <sys/types.h> /* See NOTES */

#include <sys/socket.h>

//绑定本地网络信息到套接字中

int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);

参数1:

int sockfd:绑定本地网络信息到哪个套接字上(绑定到哪一套协议上)

参数2:

const struct sockaddr *addr:结构体地址,这个结构体中存储的本地网络信息(要绑定的网络信息ip、port)

struct sockaddr {//通用结构体,表示一个网络信息内容

    sa_family_t sa_family;

    char sa_data[14];

}

//IPV4 网络信息结构体

struct sockaddr_in {

    sa_family_t sin_family;//地址族 AF_INET

    in_port_t sin_port;//端口

    struct in_addr sin_addr;//结构体变量--ip地址

};


/* Internet address. */

struct in_addr {

      uint32_t s_addr;//ipv4地址

};

参数3:

socklen_t addrlen:整数,结构体大小(确定信息结构体的大小)

返回值:

成功:返回0

失败:返回-1

3.请求连接

#include <sys/types.h>          

#include <sys/socket.h>

//请求与服务器建立连接

int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);

参数1:

int sockfd:客户端的套接字,使用套接字与服务端建立连接

参数2:

const struct sockaddr *addr:服务端的ip、prot信息,客户端要和哪个服务器建立连接

参数3:

socklen_t addrlen:结构体的大小

返回值:

成功:返回0
失败:返回-1

实现代码:

//TCP客户端进行通信(先发再收)
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
 #include <netinet/in.h>
#include <sys/types.h>         
#include <sys/socket.h>
#include <stdio.h>
#include<stdlib.h>
#include<string.h>
int main()
{

	//1、创建套接字,选择对应的网络通信协议组
	int socketfd = socket(AF_INET,SOCK_STREAM,0);//选择TCP协议
		if(socketfd < 0)
		{
			printf("socket create error\n");
			return -1;
		}
		

	//2、绑定套接字,对套接字添加自己当前进行需要到本地网络信息

	struct sockaddr_in clientaddr;//有一个IPV4网络信息结构体
	clientaddr.sin_family  = AF_INET;//地址族-----IPV4
	clientaddr.sin_port = htons(20000);//为当前进程添加的端口号为10000
	clientaddr.sin_addr.s_addr = inet_addr("0.0.0.0");

	bind(socketfd,(struct sockaddr *)&clientaddr,sizeof(clientaddr));
	//与服务器建立连接
	struct sockaddr_in serveraddr;//服务端IPV4网络信息结构体
	serveraddr.sin_family  = AF_INET;//地址族-----IPV4
	serveraddr.sin_port = htons(10000);//为当前进程添加的端口号为10000
	serveraddr.sin_addr.s_addr = inet_addr("192.168.138.1");
	if(connect(socketfd,(struct sockaddr *)&serveraddr,sizeof(serveraddr))<0)
	{
		printf("connect error\n");
	}else{
		printf("connect ok\n");
	}
	
	char buf[20];
	while(1)
	{
		memset(buf,0,20);//清空buf
   		fgets(buf,20,stdin);
		write(socketfd,buf,20);//发送

		memset(buf,0,20);
		read(socketfd,buf,20);//接收
		printf("data is %s\n",buf);
	}
	close(socketfd);
	return 0;
}

服务端:类比于接电话

类比

功能

函数

有手机

创建套接字(有对应的TCP协议)

socket()

有手机号码

绑定套接字(有自己的网络信息)

bind()

等待电话(待机)

监听服务端套接字(查看是否有客户端连接)

listen()

接听电话

接收同意客户端的连接请求

accept()

进行通话

收发数据(进行通信)

read()、write()、recv()、send()、

挂断电话

结束通信(关闭套接字)

close()

##服务端API函数

1.创建套接字

#include <sys/types.h>

#include <sys/socket.h>

//创建套接字文件,为进程添加一个对应的网络通信协议(文件),返回值就是创建号的文件描述符(socket描述符就代表一套协议---套接字)

int socket(int domain, int type, int protocol);

参数1:

int domain:地址族,选用那种网络层协议地址
AF_INET------IPV4
AF_INET6------IPV6

参数2:

int type:套接字类型

SOCK_STREAM--------TCP

SOCK_DGRAM---------UDP

SOCK_RAW:原始套接字(没有传输层协议)

参数3:

int protocol:套接字协议

 0:套接字默认协议

返回值:

int:整数---------文件描述符(套接字文件)

成功:返回套接字描述符 >= 0

失败:返回-1

2.邦定套接字

#include <sys/types.h> /* See NOTES */

#include <sys/socket.h>

//绑定本地网络信息到套接字中

int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);

参数1:

int sockfd:绑定本地网络信息到哪个套接字上(绑定到哪一套协议上)

参数2:

const struct sockaddr *addr:结构体地址,这个结构体中存储的本地网络信息(要绑定的网络信息ip、port)

struct sockaddr {//通用结构体,表示一个网络信息内容

sa_family_t sa_family;

char sa_data[14];

}

//IPV4 网络信息结构体

struct sockaddr_in {

sa_family_t sin_family;//地址族 AF_INET

in_port_t sin_port;//端口

struct in_addr sin_addr;//结构体变量--ip地址

};



/* Internet address. */

struct in_addr {

  uint32_t s_addr;//ipv4地址

};

参数3:

socklen_t addrlen:整数,结构体大小(确定信息结构体的大小)

返回值:

成功:返回0

失败:返回-1

3.监听

#include <sys/types.h>          

 #include <sys/socket.h>

监听等待客户端连接,如果有客户端的连接只会把客户端的连接存储起来(会创建一个监听队列),只要有客户端来进行连接,都会放在监听等待队列中——能够一直查看服务器自己的信息,是否有客户端连接。且之后套接字只能用于监听,不能用于与客户端进行通信

       int listen(int sockfd, int backlog);当调用后自动监听(查看是否有客户端连接)

参数1:

int sockfd:要进行监听的套接字,就是之前绑定了服务器ip、port的套接字,
            表示要监听哪个套接字是否有客户端连接

参数2:

int backlog:最多同时能够存储多少个客户端连接——等待队列大小

返回值:

成功——  0     

失败——-1

4.同意连接请求

#include <sys/types.h>          /* See NOTES */

#include <sys/socket.h>

//服务器同意连接请求(从等待队列中取出一个客户端连接请求,建立连接。如果监听队列没有连接请求,就阻塞等待监听队列有连接请求)——一定要接收一个连接请求

       int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

参数1:

int sockfd:监听套接字,从哪个监听中取出连接

参数2:

struct sockaddr *addr:用于存储客户端ip,port,不需要写NULL

参数3:

socklen_t *addrlen:结构体大小

返回值:

成功——返回与连接成功的客户端通信的套接字   

失败——-1

5.发送或接收数据

ssize_t send(int sockfd, const void *buf, size_t len, int flags);

ssize_t recv(int sockfd, void *buf, size_t len, int flags);

参数1:

int sockfd:套接字

参数2:

const void *buf:要发送或接收的数据

参数3:

size_t len:数据大小

参数4:

int flags:标志,选项

0:阻塞

实现代码:文章来源地址https://www.toymoban.com/news/detail-474455.html

//tcp服务端,与客户端进行通信
#include <sys/types.h>     
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
int main()
{
	//1、创建套接字
	int sockfd = socket(AF_INET,SOCK_STREAM,0);//在服务器创建TCP套接字
	
	//2、绑定套接字
	struct sockaddr_in serveraddr;
	serveraddr.sin_addr.s_addr = inet_addr("192.168.124.80");
	serveraddr.sin_port = htons(9999);
	serveraddr.sin_family = AF_INET;

	bind(sockfd,(struct sockaddr *)&serveraddr,sizeof(serveraddr));

	//3、监听套接字
	listen(sockfd,10);
	
	//4、接受客户端连接
	int clientfd = accept(sockfd,NULL,NULL);//返回值就是与客户端通信的套接字
	printf("ok\n");	

	//服务器与客户端如何进行通信
	char buf[50];
	while(1)
	{
		printf("recv\n");
		sleep(10);
		memset(buf,0,50);
		int num = recv(clientfd,buf,50,0);//返回值就是接收的大小
		if(strcmp(buf,"quit\n") == 0)
			break;
		printf("size is %d; data is %s",num,buf);
		send(clientfd,buf,50,0);
		printf("send ok\n");
	}
	close(clientfd);

	close(sockfd);

	return 0;
}

到了这里,关于【网络篇】socket编程——TCP(史上最全)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 基于TCP的Socket网络编程

    前言: Socket通信是基于TCP/IP协议的通信。在工作和做项目中应用非常广,下面来介绍下Socket网络编程! Socket的介绍 首先,在Socket网络编程中我们要了解两个重要的东西,ip和端口号,一台拥有IP地址的主机可以提供许多服务,比如Web服务、FTP服务、SMTP服务等。这些服务完全

    2024年02月11日
    浏览(36)
  • Go语言网络编程(socket编程)TCP粘包

    服务端代码如下: 客户端代码如下: 将上面的代码保存后,分别编译。先启动服务端再启动客户端,可以看到服务端输出结果如下: 收到client发来的数据: Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you? 收到client发来的数

    2024年02月09日
    浏览(43)
  • 【网络通信】socket编程——TCP套接字

    TCP依旧使用代码来熟悉对应的套接字,很多接口都是在udp中使用过的 所以就不会单独把他们拿出来作为标题了,只会把第一次出现的接口作为标题 通过TCP的套接字 ,来把数据交付给对方的应用层,完成双方进程的通信 在 tcpServer.hpp 中,创建一个命名空间 yzq 用于封装 在命名

    2024年02月13日
    浏览(32)
  • Socket编程接口API并实现简单的TCP网络编程

    #include sys/types.h #include sys/socket.h socket()创建套接字,成功返回套接字的文件描述符,失败返回-1 domain: 设置套接字的协议簇, AF_UNIX AF_INET AF_INET6 type: 设置套接字的服务类型 SOCK_STREAM SOCK_DGRAM protocol: 一般设置为 0,表示使用默认协议 int socket(int domain, int type, int protocol); bind()将

    2024年02月13日
    浏览(30)
  • 【网络编程】网络编程概念,socket套接字,基于UDP和TCP的网络编程

    前言: 大家好,我是 良辰丫 ,今天我们一起来学习网络编程,网络编程的基本概念,认识套接字,UDP与TCP编程.💞💞💞 🧑个人主页:良辰针不戳 📖所属专栏:javaEE初阶 🍎励志语句:生活也许会让我们遍体鳞伤,但最终这些伤口会成为我们一辈子的财富。 💦期待大家三连,关注

    2023年04月20日
    浏览(42)
  • TCP/IP(十一)TCP的连接管理(八)socket网络编程

    一  socket网络编程  socket 基本操作函数 bind、listen、connect、accept、recv、send、select、close ①  针对 TCP 应该如何 Socket 编程? ②   listen 时候参数 backlog 的意义? ③  accept 发生在三次握手的哪一步? ④   客户端调用 close 了,连接是断开的流程是什么? ⑤  没有 accept,能建立 T

    2024年02月07日
    浏览(41)
  • Linux socket网络编程实战(tcp)实现双方聊天

    在上节已经系统介绍了大致的流程和相关的API,这节就开始写代码! 回顾上节的流程: 创建一个NET文件夹 来存放网络编程相关的代码: 这部分先实现服务器的连接部分的代码并进行验证 server1.c: 代码验证: 先编译并运行这部分代码: 可见,此时没有客户端进行连接,程

    2024年02月03日
    浏览(42)
  • 网络编程——socket服务端和客户端(TCP)

    所谓套接字(Socket),就是对网络中不同主机上的应用进程之间进行双向通信的端点的抽象。一个套接字就是网络上进程通信的一端,提供了应用层进程利用网络协议交换数据的机制。从所处的地位来讲,套接字上联应用进程,下联网络协议栈,是应用程序通过网络协议进行通

    2024年02月07日
    浏览(34)
  • Linux网络编程:socket、客户端服务器端使用socket通信(TCP)

    socket(套接字),用于网络中不同主机间进程的通信。 socket是一个伪文件,包含读缓冲区、写缓冲区。 socket必须成对出现。 socket可以建立主机进程间的通信,但需要协议(IPV4、IPV6等)、port端口、IP地址。          (1)创建流式socket套接字。                 a)此s

    2024年02月11日
    浏览(45)
  • 「网络编程」第二讲:网络编程socket套接字(三)_ 简单TCP网络通信程序的实现

    「前言」文章是关于网络编程的socket套接字方面的,上一篇是网络编程socket套接字(二),下面开始讲解!  「归属专栏」网络编程 「主页链接」个人主页 「笔者」枫叶先生(fy) 「枫叶先生有点文青病」「每篇一句」 I do not know where to go,but I have been on the road. 我不知

    2024年02月11日
    浏览(37)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包