传输层协议TCP(4)

1 收到对方报文

       上一小节我们讲述了 TCP 的一方等待另一方报文超时的情形。那么,如果没有超时,在期望时间内收到了对方的报文呢?也需要分情况,TCP 收到对端的报文时,首先要做的就是合法性检查,然后再根据检查情况,决定下一步的动作。至于什么样的报文是合法的,则与当时的 TCP 的状态有关。

1.1 CLOSED 状态

       所谓 TCP 连接,指的就是一块相应的 TCB 内存块。处于CLOSED 状态的 TCP 连接,其所对应的 TCB 内存块其实已经被删除了。TCB 内存块已经被删除了,但是 TCP 协议栈还在。所以处于 CLOSED 状态的 TCP 连接,仍然可以接收报文,也仍然可以做相应的处理,这与关闭 TCP 协议栈的情形是不同的。CLOSED 状态的 TCP 连接,无论收到什么报文,它都会将其丢弃。如果所接收的报文,不是 RST 报文,TCP 还会回给对方1个 RST 报文(如果所接收的是 RST 报文,则只丢弃不回应)。

       TCP 所回应的 RST 报文,与其所接收的报文中的 ACK 标志位有关:

  • (1) 如果所接收报文的 ACK 标志位为“0”,则 RST 报文的格式为:<SEQ = 0> <AKN = SEG.SEQ + SEG.LEN> <CTL = RST, ACK>
  • (2) 如果所接收报文的 ACK 标志位为“1”,则 RST 报文的格式为:<SEQ = SEG.ACK> <CTL = RST>

      这样表达的报文格式,解释如下:

      

1.2 通用处理思路

     除了 CLOSED 状态,其他状态的 TCP 连接处理其所接收报文,都遵循一个通用的处理思路,如下图所示:

      

       上图是通用处理思路的一个抽象表达。TCP 收到1个报文时,它首先会做参数的合法性校验,然后查看该报文是否是 RST 报文。如果报文的参数合法,并且也不是 RST 报文,那么接下来就看该报文的行为是否合法。什么叫一个报文的行为呢?就是综合该报文的标志字段及数据长度,“拟人”出该报文的行为。比如“SYN 标志 = 1,数据长度 = 0”,这个报文的行为就是“请求创建连接”。不同状态的 TCP,它期望的所接收报文的行为是不同的。

       TCP 对于所接收报文的“非法参数”、“RST 报文”、“非法行为”、“合法行为”等情形,都有对应的处理。一般来说,针对前三种情形,TCP 做了相应处理以后,最后都是将其所接收到的报文丢弃。而合法行为的报文,TCP 处理完以后,如果该报文没有包含数据,那其实也是将其丢弃;如果包含数据,TCP 会将该数据暂时存放在自己的缓存中以便交给用户。

       更具体的 TCP 通用处理思路,如下图所示:

       

       图中的“2(S/A/L 是否合法)、4(S/C 是否合法)、5(优先级是否合法)”,都是属于图5-53所说的“参数是否合法”——这样一来,就比较清晰了,图5-54仍然表达的 TCP 对于所接收报文的“非法参数”、“RST 报文”、“非法行为”、“合法行为”等情形的处理流程。下面就让我们从图5-54中的第1个判断点开始,进一步分析 TCP 接收到报文时的通用处理思路。

1.2.1 ACK 判断点

       图5-54与图5-53相比,最大的不同是:图5-54中多了“是否存在 ACK”的判断点,并且“处理 NO ACK”的方框是一个虚框。“是否存在 ACK”,指的是所接收报文中的 ACK 标志是否为“1”。对于不需要 ACK 标志为“1”的报文,它就不需要判断“S/A/L 的合法性”,所以相对应的处理流程,就可以跳过“2(S/A/L 是否合法)”判断点,而直接进入“3(是否存在 RST)”判断点。

       对于参数错误和行为错误的报文,TCP 的处理对策一般是需要给对方回应一个报文。但是这个报文的格式及相关参数的值,又与其所接收的报文中的 ACK 标志是否为“1”有关。图5-54将方框“处理 NO ACK”,画成1个虚框,它并不是代表一个真正的处理过程,而是想强调:在后面的处理流程中,可能都会包含“有 ACK”和“NO ACK”两个分支,只是图形不太好表达,下文会用文字补充说明。也就说,第1个判断点“是否存在 ACK”以及其具体的处理措施,其实是融于后面其他各个判断点及处理点中介绍。

1.2.2 S/L判断点

      S/L 是 Sequence Number(SEQ)、Data Length(DataLen)的缩写,指的是所接收报文的序列号和数据长度(是数据长度,不是报文长度)。TCP 对于 S/L 的判断,有两个基本原则:(1)本次所接收数据的 SEQ,与以前曾经所接收数据的 SEQ  不能重复;(2)本次所接收数据的 SEQ,不能超出接收空间(稍微有一点点特列)。具体判断方法,如表5-20所示:

     

     第1种情形,所接收的报文的数据长度大于0并且自己的接收空间等于0(SEG.DataLen > 0 and Rcv.WND == 0)。这种情形下,这样的报文是不能接受的。

     第2种情形的接收空间等于0但是其所接收报文的数据长度也为0(SEG.DataLen == 0 and Rcv.WND == 0)。它的意思是:固然不能接收数据,但是还是能接收“控制”报文,比如单纯的 ACK 报文、SYN 报文、FIN 报文、RST 报文等等。 对于所接收的控制报文,需要满足RCV.NXT = SEG.SEQ,也就说,所接收报文的 SEQ 必须要等于接收方所期望的序列号(RCV.NXT)。

     第3种情形,从接收空间的角度来说,接收空间大于0,但是所接收的报文的数据长度仍然等于0。也需要验证序列号:RCV.NXT <= SEG.SEQ < RCV.NXT + RCV.WND。实际上第3种情形隐含了一个事实:只要所发送的报文的 SEQ 在对方的接收空间内(同时报文的数据长度为0),那么就可以连续发送报文。

     第4种情形,表达的是接收 TCP 数据的情形(前3种情形,都无法接收 TCP 数据)。这个情形有两个校验公式。

  • 第(1)个公式:RCV.NXT <= SEG.SEQ < RCV.NXT + RCV.WND,它代表的含义,如下图所示:

        

       这个公式只是限制了所接收报文的 SEQ 必须位于接收空间之内,但是并没有限制所接收报文的数据长度。也就是说,所接收报文的数据的长度,有可能大于接收空间的大小。对于这种情形 TCP 认为是可以接受的,毕竟受 MTU 的限制,一个报文的数据长度即使大于接收空间,也大不了多少,TCP 具体实现时,偷偷地给接收空间放大一点即可。

  • 第(2)个公式:RCV.NXT <= SEG.SEQ + SEG.LEN - 1 < RCV.NXT + RCV.WND,它代表的含义如下图所示:

       

      与第(1)个公式相反,第(2)公式恰恰只判断所接收报文的数据的结尾是否落在接收空间,至于数据的开头(SEG.SEQ)即使落在接收空间之外,也没有关系。

      对于以上所描述的4种情形,如果所接收的报文不能满足对应的判断法则,那么 TCP 会做两件事情:

  • (1)丢弃所接收的报文;
  • (2)如果所接收的报文是 RST 报文,则不需要再做什么。否则,会给对方回1个报文,报文格式为:<SEQ = SND.NXT> <ACK = RCV.NXT> <CTL = ACK>。这个报文的含义就是告诉对方:你发给我的报文,它的 SEQ 应该等于 RCV.NXT;

1.2.3 收到 RST 报文

       TCP 收到 RST 报文的处理流程,与其当时所处的状态有关。

CLOSED 状态

       对于处于 CLOSED 状态的 TCP 连接来说,它收到了炸弹,直接丢弃该报文,然后啥也不做。

LISTEN 状态

       对于处于 LISTEN 状态的 TCP 连接来说,收到RST报文后就丢弃该报文,并且仍然是处于 LISTEN 状态。因为此时它还处于 LISTEN 状态,还没有建立连接,也就无所谓关闭。

SYN-RECEIVED 状态

        如果一个连接处于 SYN-RECEIVED 状态,并且它前一个状态是 LISTEN 状态,那么它收到 RST 报文时,也是丢弃该报文,并且回到 LISTEN 状态。如果它前一个状态不是 LISTEN 状态,那么它收到 RST 报文时,就会 Abort 该连接,并且状态转移到 CLOSED。

可接收数据状态

       可接收数据状态,指的是:ESTABLISHED、FIN-WAIT-1、FIN-WAIT-2、CLOSE-WAIT。处于这些状态的 TCP 连接,是可以接收数据的。此时,如果收到 RST 报文,TCP 会中断(Abort)该连接:

  • (1)所有的接收、发送队列,都会被清空
  • (2)用户调用的 SEND、RECEIVE 接口也会被中断
  • (3)TCP 会给用户发送1个“connection reset”信号
  • (4)删除对应 TCB,TCP 连接状态变成 CLOSED

濒临关闭状态

       濒临关闭状态,指的是:CLOSING、LAST-ACK、TIME-WAIT。处于这些状态的 TCP 连接,已经不可以接收数据,只是在等待最后的关闭。处于濒临关闭状态的连接,如果收到RST 报文,那是相当于提前收到关闭连接的“指令”:直接删除对应的 TCB,状态变成 CLOSED。

1.2.4 S/C 判断点

      S/C 是 Security/Compartment 的缩写,指的是所接收报文中的 Security/Compartment 字段的值。TCP 一般要求其所接收报文中的 S/C 与自己(TCB 内存中的字段)的值完全相同,如果不同,就给对方回以1个 RST 报文。 

      TCP 所回应的 RST 报文,与其所接收的报文中的 ACK 标志位有关:

  • (1) 如果所接收报文的 ACK 标志位为“0”,则 RST 报文的格式为:<SEQ = 0> <AKN = SEG.SEQ + SEG.LEN> <CTL = RST, ACK>;
  • (2) 如果所接收报文的 ACK 标志位为“1”,则 RST 报文的格式为:<SEQ = SEG.ACK> <CTL = RST>;

1.2.5 优先级判断点

       对于优先级,TCP 则要相对复杂不少。

      (1) 如果所接收报文中,ACK 标志为1,那么报文中的优先级必须与自身的值相同,否则就回以1个 RST 报文:<SEQ = SEG.ACK> <CTL = RST>;

      (2) 如果所接收报文中,ACK 标志为0,那么:

           如果报文中的优先级与自身相同,则合法;

           如果报文中的优先级小于自己,则合法;

           如果报文中的优先级大于自己:

  • 如果用户允许,则也是合法,并且将对应的 TCB 中优先级的值修改为所接收报文中的值;
  • 如果用户不允许,则是非法——回以1个 RST 报文:<SEQ = 0> <AKN = SEG.SEQ + SEG.LEN> <CTL = RST, ACK>;

1.2.6 行为是否违法判断点

       TCP 报文的行为,指的是报文的格式,比如报文中标志的取值( ACK = 1/0、SYN = 1/0)、报文是否包含数据等等。至于报文是否违法,与 TCP 当时的状态有关。处于 LISTEN 状态的 TCP,它所期望接收的报文的格式是:(1) SYN = 1、ACK = 0;(2)DataLen = 0。此时,其所接收的报文的 ACK = 1,那么 TCP 就会认为它所接收的报文的行为是“非法”。对于非法行为的报文,TCP 或者是丢弃该报文,或者是除了丢弃报文还会给对方回1个 RST 报文。具体采取何种措施,仍然与 TCP 当时所处的状态有关。

1.2.7 处理合法行为的报文

    (1)根据接收报文中的相关字段值,修正自己 TCB 中的值(TCB 的解释,请参考5.2.3、5.2.7)。

    (2)如果所接收的报文中包含数据,则将该数据存储到缓存中。

    (3)给对方回以 ACK 报文。

1.2.8 通用处理思路小结

      我们将 TCP 接收到报文以后,其通用处理思路如下图:

      

     上图中的第1个判断点(是否存在 ACK),其判断及处理过程,其实是融于其他判断点和处理流程中的,具体如下表:

     

1.3 LISTEN 状态

       处于 LISTEN 状态的 TCP,它心目中苦苦等候的就是对方发送“连接创建”请求报文(SYN报文),其他报文对它来说,都是“非法报文”。处于 LISTEN 状态的 TCP,其收到报文时的处理流程,如下图所示:

       

  1. 首先,TCP 判断其所收到的报文是否是 RST 报文。如果是 RST 报文,那么直接就将其丢弃,然后继续 LISTEN。
  2. 如果不是 RST 报文,TCP 继续检查该报文中是否包含 ACK,如果包含了 ACK,那么这个报文就是非法报文,那就丢弃该报文,并给对方回以1个 RST 报文,报文格式如下:<SEQ = SEG.AKN> <CTL = RST>;
  3. 如果没有包含 ACK,TCP 继续检查该报文是否包含 SYN,如果没有包含 SYN,那么这个报文就是非法报文,那就丢弃该报文,并给对方回以1个 RST 报文,报文格式如下:<SEQ = 0> <CTL = RST>;
  4. 其实以上两个判断(对应到图5-60中“2”和“3”),就是图5-58中所说的第6个判断点“行为是否合法”。
  5. 如果报文的行为也合法,TCP 会紧接着判断 Security/Compartment(图5-60中的“4”)、优先级(图5-60中的“5”),如果它们不合法,那么 TCP 就会丢弃该报文,并给对方回以1个 RST 报文,报文格式如下:<SEQ = 0> <CTL = RST>;
  6. 当流程来到图5-60中的第“6”步,那就说明所接收的报文是“合法”的,此时 TCP 会做如下几件事:
  • (1)修改自己的 TCB:RCV.NXT = (SEG.SEQ + 1)、IRS = SEG.SEQ
  • (2)计算出 ISS(5.2.13节会讲述如何计算 ISS)
  • (3)给对方发送报文,报文格式为:<SEQ = ISS> <ACK = RCV.NXT> <CTL = SYN, ACK>
  • (4)继续修改自己的 TCB:SND.NXT = (ISS + 1)、SND.UNA = ISS
  • (5)将自己的状态迁移到 SYN-RECEIVED

1.4 SYN-SENT 状态

         处于 SYN-SENT 状态的 TCP,等待收到的报文如场景1与场景2所示:

 

       对于处于 SYN-SENT 状态的 TCP 来说,它不仅可以收到 <ACK, SYN> 报文(对应场景1),也可以收到 <SYN> 报文(对应场景2)。无论是场景1,还是场景2,TCP 收到报文的处理流程,在上半部分都是一样的,如下图。

       

       由于处于 SYN-SENT 状态的 TCP,它既可以收到 <ACK, SYN> 报文,也可以收到 <SYN> 报文,所以图5-62以上来就判断报文中的 ACK 是否等于1。如果 ACK 等于1,那么 TCP 就需要判断报文中的 AKN 是否合法,判断公式是:SND.UNA <= SEG.AKN <= SND.NXT;

       如果 SEG.AKN不满足此公式,就是非法的,那么其处理流程就是丢弃所接收的报文,并且给对方回以1个 RST 报文(如果所接收的报文是 RST 报文,就不给对方回报文了),报文格式是:<SEQ = SEG.ACK> <CTL = RST>;

      如果所接收的报文的 AKN 合法,或者所接收的报文中的 ACK = 0,那么 TCP 就检测其所接收的报文是否是 RST 报文。如果是 RST 报文,TCP 的处理流程为:

  • (1)如果所接收报文的 ACK = 1,那么:给用户发送信号“error:connection reset”、丢弃报文、删除 TCB、状态转移到 CLOSED;
  • (2)如果所接收报文的 ACK = 0,那么:丢弃报文;

      如果经过了以上关卡,那么就来到了图5-62中的“5”、“6”,判断“Security/Compartment”、“优先级”是否合法,如果不合法,则给对方回以1个 RST 报文,并且丢弃所接收的报文。

      如果两者都合法,那么就判断所接收报文中 SYN 是否等于1。如果 SYN 不等于1,那么根据前面的描述,这也是一个非法报文,它的处理流程是:丢弃该报文。如果 SYN = 1,就来到了图5-62中的“8”(处理 SYN)。根据5.2.1节和5.2.4节的描述,或者根据图5-61,即使处理流程来到了步骤“8”,它也是分为两个场景,如图5-63所示:

       

      收到报文并判断是合法以后,此时需要判断报文中的 ACK 是否等于1。ACK 等于1与否,决定了 TCP 处于图5-61所描述的哪种场景。

     1)ACK = 1。ACK = 1,就是经典的“三次握手”中的“第二次握手”。第二次握手成功以后,TCP 会做如下几件事情:

  • (1)修改 TCB:RCV.NXT = SEG.SEQ + 1,IRS = SEG.SEQ,SND.UNA = SEG.AKN;
  • (2)给对方回以1个 ACK 报文,报文格式为:<SEQ = SND.NXT> <AKN = RCV.NXT> <CTL = ACK>;
  • (3)将状态转移到 ESTABLISHED;

    2)ACK = 0。ACK = 0,就是“双方同时发送 SYN 请求”的场景:给对方发送 SYN 的请求后,也收到了对方 SYN 请求。TCP 会做如下几件事情:

  • (1)给对方回以1个 SYN/ACK 报文,报文格式为:<SEQ = ISS> <AKN = RCV.NXT> <CTL = SYN, ACK>;
  • (2)状态转移到 SYN-RECEIVED;

1.5 SYN-RECEIVED及其以后状态

       SYN-RECEIVED 及其以后状态,是指 TCP 处于如下状态:SYN-RECEIVED、ESTABLISHED、FIN-WAIT-1、FIN-WAIT-2、CLOSE-WAIT、CLOSING、LAST-ACK、TIME-WAIT。处于这些状态的 TCP,收到报文时,它的处理流程如下图所示:

        

       图5-64与图5-58(通用处理思路)完全相同,而且它们背后所代表的含义也完全相同。但是,与通用处理思路不同的是,这里还需要就非法报文、合法报文的处理,展开描述。

       1)非法报文的处理

       处于 SYN-RECEIVED 及其以后状态的 TCP,对于非法报文的处理如下:

      

       无论是非法报文还是非法数据,TCP 关于表5-22的后两行的处理,都比较温柔:仅仅是将所接受的报文(数据)做一个丢弃处理就可以了,剩下的就是保持不变,静待下一个报文的到来。只是对于表5-22的第1行的处理,TCP 像疯了一样:关闭自己、给对方发送 RST 报文(目的是让对方也关闭)。为什么会发送RST让对方也关闭连接?主要原因是:抛开实现代码的 bug 不谈,很有可能是 A 异常重启了。重启以后,A 忘记了曾经它跟 B 所建立的 TCP 连接,并且还试图建立(新的)连接。对于 B 而言,它必须告知 A:当前它还处于它们曾经所创建的连接之中,它们之间必须要将曾经的连接了断之后,才能重新开始。

       上表5-22中非法报文的处理,可以用下图表示:

        

        图5-66中的“合法报文处理”,是一个虚框,这是因为其处理流程与报文的具体内容相关,也与 TCP 当时所处状态相关。

        2)合法的单纯的 ACK 报文

       处于 SYN-RECEIVED 及其以后状态的 TCP,它所收到的报文必须包含 ACK(ACK = 1)。不过 ACK 报文,可能是单纯的 ACK 报文,也有可能叠加其他标志位(比如 FIN),也有可能叠加传输数据(即 DataLen > 0)。为了简化描述,我们将这种耦合分解,本小节只讲述“单纯的 ACK 报文”这一情形,也就是说:报文中的标志位,只有 ACK = 1,其他都等于0,并且数据长度也为0。

       

      表5-23的第4行(也即图5-70),表达的是“A 给 B 发送数据以后,B 给 A 发送 ACK 报文,确认它收到了数据”这一场景。这一场景中,TCP 可以发送数据的状态不止一(ESTABLISHED、CLOSE-WAIT),可以接收数据的状态也不止一个(ESTABLISHED、FIN-WAIT-1、FIN-WAIT-2)。这种场景下,TCP 的状态并不会迁移,如下图。

       

     接收数据的确认(表5-23的第4行),与 TCP 的发送空间有关,如图5-71所示:

     

       图5-71中的发送空间,是接收本次报文前的发送空间。接收到本次报文以后,TCP 首先要关心的是这次所接收的 ACK 报文,是否确认了一些“已发送待确认”的数据。当然,关心归关心,具体是否能够确认,还与其所接收报文的 AKN(SEG.AKN)的值有关。

     

       上表的第3行,除了解释了“SEG.AKN > SDN.NXT”时TCP 该怎么做,同时也凑巧引出了单纯发送 ACK 报文的第5种场景。

       回到第4种场景(数据接收确认)上来,处理完 SEG.AKN 与自己接收空间的 SND.UNA 之间的关系以后,TCP 还会修改自己的 SND.WND。为了预防场景4中数据报文先发后至或者说后发先至的场景导致 SND.WND错误修改,在收到报文后需要进行如下判断才能确定是否需要修改SND.WND。

    

       TCP 引入了两个变量:SND.WL1、SND.WL2。(SND.WL 是 SEND.Window LAST 的缩写)我们先不管 SND.WL1、SND.WL2 的含义,就把它们当作2个变量,初始值都为0。上表5-25中的两个条件,只要有1个条件成立,那么赋值如下:SND.WND = SEG.WND;SND.WL1 = SEG.SEQ;SND.WL2 = SEG.ACK;

       这个修改可以称为“发送窗口修改点(简称:修改点)”的话,表5-25的条件1,其实可以这样等价:上次修改点时所接收报文的 SEQ < 本次所接收报文的 SEQ。这句话的含义就是:本次所接收报文是“新”报文,没有发生“先发后至”的情形。同理,表5-25的条件2,也可以这样理解:即使“上次修改点时所接收报文的 SEQ 等于本次所接收报文的 SEQ”,但是“上次修改点时所接收报文的 AKN 小于本次所接收报文的 SEQ”,所以本次所接收报文是“新”报文,没有发生“先发后至”的情形。

       总结一下上表5-23所说的第4种场景(数据接收确认):

  • (1)如果所接收报文的 AKN 在已发送待确认的序列号之间,那么修改发送空间的 SEND.UNA 变量:SND.UNA = SEG.AKN
  • (2)同时,如果这个报文确实是最“新”的,那么也修改 SND.WND、SND.WL1、SND.WL2 这3个变量:SND.WND = SEG.WND, SND.WL1 = SEG.SEQ, SND.WL2 = SEG.ACK
  • (3)其余细节,请参见前文描述

       3)合法的 FIN 报文

        RFC 793 定义了6个标志位、RFC 3168 增加了2个、RFC 3540 增加了1个,一共9个。到目前为止,我们介绍了其中4个:ACK、RST、SYN、FIN,其余的标志位需要放到后面的章节介绍。

       收到 FIN 报文后的处理情况,也与 TCP 当时所处的状态相关。对于 CLOSED、LISTEN、SYN-SENT 状态的 TCP 来说,如果它收到 FIN 报文,会直接丢弃该报文,然后就不再做其他任何处理了。对于处于 SYN-RECEIVED 及其以后状态的 TCP 来说,它所收到的合法的 FIN 报文一定会包含 ACK(ACK = 1),所以此时 TCP 首先会做出上一小节“合法的单纯的 ACK 报文”所讲述的那些处理流程,然后再来处理 FIN 本身。具体处理流程,与 TCP 当时所处状态有关,如下表5-26所示:

       

       4)合法的数据

        处于 CLOSE-WAIT、CLOSING、LAST-ACK、TIME-WAIT 状态的 TCP,不应该能收到数据(不是本方不能接收数据,而是对方不应该发送数据),所以此时 TCP 如果收到的报文中包含数据,它会忽略这些数据。处于 ESTABLISHED、FIN-WAIT-1、FIN-WAIT-2 状态的 TCP,则可以接收数据,只要承载这些数据的报文,经过了前面层层的检验。承载合法数据的报文,可能会包含 ACK(ACK = 1),所以此时 TCP 首先会做出上一小节“合法的单纯的 ACK 报文”所讲述的那些处理流程,然后再来处理数据本身。

        经过了那么多铺垫,TCP 处理所接收的数据,就变得比较简单了:

(1)将数据传递(deliver)给用户缓存

(2)修改 TCB:

  • (A)RCV.NXT = SEG.SEQ + SEG.DataLen,
  • (B)RCV.WND = RCV.WND(数据已经传递到用户缓存)or RCV.WND = RCV.WND - SEG.DataLen(数据尚未传递到用户缓存)

(3)给对方回以 ACK 报文,报文格式为:<SEQ = SND.NXT> <AKN = RCV.NXT, WND = RCV.WND> <CTL = ACK>

 

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值