计算机网络:P5.2-运输层(下)

本文深入探讨了TCP的超时重传时间选择、可靠传输机制及连接管理,包括TCP如何通过加权平均往返时间RTTS和RTTD来避免不必要的重传,实现平滑的窗口移动,以及通过三报文握手和四报文挥手建立和释放TCP连接。此外,还介绍了TCP报文段的首部字段,如序号、确认号、窗口字段等,以及它们在确保数据传输可靠性中的作用。
摘要由CSDN通过智能技术生成

本系列文章为湖南科技大学高军老师的计算机网络微课堂学习笔记,前面的系列文章链接如下:
计算机网络:P1-概述
计算机网络:P2-物理层
计算机网络:P3.1-数据链路层(上)
计算机网络:P3.2-数据链路层(中)
计算机网络:P3.3-数据链路层(下)
计算机网络:P4.1-网络层(上)
计算机网络:P4.2-网络层(中)
计算机网络:P4.3-网络层(下)
计算机网络:P5.1-运输层(上)


六、TCP超时重传时间的选择

超时重传时间的选择是TCP最复杂的问题之一,我们来举例说明:

假设主机A和B是因特网上的两台主机,它们之间已经建立了TCP连接,纵坐标为时间。现在主机A给主机B发送TCP数据报文段0,并记录下当前的时间。主机B收到后,给主机A发送相应的确认报文段。主机A收到确认报文段后,记录下当前的时间。那么主机A记录下的这两个时间,它们的差值就是报文段的往返时间RTT。由于这是第0个报文段的RTT,我们就用RTT0来表示。
在这里插入图片描述
①如果我们将超时重传时间RTO的值设置的比RTT0的值小,会出现怎样的情况呢?
在这里插入图片描述
很显然,这会引起报文段不必要的重传,使网络负荷增大。
②如果将超时重传时间RTO的值设置的远大于RTT0的值,又会出现怎样的情况呢?
在这里插入图片描述
很显然,这会使重传推迟的时间太长,使网络的空闲时间增大,降低了传输效率。
结论: 综合上述两种情况,我们可以得出这样的结论:超时重传时间RTO的值应该设置为略大于报文段往返时间RTT的值。
在这里插入图片描述


至此,同学们可能会觉得,超时重传时间的选择也并不是很复杂嘛。然而,TCP下层是复杂的互联网环境,会面临许多情况。主机A所发送的报文段可能只经过一个高速率的局域网,也有可能经过多个低速率的网络。并且,每个IP数据报的转发路由还可能不同。


情况1

问题: 现在主机A给主机B发送TCP数据报文段1。主机B收到后,给主机A发送相应的确认报文段。主机A这次测得的报文段往返时间RTT1如图所示。显然,RTT1远大于RTT0。如果超时重传时间RTO还是我们之前所确定的略大于RTT0的话,这对于数据报文段1是不合适的,会造成该报文段不必要的重传。这样看来,超时重传时间的选择确实不那么简单了,我们不能直接使用某次测量得到的RTT样本来计算超时重传时间RTO。
在这里插入图片描述
解决方案: 我们可以利用每次测量得到的RTT样本计算加权平均往返时间 R T T s RTT_s RTTs,这样可以得到比较平滑的往返时间。
①当测量到第一个RTT样本时, R T T S RTT_S RTTS 的值直接取为第一个RTT样本的值,即 R T T S 1 = R T T 1 RTT_{S1}=RTT_1 RTTS1=RTT1
②以后每侧量到一个RTT样本时,都按该公式来计算新的 R T T S RTT_S RTTS 值。
新的 R T T S = ( 1 − α ) × 旧的 R T T S + α × 新的 R T T 样本 新的RTT_S=(1- \alpha) \times 旧的RTT_S + \alpha \times 新的RTT样本 新的RTTS=(1α)×旧的RTTS+α×新的RTT样本
在上式中, 0 ≤ α < 1 0 \le \alpha<1 0α<1
----若 α \alpha α 很接近于0,则新RTT样本对 R T T S RTT_S RTTS 的影响不大。
----若 α \alpha α 很接近于1,则新RTT样本对 R T T S RTT_S RTTS 的影响较大。
一些标准: 已成为建议标准的RFC6298推荐的 α \alpha α 值为1/8,即0.125。用这种方法得出的加权平均往返时间RTTs的值就比测量出的RTT值更加平滑。显然,超时重传时间RTO的值应略大于加权平均往返时间 R T T S RTT_S RTTS 的值。
③下面我们给出RFC6298建议使用的超时重传时间RTO的计算公式:
R T O = R T T S + 4 × R T T D RTO=RTT_S+4 \times RTT_D RTO=RTTS+4×RTTD
该公式中的 R T T S RTT_S RTTS 是加权平均往返时间,我们刚刚介绍过它的计算方法。 R T T D RTT_D RTTD 是RTT偏差的加权平均,计算方法如下:
当测量到第一个RTT样本时, R T T D RTT_D RTTD 的值取为该样本值的一半。
R T T D 1 = R T T 1 ÷ 2 RTT_{D1}=RTT_1 \div 2 RTTD1=RTT1÷2
以后每测量到一个RTT样本时,都按该公式来计算新的 R T T D RTT_D RTTD 的值:
新的 R T T D = ( 1 − β ) × 旧的 R T T D + β × ∣ 旧的 R T T S − 新的 R T T 样本 ∣ 新的RTT_D=(1- \beta) \times 旧的RTT_D + \beta \times |旧的RTT_S-新的RTT样本| 新的RTTD=(1β)×旧的RTTD+β×旧的RTTS新的RTT样本
在上式中, 0 ≤ β < 1 0 \le \beta<1 0β<1。已成为建议标准的RFC6298推荐的 β \beta β 值为1/4,即0.25。我们可以发现,不管是 R T T S RTT_S RTTS 还是 R T T D RTT_D RTTD,都是基于所测量到的RTT样本进行计算的。如果所测量到的RTT样本不正确,那么所计算出的 R T T S RTT_S RTTS R T T D RTT_D RTTD 自然就不正确。进而所计算出的超时重传时间RTO也就不正确。然而,往返时间RTT的测量确实是比较复杂的,下面我们举例说明


RTT测量的复杂性

①主机A给主机B发送TCP数据报文段,但该报文段在传输过程中丢失了。当超时重传计时器超时后,主机A就重传该报文段。主机B收到后,给主机A发送确认报文段。现在问题来了,主机A收到该确认报文段后,无法判断该报文段是对原报文段的确认还是对重传报文段的确认。如果主机A误将重传报文段的确认当做是对原报文段的确认,则所计算出的 R T T S RTT_S RTTS R T O RTO RTO 就会偏大,降低了传输效率。
在这里插入图片描述
②主机A给主机B发送TCP数据报文段。主机B收到后,给主机A发送确认报文段。由于某种原因,该确认报文段没有在正常时间内到达主机A,这必然会导致主机A对之前所发送的数据报文段的超时重传。现在问题又来了,主机A收到迟到的确认报文段后,无法判断该报文段是对原报文段的确认还是对重传报文段的确认。如果主机A误将对原报文段的确认当做是对重传报文段的确认,则所计算出的 R T T S RTT_S RTTS R T T D RTT_D RTTD 就会偏小,这会导致报文段没有必要的重传,增大网络负荷。
在这里插入图片描述
总结: 通过这两个例子可以看出:当发送方出现超时重传后,收到确认报文段时,是无法判断出该确认到底是对原报文段的确认还是重传报文段的确认。也就是无法准确测量出RTT,进而无法正确计算超时重传时间RTO。


针对出现超时重传时无法测准往返时间RTT的问题的解决方案

①针对出现超时重传时无法测准往返时间RTT的问题,Karn提出了一个算法。在计算加权平均往返时间 R T T S RTT_S RTTS 时,只要报文段重传了,就不采用其往返时间RTT样本,也就是出现重传时不重新计算 R T T S RTT_S RTTS,进而超时重传时间RTO也不会重新计算。
问题: 然而,这又引起了新的问题。设想出现这样的情况,报文段的时延突然增大了很多,并且之后很长一段时间内都会保持这种时延。因此,在原来得出的重传时间内,不会收到确认报文段,于是就重传报文段。根据Karn算法,不考虑重传的报文段的往返时间样本。这样,超时重传时间就无法更新,这会导致报文段反复被重传。
②因此,要对Karn算法进行修正。方法是:报文段每重传一次,就把超时重传时间RTO增大一些。典型的做法是将新RTO的值取为旧RTO值的2倍。

例子:

我们举例说明TCP超时重传时间的计算。
①这是测量得到的第一个RTT样本 R T T 1 = 30 RTT_1=30 RTT1=30
R T T S 1 = R T T 1 = 30 RTT_{S1} = RTT_1=30 RTTS1=RTT1=30
R T T D 1 = R T T 1 ÷ 2 = 15 RTT_{D1} = RTT_1 \div2=15 RTTD1=RTT1÷2=15
R T O 1 = R T T S + 4 × R T T D = 30 + 15 × 4 = 90 RTO_1=RTT_S+4 \times RTT_D=30+15 \times 4 = 90 RTO1=RTTS+4×RTTD=30+15×4=90
在这里插入图片描述
②这是测量得到的第2个RTT样本 R T T 2 = 26 RTT_2=26 RTT2=26
R T T S 2 = ( 1 − α ) × R T T S 1 + α × R T T 2 = 0.875 × 30 + 0.125 × 26 = 26.25 + 3.25 = 29.5 RTT_{S2}=(1-\alpha)\times RTT_{S1}+\alpha \times RTT_2=0.875 \times 30+0.125 \times 26=26.25+3.25=29.5 RTTS2=(1α)×RTTS1+α×RTT2=0.875×30+0.125×26=26.25+3.25=29.5
R T T D 2 = ( 1 − β ) × R T T D 1 + β × ∣ R T T S 1 − R T T 2 ∣ = 0.75 × 15 + 0.25 × ∣ 29.5 − 26 ∣ = 11.25 + 1 = 12.25 RTT_{D2}=(1-\beta)\times RTT_{D1}+\beta \times |RTT_{S1}-RTT_2|=0.75 \times 15 +0.25 \times |29.5-26|=11.25+1=12.25 RTTD2=(1β)×RTTD1+β×RTTS1RTT2=0.75×15+0.25×∣29.526∣=11.25+1=12.25
R T O 2 = R T T 2 + 4 × R T T D 2 = 29.5 + 4 × 12.25 × 4 = 29.5 + 49 = 78.5 RTO_2=RTT_{2}+4 \times RTT_{D2}=29.5+4 \times 12.25 \times 4=29.5+49=78.5 RTO2=RTT2+4×RTTD2=29.5+4×12.25×4=29.5+49=78.5
在这里插入图片描述
假设测量得到的第3个和第4个RTT样本分别为32、24,根据前面的公式可以计算出 R T O 3 RTO_3 RTO3 R T O 4 RTO_4 RTO4的值如下
在这里插入图片描述
假设这是测量得到的第五个RTT样本。但是根据 R T O 4 RTO_4 RTO4 的值可知,在收到确认之前就会产生超时重传。我们之前介绍过,若出现超时重传,则不采用上述公式计算RTO,而是将新RTO的值取为旧RTO值的2倍。因此 R T O 5 = 2 × R T O 4 = 128.6718 RTO_5=2 \times RTO_4=128.6718 RTO5=2×RTO4=128.6718
在这里插入图片描述


小测验

1、以下关于TCP超时重传时间RTO的叙述中,正确的是
A. RTO应小于TCP端到端加权平均往返时间RTTs
B. RTO应远大于TCP端到端加权平均往返时间RTTs
C. RTO应等于TCP端到端加权平均往返时间RTTs
D. RTO应略大于TCP端到端加权平均往返时间RTTs
答案:D

2、若出现超时重传,则以下关于TCP超时重传时间RTO的叙述中,正确的是
A. RTO的值应清零
B. RTO的值应扩大两倍
C. RTO的值应减1
D. RTO的值应保持不变
答案:B


七、TCP可靠传输的实现

TCP基于以字节为单位的滑动窗口来实现可靠传输

例子: 这是因特网上的两台主机,它们之间已经建立了一个TCP连接。为了简单起见,我们假定数据传输只在一个方向进行。换句话说,发送方给接收方发送TCP数据报文段。接收方给发送方发送相应的TCP确认报文段。这样的好处是使讨论仅限于两个窗口,也就是发送方的发送窗口和接收方的接收窗口。
在这里插入图片描述
TCP的滑动窗口是以字节为单位的。如图所示,这是发送方待发送数据字节的序号。为了方便讲解和在有限的屏幕上显示更多的字节序号,字节序号的值都取得很小。
在这里插入图片描述
现在,假设发送方收到了一个来自接收方的确认报文段。在报文段首部中的窗口字段的值为20,也就是接收方表明自己的接收窗口的尺寸为20字节。确认号字段的值为31,这表明接收方希望收到下一个数据的序号是31,而序号30为止的数据已经全部正确接收了。
在这里插入图片描述
因此,发送方根据这两个字段的值构造出自己的发送窗口,如图所示。
----为了简单起见,我们假定网络不存在拥塞问题。也就是发送方在构造自己的发送窗口时,仅考虑接收方的接收窗口,而不考虑拥塞窗口。
----由于本例中接收方告诉发送方自己的接收窗口尺寸为20,因此发送方将自己的发送窗口尺寸也设置为20。
----发送方在没有收到接收方确认的情况下,可以把发送窗口内的数据依次全部发送出去。
----凡是已经发送过的数据,在未收到确认之前都必须暂时保留,以便在超时重传时使用。
----发送窗口包含后沿前沿。发送窗口后沿的后面部分是已发送并已收到确认的数据字节的序号,这些数据字节显然不需要再保存在发送缓存中了,可以将它们删除。发送窗口前沿的前面部分是当前不允许发送的数据字节的序号。
1、发送窗口后沿的移动情况有以下两种可能:
①不动,也就是没有收到新的确认,发送窗口的后沿不会移动。
②向前移动,也就是收到了新的确认,发送窗口的后沿向前移动。
发送窗口的后沿不可能向后移动,因为不能撤销掉已收到的确认。
2、发送窗口前沿的移动情况有以下三种可能:
①通常情况下,发送窗口的前沿是不断向前移动的。
②但也有可能不动。一种是由于没有收到新的确认,接收方通知的窗口大小也没有改变。另一种是收到了新的确认,可向前移动相应位置。但接收方通知的窗口缩小了,前沿应该向后回缩。如果向前移动和向后回缩的尺寸恰好相等,就会使得发送窗口的前沿不动。
③发送窗口的前沿还可能向后收缩,这发生在接收方通知的窗口变小了。但TCP标准强烈不赞成这样做。因为很可能发送方在收到这个通知之前就已经发送了窗口中的许多数据。现在又要收缩窗口,不让发送这些数据,显然就会产生错误。
在这里插入图片描述
现在,假定发送方将发送窗口内序号31到41的数据封装在几个不同的报文段中发送出去。
----此时,发送窗口的位置并没有改变。
----发送窗口内序号31到41的数据已经发送,但未收到确认。而序号42到50的数据是允许发送但还未发送的。
思考: 我们如何描述发送窗口的状态呢?换句话说,如果我们要编程实现滑动窗口机制,那么对于发送窗口的状态应该如何标记和维护呢?
回答: 如图所示,可以使用三个指针P1 P2 P3分别指向相应的字节序号。
----小于P1的就是已发送并已收到确认的部分。
----大于等于P3的是不允许发送的部分。
----P3-P1可以得出当前发送窗口的尺寸。
----P2-P1可以得出已发送但尚未收到确认的字节数量。
----P3-P2可以得出允许发送但当前尚未发送的字节数量。
在这里插入图片描述
我们再来看看接收方的接收窗口,它的尺寸为20。
----在接收窗口外面到30号为止的数据是已经发送过相应确认并已交付给应用进程的数据,因此无需再保留这些数据,可将它们从接收缓存中删除了。
----接收窗口内31到50号数据是允许接收的数据,接收窗口外51号及其后续数据目前不允许接收。
----假设发送方之前发送的封装有32和33号数据的报文段到达了接收方。
在这里插入图片描述
由于数据序号落在接收窗口内,所以接收方接受它们并将它们存入接收缓存。但是它们是未按序到达的数据,因为31号数据还没有到达。这有可能是丢了,也有可能是滞留在网络中的某处。请注意,接收方只能对按序收到的数据中的最高序号给出确认。因此,接收方发出的确认报文段中的确认序号仍然是31,也就是希望收到31号数据。窗口字段的值仍是20,表明接收方没有改变自己接收窗口的大小。
在这里插入图片描述
发送方收到该确认报文段后发现,这是一个针对31号数据的重复确认,就知道接收方收到了未按序到达的数据。由于这是针对31号数据的第一个重复确认,因此这并不会引起发送方针对该数据的快重传。另外,接收方通知的窗口尺寸仍是20。因此,发送方保持自己的发送窗口尺寸为20。现在,假设封装有31号数据的报文段到达了接收方。
在这里插入图片描述
接受方接受该报文段,将其封装的31号数据存入接收缓存。接收方现在可将接收到的31到33号数据交付给应用进程,然后将接收窗口向前移动3个序号,并给发送方发送确认报文段。该确认报文段中窗口字段的值仍为20,表明接收方没有改变自己接收窗口的大小。确认号字段的值为34,这表明接收方已经收到了序号33为止的全部数据。
在这里插入图片描述
现在,假设又有几个数据报文段到达了接收方,它们封装有37、38以及40号数据。这些数据的序号虽然落在接收窗口内,但他们都是未按序到达的数据,只能先暂存在接收缓存中。
在这里插入图片描述
假设接收方先前发送的确认报文段到达了发送方。发送方接收后,将发送窗口向前滑动3个序号,发送窗口的尺寸保持不变。这样就有新序号51~53落入发送窗口内。而序号31到33移出了发送窗口,现在可将31到33号数据从发送缓存中删除了,因为已经收到了接收方针对它们的确认。
在这里插入图片描述
发送方继续将发送窗口内序号42到53的数据封装在几个不同的报文段中发送出去。现在发送窗口内的序号儿已经用完了,发送方在未收到接收方发来确认的情况下,不能再发送新的数据。序号落在发送窗口内的已发送数据,如果迟迟收不到接收方的确认,则会产生超时重传。
在这里插入图片描述


接下来,我们对TCP可靠传输的实现做几点补充说明:

①虽然发送方的发送窗口是根据接收方的接收窗口设置的,但在同一时刻,发送方的发送窗口并不总是和接收方的接收窗口一样大。这是因为网络传送窗口值需要经历一定的时间滞后,并且这个时间还是不确定的。另外,发送方还可能根据网络当时的拥塞情况,适当减小自己的发送窗口尺寸。
②对于不按序到达的数据应如何处理?TCP并无明确规定。如果接收方把不按序到达的数据一律丢弃,那么接收窗口的管理将会比较简单,但这样做对网络资源的利用不利,因为发送方会重复传送较多的数据。TCP通常对不按序到达的数据是先临时存放在接收窗口中,等到字节流中所缺少的字节收到后,再按序交付上层的应用进程。
③TCP要求接收方必须有累积确认和捎带确认机制,这样可以减小传输开销。接收方可以在合适的时候发送确认,也可以在自己有数据要发送时把确认信息顺便捎带上。但是,接收方不应过分推迟发送确认,否则会导致发送方不必要的超时重传,这反而浪费了网络资源。TCP标准规定,确认推迟的时间不应超过0.5秒。若收到一连串具有最大长度的报文段,则必须每隔一个报文段就发送一个确认。另外,捎带确认实际上并不经常发生,因为大多数应用程序很少同时在两个方向上发送数据。
④TCP的通信是全双工通信,通信中的每一方都在发送和接收报文段。因此,每一方都有自己的发送窗口和接收窗口。在谈到这些窗口时,一定要弄清楚是哪一方的窗口。


例题

【2009年题38】主机甲与主机乙之间已建立一个TCP连接,主机甲向主机乙发送了两个连续的TCP段,分别包含300字节和500字节的有效载荷,第一个段的序号为200,主机乙正确接收到两个段后,发送给主机甲的确认序号是 (D)
A. 500
B. 700
C. 800
D. 1000
解析: 根据题意可知,这是主机甲给主机乙发送的第1个数据报文段。其首部中序号字段的值为200,也就是说,其数据载荷的第1个字节的序号为200。由于数据载荷共有300个字节。因此,数据载荷的最后一个字节的序号为499。
在这里插入图片描述
这是主机甲给主机已发送的第2个数据报文段的数据载荷部分。由于前一个报文段数据载荷的最后1个字节的序号是499,因此本报文段数据载荷的第1个字节的序号应为500。相应的,其首部中序号字段的值应为500。由于数据载荷共有500个字节。因此,数据载荷的最后一个字节的序号为999。
在这里插入图片描述
这是主机已给主机甲发送的确认报文段的首部。其中确认序号字段的值为1000,这是因为主机已目前已经按序正确接收了序号到999为止的全部数据,现在希望接收序号为1000及其后续的数据。该确认报文段实际上是对已正确接收的两个TCP数据报文段的累积确认。
在这里插入图片描述


【2011年题40】主机甲与主机乙之间已建立一个TCP连接,主机甲向主机乙发送了3个连续的TCP段,分别包含300字节、400字节和500字节的有效载荷,第3个段的序号为900。若主机乙仅正确接收到第1个和第3个段,则主机乙发送给主机甲的确认序号是 (B)
A. 300
B. 500
C. 1200
D. 1400
解析: 我们从已知的第3个数据报文段首部中序号字段的值为900开始分析,这就表明其数据载荷的第1个字节的序号为900。由于数据载荷共有500个字节,因此数据载荷的最后1个字节的序号为1399。
----根据第3个数据报文段数据载荷的第一个字节的序号为900可以得出:第2个数据报文段数据载荷的最后一个字节的序号为899。由于数据载荷共有400个字节,因此,数据载荷的第1个字节的序号为500,相应的第2个数据报文段首部中序号字段的值应为500。
----根据第2个数据报文段数据载荷的第1个字节的序号为500,可以得出:第1个数据报文段数据载荷的最后1个字节的序号为499。由于数据载荷共有300个字节,因此数据载荷的第1个字节的序号为200。相应的,第一个数据报文段首部中序号字段的值应为200。
----由于TCP规定,只能对按序到达的最高序号进行确认。因此,主机已发送的确认报文段实际上是对第1个数据报文段的确认。由于第1个数据报文段数据载荷的最后一个字节的序号为499,因此针对该序号的确认序号因为500,表明序号499为止的全部数据已经收到了,现在希望接收500号及其后续数据。
在这里插入图片描述


小测验

1、主机甲和乙建立了TCP连接,主机甲向主机乙发送了两个连续的TCP段,分别包含200字节和300字节的有效载荷,第一个段的序号为100,主机乙正确接收到两个段后,发送给主机甲的确认号是
A. 500
B. 600
C. 800
D. 900
答案:B

2、主机甲和乙建立了TCP连接,主机甲向主机乙发送了3个连续的TCP段,分别包含200字节、300字节、400字节的有效载荷,第3个段的序号为1000。若主机乙仅正确接收到第1和第3个段,则主机乙发送给主机甲的确认号是
A. 500
B. 600
C. 700
D. 800
答案:C

3、主机甲和乙建立了TCP连接,双方持续有数据传输,且数据无差错与丢失。若甲收到1个来自乙的TCP段,该段的序号为1024,确认序号为2048,有效数据载荷为200字节,则甲立即发送给乙的TCP段的序号和确认号分别是
A. 2048、1223
B. 2048、1224
C. 2049、1223
D. 2049、1224
答案:B


八、TCP的运输连接管理

TCP的运输连接

①TCP是面向连接的协议,它基于运输连接来传送TCP报文段。
②TCP运输连接的建立和释放是每一次面向连接的通信中必不可少的过程。
③TCP运输连接有以下三个阶段:
----建立TCP连接。也就是通过三报文握手来建立TCP连接。
----数据传送。也就是基于已建立的TCP连接进行可靠的数据传输。
----释放连接。也就是在数据传输结束后,还要通过四报文挥手来释放TCP连接。
④TCP的运输连接管理就是使运输连接的建立和释放都能正常进行。
在这里插入图片描述


8.1 TCP的连接建立

TCP的连接建立要解决以下三个问题:

①使TCP双方能够确知对方的存在。
②使得TCP双方能够协商一些参数。例如最大窗口值、是否使用窗口扩大选项、时间戳选项、服务质量等。
③使得TCP双方能够对运输实体资源(例如缓存大小、连接表中的项目等)进行分配。


TCP使用三报文握手建立连接的具体过程

背景: 这是两台要基于TCP进行通信的主机。其中一台主机中的某个应用进程主动发起TCP连接建立,称为TCP客户。另一台主机中被动等待TCP连接建立的应用进程,称为TCP服务器。我们可以将TCP建立连接的过程比喻为握手,握手需要在TCP、客户和服务器之间交换三个TCP报文段。最初,两端的TCP进程都处于关闭状态
在这里插入图片描述
一开始,TCP服务器进程首先创建传输控制块,用来存储TCP连接中的一些重要信息(例如TCP连接表、指向发送和接收缓存的指针、指向重传队列的指针、当前发送和接收序号等)。之后就准备接受TCP客户进程的连接请求。此时,TCP服务器进程就要进入监听状态,等待TCP客户进程的连接请求。
在这里插入图片描述
TCP服务器进程是被动等待来自TCP客户进程的连接请求,而不是主动发起,因此称为被动打开连接。
在这里插入图片描述
TCP客户进程也是首先创建传输控制块
在这里插入图片描述
然后在打算建立TCP连接时,向TCP服务器进程发送TCP连接请求报文段,并进入同步已发送状态。
----TCP连接请求报文段首部中的同步位SYN被设置为1,表明这是一个TCP连接请求报文段
----序号字段seq被设置了一个初始值x,作为TCP客户进程所选择的初始序号。请注意,TCP规定,SYN被设置为1的报文段不能携带数据,但是要消耗掉一个序号。
----由于TCP连接建立是由TCP客户进程主动发起的,因此称为主动打开连接。
在这里插入图片描述
TCP服务器进程收到TCP连接请求报文段后,如果同意建立连接,则向TCP客户进程发送TCP连接请求确认报文段,并进入同步已接收状态。
----该报文段首部中的同步位SYN和确认位ACK都设置为1,表明这是一个TCP连接请求确认报文段
----序号字段seq被设置了一个初始值y,作为TCP服务器进程所选择的初始序号
----确认号字段ack的值被设置成了x+1,这是对TCP客户进程所选择的初始序号的确认
请注意,这个报文段也不能携带数据,因为他是SYN被设置为1的报文段,但同样要消耗掉一个序号。
在这里插入图片描述
TCP客户进程收到TCP连接请求确认报文段后,还要向TCP服务器进程发送一个普通的TCP确认报文段,并进入连接已建立状态。
----该报文段首部中的确认位ACK被设置为1,表明这是一个普通的TCP确认报文段
----序号字段seq被设置为x+1,这是因为TCP客户进程发送的第一个TCP报文段的序号为x,并且不携带数据,因此第二个报文段的序号为x+1
请注意,TCP规定普通的TCP确认报文段可以携带数据。但如果不携带数据,则不消耗序号。在这种情况下,所发送的下一个数据报文段的序号仍是x+1。确认号字段ACK被设置为y+1,这是对TCP服务器进程所选择的初始序号的确认。
在这里插入图片描述
TCP服务器进程收到该确认报文段后,也进入连接已建立状态。现在TCP双方都进入了连接已建立状态,它们可以基于已建立好的TCP连接进行可靠的数据传输了。
在这里插入图片描述
思考: 为什么客户进程最后还要发送一个普通的TCP确认报文段呢,这是否多余?换句话说,能否使用两报文握手建立连接呢?
答案: 并不多余,不能简化为两报文握手。
例子: 考虑这样一种情况,TCP客户进程发出一个TCP连接请求报文段,但该报文段在某些网络结点长时间滞留了,这必然会造成该报文段的超时重传。
在这里插入图片描述
假设重传的报文段被TCP服务器进程正常接收,TCP服务器进程给TCP客户进程发送一个TCP连接请求,确认报文段并进入连接已建立状态。
请注意,由于我们改为两报文握手,因此TCP服务器进程发送完TCP连接请求确认报文段后进入的是连接已建立状态,而不像三报文握手那样进入同步已接收状态并等待TCP客户进程发来针对TCP连接请求确认报文段的普通确认报文段。
在这里插入图片描述
TCP客户进程收到TCP连接请求确认报文段后,进入TCP连接已建立状态,但不会给TCP服务器进程发送针对该报文段的普通确认报文段。现在TCP双方都处于连接已建立状态,它们可以相互传输数据。
在这里插入图片描述
之后可以通过四报文挥手来释放连接,TCP双方都进入了关闭状态。
在这里插入图片描述
一段时间后,之前滞留在网络中的那个失效的TCP连接请求报文段到达了TCP服务器进程。TCP服务器进程会误认为这是TCP客户进程又发起了一个新的TCP连接请求,于是给TCP客户进程发送TCP连接请求确认报文段,并进入连接已建立状态。
在这里插入图片描述
该报文段到达TCP客户进程,由于TCP客户进程并没有发起新的TCP连接请求,并且处于关闭状态,因此不会理会该报文段。但TCP服务器进程已进入了连接已建立状态,它认为新的TCP连接已建立好了,并一直等待TCP客户进程发来数据。这将白白浪费TCP服务器进程所在主机的很多资源。
在这里插入图片描述
总结: 综上所述,采用三报文握手而不是两报文握手来建立TCP连接,是为了防止已失效的连接请求报文段突然又传送到了TCP服务器进程,因而导致错误。


例题

【2011年 题39】主机甲向主机乙发送一个(SYN=1,seq=11220)的TCP段,期望与主机乙建立TCP连接,若主机乙接受该连接请求,则主机乙向主机甲发送的正确的TCP段可能是 (C)
A. (SYN=0,ACK=0,seq=11221,ack=11221)
B. (SYN=1,ACK=1,seq=11220,ack=11220)
C. (SYN=1,ACK=1,seq=11221,ack=11221)
D. (SYN=0,ACK=0,seq=11220,ack=11220)
解析: 如图所示,这是本节课所介绍的TCP通过三报文握手建立连接的过程。
在这里插入图片描述
根据题意,主机甲中的是TCP客户进程,主机乙中的是TCP服务器进程。
----主机甲中TCP客户进程向主机乙中TCP服务器进程发送的第一个TCP段中,其首部中的同步位SYN的值为1,序号字段seq的值就是题目所给的11220。
----主机乙中TCP服务器进程给主机甲中TCP客户进程发送的TCP连接请求确认报文段中,其首部中的同步位SYN和确认位ACK的值都被设置为1,表明这是一个TCP连接请求确认报文段。确认号字段ack的值是对主机甲中TCP客户进程所选择初始序号11220的确认,因此为11221。
在这里插入图片描述
至此,我们就已经可以选出正确答案为选项C了。序号字段seq的值是主机乙中TCP服务器进程所选择的初始序号,可由TCP服务器进程随意指定,与其他报文段中的值无关。在本题的正确选项C中,序号字段seq的值恰好与确认号字段AK的值同为11221,这正是本题迷惑大家的地方,使很多同学认为该选项有点儿别扭。


小测验

1、TCP采用三报文握手建立连接,其中第一个报文首部中的同步标志位SYN和确认标志位ACK的取值分别是
A. 0,0
B. 0,1
C. 1,0
D. 1,1
答案:C

2、TCP采用三报文握手建立连接,其中第二个报文首部中的同步标志位SYN和确认标志位ACK的取值分别是
A. 0,0
B. 0,1
C. 1,0
D. 1,1
答案:D

3、TCP采用三报文握手建立连接,其中第三个报文是
A. TCP连接请求
B. 对TCP连接请求的确认
C. 对TCP连接请求确认的确认
D. TCP普通数据
答案:C

4、主机甲向主机乙发送一个(SYN=1, seq=100)的TCP段,期望与主机乙建立TCP连接,若主机乙接受该连接请求,则主机乙向主机甲发送的正确的TCP段可能是
A. (SYN=0, ACK=0, seq=101, ack=101)
B. (SYN=1, ACK=1, seq=100, ack=100)
C. (SYN=0, ACK=0, seq=123, ack=123)
D. (SYN=1, ACK=1, seq=123, ack=101)
答案:D

5、主机甲发起与主机乙的TCP连接,主机甲选择的初始序号为200,如果三报文握手建立连接过程中最后一个报文不携带数据载荷,则TCP连接建立成功后主机甲给主机乙发送的第一个数据报文段的序号为
A. 199
B. 200
C. 201
D. 202
答案:C


8.2 TCP的连接释放

TCP使用四报文挥手释放连接的具体过程

数据传输结束后,TCP通信双方都可以释放连接。现在TCP客户进程和TCP服务器进程都处于连接已建立状态。
在这里插入图片描述
假设使用TCP客户进程的应用进程通知其主动关闭TCP连接,TCP客户进程会发送TCP连接释放报文段,并进入中止等待1状态。
----该报文段首部中的终止位FIN、确认位ACK的值都被设置为1,表明这是一个TCP连接释放报文段,同时也对之前收到的报文段进行确认。
----序号seq字段的值设置为u,它等于TCP客户进程之前已传送过的数据的最后一个字节的序号+1。请注意,TCP规定终止位FIN等于1的报文段即使不携带数据也要消耗掉一个序号
----确认号ack字段的值设置为v,它等于TCP客户进程之前已收到的数据的最后一个字节的序号+1
在这里插入图片描述
TCP服务器进程收到TCP连接释放报文段后,会发送一个普通的TCP确认报文段,并进入关闭等待状态。
----该报文段首部中的确认位ACK的值被设置为1,表明这是一个普通的TCP确认报文段。
----序号seq字段的值设置为v它等于TCP服务器进程之前已传送过的数据的最后一个字节的序号+1。这也与之前收到的TCP连接释放报文段中的确认号匹配。
----确认号ack字段的值设置为u+1,这是对TCP连接释放报文段的确认。
在这里插入图片描述
TCP服务器进程这时应通知高层应用进程:TCP客户进程要断开与自己的TCP连接。
----此时,从TCP客户进程到TCP服务器进程这个方向的连接就释放了。
----这时的TCP连接处于半关闭状态,也就是TCP客户进程已经没有数据要发送了。但TCP服务器进程如果还有数据要发送,TCP客户进程仍要接收。也就是说,从TCP服务器进程到TCP客户进程这个方向的连接并未关闭,这个状态可能会持续一段时间。
在这里插入图片描述
TCP客户进程收到TCP确认报文段后就进入终止等待2状态,等待TCP服务器进程发出的TCP连接释放报文段。
在这里插入图片描述
若使用TCP服务器进程的应用进程已经没有数据要发送了,应用进程就通知其TCP服务器进程释放连接。
----由于TCP连接释放是由TCP客户进程主动发起的,因此,TCP服务器进程对TCP连接的释放称为被动关闭连接。
----TCP服务器进程发送TCP连接释放报文段并进入最后确认状态。
----该报文段首部中的终止位FIN和确认位ACK的值都被设置为1,表明这是一个TCP连接释放报文。同时也对之前收到的报文段进行确认。
----现在假定序号seq字段的值为w。这是因为在半关闭状态下,TCP服务器进程可能又发送了一些数据。
----确认号ack字段的值为u+1,这是对之前收到的TCP连接释放报文段的重复确认。
在这里插入图片描述
TCP客户进程收到TCP连接释放报文段后,必须针对该报文段发送普通的TCP确认报文段,之后进入时间等待状态。
----该报文段首部中的确认位ACK的值被设置为1,表明这是一个普通的TCP确认报文段。
----序号seq字段的值设置为u+1,这是因为TCP客户进程之前发送的TCP释放报文段虽然不携带数据,但要消耗掉一个序号。
----确认号ack字段的值设置为w+1,这是对所收到的TCP连接释放报文段的确认。
在这里插入图片描述
TCP服务器进程收到该报文段后就进入关闭状态,而TCP客户进程还要经过2MSL后才能进入关闭状态。MSL(Maximum Segment Lifetime)的意思是最长报文段寿命,RFC793文档建议为两分钟。也就是说,TCP客户进程进入时间等待状态后,还要经过4分钟才能进入关闭状态。这完全是从工程上来考虑的,对于现在的网络,MSL取为两分钟可能太长了。因此,TCP允许不同的实现,可根据情况使用更小的MSL值。
在这里插入图片描述

思考:

问题: TCP客户进程在发送完最后一个确认报文段后,为什么不直接进入关闭状态,而是要进入时间等待状态,2MSL后才进入关闭状态,这是否有必要呢?
答案: 有必要。来看这种情况,TCP服务器进程发送TCP连接释放报文段后,进入最后确认状态。
在这里插入图片描述
TCP客户进程收到该报文段后,发送普通的TCP确认报文段,并进入关闭状态而不是时间等待状态。
在这里插入图片描述
然而,该TCP确认报文段丢失了。这必然会造成TCP服务器进程对之前所发送的TCP连接释放报文段的超时重传,并仍处于最后确认状态。
在这里插入图片描述
重传的TCP连接释放报文段到达TCP客户进程。由于TCP客户进程处于关闭状态,因此不理睬该报文段。这必然会造成TCP服务器进程反复重传TCP连接释放报文段,并一直处于最后确认状态而无法进入关闭状态。
在这里插入图片描述
总结: 因此,时间等待状态以及处于该状态2MSL的时长,可以确保TCP服务器进程可以收到最后一个TCP确认报文段而进入关闭状态。另外,TCP客户进程在发送完最后一个TCP确认报文段后,再经过2MSL时长,就可以使本次连接持续时间内所产生的所有报文段都从网络中消失,这样就可以使下一个新的TCP连接中不会出现旧连接中的报文段。


保活计时器的作用

设想这样一种情况:TCP双方已经建立了连接。后来,TCP客户进程所在的主机突然出现了故障。显然,TCP服务器进程以后就不能再收到TCP客户进程发来的数据。因此,应当有措施使TCP服务器进程不要再白白等待下去。换句话说,TCP服务器进程应该如何发现这种情况呢?
在这里插入图片描述
答案: 方法就是使用保活计时器。
----TCP服务器进程每收到一次TCP客户进程的数据,就重新设置并启动保活计时器。
----若保活计时器定时周期内未收到TCP客户进程发来的数据,则当保活计时器到时后,TCP服务器进程就向TCP客户进程发送一个探测报文段。
----以后每隔75秒钟发送一次。
----若一连发送10个探测报文段后,仍无TCP客户进程的响应。TCP服务器进程就认为TCP客户进程所在主机出了故障,接着就关闭这个连接。


小测验

1、TCP的通信双方,有一方发送了FIN标志位为1的报文段,表示
A. 将断开通信双方的TCP连接
B. 单方面释放连接,表示本方已经无数据发送,但可以接收对方的数据
C. 中止数据发送,双方都不能发送数据
D. 连接被重新建立
答案:B

2、假设TCP客户与TCP服务器的通信已经结束,TCP客户与TCP服务器之间端到端的平均往返时间为RTT,在t时刻TCP客户请求断开连接,则从t时刻起,TCP服务器释放该连接的最短时间是
A. 0.5个RTT
B. 1个RTT
C. 1.5个RTT
D. 2个RTT
答案:C


九、TCP报文段的首部格式

前言

在之前的课程中我们曾介绍过,为了实现可靠传输,TCP采用了面向字节流的方式。如图所示,TCP将应用进程交付下来的应用报文看作是字节流,存入TCP发送缓存中。但TCP在发送数据时,是从发送缓存取出一部分或全部字节,并给其添加一个首部,使之成为TCP报文段后进行发送。一个TCP报文段由首部和数据载荷两部分构成,TCP的全部功能都体现在它首部中各字段的作用。
在这里插入图片描述


TCP报文段的首部格式

TCP报文段的首部格式与IP数据报的首部格式类似,都是由20字节的固定首部和最大40字节的扩展首部构成。
在这里插入图片描述


源端口字段、目的端口字段

源端口字段占16比特,用来写入源端口号,源端口号用来标识发送该TCP报文段的应用进程。
目的端口号字段占16比特,用来写入目的端口号,目的端口号用来标识接收该TCP报文段的应用进程。
在这里插入图片描述
例子: 假设主机中的浏览器进程要访问Web服务器中的Web服务器进程。为了简单起见,我们仅从运输层端口号这个角度来举例说明,而不考虑其他细节,例如ARP、域名解析、TCP连接建立等。
在这里插入图片描述
当在浏览器地址栏中输入了Web服务器的域名后,浏览器进程会构建一个封装有HTTP请求报文的TCP报文段。
----该报文段首部中的源端口字段会填写一个短暂端口号,例如49152,用来标识发送该报文段的浏览器进程。
----目的端口字段会填写熟知端口号80,因为使用HTTP协议的Web服务器进程默认监听该端口。
在这里插入图片描述
Web服务器收到该TCP报文段后,从中解封出HTTP请求报文,并根据TCP报文段首部中目的端口字段的值80,将HTTP请求报文上交给Web服务器进程。
----Web服务器进程根据HTTP请求报文的内容进行相应处理,并构建一个HTTP响应报文。H
----TTP响应报文需要封装成TCP报文段进行发送。
----该报文段首部中的源端口字段会填写熟知端口号80,用来标识发送该TCP报文段的Web服务器进程。
----而目的端口字段会填写49152,这是主机中需要接收该TCP报文段的浏览器进程所对应的端口号。
在这里插入图片描述
主机收到该TCP报文段后,从中解封出HTTP响应报文,并根据TCP报文段首部中目的端口字段的值49152,将HTTP响应报文上交给浏览器进程。浏览器进程对HTTP响应报文的内容进行解析并显示。
在这里插入图片描述


序号字段、确认号字段、确认标志位ACK

接下来,我们再来看看与TCP实现可靠传输相关的序号字段、确认号字段、确认标志位ACK。
在这里插入图片描述
①序号字段占32比特,因此取值范围是 [ 0 , 2 32 − 1 ] [0,2^{32}-1] [0,2321]。当序号增加到最后一个后,下一个序号就又回到0。序号字段的值用来指出本TCP报文段数据载荷的第一个字节的序号。
例子: 这是一个TCP报文段,它由首部、数据载荷两部分构成。数据载荷中的每个字节数据都有序号,如图所示。请注意,他们是字节数据的序号,而不是内容。
在这里插入图片描述
对于本例,首部中序号字段应填入的十进制值为166,用来指出数据载荷的第1个字节的序号为166。
②确认号字段占32比特,因此取值范围是 [ 0 , 2 32 − 1 ] [0,2^{32}-1] [0,2321]。当确认号增加到最后一个后,下一个确认号就又回到0。确认号字段的值用来指出,期望收到对方下一个TCP报文段的数据载荷的第一个字节的序号,同时也是对之前收到的所有数据的确认。可以这样理解,若确认号等于N,则表明到序号N-1为止的所有数据都已正确接收,期望接收序号为N的数据。
③只有当确认标志位ACK取值为1时,确认号字段才有效。取值为0时,确认号字段无效。TCP规定,在连接建立后,所有传送的TCP报文段都必须把ACK置1。
举例说明这三个字段的作用:
TCP客户进程发送一个TCP报文段。该报文段首部中序号字段的取值为201,这表示该TCP报文段数据载荷的第一个字节的序号为201。假设数据载荷的长度为100字节。首部中确认号字段的取值为800,这表示TCP客户进程收到了TCP服务器进程发来的序号到799为止的全部数据,现在期望收到序号从800开始的数据。为了使确认号字段有效,首部中中的确认标志位ACK的值必须设置为1。
在这里插入图片描述
TCP服务器进程收到该报文段后,也给TCP客户进程发送TCP报文段。该报文段首部中序号字段的取值为800,这表示该TCP报文段数据载荷的第一个字节的序号为800,这正好与TCP客户进程的确认相匹配。假设数据载荷的长度为200字节,首部中确认号字段的取值为301,这表示TCP服务器进程收到了TCP客户进程发来的序号到300为止的全部数据,现在期望收到序号从301开始的数据。为了使确认号字段有效,首部中的确认标志位ACK的值必须设置为1。
在这里插入图片描述


数据偏移字段

该字段占4比特,并以4字节为单位,用来指出TCP报文段的数据载荷部分的起始处距离TCP报文段的起始处有多远。
在这里插入图片描述
该字段实际上指出了TCP报文段的首部长度。首部固定长度为20字节,因此数据偏移字段的最小值为二进制的0101。加上最大扩展首部40字节,首部最大长度为60字节。因此,数据偏移字段的最大值为二进制的1111。
例子: ①假设这个TCP报文段首部中的数据偏移字段的取值为二进制的0101。那么首部长度为20字节,因为二进制的0101的十进制值是4,而该字段以4字节为单位,5×4=20。
②假设这个TCP报文段首部中的数据偏移字段的取值为二进制的1111,那么首部长度就为60字节。因为二进制1111的十进制值为15,而该字段以4字节为单位,因此15×4=60。
在这里插入图片描述


保留字段

TCP报文段首部中的保留字段占6比特,保留为今后使用,目前应置为0。
在这里插入图片描述


窗口字段

该字段占16比特,以字节为单位。该字段指出的是发送本报文段的一方的接收窗口。窗口值作为接收方让发送方设置其发送窗口的依据,这是以接收方的接收能力来控制发送方的发送能力,也就是所谓的流量控制。需要注意的是,发送窗口的大小还取决于拥塞窗口的大小,也就是应该从接收窗口和拥塞窗口中取小者。
在这里插入图片描述


校验和字段

TCP报文段首部中的校验和字段占16比特,用来检查整个TCP报文段在传输过程中是否出现了误码。与UDP类似,在计算校验和时,要在TCP报文段的前面加上12字节的伪首部。具体的校验算法就不再赘述了,因为它仅仅是一种检错算法,与TCP的其他重要功能相比,检错算法并不是重点。
在这里插入图片描述


同步标志位SYN

该标志位在TCP连接建立时用来同步序号。
在这里插入图片描述
如图所示,这是我们之前课程中层介绍过的TCP通过三报文握手建立连接的过程。
①TCP客户进程发送的TCP连接请求报文段首部中的同步标志位SYN被置1,表明这是一个TCP连接请求报文段。
②TCP服务器进程发送的TCP连接请求确认报文段首部中的同步标志位SYN被置1,确认位ACK也被置1,表明这是一个TCP连接请求确认报文段。
在这里插入图片描述


终止标志位FIN

该标志位用来释放TCP连接。
在这里插入图片描述
如图所示,这是我们之前课程中曾介绍过的TCP通过四报文挥手释放连接的过程。不管是TCP客户进程还是TCP服务器进程,他们所发送的TCP连接释放报文首部中的终止标志位FIN都被置1,表明这是TCP连接释放报文段。
在这里插入图片描述


复位标志位RST

首部中的复位标志位RST用来复位TCP连接。当RST=1时,表明TCP连接出现了异常,必须释放连接,然后再重新建立连接。RST置1还可以用来拒绝一个非法的报文段或拒绝打开一个TCP连接。
在这里插入图片描述


推送标志位PSH

首部中的推送标志位PSH用来实现推送操作。当接收方的TCP收到该标志为为1的报文段,会尽快上交应用进程,而不必等到接收缓存都填满后再向上交付。
在这里插入图片描述


紧急标志位URG、紧急指针字段

首部中的紧急标志位URG和紧急指针字段用来实现紧急操作。
----紧急标志位URG取之为1时,紧急指针字段有效。取值为0时,紧急指针字段无效。
----紧急指针字段占16比特,以字节为单位,用来指明紧急数据的长度。当发送方有紧急数据时,可将紧急数据插队到发送缓存的最前面,并立刻封装到一个TCP报文段中进行发送。紧急指针会指出本报文段数据载荷部分包含了多长的紧急数据。紧急数据之后是普通数据。
----接收方收到紧急标志为1的报文段,会按照紧急指针字段的值从报文段数据载荷部分取出紧急数据,并直接上交应用进程,而不必在接收缓存中排队。
在这里插入图片描述


选项部分

TCP报文段首部除了20字节的固定部分,还有最大40字节的选项部分。增加选项可以增加TCP的功能,目前有以下选项:
①最大报文段长度MSS选项,它用来指出TCP报文段数据载荷部分的最大长度。
②窗口扩大选项:用来扩大窗口,提高吞吐率。
③时间戳选项有以下两个功能:
----用来计算往返时间。
----用来处理序号超范围的情况,又称为防止序号绕回PAWS。
④选择确认选项:用来实现选择确认功能。
在这里插入图片描述


填充字段

由于选项的长度可变,因此,还需要使用填充字段来确保报文段首部能被4整除。这是因为数据偏移字段,也就是首部长度字段是以4字节为单位的。如果选项的长度加上20字节固定首部的长度不能被4整除,则需要使用填充字段来确保首部能被4整除。
在这里插入图片描述


小测验

1、TCP报文段首部的最小长度为
A. 20字节
B. 40字节
C. 60字节
D. 80字节
答案:A

2、TCP报文段首部的最大长度为
A. 20字节
B. 40字节
C. 60字节
D. 80字节
答案:C

3、TCP报文段首部中序号字段的作用是
A. 指明TCP报文段首部第一个字节的序号
B. 指明TCP报文段的数据载荷的第一个字节的序号
C. 指明TCP报文段的数据载荷的最后一个字节的序号
D. 指明TCP报文段的数据载荷的长度
答案:B

4、TCP报文段首部中窗口字段的值
A. 指明自己的拥塞窗口的尺寸
B.指明对方的发送窗口的尺寸
C. 指明自己的接收窗口的尺寸
D. 指明对方的拥塞窗口的尺寸
答案:C

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

知初与修一

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

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

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

打赏作者

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

抵扣说明:

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

余额充值