传输层之TCP协议

传输层协议

端到端之间的传输,重点关注的是起点和终点。

TCP协议报文格式

image-20230315194652185

  • 源/目的端口号:表示数据是从哪个进程来,到哪个进程去;

  • 32位序号/32位确认号:用于确认应答机制的编号;

  • 4位TCP报头长度:描述TCP报头有多长(4个比特位,但是单位是4字节);所以TCP头部最大长度是15 * 4 = 60

  • 6位标志位:

​ URG:紧急指针是否有效

☆ACK:确认号是否有效,携带ACK的表示为应答报文

​ PSH:提示接收端应用程序立刻从TCP缓冲区把数据读走

​ RST:对方要求重新建立连接;我们把携带RST标识的称为复位报文段

☆SYN:请求建立连接;我们把携带SYN标识的称为同步报文段

☆FIN:通知对方,本端要关闭了,我们称携带FIN标识的为结束报文段

  • 选项:可有可无

剩余的学习过程中一一讲解。

TCP基本特性

特点:有连接,可靠传输,面向字节流,全双工

有连接,面向字节流,全双工在我们实现的回显服务器代码上都有体现。

有连接:image-20230315195328615

面向字节流:
image-20230315195342895

全双工:

clientSocket既可以写,也可以读:

image-20230315195426967

唯独可靠性在代码当中没有体现,那从哪些地方可以体现出TCP协议的可靠性呢?

TCP原理

TCP协议的可靠传输【核心特性

1、确认应答机制【可靠传输核心机制】-安全机制

假设小明想周末找小红出去玩,就这么发了一条短信,但是这条短信到底发送过去了么?小明怎么才能知道呢?显然无法判定是否传输成功,就不是可靠传输

image-20230315200327711

如果此时小红回复一个No Problem,此时我们就能认为上次发送的数据已经超过到达了。如果隔了一段时间小红没有回复,就可以让我上次发送的数据丢失了。

image-20230315200616675

通过这样的形式,我们就能够判断上次发送的数据是成功还是失败,传输就变得可靠了

这就是确认应答机制

回答的【No Problem】就是应答报文也称为ACK报文

回到TCP协议的6个标志位:

image-20230315200904469

如果当前报文是应答报文,则ACK这一位为1

如果当前报文是普通报文,则ACK这一位为0

确认应答机制是TCP保证可靠传输的最核心机制

引入序号和确认序号让请求和响应对应

本来正常的小明和小红的对话是这样的:

image-20230315201439530

这样的对话,确实是小明还能明白小红的意思,因为是有序的,但是网络上可能会出现一种情况,“先来后到”,后发的请求,可能会先达到小明这里。

image-20230315201620958

一旦出现这样的情况,小明就可能认为不出去耍有什么大不了了,都是我女朋友了~这是一个巨大的误会!!!

出现这样问题的原因:后发的请求,可能会先到达对方这里!!!
网络上通信传输的路径,是复杂的!!两点之间,两个报文走不同的路线

解决办法:针对请求和应答报文进行编号

image-20230315201900084

经过这样的编号,我们就可以防止小明做白日梦了。

但是在确认应答机制中,并不是简单的这样在报文前面加上1,2,3。
而是通过引入序号和确认序号来保证不出现歧义,我们回到TCP报头中来看看:

image-20230315202038578

32位序号字段:针对请求数据进行的编号;

32位确认序号:字段针对ACK报文有效

TCP是一个面向字节流的协议,编号的时候,也是以字节为单位进行编号的。

image-20230315202301251

TCP讲每个字节的数据都进行了编号,即为序列号。

理解:

image-20230315202326706

报文的序号是1,报文的长度是1000(最后一个字节的数据编号是1000)【注意:TCP报头里只能存一个序号,最后一个字节的序号是根据报文长度来计算的,报文长度在存储在IP协议中】

image-20230315202433150

确认应答报文中的确认序号是1001:表示1001之前的数据我都收到了【请主机A开始发送从序号1001开始的数据】

image-20230315202544015

报文序号是1001,报文长度是1000(最后一个字节的数据编号是2000)

2、超时重传【解决丢包问题】-安全机制

确认应答机制描述的是数据报顺利到达对方,对方给个响应,但是传输过程中,可能会丢包,如果丢包,又如何处理呢?

丢包的原因?

丢包情况1:

image-20230315203216449

超时重传机制:小明给小红发送消息说想找小红周末出去耍,过了一会,小明依然等不到小红的回复(ACK报文)就认为之前的消息丢包,于是发送第二遍。

第二遍是否也会丢包?答案是可能的,但是概率很小,即使第二遍也会丢包,超时重传机制也会再次发送,只不过会比第一次等待时间更久。

丢包情况2:

image-20230315203721794

站在小明的角度,只能确定小明没有收到ACK报文,无法区分是小明发送的数据丢了还是ACK报文丢了。

以上两种情况都要进行重传

超时重传的优点-去重:

如果是第二种情况是ACK报文丢了,但是小红已经收到了消息,然后小明这边又重传,小红会收到两个一模一样的消息。小红此时就会想我刚刚是不是丢包了?于是乎会自动把两个消息当做成一个消息【去重】

TCP也是如此,接收方因为丢失ACK就会导致收到重复的消息,TCP就会针对相同的消息进行去重(根据序号来去重即可

保证了应用层代码通过socket读取数据的时候,读到的不是重复的数据。

超时时间如何确定

一般系统里面会有一个配置项,用于描述超时的时间阈值。
例如第一次出现丢包,发送方就会在达到超时时间阈值之后进行重传,如果重传的数据仍然无响应,还会举行超市重传,第二次的超时时间一般比第一次更加长。

超时间就并非是均等的

超时时间是逐渐变大的,由于刚才假设,如果单个数据报丢包概率较小,这个时候,第二次传输,大概率是可以顺利到达的
如果第二次传输也没有达到,说明当前网络环境比较糟糕。单个数据报丢包概率可能非常大了!!(甚至可能是断网了)
如果网都断了,再怎么频繁的重传,也没什么用
就不如传的频率降低一点(时间间隔长一点)至少可以节省点主机的开销~~—(摆烂)

3、连接管理-安全机制

连接管理描述的是TCP建立连接和断开连接的过程

主机 A和主机 B 建立连接~
主机 A 的系统内核里,记录一个数据结构,包含了和他连接的对方,是谁(IP,端口,使用的协议)
主机 B 的系统内核里,记录一个数据结构,包含了和他连接的对方,是谁(IP,端口,使用的协议)

1、建立连接【三次握手】

其实是4次,但是中间两次可以合并在一起

image-20230315210455919

合并以后:

image-20230315210523636

解释:

1.小明和小红一开始是普通的关系,没有建立认同。

2.申请一下,尝试建立连接:

3.通信双方,各自向对方申请,尝试和对方建立连接,然后在各自给对方回应。

4.建立连接的过程确实是四次的数据交互。

简图:

image-20230315210841843

(第二次ACK画图不准确,应该是ACK+SYN报文到达小明这边后马上触发内核发送ack报文,图中没有连接了一起)

建立连接的意义

1、检查一下当前的网络情况是否畅通

三次握手建立连接并不传输如何业务数据

2、三次握手同时也是在检查通信双方发送能力接受能力都是正常的

比如小明和小红打电话:

image-20230315212050515

3、三次握手过程中,也在协商重要的参数

image-20230315212513580

TCP里面有很多参数需要协商,但是并不详细介绍。

最重要的就是起始序号的确定

举个栗子,TCP的序号并非是从1开始的,通常都是建立连接的时候协商了一个数字,目的保证两个连接的序号有差别,如果连接断开又快速重连,接收方就可以区分当前收到的数据是当前连接的还是上个连接的

TCP状态-建立链接时

image-20230315213434263

上面的图包含的信息:

1、三次握手的流程

2、三次握手TCP状态转换

3、每个环节,涉及到的socket【Linux系统原生socket】的执行

两个重点的TCP状态

LISTEN:服务器启动之后,绑定端口之后(new ServerSocket)完成 【表示手机开启,信号良好,等待别人打电话过来】

ESTABLISHED:连接建立好了之后的稳定状态。【表示待会接通了,可以说话了】

2、断开连接【四次挥手】

双方取消相互认同的关系

通信双方,各自向对方申请断开连接,在各自给对方回应:

image-20230315213906708

结束报文段的FIN标志位为1

为什么这里B的ACK和FIN不能结合成一个操作呀?

原因:

image-20230315214138665

而在三次握手中,B给A返回的ACK是收到A给B的syn之后立即触发的(内核中完成)并且A给B发的syn也是内核触发,立即发送

对比三次握手:

image-20230315214241234

ACK和SYN都是内核触发的,时机完全相同,操作系统就会把两个包合并成一个。

小TIPS:但是也有可能做到三次挥手【捎带应答机制】

TCP状态-断开链接时

左边:客户端 右边:服务器

image-20230315214422037

两个重要的TCP状态

CLOSE_WAIT: 等待代码中调用close操作,如果服务器上出现大量的CLOSE_WAIT状态的连接,说明代码bug,close操作没有被及时调用到。

TIME_WAIT:主动发起关闭的一方,会进入TIME_WAIT

A处理完最后一个ACK之后,不能立即释放连接,而需要保持一定的时间,是为了防止最后的ACK丢包,还有办法能够重传。

image-20230315214700535

有关TIME_WAIT的时间是2MSL,为什么?

  • MSL是TCP报文的最大生存时间【网络上册两个位置之间传输数据消耗的最大时间】,因此TIME_WAIT持续存在2MSL的话
  • 就能保证在两个传输方向上的尚未被接收或迟到的报文段都已经消失(否则服务器立刻重启,可能会收到来自上一个进程的迟到的数据,但是这种数据很可能是错误的);
  • 同时也是在理论上保证最后一个报文可靠到达(假设最后一个ACK丢失,那么服务器会再重发一个FIN。这时虽然客户端的进程不在了,但是TCP连接还在,仍然可以重发LAST_ACK);

4、滑动窗口-效率机制

以上的确认应答机制、超时重传机制、连接管理机制可以保证TCP的可靠传输,但是付出了效率的代价,TCP协议怎么做到能够在保证可靠性的前提下,尽可能的提高传输效率的?—滑动窗口机制。

滑动窗口:提高TCP传输效率的有效机制

在没有滑动窗口之前,我们看看主机A和主机B的沟通过程。

image-20230316120711619

主机A一发送,主机B一确认~很朴素的确认应答。

问题在于主机A必须等待主机B返回了ACK才继续发送下一条报文,导致效率低。

滑动窗口介绍

解决方式:不等ACK,直接往下发下一条。【不是完全不等,而是每次批量发送一批消息,然后再等一批ACK,再发一批消息。

我们把不需要等待,直接发送的数据的量称为【窗口大小】

image-20230316121149853

  • 窗口大小指的是无需等待确认应答而可以继续发送数据的最大值。上图的窗口大小就是4000个字节(四个段)。
  • 发送前四个段的时候,不需要等待任何ACK,直接发送;
  • 收到第一个ACK后,滑动窗口向后移动,继续发送第五个段的数据;依次类推;
  • 操作系统内核为了维护这个滑动窗口,需要开辟 发送缓冲区 来记录当前还有哪些数据没有应答;只有确认应答过的数据,才能从缓冲区删掉;
  • 窗口越大,则网络的吞吐率就越高;

批量发送4条数据,批量等到4条ACK

主机A发送1 - 4000的数据给主机B,主机B收到1 - 1000的数据后后就返回1001的ACK给主机A,告诉主机A:1000之前的数据我收到了,主机A此时就会发送4001 - 5000的数据给主机B,相当于将1 - 1000数据移出窗口,,同时4001-5000数据移入窗口

窗口大小:4000(1 - 4000)

注意主机A是收到了一个ACK就继续发一条数据,而不是等所有的ACK都到了,才统一发下一组。

image-20230316121404495

白色的框就是我们的窗口,表示给主机B发送了4条数据(1001 - 5000),此时A等待的ACK的范围是1001 - 5000

当2001这个ACK到达主机A的时候,表明2000之前的数据都收到了,此时主机A就立即发送5001 - 6000【立即将1001 - 2000移出窗口,,同时5001-6000移入窗口】

此时A等待的ACK的范围2001 - 6000

细心的人会发现:如果我把窗口调到无穷大,发送反就完全不需要等待ACK,不就和UDP一样咔咔咔一顿乱发,此时效率确实和UDP一样,但是之前的可靠性又不能保证了。

效率高低取决于窗口大小。

异常情况:

1、发送方发送的数据丢包了

2、响应的ACK丢包了

情况一:ACK丢包

image-20230316122554976

遇到这种情况不用做任何处理,对于可靠传输没有任何影响

原因:

ACK报文的序号1001表示小于1001的数据主机B都收到了,假设此时1001的ACK丢包了,但是2001的ACK主机A收到了,它就代表着小于2001序号的数据报我都收到了,这里面涵盖了1 - 1000的数据报。

情况二:发送的数据报丢了

image-20230316123458989

解释:

①当1001-2000这个数据丢失的时候,开始的时候主机A是不知道的,仍然在直接将一个窗口的数据批量发出去

②此时主机B返回的ack就是在向A索要1001之后的,也就是一直给A发送 1001 这样的ACK,就像是在提醒发送端 “我想要的是 1001” 一样;但是主机A不理睬,继续自顾自的将窗口内的数据发送给主机B

③当主机A发现主机B连续索要3次之后,就是收到主机B机连续三次发送同样一个 “1001” 这样的应答;这个时候主机A就意识到自己的1001 - 2000包丢包了

④于是主机A就通过快速重传重传1001为序号的报文,会将对应的数据 1001 - 2000 重新发送;

⑤这个时候接收端收到了 1001 之后,再次返回的ACK就是7001了(因为2001 - 7000)接收端其实之前就已经收到了,被放到了接收端操作系统内核的接收缓冲区中;

image-20230316124241157

注意:当1001发过去以后,主机B收到了1001 - 2000的数据由于前面已经把7001之前的数据都传输过去了并且主机B正常收到,紧接着主机B返回的ACK确认序号就是7001了,也就代表着7001之前的数据已经接受到了。

情况二的更加特殊的情况:

1、第一条报文丢包:窗口里面的其他报文继续发,那接受反B怎么知道第一条报文序号是多少呢?在三次握手建立连接时候,他们就已经确定好了第一条报文的序号了,他们沟通好了

2、最后一条报文丢包:按照正常的超时重传即可

快速重传

在情况二这种异常中,只是把丢的数据报进行了重传,没有丢的包没有重传,这种重传的效率很高,所以我们将其称之为快速重传

快速重传:搭配滑动窗口机制的超时重传,是超时重传的特殊情况

5、流量控制 -安全机制

控制发送方的窗口大小

窗口越大,发送速度越快,效率确实越高,但是窗口无限大难道没有坏处么?发送的太快,如果接收方来不及处理跟不上,就会导致接收方丢弃一部分数据!!!Amazing啊,TCP是可靠传输,要保证可靠性,你给我丢了那能行啊,TCP为了保证可靠性,又要重新穿一遍,本来是为了提升效率,这重传一遍效率就更加低了啊。

流量控制机制:在滑动窗口的基础之上,对发送速率做出限制的机制。限制发送方的窗口大小,不要过大。

如何来控制窗口的大小呢?通过询问接收方的方式,确定窗口的大小

接收方对于发送方的反制。

接收方根据自己的接收速率(接收能力)来反向影响发送方接下来的发送速率(窗口大小)

如何量化接收方的接收速率?通常使用接收缓冲区的剩余空间大小来作为发送方发送速率的参考数值。

接收缓冲区

接收缓冲区:一个内存空间(字节数组)

image-20230316125609712

TCP和UDP协议都是内核中的协议(传输层,由操作系统负责)

①主机A使用TCP协议向主机B发送数据,B作为接收方收到的数据放到系统内核接收缓冲区中

②等待主机B这边的应用程序通过socket api把数据从接收缓冲区中读取走,这样就可以在应用程序中处理这些数据了

类比生产者消费者模型:

主机A生产者,主机板内核接收缓冲区是交易场所,主机B应用程序是消费者。

主机B利用接收缓冲区剩余的空间大小的值,作为接收方的接收速率,反馈给主机A,作为发送方发送速率的参考数值

image-20230316130034506

主机B通过什么方式反馈给A呢?

接收方B收到发送方A的数据之后,就会在ACK应答报文中,把当前接收接收缓冲区剩余的空间大小的值反馈给接收方,也就是在ACK报文的16位窗口大小这个字段携带反馈

注意:这个字段只有当ACK的值为1时候才有效,负责无效

问题1:16位一共才64KB,那么滑动窗口的大小最大只能为64KB么?

并不是,TCP协议还可以通过选项这个字段里面的窗口大小的扩展因子来表示更大的窗口大小的值。

TCP首部40字节选项中还包含了一个窗口扩大因子M,实际窗口大小是 窗口字段的值左移 M 位;

问题2:第一次传输怎么确定窗口大小的值?

第一次先按照一个小一点的窗口,慢点发数据,探探路,也就是让主机B返回一个ACK报文看看应该设置的窗口大小是多少合适。

问题3:如果中途接收方告诉发送方我这里满了,别再发送了,ACK报文里面的窗口大小为0,那么发送方何时才能继续发下一条数据?

如果接收端缓冲区满了,就会将窗口置为0;这时发送方不再发送数据,但是需要定期发送一个窗口探测数据段,使接收端把窗口大小告诉发送端。

image-20230316131101994

6、拥塞控制-安全机制

控制发送方的窗口大小

流量控制机制是通过接收方的处理能力来衡量发送方的速率的

难道只需要考虑发送方的接收速率,不考虑发送方的网络影响等因素了么?显然不是。

网络上有很多的计算机,可能当前的网络状态就已经比较拥堵。在不清楚当前网络状态下,贸然发送大量的数据,是很有可能引起雪上加霜的。

image-20230316131837294

那么也就是说,除了接收方的接收速率需要衡量,发送方的发送速率也要衡量,通过什么来衡量呢?就是这些中间的节点,但是怎么样来考虑呢?

发送方速率:实验的方式动态平衡

我们通过做实验的方式,来验证发送速率多少合适。

①刚刚开始发送,发的慢一点 ,如果一路通常,就提速

②提高到一定程度,发生丢包,降速

③降低速度之后,发现又通畅了,再次提速

通过这样的动态平衡,实验来调整

image-20230316132335854

TCP实现拥塞控制具体方式:慢启动机制

①先发少量的数据,发送之初,网络的状况是不知道的,通过这个数据去看看网络的状况如何

②如果不丢包,就放大拥塞窗口,开始的时候窗口大小指数增长【短时间摸清楚网络承载的底线】

③达到阈值【防止增长太快超过上限,达到阈值不指数了】就线性增长

image-20230316132717121

解析上图:

image-20230316133012757

image-20230316133254322

少量的丢包,我们仅仅是触发超时重传;大量的丢包,我们就认为网络拥塞;

为什么是窗口大小一下子回到初始值而不是回归一点?这是TCP传统的拥塞控制实现,现在TCP随着迭代,拥塞控制也做出了一些改进,其中的改进就是回归不再回归到初始值,而是回归到中间的值.

7、延迟应答-效率机制

基于流量控制,来引入提高效率的机制

延迟应答举栗子:

老师检查暑假作业:

A同学因为玩了一个暑假,什么都没有写,他也不想补,就听天由命,不管了。

什么是延时应答呢?

B同学也是玩了一个暑假,也是没写,但是他和老师说忘家里了,下午带过来,那么不交的这段时间,他就可以补一点,可能就不会被老师点名,至少写了。

如果接收数据的主机立刻返回ACK应答,这时候返回的窗口可能比较小。

  • 假设接收端缓冲区为1M。一次收到了500K的数据;如果立刻应答,返回的窗口就是500K;
  • 但实际上可能处理端处理的速度很快,10ms之内就把500K数据从缓冲区消费掉了;
  • 在这种情况下,接收端处理还远没有达到自己的极限,即使窗口再放大一些,也能处理过来;
  • 如果接收端稍微等一会再应答,比如等待200ms再应答,那么这个时候返回的窗口大小就是1M;

image-20230316180413586

实际TCP的延时实际不一定是用时间来衡量,也可能是用传输轮次来衡量,比如上图,每发送两次请求报文,主机B发送一次ACK报文。

8、捎带应答-效率机制

在延迟应答的基础上,我们发现,很多情况下,客户端服务器在应用层也是 “一发一收” 的。意味着客户端给服务器说了 吃中午饭了么",服务器也会给客户端回一个 “吃了”;

image-20230316181313546

TCP中只要把数据传过去,对方收到之后,就会立即由内核(操作系统)返回一个ack确认报文。

另外响应数据则是应用程序里负责传输,应用程序计算响应并返回,具体什么时候传输看代码

由于这两个操作是不同的时机传输的,因此不能把这两次传输合并起来。

但是通过捎带应答,那么这个时候ACK就可以搭顺风车,和服务器回应的 “吃了” 一起回给客户端

本来ACK报文是立即就要返回的,由于延时应答机制,稍等一会返回,正好业务上也要返回这个响应,此时就把两个报文合为一了。

image-20230316181356366

两个报文合二为一是在内核处理的。

响应最后都是要到传输层中,由应用层程序调用socket将响应传到传输层,而ack报文本来就在传输层(内核),所以合并操作是在内核处理的。

注意:捎带应答是偶然的,不是100%的

9、面向字节流 -解决粘包问题(安全机制)

TCP协议是面向字节流的,面向字节流的协议都存在一个典型问题,粘包问题

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

image-20230316182737299

粘包问题:

TCP自身对于应用层数据报无法做区分,UDP面向数据报是不存在这个问题。一个UDP数据报就对应一个应用层报文。

解决办法粘包问题:

在应用层协议里面进行区分:

1、通过分隔符,例如约定使用:作为包的结束标记

2、通过指定包的长度,比如在数据报的开头位置声明长度

例如通过分隔符:

image-20230316183032215

自定义应用层协议,有几个典型的实现:

1、xml:分隔符就相当于结束标签

2、json:分隔符就相当于}

3、protobuffer:里面通过声明长度的方式来确定边界

4、http:分隔符和长度这两个方式都会用到

10、TCP中的异常处理-注意事项

连接发生了异常,怎么办???

1、程序崩溃

进程异常退出

  • 操作系统会回收进程的资源,包括释放文件描述符表
  • 释放文件描述符表,相当于调用了对应socket的close
  • 执行close就会触发FIN报文,进一步的开始四次挥手

这种情况和普通的四次挥手没什么区别

2、正常关机

关机的时候,系统会强制结束所有用户进程,和第一章情况类似。

3、主机掉电

a)掉电的是接收方,发送方不知道对面掉电了,继续发数据

  • 发送方继续发送数据,等待接收方返回ACK
  • 没有ACK返回,发送方触发超时重传
  • 超时重传几次仍然无应答,尝试重置连接【复位报文段】
  • 重置连接失败,放弃连接

image-20230316183926958

RST为1的报文就是复位报文段

b)掉电的是发送方,此时接收方等发送方发送数据

  • 接收方等了一段时间,还没发,接收方就会给发送方发送一个【心跳包】
  • 如果对方不返回【心跳包】说明心跳遗失,进一步说明对方掉线
  • 放弃连接

心跳包:周期性触发的不携带任何业务数据的包,存在的意义就是确认对方是否还在。

4、网线断开

情况和主机掉电相同,只不通信双方的主机两端各按照上诉讲述的两种情况分别进行。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

XY枫晨

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

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

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

打赏作者

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

抵扣说明:

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

余额充值