原文出处:TCP/IP协议:最大报文段长度(MSS)是如何确定的

TCP提供的是一种面向连接的,可靠的字节流服务,TCP提供可靠性的一种重要的方式就是MSS。通过MSS,应用数据被分割成TCP认为最适合发送的数据块,由TCP传递给IP的信息单位称为报文段或段(segment)。代表一个TCP socket的结构体struct tcp_sock中有多个成员用于确定应用数据被分割成最大为多大的数据块较为合适(最大报文段长度MSS)。

用户在使用路由器访问Internet时,经常会反馈不能访问网页(或部分网页)以及使用Outlook收发邮件(这些应用是基于TCP或UDP的),但进行Ping 包时没有问题,这时候检查配置时也没有错误。出现这种情况的时候,多半是因为在设备上进行了NAT应用,同时设备对报文进行了分片操作。

(没有支持路径MTU;设备没有参与MSS协商;结果TCP协商的MSS大于了设备的MTU-TCP头部-IP头部)

IP报文里是有五元组的,但报文要进行分片时,只有第一片报文带有IP的五元组信息(源目的ip位址,源目的端口号,协议号),后续的分片不会保留TCP/UDP报文所有的标识信息,如端口号信息等,这种情况下,如果设备又实现了NAT转换操作(NAT转换过程中,会随机地做埠转换),并且应用又是基于TCP/UDP的,这就导致报文不能正确组包,会出现上述的问题现象。

(不是所有TCP报文都指定IP报文不分片,特殊报文会指定,比如路径MTU发现机制)

TCP/IP连接时建立的过程中会协商很多参数的,其中TCP MSS参数就是用于协商TCP报文大小的,如果协商出来的TCP MSS的参数值小于设备的MTU的值时,TCP报文在设备上就不会被分片,否则就会出现报文分片并导致上述现象的发生。因此,为了避免上述情况的发生,一定要保证协商的TCP MSS参数小于设备的MTU的值。为此,Quidway路由器上有一个设置TCP MSS值的命令,如果配置了这条命令,路由器设备在建立TCP/IP连接的过程中就按照这个配置的值来修改协商报文中关于TCP MSS的值,在同对端协商的过程中也就能够协商出这个值来,如果不配置这条命令,路由器设备就不会修改报文中的这个值(有时对端设备发送过来的协商报文中的这个值会很大,如8000)。一般来说,默认或配置的MTU的值一般在1500左右,将TCP MSS的值设备为小于1500就可以,如1400或1024等。

如果TCP MSS值设置的过小,报文数量明显增多又导致效率下降,特别是没有配置NAT应用的情况下,限制TCP报文大小更没有必要,由于应用情况比较复杂,设置默认的TCP MSS的值也不是特别合适(设备会在建立连接时均要修改TCP MSS的值),因此,还是在应用中加以注意比较好,思科设备也是有这个配置命令的。

MTU: Maxitum Transmission Unit 最大传输单元

MSS: Maxitum Segment Size 最大分段大小

MSS最大传输大小的缩写,是TCP协议里面的一个概念。
MSS就是TCP数据包每次能够传输的最大数据分段。为了达到最佳的传输效能TCP协议在建立连接的时候通常要协商双方的MSS值,这个值TCP协议在实现的时候往往用MTU值代替(需要减去IP数据包包头的大小20Bytes和TCP数据段的包头20Bytes),通讯双方会根据双方提供的MSS值得最小值确定为这次连接的最大MSS值。而一般以太网MTU都为1500, 所以在以太网中, 往往TCP MSS为1460。

对于涉及PPPOE+NAT、IPsec、L2TP、GRE等组网,通常由于报文太大需要分片,这样会降低传输速率; 所以选择一个合适的MSS对传输数据来说比较重要. linux中一般可以通过netfilter iptables设置TCP MSS来解决。

iptables -A FORWARD -p tcp- -tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu

这条规则的目的就是改变TCP MSS以适应PMTU(Path MTU)

iptables -A FORWARD -p tcp --tcp-flags SYN,RST SYN- j TCPMSS --set-mss 128

设置MSS为128

1)Linux主机接口MTU可通过如下命令设置

ifconfig mtu

2)PPPoE MTU设置,可以通过在配置文件中添加

mtu

mru

3)NAT自动设置MSS值

iptables -A FORWARD -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu

一旦DF设置为一,将不允许中间设备对该报文进行分片,那么在遇到IP报文长度超过中间设备转发接口的MTU值时,该IP报文将会被中间设备丢弃。在丢弃之后,中间设备会向发送方发送ICMP差错报文。

为了简单直观的展示这个交互的过程,我做了下面这个图示:

点击查看原图

我找了一个实际环境下捕获的ICMP需要分片但DF位置一的差错报文,下图为其解码格式:

点击查看原图

我们可以看到其差错类型为3,代码为4,并且告知了下一跳的MTU值为1478。在ICMP差错报文里封装导致此差错的原始IP报文的报头(包含IP报头和四层报头)。

一旦出现这种因DF位置一而引起丢包,如果客户端无法正常处理的话,将会导致业务应用出现异常,外在表现为页面无法打开、页面打开不全、某些大文件无法传输等等,这将严重影响业务的正常运行。

那么客户端如何处理这种状况呢?

TCP主要通过两种方式来应对:

  1. 协商MSS,在交互之前避免分片的产生
  2. 路径MTU发现(PMTUD)

TCP MSS

TCP在三次握手建立连接过程中,会在SYN报文中使用MSS(Maximum Segment Size)选项功能,协商交互双方能够接收的最大段长MSS值。

MSS是传输层TCP协议范畴内的概念,顾名思义,其标识TCP能够承载的最大的应用数据段长度,因此,MSS=MTU-20字节TCP报头-20字节IP报头,那么在以太网环境下,MSS值一般就是1500-20-20=1460字节。

客户端与服务器端分别根据自己发包接口的MTU值计算出相应MSS值,并通过SYN报文告知对方,我们还是通过一个实际环境中捕获的数据报文来看一下MSS协商的过程:

点击查看原图

这是整个报文交互过程的截图,我们再来看一下客户端的报文详细解码:

点击查看原图

上图为客户端的SYN报文,在其TCP选项字段,我们可以看到其通告的MSS值为1460;我们在看看服务器端的SYN/ACK报文解码:

点击查看原图

上图为服务器端给客户端回应的SYN/ACK报文,查看其TCP选项字段,我们可以发现其通告的MSS值为1440。

交互双方会以双方通告的MSS值中取最小值作为发送报文的最大段长。在此TCP连接后续的交互过程中,我们可以清楚的看到服务器端向客户端发送的报文中,TCP的最大段长度都是1440字节,如下图解码所示:

点击查看原图

通过在TCP连接之初,协商MSS值巧妙的解决了避免端系统分片的问题,但是在复杂的实际网络环境下,影响到IP报文分片的并不仅仅是发送方和接收方,还有路由器、防火墙等中间系统,假设在下图的网络环境下:

点击查看原图

中间路径上的MTU问题,端系统并不知道,因此需要一个告知的机制,这个机制就是路径MTU发现(PMTUD: Path MTU Discovery )!

PMTUD

说起PMTUD,我们必须在此回到上面讲到的ICMP需要分片但DF位置一差错报文,还记得那个ICMP差错报文中有一个字段是告知下一跳的MTU值的吗?PMTUD正是利用ICMP需要分片但DF位置一差错报文的这一特性来实现的。

发送方在接收到该差错报文后,会根据该报文给出的下一跳的MTU值计算适合传输的最大段长度,从而在后续的发送报文过程中,避免在中间路径被分片的情况产生。

这在端系统主要是通过在路由表里临时添加目的主机路由并将ICMP差错报文告知的下一跳MTU值跟该主机路由关联起来来实现。

PMTUD的确是个非常不错的机制,但是在复杂的实际网络环境中,有时候会失效,因为为了安全起见,有些网络管理员会在路由器、防火墙等中间设备上设置过滤ICMP报文的安全策略,这将导致ICMP差错报文被这些中间设备丢弃,无法达到发送方,从而引起PMTUD的失效,网上有个宫一鸣前辈共享的案例——《错误的网络访问控制策略导致PMTUD 实现故障一例》,该案例正是说明这种情况绝好的例子,大家可以自行百度此文档学习参考。

值得一提的是PMTUD仅TCP支持,UDP并不支持PMTUD。

由于PMTUD可能存在ICMP差错报文被过滤的情况,很多中间设备的接口支持adjust tcp mss设置功能,思科路由器一般是在接口模式下使用命令“ip tcp adjust-mss 1400 ”来做设置,其他的品牌产品的相关设置大家可在实际工作环境下自查相关品牌和产品的使用手册。

这个功能主要是通过由中间设备修改经过其转发的TCP SYN报文中的MSS值,让中间设备参与进TCP 三次握手时SYN报文的MSS协商来避免分片。

需要注意的是,该功能不像MTU值,只针对出接口,此功能一旦开启,其将针对该接口的收发双向有效。

我做一个简化环境下的工作过程图示以便于大家理解其工作过程:

点击查看原图


原文出处:TCP拥塞控制机制(附面试题)

产生的原因

∑对资源的需求>可用资源∑对资源的需求>可用资源

注意
单纯的增加网络资源无法解决问题
例如:把结点的存储空间扩大,更换更高速率的链路,提高结点处理机的运算速度,不仅不能解决问题,而且可能使网络性能更坏。
原因:网络拥塞是许多因素引起的,单纯的解决一个可能会使上述情况得到一些缓解,但是会把拥塞转移到其他地方。
扩大结点存储空间——>由于输出链路的容量和处理机的速度并未提高,增大排队等待时间,超时重传,浪费资源。
更换更高速率的链路——>可能会缓解,,有可能造成各部分不匹配。

拥塞控制的作用

拥塞控制的作用

注意
拥塞控制与流量控制的区别
拥塞控制是防止过多的数据注入到网络中,可以使网络中的路由器或链路不致过载,是一个全局性的过程。
流量控制是点对点通信量的控制,是一个端到端的问题,主要就是抑制发送端发送数据的速率,以便接收端来得及接收。

拥塞的标志

1.重传计时器超时
2.接收到三个重复确认20191205-164740.png

拥塞控制的机制

TCP拥塞控制流程图

从连续收到三个重复的确认转入拥塞避免

慢开始与拥塞避免
慢开始

1.慢开始不是指cwnd的增长速度慢(指数增长),而是指TCP开始发送设置cwnd=1。
2.思路:不要一开始就发送大量的数据,先探测一下网络的拥塞程度,也就是说由小到大逐渐增加拥塞窗口的大小。这里用报文段的个数的拥塞窗口大小举例说明慢开始算法, 实时拥塞窗口大小是以字节为单位的。如下图:

每经过一个传输轮次,cnwd指数增长

3.为了防止cwnd增长过大引起网络拥塞,设置一个慢开始门限(ssthresh状态变量)
当cnwd<ssthresh,使用慢开始算法
当cnwd=ssthresh,既可使用慢开始算法,也可以使用拥塞避免算法
当cnwd>ssthresh,使用拥塞避免算法

拥塞避免(按线性规律增长)

1.拥塞避免并非完全能够避免拥塞,是说在拥塞避免阶段将拥塞窗口控制为按线性规律增长,使网络比较不容易出现拥塞。
2.思路:让拥塞窗口cwnd缓慢地增大,即每经过一个往返时间RTT就把发送方的拥塞控制窗口加一。

无论是在慢开始阶段还是在拥塞避免阶段,只要发送方判断网络出现拥塞(其根据就是没有收到确认,虽然没有收到确认可能是其他原因的分组丢失,但是因为无法判定,所以都当做拥塞来处理),就把慢开始门限设置为出现拥塞时的发送窗口大小的一半。然后把拥塞窗口设置为1,执行慢开始算法。

慢开始与拥塞避免算法的实现

加法增大与乘法减小
乘法减小:无论是慢开始阶段还是拥塞避免,只要出现了网络拥塞(超时),就把慢开始门限值ssthresh减半
加法增大:执行拥塞避免算法后,拥塞窗口线性缓慢增大,防止网络过早出现拥塞

快重传与快恢复

增加快重传与快恢复

快重传

1.快重传要求接收方在收到一个失序的报文段后就立即发出重复确认(为的是使发送方及早知道有报文段没有到达对方)而不要等到自己发送数据时捎带确认。快重传算法规定,发送方只要一连收到三个重复确认就应当立即重传对方尚未收到的报文段,而不必继续等待设置的重传计时器时间到期。
快重传示意图
2.由于不需要等待设置的重传计时器到期,能尽早重传未被确认的报文段,能提高整个网络的吞吐量。

快恢复(与快重传配合使用)

1.采用快恢复算法时,慢开始只在TCP连接建立时和网络出现超时时才使用。
2.当发送方连续收到三个重复确认时,就执行“乘法减小”算法,把ssthresh门限减半。但是接下去并不执行慢开始算法。
3.考虑到如果网络出现拥塞的话就不会收到好几个重复的确认,所以发送方现在认为网络可能没有出现拥塞。所以此时不执行慢开始算法,而是将cwnd设置为ssthre sh的大小,然后执行拥塞避免算法。

注意
发送方窗口的上限值=Min(接受窗口rwnd,拥塞窗口cwnd)
rwnd>cwnd 接收方的接收能力限制发送方窗口的最大值
rwnd<cwnd 网络的拥塞限制发送方窗口的最大值

面试题

腾讯面试题
TCP的拥塞控制机制是什么?请简单说说。
答:我们知道TCP通过一个定时器(timer)采样了RTT并计算RTO,但是,如果网络上的延时突然增加,那么,TCP对这个事做出的应对只有重传数据,然而重传会导致网络的负担更重,于是会导致更大的延迟以及更多的丢包,这就导致了恶性循环,最终形成“网络风暴” —— TCP的拥塞控制机制就是用于应对这种情况。
首先需要了解一个概念,为了在发送端调节所要发送的数据量,定义了一个“拥塞窗口”(Congestion Window),在发送数据时,将拥塞窗口的大小与接收端ack的窗口大小做比较,取较小者作为发送数据量的上限。
拥塞控制主要是四个算法:
1.慢启动:意思是刚刚加入网络的连接,一点一点地提速,不要一上来就把路占满。
连接建好的开始先初始化cwnd = 1,表明可以传一个MSS大小的数据。
每当收到一个ACK,cwnd++; 呈线性上升
每当过了一个RTT,cwnd = cwnd2; 呈指数让升
阈值ssthresh(slow start threshold),是一个上限,当cwnd >= ssthresh时,就会进入“拥塞避免算法”
2.拥塞避免:当拥塞窗口 cwnd 达到一个阈值时,窗口大小不再呈指数上升,而是以线性上升,避免增长过快导致网络拥塞。
每当收到一个ACK,cwnd = cwnd + 1/cwnd
每当过了一个RTT,cwnd = cwnd + 1
拥塞发生:当发生丢包进行数据包重传时,表示网络已经拥塞。分两种情况进行处理:
等到RTO超时,重传数据包
sshthresh = cwnd /2
cwnd 重置为 1
3.进入慢启动过程
在收到3个duplicate ACK时就开启重传,而不用等到RTO超时
sshthresh = cwnd = cwnd /2
进入快速恢复算法——Fast Recovery
4.快速恢复:至少收到了3个Duplicated Acks,说明网络也不那么糟糕,可以快速恢复。
cwnd = sshthresh + 3
MSS (3的意思是确认有3个数据包被收到了)
重传Duplicated ACKs指定的数据包
如果再收到 duplicated Acks,那么cwnd = cwnd +1
如果收到了新的Ack,那么,cwnd = sshthresh ,然后就进入了拥塞避免的算法了。


原文出处:TCP之 流量控制(滑动窗口)和 拥塞控制(拥塞控制的工作过程)

一、流量控制

1.什么是流量控制

Sender won’t overflow receiver’s buffer by transmitting too much, too fast. (防止发送方发的太快,耗尽接收方的资源,从而使接收方来不及处理)

2.流量控制的一些知识点

(1)接收端抑制发送端的依据:接收端缓冲区的大小
(2)流量控制的目标是接收端,是怕接收端来不及处理
(3)流量控制的机制是丢包

3.怎么样实现流量控制?

使用滑动窗口
滑动窗口
1.滑动窗口是什么?
滑动窗口是类似于一个窗口一样的东西,是用来告诉发送端可以发送数据的大小或者说是窗口标记了接收端缓冲区的大小,这样就可以实现
ps:窗口指的是一次批量的发送多少数据

2.为什么会出现滑动窗口?

在确认应答策略中,对每一个发送的数据段,都要给一个ACK确认应答,收到ACK后再发送下一个数据段,这样做有一个比较大的缺点,就是性能比较差,尤其是数据往返的时间长的时候

使用滑动窗口,就可以一次发送多条数据,从而就提高了性能

3.滑动窗口的一些知识点

(1)接收端将自己可以接收的缓冲区大小放入TCP首部中的“窗口大小”字段,通过ACK来通知发送端
(2)窗口大小字段越大,说明网络的吞吐率越高
(3)窗口大小指的是无需等待确认应答而可以继续发送数据的最大值,即就是说不需要接收端的应答,可以一次连续的发送数据
(4)操作系统内核为了维护滑动窗口,需要开辟发送缓冲区,来记录当前还有那些数据没有应答,只有确认应答过的数据,才能从缓冲区删掉

ps:发送缓冲区如果太大,就会有空间开销

(5)接收端一旦发现自己的缓冲区快满了,就会将窗口大小设置成一个更小的值通知给发送端,发送端收到这个值后,就会减慢自己的发送速度
(6)如果接收端发现自己的缓冲区满了,就会将窗口的大小设置为0,此时发送端将不再发送数据,但是需要定期发送一个窗口探测数据段,使接收端把窗口大小告诉发送端
ps:在TCP的首部中,有一个16为窗口字段,此字段就是用来存放窗口大小信息的

这里写图片描述

4.滑动窗口的优点

可以高效可靠的发送大量的数据

二、拥塞控制
1.什么是拥塞控制

too many sources sending too much data too fast for network to handle
防止发送方发的太快,使得网络来不及处理,从而导致网络拥塞

2.拥塞控制使用的机制:AIMD\slow start

slow start: 慢启动
A: additive(加法的)
I: increase(增加)
M: multiplicative(乘法的)
D: decrease(减少)
即就是加法增加,乘法减少---->加增乘减

加法增加

> 是指执行拥塞避免算法后,在收到对所有报文段的确认后(即经过一个往返时间),就把拥塞窗口cwnd增加一个MSS大小,使拥塞窗口缓慢增大,以防止网络过早出现拥塞

乘法减少

出现一次超时(即出现一次网络拥塞),就把慢开始门限值ssthresh设置为当前的拥塞窗口值乘以0.5

ps:当网络频繁出现拥塞时,ssthresh值就下降的很快,以大大减少注入到网络中的分组数

3.发送端如何知道已经丢包?
这里写图片描述
4.为什么会有拥塞控制?

流量控制虽然可以高效可靠的传送大量的数据,但是如果在刚开始阶段就发送大量的数据,可能会导致网络拥堵,因为网络上的计算机太多了

5.拥塞控制的表现?

丢包
延时变长

6.拥塞控制的工作过程
初始化阶段
这里写图片描述
慢开始阶段
阶段(一)
这里写图片描述
阶段(二)
这里写图片描述
阶段(三)
这里写图片描述
阶段(四)
这里写图片描述
拥塞避免阶段
这里写图片描述
拥塞调整阶段
阶段(一)
这里写图片描述
阶段(二)
这里写图片描述
阶段(三)
这里写图片描述

三、流量控制和拥塞控制的区别
1.相同点

(1)现象都是丢包;
(2)实现机制都是让发送方发的慢一点,发的少一点

2.不同点

(1)丢包位置不同
流量控制丢包位置是在接收端上
拥塞控制丢包位置是在路由器上
(2)作用的对象不同
流量控制的对象是接收方,怕发送方发的太快,使得接收方来不及处理
拥塞控制的对象是网络,怕发送发发的太快,造成网络拥塞,使得网络来不及处理
3.联系

拥塞控制
     拥塞控制通常表示的是一个全局性的过程它会涉及到网络中所有的主机
     所有的路由器和降低网络传输性能的所有因素
流量控制
     流量控制发生在发送端和接收端之间只是点到点之间的控制

这里写图片描述


原文出处:TCP ex Machina: Computer-Generated Congestion Control

注意:本文仅代表个人对该篇论文的理解,如果对您有帮助那是我的荣幸,如有不当之处欢迎留言讨论,如需转发请注明出处链接

原文在这里,是MIT AI Lab在13年中的一篇SIGCOMM。

Remy 与 RemyCC

首先,这篇论文所探究的问题最近也正困扰着我:TCP拥塞控制本身是一个动态的过程,每一步的选择都可能造成后面所有反馈的不同,如何能判定一个拥塞控制是“表现得最好”的?

于是这篇论文的解决办法是:既然人看不出好不好,那就用机器预先算出给定网络里每个决策可能造成的所有后果,选最终评分最高的,把它一路过来的所有决策用来生成一个CC,那肯定就是“表现得最好的”。

因此,Winstein和Balakrishnan设计了Remy算法来算出不同参数的拥塞控制策略产生的结果,细化较优结果的参数进行优化,经过几轮迭代后,用生成最优结果的所有决策生成最优CC,就是RemyCC

Fig.1 Remy Design

Remy的输入

Fig.1 展示了Remy的大致流程,其中第一个输入是网络的先验假设,也就是把网络参数化。作者用ns-2进行网络环境的模拟,具体设置的参数包括:

第二个输入是发送端的流量发送模型。Remy建模时把流量变化过程看作很多对独立的sender-receiver链路随机开/关的过程,每条链路开的持续时间遵循特定的分布。

第三个输入是目标方程。用于计算给某链路分配资源的得分。其中x代表链路比特率,alpha和beta代表throughput和delay的权衡,权重表示对其赋予的重要性。

Fig.2 The Flow's Score

Fig.3 Basic Object Function

#

Remy寻找最佳拥塞控制策略的过程

一些概念

Remy把在特定网络环境下寻找最佳RemyCC的过程看作为一个Dec-POMDP搜索最佳策略的过程。对于端到端的拥塞控制来说,每一个时间点,agent根据接收的observation,做出调整拥塞窗口的action。

作者把sender端的observation用三个参数来表示,统称memory

把sender端的action(控制拥塞窗口大小)也用三个参数来表示:

把一条memory映射到一条action的过程称为一条rule。一个完整的拥塞控制策略由很多条rule构成,即一张rule table。Remy的自动搜索过程就是用贪心算法建立和优化这张rule table的过程。

Remy的自动设计过程

初始action: m=1, b=1, r=0.01。所有的memory都指向该action,作为rule table的所有初始rule。

rule table的优化过程:

1.设置所有rule的epoch为current epoch

2.用current rule table构建一个CC,放入建模的网络中运行,找到current epoch下使用次数最多的rule(这一步主要是找到放入的网络trace中出现频率最高的memory);都没用,转4

3.针对2找到的rule搜索最佳action:计算最接近原本action的约100种action(e.g. 对r来说,r+-0.01, r+-0.08, r+-0.64...),抽取16种网络的traces,把调整后的action应用到所有的sender,再仿真。 若某种调整后的action表现出优化,替换原本的action并重复搜索过程,使用相同的random seed;若没有action表现出优化(说明对这条memory已经找到了最优的action),这条rule的epoch++,转回2

4.如果遍历完current epoch下所有的rule,current epoch++。若新的current epoch是K的倍数,转5;否则转1。(K代表迭代的轮数,用于控制算法优化的时间)

5.recall:每条rule代表的是从一个三维长方体的memory空间到一个action的一个映射。这一步里,找到表中最多使用的rule,以及触发这条rule的memory中间值(每一维一个),产生八个新的memory(每一维一个,就想象成包裹着中间值点的一个正方体的八个顶点)赋予 和中间值同样的action,然后转1.

整个算法流程至此结束。算法理解起来需要一定的时间,但如果动手跟着步骤画一下rule table的建立和优化过程,就能形象的理解了。

Remy算法的一些问题

首先,同论文里作者也发现的一样,用Remy搜索找到的最佳RemyCC,当把它用于和生成RemyCC时使用的网络相似的网络上时,效果当然非常不错,但一旦用于不同类型网络时,效果就不太如意了。这里当然是因为RemyCC只能预测它所见过的网络的最优决策,一旦遇到没见过的网络,RemyCC仍使用本身的预见方法来应对,这显然是不够灵活的。

其次,是个人的一个发现。算法的第二步里,找current epoch里使用次数最多的rule这一步,比较冗余,而且增加算法时间复杂度。原因是,既然你要遍历整个rule table,何须找使用次数最多的?直接从头开始不会比较快吗?因此个人认为不需要找使用最多的rule这一步骤。