计算机网络——自顶向下的传输层

一、传输协议原理

1.1 传输层与网络层
  传输层协议为运行在不同Host上的进程提供了一种逻辑通信机制。在端系统运行传输层协议时,发送方将应用递交的消息分成一个或多个报文段【Segment】,并下传给网络层;而接收方将收到的segment组装成消息,并上交给应用层。
  运输层提供了应用进程之间的逻辑通信机制,而网络层提供了Host之间的逻辑通信机制。传输层位于网络层之上,依赖于网络层服务的同时又对网络层服务进行可能的增强。
  segment的首部封装了特殊的字段,分别是源端口号与目的端口号。其中,无连接的UDP仅标识目的IP与目的端口号,而面向连接的TCP标识源IP、源端口号、目的IP、目的端口号。
  传输层协议包括可靠、按序交付的服务TCP与不可靠的交付服务UDP,两者均不保证延迟与带宽。

1.2 多路复用与多路分用
  一个进程有一个或多个套接字,其相当于从网络向进程传递数据和从进程像网络传递数据的门户。因此,在接受主机中的传输层实际上并没有直接将数据交付给进程,而是交给了一个中间的套接字。而为了将segment定向到适当的套接字,segment具有几个字段,在接收端识别处套接字,交付到正确的套接字的工作称为多路分用;而在源Host封装首部信息生成segment并传递到网络层的工作称为多路复用


二、UDP

2.1 UDP报文段格式
  用户数据报协议【User Datagram Protocal,UDP】是基于IP协议的提供多路复用/分用服务与简单错误校验的无连接的传输层协议。其提供尽力而为的交付服务,其segment可能丢失,且不保证按序到达。
  其报文段的通用格式为
在这里插入图片描述

其中,UDP报文段的长度指示了首部与应用数据的总字节数。

2.2 UDP校验和
  UDP校验和提供了差错检测功能。其将segment的内容解释为16bit的整数,并将校验和字段置0,在发送方计算所有16bit整数的和,并进行反码运算,并且在求和时遇到的溢出回卷,即最高位的溢出将进位到最低位。此时得到的结果被放入UDP报文段中的校验和字段。而在接收方,其计算16bit整数的和与校验和的和。如果该分组中没有引入差错,那么和将为0xFFFF。
  UDP在端到端基础上在传输层提供了差错检测,这是一个系统设计中被称颂的端到端原则,其表述为因为某种功能必须基于端到端实现:与在较高级别提供这些功能的代价相比,在较低级别上设置的功能可能是冗余的或几乎没有价值的。


三、可靠数据传输原理
  借助不可靠信道,传输的bit不会损坏、丢失、乱序,这种服务抽象为可靠数据传输【Reliable Data Transform,Rdt】。

3.1 Rdt1:可靠信道上的可靠数据传输
  当底层信道完全可靠,即不会发生bit的损坏与丢失,称该协议为rdt1.0,该协议本身是简单的。

  rdt1.0发送端可能的状态为:
  0.等待上层调用:
    0.0.在上层调用时发送数据,在发送结束后转化为0。

  rdt1.0接收端可能的状态为:
  0.等待下层调用:
    0.0.在下层调用时接收数据,在接受结束后转化为0。

3.2 Rdt2:经具有比特差错信道的可靠数据传输
  底层信道更为实际的模型是分组中的比特可能受损的模型。当segment受损时,使用肯定确认【Acknowledgements,ACK】与否定确认【Negtive Acknowledgements,NAK】使得接收方可以让发送方直到哪些内容被正确接收,那些内容接收有误并因此需要重复。在计算机网络环境中,基于这样重传机制的可靠数据传输协议称为自动重传请求【Automatic Repeat reQuest,ARQ】协议。
  rdt2.0采用ARQ协议,引入了差错检测、反馈与重传的机制。

  rdt2.0发送端可能的状态为:
  0.等待上层调用:
    0.0.在上层调用时发送数据,在发送结束后转化为1。
  1.等待ACK或NAK反馈:
    1.0.收到ACK,转化为0;
    1.1.收到NAK,重传segment,转化为1。

  rdt2.0接收端可能的状态为:
  0.等待下层调用:
    0.0.在下层调用时接收数据,校验和正确,发送ACK,转化为0;
    0.1.在下层调用时接收数据,校验和错误,发送NAK,转化为0。

  由于发送方将等待接收方已正确接收当前分组后才会继续发送新数据,具有类似行为的协议称为停等协议。

  然而,要考虑到一个致命的缺陷,即ACK与NAK分组受损的可能性。解决这个问题的一个简单方法是为每个分组增加序列号。在ACK与NAK分组受损时,直接重传当前数据分组,但可能会引入冗余分组,rdt2.1通过序列号便可以识别出重复分组并丢弃。

  rdt2.1发送端可能的状态为:
  0.等待上层调用0:
    0.0.在上层调用时发送数据0,在发送结束后转化为2。
  1.等待上层调用1:
    1.0.在上层调用时发送数据1,在发送结束后转化为3。
  2.等待ACK或NAK反馈0:
    2.0.收到ACK0,转化为1;
    2.1.收到NAK0,重传segment,转化为2。
  3.等待ACK或NAK反馈1:
    3.0.收到ACK1,转化为0;
    3.1.收到NAK1,重传segment,转化为3。

  rdt2.1接收端可能的状态为:
  0.等待下层调用0:
    0.0.在下层调用时接收数据0,校验和正确,发送ACK,转化为1;
    0.1.在下层调用时接收数据0,校验和错误,发送NAK,转化为0;
    0.2.在下层调用时接收数据1,丢弃,转化为0。
  1.等待下层调用1:
    1.0.在下层调用时接收数据1,校验和正确,发送ACK,转化为0;
    1.1.在下层调用时接收数据1,校验和错误,发送NAK,转化为1;
    1.2.在下层调用时接收数据0,丢弃,转化为1。

  实际上,不使用ACK依然能达到以上效果,只需要在ACK中显式的加入被确认分组的序列号。rdt2.2通过重复ACK便可以识别出重复分组并丢弃。

  rdt2.2发送端可能的状态为:
  0.等待上层调用0:
    0.0.在上层调用时发送数据0,在发送结束后转化为2。
  1.等待上层调用1:
    1.0.在上层调用时发送数据1,在发送结束后转化为3。
  2.等待ACK0反馈:
    2.0.收到ACK0,转化为1;
    2.1.收到ACK1,重传segment,转化为2。
  3.等待ACK1反馈:
    3.0.收到ACK1,转化为0;
    3.1.收到ACK0,重传segment,转化为3。

  rdt2.2接收端可能的状态为:
  0.等待下层调用0:
    0.0.在下层调用时接收数据0,校验和正确,发送ACK0,转化为1;
    0.1.在下层调用时接收数据0,校验和错误,发送ACK1,转化为0;
    0.2.在下层调用时接收数据1,丢弃,转化为0。
  1.等待下层调用1:
    1.0.在下层调用时接收数据1,校验和正确,发送ACK1,转化为0;
    1.1.在下层调用时接收数据1,校验和错误,发送ACK0,转化为1;
    1.2.在下层调用时接收数据0,丢弃,转化为1。

3.3 Rdt3:经具有比特差错的丢包信道的可靠数据传输
  现在假定除了比特受损外,底层信道还会丢包,这在如今的计算机网络中并不罕见。在丢包可能发生时,需要发送方等待一定的时间后就进行重传,需要定时器,同时也会引入冗余分组的情况,不过rdt2.2已经解决了冗余分组问题,而rdt3.0通过引入计时器解决丢包问题。

  rdt3.0发送端可能的状态为:
  0.等待上层调用0:
    0.0.在上层调用时发送数据0,在发送结束后设定计时器,并转化为2。
  1.等待上层调用1:
    1.0.在上层调用时发送数据1,在发送结束后设定计时器,并转化为3。
  2.等待ACK0反馈:
    2.0.收到ACK0,转化为1;
    2.1.收到ACK1,重传segment,转化为2;
    2.2.定时器超时,重传segment后设定计时器,并转化为2。
  3.等待ACK1反馈:
    3.0.收到ACK1,转化为0;
    3.1.收到ACK0,重传segment,转化为3。
    3.2.定时器超时,重传segment后设定计时器,并转化为3。

  rdt3.0接收端可能的状态为:
  0.等待下层调用0:
    0.0.在下层调用时接收数据0,校验和正确,发送ACK0,转化为1;
    0.1.在下层调用时接收数据0,校验和错误,发送ACK1,转化为0;
    0.2.在下层调用时接收数据1,丢弃,转化为0。
  1.等待下层调用1:
    1.0.在下层调用时接收数据1,校验和正确,发送ACK1,转化为0;
    1.1.在下层调用时接收数据1,校验和错误,发送ACK0,转化为1;
    1.2.在下层调用时接收数据0,丢弃,转化为1。

  rdt3.0由于分组序号在0和1之间交替,因此有时也称为比特交替协议

3.4 流水线可靠数据传输协议
  rdt3.0是一个功能正确的协议,但是其性能难以令人满意,其性能问题的核心在于它是一个停等协议。
  考虑两个端系统之间的光速往返传播时延 R T T = 30 m s RTT=30ms RTT=30ms,彼此通过一条发送速率 R = 1 G b p s R = 1Gbps R=1Gbps的信道相连。包括首部字段和数据的分组长 L = 1000 B L = 1000B L=1000B,那么发送一个分组进入信道所需的时间 T t r a n s = L / R = 8 u s T_{trans} = L/R = 8us Ttrans=L/R=8us由于是停等协议,在接收端接收到数据,并相应ACK的过程经过了RTT的时间,发送端方可发送下一分组,此时时间的利用率为 L / R R T T + L / R = 0.00027 \frac{L / R}{RTT + L / R} = 0.00027 RTT+L/RL/R=0.00027即发送端仅有万分之2.7的时间在忙,且吞吐率 L R T T + L / R = 267 k b p s \frac{L}{RTT + L / R} = 267kbps RTT+L/RL=267kbps即使1Gbps的信道也是如此。
  有效的方法是不以停等方式运行,而允许发送方发送多个分组并无须等待确认,该技术称为流水线。流水线可靠数据传输协议也带来了如下影响:
  -必须增加序号范围;
  -协议的两端不得不缓存更多的分组,防止乱序;
  -需要制定处理丢失、损坏和延时过大的分组的策略。

3.5 GBN
  回退N步【Go Back N】协议采用流水线可靠数据传输,但其限制了流水线中未确认的分组数不能超过某个最大允许数 N N N,其被称为窗口长度,故GBN协议也被称为滑动窗口协议
  GBN的ACK是一种累加机制,ACKn确认了到序列号n(包括n)为止的分组均已被正确接收。
  GBN为发送未确认的分组设置计时器,当第n分组的计时器超时时,将重传所有序列号不小于n也未ACK的分组。

  GBN发送端可能的状态为:
  0.等待事件:
    0.0.在上层调用时,窗口不满,发送segment n,在发送结束后设定计时器n,并转化为0;
    0.1.定时器n超时,重传序列号不小于n的segment后设定计时器n,并转化为0;
    0.2.收到ACKn,窗口前移,并转化为0;
    0.3.重复收到ACKn,转化为0。

  GBN接收端可能的状态为:
  0.等待事件:
    0.0.在下层调用时,收到的数据正确且为期望的序列号n,发送ACKn,并转化为0;
    0.1.在下层调用时,收到的数据错误或不为期望的序列号,丢弃,发送目前为止已经正确接收的序列号n相应的ACKn,并转化为0。

  考虑GBN传输的数据序列号位数为 n n n,那么其窗口长度的最大值为 2 n − 1 2^n - 1 2n1。考虑序列号为 0 , 1 , . . . , 2 n 0, 1, ..., 2^n 0,1,...,2n的数据通过窗口长度为 N = 2 n N = 2^n N=2n发送,接收端接受了序列号0的数据,发送了ACK0,那么就有:
  -ACK0丢失,发送端重传数据0;
  -ACK0正确到达,发送端窗口移动,发送下一数据0。
这种情况下,接收端并不能判断再次收到的数据0是发送端的重传还是下一序列的数据。

3.6 SR
  GBN依然存在着性能问题,单个分组的差错就能引起GBN重传大量分组。选择重传【Selective Repeat,SR】协议通过让发送方仅重传那些可能出错的分组而避免不必要的重传。SR协议在发送端与接收端设立了不同步的同等窗口长度,用于限制已经发送而未确认的分组。
  由于GBN对乱序到达的分组直接丢弃,而SR设立缓存机制,缓存乱序到达的接收端窗口内的分组;GBN超时的完全重传,SR则为每个分组设置定时器,并单独重传超时的分组。

  SR发送端可能的状态为:
  0.等待事件:
    0.0.在上层调用时,窗口不满,发送segment n,在发送结束后设定计时器n,并转化为0;
    0.1.定时器n超时,重传segment n后设定计时器n,并转化为0;
    0.2.收到ACKn,尝试窗口前移,并转化为0。

  SR接收端可能的状态为:
  0.等待事件:
    0.0.在下层调用时,收到的数据正确且落在窗口内,则缓存或交付,发送ACKn,并转化为0;
    0.1.在下层调用时,收到的数据错误或不落在窗口内,丢弃,并转化为0。

  SR具有一个问题,即当窗口过大而序列号过少时,可能会发生序列号冲突的问题。考虑发送端发送了数据0,接收端收到数据0并反馈ACK0,同时窗口前移,并在经过了一定次传输后到达了下一轮次的序列号0;而此时,ACK0丢失,发送端定时器0超时,重传了数据0,导致接收端在本轮次的序号0位置接收了上一轮次中序号为0的数据。因此,要求发送端的窗口大小 N S N_S NS与接收端的窗口大小 N R N_R NR,以及序号空间 0 , 1 , . . . k {0, 1, ...k} 0,1,...k满足 N S + N R ≤ 2 k N_S + N_R \le 2^k NS+NR2k同时,接收窗口一般不大于发送窗口,实际上一般情况下,两个窗口的尺寸是相同的,因此有 N R ≤ 2 k − 1 N_R \le 2^{k-1} NR2k1


四、TCP
4.1 TCP报文段格式
  传输控制协议【Transmission Control Protocol,TCP】是面向连接的,在进程传输数据之前,两个进程需要互相发送预备报文段,以建立确保数据传输的参数。
  TCP连接提供全双工服务,且要求点对点连接,即仅允许单个发送方与单个接收方之间连接,并且可以同时互相发
  TCP报文段的通用格式为
在这里插入图片描述
  序号【sequence number】用于实现可靠数据传输,其在TCP连接建立时被随机选择。TCP将数据解释称无结构的、有序的字节流,一个报文段的序号建立在传送的字节流上,是该报文段首字节的字节流编号。假设一个报文大小为500K,而最大报文段长度【Maximum Segment Size,MSS】为1K,那么若首个报文段的序号为0,则第二个报文段的序号为1000。
  确认号【acknowledgement number】用于实现可靠数据传输,其使用累积确认的机制,接收方会在该字段添加期望得到的字节序号。TCP规范中没有规定乱序到达的segment的处理机制,而由实现TCP的编程人员去处理。

4.2 TCP可靠数据传输
  TCP在IP不可靠的尽力而为服务之上创建了一种可靠数据传输服务,包含了流水线机制、累积确认机制与单一重传定时器机制。

  首先,一个实际的问题是如何设置定时器的超时时间。定时器的超时时间应该大于RTT,但是RTT是实时变化的,如果设定过短会引起不必要的重传,而过长则会导致对segment的丢失响应过慢。
  考虑某个时刻从某报文段被发出到不重传情况下,对该报文段的确认被收到之间的测量的样本RTT,记为SampleRTT。由于SampleRTT值会实时波动,任何给定的SampleRTT值也许都是非典型的。因此,TCP维持一个SampleRTT均值,记为EstimatedRTT,在SampleRTT更新时随之更新,形如 E s t i m a t e d R T T = ( 1 − α ) E s t i m a t e d R T T + α S a m p l e R T T EstimatedRTT = (1 - \alpha)EstimatedRTT + \alpha SampleRTT EstimatedRTT=(1α)EstimatedRTT+αSampleRTT典型的, α = 0.125 \alpha = 0.125 α=0.125。从统计学观点讲,这种平均被称为指数加权移动平均
  除了估算RTT之外,测量RTT的变化也是有价值的,用于获得较大的安全边界。使用RTT偏差DevRTT估算SampleRTT一般会偏离EstimatedRTT的程度,形如 D e v R T T = ( 1 − β ) D e v R T T + β ∣ S a m p l e R T T − E s t i m a t e d R T T ∣ DevRTT = (1 - \beta)DevRTT + \beta|SampleRTT - EstimatedRTT| DevRTT=(1β)DevRTT+βSampleRTTEstimatedRTT典型的, β = 0.25 \beta = 0.25 β=0.25
  TCP考虑了RTT均值与波动的情况,给出了超时界限 T i m e o u t I n t e r v a l = E s t i m a t e d R T T + 4 D e v R T T TimeoutInterval = EstimatedRTT + 4DevRTT TimeoutInterval=EstimatedRTT+4DevRTT推荐的初始值为1秒,并在出现超时后加倍,直到收到报文段更新。

  当超时出发重传依然导致了时延过长时,使用快速重传机制。发送方在等待ACK的同时会检测再次确认某个报文的ACK,因为一个报文段的丢失在流水线的情况下会导致许多一个接一个的冗余ACK。如果TCP发送方接收到相同数据的3个冗余ACK,就将其当作一种指示,执行快速重传,即在定时器超时之前重传丢失的报文段。

4.3 TCP流量控制
  TCP为应用程序提供了流量控制服务,以消除发送方使接收方缓存溢出的可能性。
  TCP为发送方维护一个接收窗口【receive window】,其指示了接收方可用的缓存空间。考虑接收端为一个TCP连接分配了一个接收缓存,其大小为RcvBuffer;而接收端从缓存读出的数据流的最后一个字节的编号为LastByteRead;进入接收缓存的数据流的最后一个字节的编号为LastByteRcvd。由于TCP不允许已分配的缓存溢出,故要求 L a s t B y t e R c v d − L a s t B y t e R e a d ≤ R c v B u f f e r LastByteRcvd - LastByteRead \le RcvBuffer LastByteRcvdLastByteReadRcvBuffer那么可接收窗口大小为 r w n d = R c v B u f f e r − [ L a s t B y t e R c v d − L a s t B y t e R e a d ] rwnd = RcvBuffer - [LastByteRcvd - LastByteRead] rwnd=RcvBuffer[LastByteRcvdLastByteRead]发送端则需要保证 L a s t B y t e S e n t − L a s t B y t e A C K ≤ r w n d LastByteSent - LastByteACK \le rwnd LastByteSentLastByteACKrwnd即发送还未确认的字节数不得超过接收窗口。
  要注意的是,在 r w n d = 0 rwnd = 0 rwnd=0的情况下,发送端与接收端仍需要保持通信,否则发送端将不会再得到继续传输数据的指示。为此,在这种情况下,发送端与接收端会持续进行请求与响应,直到缓存出现空间,接收端响应报文中的rwnd值非0,而开始重新传输。

4.4 TCP连接管理
  当一个客户进程想和一个服务进程建立TCP连接时,TCP会用以下方式建立连接:
  -客户端向服务端发送特殊的TCP报文段,其不包括有效载荷,但同步序列号【Synchronize Sequence Numbers,SYN】标志位置1,故称SYN报文段。此外,客户端随机的选择初始序号client_isn,置于SYN报文段的序号字段;
  -服务器端接收SYN报文段,为该TCP连接分配TCP缓存和变量,并响应允许连接的报文段,称为SYNACK报文段。该报文段中SYN标志位置1,确认号字段为client_isn+1,并且服务器选择初始序号server_isn,置于SYNACK报文段的序号字段;
  -客户端接收SYNACK报文段,客户为该TCP连接分配TCP缓存和变量,对允许连接进行确认。该报文段中SYN标志位置0,序列号字段client_isn+1,确认号server_isn+1,并可以携带有效载荷。
TCP连接使用结束后,参与TCP连接的两个进程中的任意一方都能终止该连接。考虑客户关闭连接:
  -客户端向服务端发送特殊的TCP报文段,该报文段的结束【Finish,FIN】标志位置1;
  -服务器端接收FIN报文段,发送ACK报文,并关闭连接;
  -服务器关闭连接完成,向客户端发送FIN报文段;
  -客户端接收FIN报文段,发送ACK报文,并关闭连接。
此时,两台主机上用于该连接的所有资源都被释放了。

4.5 TCP拥塞控制
  TCP为应用程序提供了拥塞控制服务,以消除IP网络的拥塞。
  在网络拥塞时,分组的到达速率接近链路容量,分组经历巨大的排队时延甚至因为缓存溢出而丢弃;而为了补偿因为缓存溢出而丢失的分组而引发的大量重传;而重传时,每个上游路由器用于转发到丢弃该分组使用的传输容量又被浪费掉了。
  TCP采用让发送方根据所感知到的网络拥塞程度限制其所能向连接方发送流量的速率。运行在发送方的TCP拥塞控制机制跟踪了一个拥塞窗口【congestion window】变量cwnd。其对发送方向网络发送流量的速率进行了限制,即 L a s t B y t e S e n t − L a s t B y t e A C K ≤ m i n { r w n d , c w n d } LastByteSent - LastByteACK \le min\{rwnd, cwnd\} LastByteSentLastByteACKmin{rwnd,cwnd}  TCP的丢包事件定义为超时或收到3个冗余ACK,而当丢包事件发生时,发送方就认为出现了拥塞。

  TCP拥塞控制被称为加性增-乘性减【Additive-Increase, Multiplicative-Decrease,AIMD】方法,其原理为逐渐增加发送速率,谨慎探测可用带宽,直到发生丢包后迅速降低。
  在TCP连接开始时,cwnd的值通常初始置为1MSS的较小值,可用宽带可能远远高于初始速率,使用慢启动【Slow Start,SS】机制,在每次ACK后指数性的快速增加cwnd的值,直到以下情况发生:
  -超时,则设置慢启动阈值 s s t h r e s h = c w n d / 2 c w n d = 1 M S S ssthresh = cwnd/2 \\ cwnd = 1MSS ssthresh=cwnd/2cwnd=1MSS进入指数阶段,重新慢启动;
  -收到冗余ACK,则令 s s t h r e s h = c w n d / 2 c w n d = c w n d / 2 ssthresh = cwnd/2 \\ cwnd = cwnd / 2 ssthresh=cwnd/2cwnd=cwnd/2  -达到ssthresh,cwnd转换为线性增长,在每次ACK后有 c w n d = c w n d + 1 M S S cwnd = cwnd + 1MSS cwnd=cwnd+1MSS进入线性阶段,并持续等待丢包事件的发生。

4.6 TCP性能分析
  不考虑慢启动的情况下,给定cwnd的大小W与RTT,TCP的超时前一时刻的吞吐率为 W / R T T W/RTT W/RTT,而超时后一时刻,cwnd被降低为一半,吞吐率为 W / 2 R T T W/2RTT W/2RTT,那么平均吞吐率大约为 3 W / 4 R T T 3W/4RTT 3W/4RTT
  考虑cwnd从W的一半上升到第一次丢包时,一共发送的分组数为 ∑ i = 0 W / 2 W + i = 3 W 2 / 8 + 3 W / 4 \sum_{i = 0}^{W/2}W + i = 3W^2/8 + 3W/4 i=0W/2W+i=3W2/8+3W/4当W足够大时,共发送了 3 W 2 / 8 3W^2/8 3W2/8个分组的过程中发生了一次丢包。故丢包的概率为 L = 1 / ( 3 W 2 / 8 ) = 8 / ( 3 W 2 ) L = 1/(3W^2/8) = 8/(3W^2) L=1/(3W2/8)=8/(3W2)那么吞吐率为 T h r o u g h p u t = 3 W / 4 R T T = 1.22 / ( R T T ⋅ L 1 / 2 ) Throughput = 3W/4RTT = 1.22/(RTT·L^{1/2}) Throughput=3W/4RTT=1.22/(RTTL1/2)为了得到10Gbps的吞吐量,需要丢包概率仅为 L = 2 ⋅ 1 0 − 10 L = 2·10^{-10} L=21010,即50亿个报文段中仅允许丢失1个。
  多个TCP共享相同的瓶颈宽带时,由于拥塞控制的机制,会使得每个用户的可用吞吐量近似相等。但UDP不使用拥塞保护,不会限制吞吐量,会破坏TCP的公平性;此外,并发的TCP连接同样占用了多个用户的可用吞吐量。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值