TCP协议

1.TCP框架

1.1TCP概念

传输控制协议(TCP,Transmission Control Protocol)是一种面向连接的、可靠的、基于字节流的传输层通信协议。

1.2TCP协议段格式(Tcp报头)

在这里插入图片描述

1 . 源 / 目的端口号:
表示数据是从哪个进程来,到哪个进程去
2 . 32序号 :
作用:
(1)保证数据按位到达,由发送方来填。
(2)去重功能
假若数据在网络中出现拥堵现象,到达一定时间(由超时重传定时器设置RTO)未收到ACK就会进行超时重传,但若第二次发送出去之后,之前拥堵的数据也被服务器接收到时,就会产生冗余数据,这时可利用32位序号来去重。

3 . 32位确认号
ACK的确认序号 = seq + 1;由接收方发送

4 . 4位TCP报头长度(5~15)
表示该TCP头部有多少个32位bit(有多少个4字节);所以TCP头部最大长度是15 * 4 = 60;

5 . 16位紧急指针
标识哪部分数据是紧急数据(带外数据:通信之外的数据out - of - banddata)
只有URG标志位被设置时该字段才有意义,表示紧急数据相对序列号(Sequence Number字段的值)的偏移。

6 . 16位校验和
保证数据的完整性,由发送端填充。校验方案:CRC校验。接收端校验不通过,则认为数据有问题,此处的校验和不光包含TCP首部,也包含TCP数据部分。

7 . 16位窗口大小
由发送方填,填上自己接收缓冲区剩余空间大小。来实现流量控制
7 . 16位标志位
URG:紧急指针是否有效
ACK:确认号是否有效
PSH:提示接收端应用程序立刻从TCP缓冲区把数据读走,交付给上层或者提示发送方尽快把发送缓冲区的数据发过来
RST:三次握手时,在第三步阶段,若服务器没有收到客户端的ACK,这时客户端若向服务器发送数据时,服务器会给客户端发送一个响应reset,要求客户端重新进行三次握手。我们把携带RST标识的成为复位报文段
SYN:请求建立连接;我们把携带SYN标识的的成为同步报文段
FIN:通知对方,本端要关闭了,我们称携带FIN标识的为结束报文段

2.确认应答(ACK)机制

在这里插入图片描述TCP协议是基于确认应答机制的只要收到了对方的确认应答ACK,那么就会认为之前发送的数据已经被对方收到了。
序列号:TCP将每个字节的数据都进行了编号,即为序列号。
确认序号:每一个ACK都带有对应的确认序列号,意思是告诉发送者,我已经收到了哪些数据,下一次你从哪里开始发。
提问:为什么会有32位序号和32位确认序号这两种序号?
回答:因为TCP是全双工通信,因此存在同时发送数据的可能,所以得有两种序号。

3.超时重传机制

情况一:
主机A发送数据给B之后,可能因为网络拥堵等原因,数据无法到达主机B。
如果主机A在一个特定时间间隔内没有收到B发来的确认应答,就会进行重发。
在这里插入图片描述
情况二:
假若主机B收到了来自主机A的数据,但是主机A未收到主机B的ACK,ACK丢失也会触发超时重传机制。
在这里插入图片描述
Linux中,超时以500ms为一个单位进行控制,每次判定超时重发的超时时间都是500ms的整数倍。
如果重发一次之后,仍然得不到应答,等待2*500 ms进行重传,依此类推,以指数形式递增。
累计到一定的重传次数,TCP认为网络或者对端主机出现异常,强制关闭连接。

4.连接管理机制

在正常情况下,TCP要经过三次挥手建立连接,四次挥手断开连接。
在这里插入图片描述
服务端状态转化
[CLOSED -> LISTEN] 服务器端调用listen后进入LISTEN状态,等待客户端连接。

[LISTEN -> SYN_RCVD] 一旦监听到连接请求,就将该连接放入内核等待队列中,并向客户端发送SYN确认报文。

[SYN_RCVD -> ESTABLISHED]服务端一旦受到客户端的确认报文,就会进入ESTABLISHED状态,这时就可以进行读写数据了。

[ESTABLISHED -> CLOSE_WAIT]当客户端主动关闭连接时(调用close);服务器会收到结束报文段,服务器会返回确认报文段,并进入CLOSE_WAIT状态。

[CLOSE_WAIT -> LAST_ACK]进入CLOSE_WAIT状态,说明的服务器准备关闭连接(需要处理完之前的数据);当服务器真正调用close关闭连接时,会向客户端发送FIN,此时服务器进入LAST_ACK状态,等待最后一个ACK到来(这个ACK是客户端确认收到了FIN)

[LAST_ACK -> CLOSED] 服务器收到了对FIN的ACK,彻底关闭连接。

客户端状态转化
[CLOSED -> SYN_SENT] 客户端调用connect,发送同步发送报文段;

[SYN_SENT -> ESTABLISHED] connect调用成功,则进入ESTABLISHED状态,开始读写数据。

[ESTABLISHED -> FIN_WAIT_1] 客户端主动调用close时,向服务端发送结束报文段,并进入FIN_WAIT_1状态;

[FIN_WAIT_1 -> FIN_WAIT_2] 客户端收到服务器对结束报文段的确认,则进入FIN_WAIT_2,,开始等待服务器的结束报文段;

[FIN_WAIT_2 -> CLOSE] 客户端需要等待2MSL(Max Segment Life, 报文最大生存时间的时间。)才会进入CLOSE状态

总结: \color{Blue}总结: 总结:
问题一 : 如何理解连接: \color{Brown}问题一:如何理解连接: 问题一:如何理解连接:
1. 连接本身是有成本的,会创建相对应的文件描述符,和 s o c k 系列的结构(空间加时间) \color{Brown}1. 连接本身是有成本的,会创建相对应的文件描述符,和sock系列的结构(空间加时间) 1.连接本身是有成本的,会创建相对应的文件描述符,和sock系列的结构(空间加时间)
2. T C P 面向连接, 3 次握手成功,意味着 c l i e n t 和 s e r v e r 都要去维护连接 \color{Brown}2.TCP面向连接,3次握手成功,意味着client和server都要去维护连接 2.TCP面向连接,3次握手成功,意味着clientserver都要去维护连接
在这里插入图片描述
问题二:为什么是 3 次握手 \color{Brown}问题二:为什么是3次握手 问题二:为什么是3次握手
1次,2次握手的话,容易受到SYN洪水攻击,(SYN洪水攻击是DDOS攻击中最常见的攻击类型之一,是一种利用TCP 协议缺陷,攻击者向被攻击的主机发送大量伪造的TCP连接请求,从而使得被攻击方主机服务器的资源耗尽 (CPU 满负荷或内存不足) 的攻击方式。)
1. 最小成本验证全双工,双方均确认彼此可以正常发送和接收数据 \color{Red}1.最小成本验证全双工,双方均确认彼此可以正常发送和接收数据 1.最小成本验证全双工,双方均确认彼此可以正常发送和接收数据
2. 让服务器不要出现连接误判的情况,从而减小服务器的资源浪费 \color{Red}2.让服务器不要出现连接误判的情况,从而减小服务器的资源浪费 2.让服务器不要出现连接误判的情况,从而减小服务器的资源浪费

问题三 : \color{Brown}问题三: 问题三:FIN_WAIT_1的解释:
FIN_WAIT_1状态: 表示主动断开连接的一方不会向对方发送数据了,但是后续传输层还会发 A C K 确认报文 \color{Red}表示主动断开连接的一方不会向对方发送数据了,但是后续传输层还会发ACK确认报文 表示主动断开连接的一方不会向对方发送数据了,但是后续传输层还会发ACK确认报文

问题四 : \color{Brown}问题四: 问题四:CLOSE_WAIT的解释:
当客户端直接断开连接时,服务器在收到关闭请求要给对方发送 A C K 报文,但这时客户端已经断开连接了,因此服务器会一直处于 \color{Red}当客户端直接断开连接时,服务器在收到关闭请求要给对方发送ACK报文,但这时客户端已经断开连接了,因此服务器会一直处于 当客户端直接断开连接时,服务器在收到关闭请求要给对方发送ACK报文,但这时客户端已经断开连接了,因此服务器会一直处于CLOSE_WAIT状态 若有大量无用的连接时,会增大服务器的负担,在保活定时器的作用下,服务器最终也会自动断开连接。 \color{Red}若有大量无用的连接时,会增大服务器的负担,在保活定时器的作用下,服务器最终也会自动断开连接。 若有大量无用的连接时,会增大服务器的负担,在保活定时器的作用下,服务器最终也会自动断开连接。

问题五 : \color{Brown}问题五: 问题五:TIME_WAIT的解释:
主动断开的一方将会进入该状态 , 并进行等待 \color{Red}主动断开的一方将会进入该状态,并进行等待 主动断开的一方将会进入该状态,并进行等待
等待的意义 \color{OrangeRed}等待的意义 等待的意义
1. 尽量尽量保证最后一个 A C K 被对方接受到,进而尽快的释放服务器的资源; \color{OrangeRed}1.尽量尽量保证最后一个ACK被对方接受到,进而尽快的释放服务器的资源; 1.尽量尽量保证最后一个ACK被对方接受到,进而尽快的释放服务器的资源;
等待历史数据在网络上进行消散,等待的时间为 2 ∗ M S L ( M a x S e g m e n t L i f e , 报文最大生存时间的时间。 ) 才会进入 C L O S E 状态 ∗ ∗ 。 \color{OrangeRed}等待历史数据在网络上进行消散,等待的时间为2* MSL(Max Segment Life, 报文最大生存时间的时间。)才会进入CLOSE状态**。 等待历史数据在网络上进行消散,等待的时间为2MSL(MaxSegmentLife,报文最大生存时间的时间。)才会进入CLOSE状态

TIME_WAIT系列问题:
当 C t r l 加 C 终止掉 s e r v e r 时,这时 s e r v e r 是主动关闭连接的一方,这时 s e r v e r 端会进入 \color{SpringGreen}当Ctrl加C终止掉server时,这时server是主动关闭连接的一方,这时server端会进入 CtrlC终止掉server时,这时server是主动关闭连接的一方,这时server端会进入TIME_WAIT 状态。 \color{SpringGreen}状态。 状态。
注意:在该状态下 s e r v e r 端不能再次监听同样的 s e r v e r 端口,会出现 b i n d e r r o r 现象 \color{SpringGreen}注意:在该状态下server端不能再次监听同样的server端口,会出现bind error现象 注意:在该状态下server端不能再次监听同样的server端口,会出现binderror现象
解决方案:
int opt = 1;
使用setsockopt(listenfd, SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));

4.滑动窗口

在这里插入图片描述
这种一发一收的方式性能较低(滑动窗口=1),若我们一次发送多条数据,就可以大大的提高性能(其实是将多个段的等待时间重叠在一起了)

在这里插入图片描述
指的是暂时无需等待确认应答而可以继续发送数据的最大值。
1、窗口类型
接收端窗口rwnd(recv window):接收端缓冲区剩余大小。接收端将此窗口值放在 TCP 报文的首部中的窗口字段,传送给发送端;

拥塞窗口cwnd(congestion window):发送端缓冲区大小;

窗口大小的值 = m i n ( M S S , 对方的 w i n 值,拥塞窗口 c w n d 值 ) \color{OrangeRed}窗口大小的值 = min(MSS,对方的win值,拥塞窗口cwnd值) 窗口大小的值=min(MSS,对方的win值,拥塞窗口cwnd)
滑动窗口越大,网络的吞吐率越高 \color{OrangeRed}滑动窗口越大,网络的吞吐率越高 滑动窗口越大,网络的吞吐率越高
因此缓冲区可以分为三个区域:
1.滑动窗口之前的表示已经发送并且受到对方的ACK
2.滑动窗口之内的表示已经发送但未收到ACK
3.滑动窗口之后的表示尚未发送

滑动窗口的丢包问题:
情况一: 数据包已经抵达,但 A C K 丢了 \color{SpringGreen}数据包已经抵达,但ACK丢了 数据包已经抵达,但ACK丢了

在这里插入图片描述
这时候部分ACK丢了并不要紧,因为可以通过后续的ACK进行确认。

情况二: 数据包丢了 \color{SpringGreen}数据包丢了 数据包丢了
在这里插入图片描述
接收到三个重复的ACK响应,就开始重传响应所要求的报文的机制就是快重传速机制。
快重传机制为什么是收到三个重复的ACK响应就会发生数据重传?
因为倘若是两个重复的ACK,可能会是超时重传情况下的数据冗余问题,因此当收到两个重复的ACK而没有收到第三个ACK就会进行超时重传
因此超时重传和快重传是相辅相成的。

5.流量控制(通信双方控制)

接收端处理数据的速度是有限的,如果发送端发的太快,导致接收端的缓冲区被打满,这个时候如果发送端继续发送,就会造成丢包现象,继而引起丢包重传等等一系列连锁反应。
因此TCP支持根据接收端的处理能力,来决定发送端的发送速度,这个机制叫做流量控制(Flow Control);
所谓的流量控制就是让发送方的发送速率不要太快,让接收方来得及接收。利用滑动窗口机制可以很方便的在TCP连接上实现对发送方的流量控制。

如果接收端缓冲区满了,就会将滑动窗口置为0;这是发送方不在发送数据,但是需要定期发送一个窗口探测数据段(窗口更新通知),使接收端把窗口大小告诉发送端。
在这里插入图片描述

6.拥塞控制(多人控制)

TCP有了滑动窗口能够高效可靠的发送大量的数据。但是如果在刚开始阶段就发送大量数据,仍然可能发生问题。
因为网络有很多的计算机,可能当时的网络状态就已经比较拥堵。在不清楚当前网络状态下,贸然发送大量的数据,是很有可能引起雪上加霜的。
T C P 引入慢启动机制,先发少量的数据,探探路,摸清当前网络的拥堵状况,然后再决定按照多大的速度传输数据。 \color{OrangeRed}TCP引入慢启动机制,先发少量的数据,探探路,摸清当前网络的拥堵状况,然后再决定按照多大的速度传输数据。 TCP引入慢启动机制,先发少量的数据,探探路,摸清当前网络的拥堵状况,然后再决定按照多大的速度传输数据。
在这里插入图片描述

6.1 慢启动和拥塞避免 \color{Blue}6.1慢启动和拥塞避免 6.1慢启动和拥塞避免

发送开始的时候,定义拥塞窗口大小为1;
在开始发送信息时,由于不知道具体的网络环境,为避免大量信息造成的拥塞现象,此时的拥塞窗口以最小值(即拥塞窗口和接收端窗口中的较小值)进行数据发送,并设定门限值作为慢启动算法和拥塞避免算法的分割点。
慢启动只是初始时慢,但是增速率非常快 慢启动是指以最小的拥塞窗口按照指数形式递增,达到门限值后,以拥塞避免算法,即线性递增方式增大拥塞窗口(这里递增时间间隔为一个往返时间RTT)
在上述过程中,无论是窗口大小指数递增或者线性递增,当发生拥塞现象,则门限值更新为当前窗口大小的一半,拥塞窗口大小变为最小值,重复上述递增过程(此时属于网络环境限制,所以在接收端和拥塞窗口两个限制条件中选择拥塞窗口作为限制)

在这里插入图片描述

6.2 与快重传配合使用的还有快恢复算法 \color{Blue}6.2与快重传配合使用的还有快恢复算法 6.2与快重传配合使用的还有快恢复算法

其过程有以下两个要点:
当发送方连续收到三个重复确认时,就执行“乘法减小”算法,把慢开始门限ssthresh 减半。这是为了预防网络发生拥塞。请注意,接下去不执行慢开始算法。
由于发送方现在认为网络很可能没有发生拥塞(如果网络发生了严重的拥塞,就不会一连有好几个报文段连续到达接收方,也就不会导致接收方连续发送重复确认),因此与慢开始不同之处是现在不执行慢开始算法(即拥塞窗口 cwnd 现在不设置为 1),而是把 cwnd 值设置为慢开始门限 ssthresh 减半后的数值,然后开始执行拥塞避免算法(“加法增大”),使拥塞窗口缓慢地线性增大。
如下图所示:注意图上关键点,从收到 3 个重复的确认,执行快重传算法。
在这里插入图片描述

6.3 拥塞窗口大小为什么先以指数增加再以线性增加? \color{Blue}6.3拥塞窗口大小为什么先以指数增加再以线性增加? 6.3拥塞窗口大小为什么先以指数增加再以线性增加?

窗口大小首先以指数递增去探测一下网络的拥塞程度,执行拥塞避免算法后,拥塞窗口线性缓慢增大,防止网络过早出现拥塞。

7.延迟应答(解决效率问题)

延迟应答的目的是尽可能地接收端尽可能延迟应答客户端,以保证接收窗口变得更大,这样的话吞吐率就会越高,在保证网络不拥塞的情况下尽量提高传输效率。
延迟应答的机制:数量限制:每隔 N 个包就应答一次;时间限制:超过最大延迟时间就应答一次 \color{Blue}延迟应答的机制:数量限制:每隔N个包就应答一次;时间限制:超过最大延迟时间就应答一次 延迟应答的机制:数量限制:每隔N个包就应答一次;时间限制:超过最大延迟时间就应答一次

一般N取2,超时时间取200ms
在这里插入图片描述

8.捎带应答

捎带应答实质就是数据+确认应答(ACK),提高了传输效率
在这里插入图片描述

9. 面向字节流

若创建了一个TCP的socket,那么在内核中会创建一个发送缓冲区和一个接收缓冲区;
由于缓冲区的存在,TCP程序的读和写不需要一 一匹配
例如写100个字节数据时,可以调用一次write写100个字节,也可以调用100次write,每次写一个字节;
例如读100个字节数据时,可以调用一次read写100个字节,也可以调用100次read,每次读一个字节;

10.粘包问题

首先明确,粘包问题指的是应用层的数据包
在TCP包头中没有如同UDP一样拥有报文长度,但是有一个序号的字段,站在传输层角度,TCP是一个个报文发过来的,按照序号排好放在缓冲区中。
站在应用层角度,看到的只是一串连续的字节数据。
当应用程序看到这一连串的字节数据时,不知道从哪个部分开始到哪个部分结束,是一个完整的应用层数据包。

避免粘包问题的方法:明确两个包之间的边界 \color{SpringGreen}避免粘包问题的方法:明确两个包之间的边界 避免粘包问题的方法:明确两个包之间的边界
1.对于定长的包,保证每次都按固定大小读取即可
2.对于变长的包,可以在包头的位置, 约定一个包总长度的字段, 从而就知道了包的结束位置;
`3.对于变长的包,还可在包和包之间使用明确的分隔符(应用层协议,由程序员自己来决定,只要保证分隔符不和正文冲突即可);

$\color{SpringGreen}对于UDP协议来说,是否也存在“粘包问题”呢?$ 由于UDP是面向数据报的,因此站在应用层角度,使用UDP的时候,要么不发,要么发一个完整的报文`

11.TCP异常情况

1.进程终止:进程终止会释放文件描述符,仍然可以发送FIN,和正常关闭没有什么区别。
2.机器重启:和进程终止的情况相同。
4.机器掉电/网线断开:接收端认为连接还在,一旦接受端有写入操作,接收端发现连接已经不在了,就会发送复位报文reset,即使没有写入操作,TCP自己也内置了一个保活定时器,会定期询问对方是否还在,如果对方不在,也会把连接释放掉。

12. TCP小总结

12.1可靠性

(1)校验和
(2)序列号(按序到达)
(3)确认应答
(4)超时重发
(5)连接管理(三次握手建立连接,四次挥手断开连接)
(6)流量控制(防止来不及接受,导致的丢包现象)
(7)拥塞控制(防止因网络堵塞而出现大面积丢包现象)

12.2提高性能

(1)滑动窗口
(2)快速重传和快速回复
(3)延迟应答
(4)捎带应答

12.3其他

重传计时器:Retransmission Timer
重传定时器:为了控制丢失的报文段或丢弃的报文段,也就是对报文段确认的等待时间。当TCP发送报文段时,就创建这个特定报文段的重传计时器,可能发生两种情况:若在计时器超时之前收到对报文段的确认,则撤销计时器;若在收到对特定报文段的确认之前计时器超时,则重传该报文,并把计时器复位;

重传时间=2*RTT;
RTT(Round-Trip Time)往返时间在计算机网络中它是一个重要的性能指标。表示从发送端发送数据开始,到发送端收到来自接收端的确认(接收端收到数据后便立即发送确认,不包含数据传输时间)总共经历的时间。

RTT的值应该动态计算。常用的公式是:RTT=previous RTT*i + (1-i)*current RTT。i的值通常取90%,即新的RTT是以前的RTT值的90%加上当前RTT值的10%.

Karn算法:对重传报文,在计算新的RTT时,不考虑重传报文的RTT。因为无法推理出:发送端所收到的确认是对上一次报文段的确认还是对重传报文段的确认。干脆不计入。

坚持计时器:Persistent Timer

保活计时器:Keeplive Timer
每当服务器收到客户的信息,就将keeplive timer复位,超时通常设置2小时,若服务器超过2小时还没有收到来自客户的信息,就发送探测报文段,若发送了10个探测报文段(没75秒发送一个)还没收到响应,则终止连接。

时间等待计时器:Time_Wait Timer。
在连接终止期使用,当TCP关闭连接时,并不认为这个连接就真正关闭了,在时间等待期间,连接还处于一种中间过度状态。这样就可以时重复的fin报文段在到达终点后被丢弃,这个计时器的值通常设置为一格报文段寿命期望值的两倍。

12.4基于TCP应用层协议

HTTP
HTTPS
SSH
Telnet
FTP
SMTP
也包括我们自己写TCP程序时自定义的应用层协议。

13.理解listen的第二个参数

SYN Cookie是对TCP服务器端的三次握手协议作一些修改,专门用来防范SYN Flood攻击的一种手段。它的原理是,在TCP服务器收到TCP SYN包并返回TCP SYN+ACK包时,不分配一个专门的数据区,而是根据这个SYN包计算出一个cookie值。在收到TCP ACK包时,TCP服务器再根据那个cookie值检查这个TCP ACK包的合法性。如果合法,再分配专门的数据区进行处理未来的TCP连接。

SYN Cookie打开的情况下
服务器接收到第一次握手的消息后,不会立刻将相关信息放进半连接队列,而是根据对面发过来的报文计算自己的SYN初始序列号。

利用下面几个部分:

(1)客户端IP、客户端端口号、服务端IP、服务端端口号,这4个部分计算一个哈希值
(2)一个缓慢增长的时间戳t
(3)客户端发来的SYN序列号
(4)客户端发来的MSS协商值
利用这4个部分计算一个序列号,作为服务端的初始序列号,发送给客户端。

从客户端收到第三次握手的信息,先提取它的ACK序列号,然后减1,就应该是服务端计算出来的初始序列号。然后解码出上面提到的4个部分,如果IP地址和端口号的四元数是匹配的,客户端第一次握手的SYN序列号和MSS都是匹配的,而时间戳t的差值在接收范围内,那么就直接将这个连接的信息放进全连接队列中。

在这种情况下,半连接队列不会发生溢出,相当于容量是无限大的。

SYN Cookie没有打开
半连接队列的容量的计算方法是max(64, /proc/sys/net/ipv4/tcp_max_syn_backlog)。

SYN_RECV状态
被放置在半连接队列中的连接处于**SYN_RECV**状态。
全连接队列
ESTABLISHED状态
处于全连接队列中的连接属于ESTABLISHED状态。

listen函数
listen函数用来建立半连接队列和全连接队列。
在这里插入图片描述
listen函数的第二个参数确定了半连接队列和全连接队列的长度的和。

accpet函数
对于服务器,没有任何API控制三次握手。在listen建立好两个队列后,往半连接队列插入、从半连接队列取出放进全连接队列,这些操作都是内核自动完成的。

accept函数的功能仅仅是从全连接队列中取出一个已经完成三次握手的连接。
队列溢出
如果两个队列中的任意一个发生了溢出,就会直接向客户端返回一个rst
listen第二个参数参考文献:
https://blog.csdn.net/sinat_41619762/article/details/120105512

  • 6
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

倚心

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

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

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

打赏作者

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

抵扣说明:

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

余额充值