TCP协议详解

目录

TCP协议

TCP报文格式

TCP原理

1.确认应答机制(安全机制)

2.超时重传机制(安全机制)

3.连接管理机制(安全机制)

1)三次握手(经典面试题)

2)四次挥手

4.滑动窗口(效率机制)

5.流量控制(安全机制)

6.拥塞控制(安全机制)

7.延时应答(效率机制)

8.捎带机制(效率机制)

9.面向字节流

10.异常情况处理

1).进程崩溃

2).主机关机(正常流程)

3).主机掉电(非正常流程)

4).网线断开

TCP小结


TCP协议

TCP ,即 Transmission Control Protocol ,传输控制协议。人如其名,要对数据的传输进行一个详细的控制。

TCP报文格式

  • /目的端口号:表示数据是从哪个进程来,到哪个进程去;
  • 32位序号/32位确认号:;
  • 4TCP报头长度:表示该TCP头部有多少个32bit(有多少个4字节);所以TCP头部最大长度是15 * 4 = 60
  • 6 位标志位 :
    URG :紧急指针是否有效
    ACK :确认号是否有效
    PSH :提示接收端应用程序立刻从 TCP 缓冲区把数据读走
    RST :对方要求重新建立连接;我们把携带 RST 标识的称为 复位报文段
    SYN :请求建立连接;我们把携带 SYN 标识的称为 同步报文段
    FIN :通知对方,本端要关闭了,我们称携带 FIN 标识的为 结束报文段
  • 16 位窗口大小:
  • 16 位校验和:发送端填充, CRC校验。接收端校验不通过,则认为数据有问题。此处的检验和不光包含 TCP 首部,也包含 TCP 数据部分。
  • 16位紧急指针:标识哪部分数据是紧急数据;
  • 选项:option,可选的,可以有也可以没有,选项也是报头的一部分

    TCP原理

    TCP 对数据传输提供的管控机制,主要体现在两个方面:安全和效率。
    这些机制和多线程的设计原则类似:保证数据传输安全的前提下,尽可能的提高传输效率。
    TCP有四大特性:有连接、可靠传输、面向字节流、全双工
 其中可靠连接是TCP最最核心的特征,也是TCP的初心。当然,可靠传输不是说发送方把数据能够100%的传输给接收方,而是说发送方把数据发送出去之后,能够知道接收方是否收到数据,并且一旦发现对方没有收到,就可以通过一些列的手段来"补救"。那TCP怎样做到可靠连接的呢?主要是依靠几个重要的机制。

1.确认应答机制(安全机制)

什么是确认应答?

发送方把数据发给接收方后,接收方收到数据就会给发送方返回一个应答报文(acknowledge,ack),发送方如果收到了这个应答报文就知道自己的数据是否发送成功了。那确认应答是怎样完成的呢?

TCP将每个字节的数据都进行了编号,即为序列号,序列号的大小关系就描述了数据的先后顺序。
①数据(1~1000):一个TCP数据包里共有1000个字节的载荷数量,其中第一个字节的序号是1,就在TCP报头的序号字段中写"1";由于一共是1000个字节,此时最后一个字节的序号是就1000,但是1000这样的数据并没有在TCP报头中记录,TCP报头中记录的序号是这一次传输的载荷数据中第一个字节的序号,其他字节的序号需要依次推出。
②(确认应答(下一个是1001):在应答报文中,会在确认序号字段中填写1001,因为收到的数据是1-1000,所以1001之前的数据都被B收到了,所以B接下来向A"索要"1001开始的数据了。
通过特殊的ack数据包里面携带的"确认序号"告诉发送方,哪些数据已经被确认收到了,此时发送方就心中有数了,就知道了自己刚刚发的数据是到了还是没到,这就是可靠传输。
TCP的初心就是为了是实现可靠传输,而达成 可靠传输的最核心的机制就是确认应答。
如何区分一个数据包是普通的数据还是ack应答数据呢?
当TCP报文中的标志位ACK为"1"时,表示当前的数据包是一个应答报文,此时该数据包中的"确认序号字段"就能够生效;当标志位ACK为"0"时,表示当前数据包是一个普通报文,此时该数据包中的"确认序号字段"就不生效。

2.超时重传机制(安全机制)

确认应答描述的是一个比较理想的情况,如果网络传输中出现丢包了,发送方就无法收到ack了这种情况该怎么办?遇到这种情况就可以使用超时重传机制,针对确认应答进行补充。

由于丢包是一个"随机"的事件,因此在TCP传输过程中就存在两种情况:

1.传输的数据丢了

2.返回的ack丢了

     站在发送方的角度是无法区分究竟是传输的数据丢了还是返回的ack丢了,所以无论出现哪种情况,发送方都会进行"重新传输"。假设丢包的概率是10%(是一个比较大的数据了),那传输两次都丢包的概率就是10%*10%=1%,所以重传操作大幅度提升了数据能够被传过去的概率,是一个很好的丢包补救措施。
   发送方何时进行重传?这就涉及到等待时间了,发送方发出去数据之后会等待一段时间,如果这个时间内收到接收方发来的ack,此时就自然视为数据已经到达;如果到达这个时间之后数据还没到就会触发重传机制了。 初始的等待时间是可配置的,不同系统上的可能不一样,也可以通过修改一些内核参数来引起这里的时间变化;并且等待时间也是动态变化的,每经历一次超时,下一次的等待的时间就会变长,但也不是无限制的变长,重传若干次时,时间拉长到一定程度,TCP就认为数据再怎么重传也没用了,就会放弃连接了(准确的说是会触发TCP的重置连接操作)。
    如果发生第二种情况,返回的ack丢了,那A就会重传,站在B的视角就收到了两条一样的数据,收到重复数据是否会给程序带来一些bug?很显然,这是有后果的,比如说你玩游戏,花钱买了一个皮肤,但是一直没有显示购买成功,这是你就会以为刚才没有充好,就重新在充值了一次。很明显这样是非常不合理的。那如何解决这种问题呢? TCP已经非常贴心的帮我们把这个问题解决了。
   TCP会有一个"接收缓冲区",就是一个内存空间,会保存当前已经收到的数据以及数据的序号, 如果接收方发现,当前发送方发来的数据是已经在接收缓冲区中存在(利用前面提到的序列号就很容易完成去重) ,接收方就会直接把后来的这个数据丢掉,确保应用程序进行读的时候,读到的只有一条数据。接收缓冲区不仅仅是能去重,还能重新进行排序,确保发送的顺序和应用程序读取的顺序是一致的。

3.连接管理机制(安全机制)

   连接管理分为建立连接和断开连接,也就是我们常说的三次握手和四次挥手,在正常情况下,TCP要经过三次握手建立连接,四次挥手断开连接。

   TCP的握手就是给对方传输一个简短的,没有实际意义的数据包,通过这个数据包来唤起对方的注意,从而触发后续的操作。握手这个操作不是TCP独有的,甚至不是网络通信独有的,计算机中的很多操作都会涉及到"握手"。比如给手机充电时,使用不同的充电头,手机这边显示的充电方式不一样(普通充电/快速充电),这个插的过程就要先"握手"。

1)三次握手(经典面试题)

TCP的三次握手,是在建立连接的过程中,需要通信双方一共"打三次招呼"才能够完成连接的建立。

A想和B建立连接,A就会主动发起握手操作。实际开发中,主动发起的一方就是所谓的"客户端",被动接受的一方就是"服务器"。

syn:同步报文段,就是一个特殊的TCP数据包,不携带任何的业务数据。

此时A和B都已经记录了对方的信息,就握手完成了(构成了逻辑上的连接)。
建立连接的过程其实就是通信双方都要给对方发起syn,也都要给对方反馈ack,一共是4次握手了,但是中间的两次,可以合并成一次,所以才是三次握手。中间的两次是怎样合并的呢?在TCP报文中,只要标志位ack和syn同时为1就可以了。
三次握手是要解决什么问题?通过四次握手是否可行?通过两次握手是否可行?
    TCP的初心是为了是实现可靠传输,进行确认应答和超时重传有个大前提,就是当前的网络环境是可用的,通畅的,如果当前网络已经存在重大故障,此时,可靠传输就无从谈起了。而通过三次握手就可以知道当前的网络是否是通畅可用的,从而实现可靠传输。
    四次握手是可行的,但是没有必要,将两个数据合并成一个数据,效率更高;而两次握手显而易见是不行的。
三次握手的核心作用:
1.投石问路,确认当前的网络是否是通畅的;
2.让发送方和接收方都能确认自己的发送能力和就收能力是否正常;
3.让通信双方在握手的过程中,针对一些重要的参数进行协商.
   握手这里协商的信息是有好几个的,但是这里就不做过多讨论了,至少大家要知道TCP通信过程中的序号从几开始都是双方协商出来的(一般不是从1开始)。
   每次建立连接的时候,都会协商出一个较大的,和上次不太一样的值。这种设定方式是为了避免"前朝的剑,斩本朝的官"。有的时候如果网络状态不太好,客户端与服务器之间的连接可能会断开,再重新建立连接,可能在新的连接建立好之后,旧连接的数据姗姗来迟,这种迟到的数据应该要丢弃的,不应该让上个朝代的数据影响到本朝代的业务逻辑。如何区分数据是否来自于上个朝代?就可以通过上述序列号的设定规则来实现,如果发现收到的数据序号与当前正常数据的序号差异很大,就可以判定是上个朝代的数据,就可以直接丢弃了。

2)四次挥手

建立连接,一般都是由客户端主动发起的;但是断开连接,客户端和服务器都可以主动发起

FIN:结束报文段

当完成四次挥手后,此时A和B的连接就断开了,相当于A和B把对端的信息都删除了。
那这里的四次挥手能否和三次握手一样,把中间的两次交互合二为一?不一定。  
  不能合并的原因是因为ACK和FIN的触发时机是不一样的。ACK是内核响应的,B收到FIN就会立刻返回ACK,而B返回的FIN是应用程序的代码触发,B这边要调用了close方法才会触发FIN,从服务器收到FIN(同时返回ACK),再到执行close发起FIN,这中间要经历多少时间,经历多少代码是不确定的,这得看代码是怎么写的。
    前面的三次握手,ACK和第二个SYN都是内核触发的,是同一个时机,因此可以合并;而这里的四次挥手,ACK是内核触发的,但第二个FIN是应用程序执行close触发的,时机不同,因此不一定能合并。
 但这就是否意味着,如果代码close没写/没执行到,是不是第二个FIN就一直发不出去?
   是有可能的。如果是正常的四次挥手,"好聚好散",正常的流程断开连接;如果是不正常的挥手(没有挥完四次),异常的流程断开连接,这种情况也是存在的。
   为什那么说是不一定呢?因为TCP中还有一个延时应答机制,能够拖延ACK回应时间,一旦ACK滞后了,就有机会和下一个FIN合并在一起了。
在四次挥手过程中,会有几种状态
CLOSED:表示此时连接已经彻底断开,可以释放了
TIME_WAIT:哪一方主动断开连接,哪一方就会进入TIME_WAIT
TIME_WAIT状态存在的意义,就是为了防止最后一个问题ACK丢失
如果最后一个ACK丢了,站在B的角度,B就会触发超时重传,重新把刚才的FIN传一遍,如果A没有TMIE_WAIT状态进行等待,就意味着A这个时候就真正的释放连接了,此时重传的FIN就没办法处理,就返回不了ACK,B就永远也收不到ACK了。
  A这边使用TIME_WAIT等待的这个时间就是为了处理B重传的FIN,此时有重传的FIN来了就可以继返回ACK了,B这边的重传才有意义。
那TMIE_WAIT会等多久呢?假设网络上两个节点通信消耗的最大时间是MSL,此时TIME_WAIT的时间就是2MSL(这个时间已经是上限了,绝大部分数据包不会达到这个时间)

4.滑动窗口(效率机制)

前面三个机制都是在保证TCP的可靠性,但是TCP的可靠传输是会影响传输的效率的(多出了等待ack的时间,单位时间内传输的数据就少了),而滑动窗口就可以让可靠传输对性能的影响更少一些。TCP只要引入了可靠性,传输效率就不可能超过没有可靠性的UDP,TCP这里的"效率机制是为了让影响更小,缩短和UDP的差距。
如果是每一次收到一个应答报文再发送下一个数据,这个过程等待之间是比较长的。
如果是批量传输数据,不用等ack回来就直接发送下一个数据,批量传输也不是无限制的传输,是存在一定上限的,达到上限之后再统一等待ack;不等待的情况下,批量最多发送多少数据,这个数据量就是"窗口大小"。
滑动窗口是怎样运行的呢?
举个栗子:
当前A向B批量发送了4份数据,此时B也要向A回应4组ack;此时A已经达到窗口大小,在收到ack之前,不能再继续向B发送数据了,需要等待有ack回来了之后才能继续发送。这里是怎么继续发送的?是等待4个ack都回来了再继续发送4份,还是回来一个ack就发送一个数据?答案是后者。这样看起来的直观效果就像是这个"窗口"在往后"滑动"。
窗口越大,等待的ack就越多,传输的效率也就越高。
TCP的初心是"可靠传输",上述滑动窗口中,确认应答是可以正常工作的,但是,如果出现了丢包怎么办?这里依旧是重传操作。但是这里的重传与前面的超时重传又有所不同。
这里的丢包分为两种情况:
1)ack丢了
这种情况不需要任何重传,因为ack中含有确认序号,表示的含义是,当前序号之前的数据,已经确认收到了,下一个你该从确认序号这里继续发送。比如说,如果1001这个ack丢了,但是2001ack到了,即是2001之前的数据都确认传输成功了,涵盖了1001的情况。
2)数据包丢了
如果是数据包丢了,那么A需要知道是哪个数据丢了,而B也得告诉A是哪个数据丢了。
由于前面的1001-2000这个数据没了,此处返回的ack仍然是索要1001,无论当前传输的数据具体是几,ack都在索要1001这个数据,此时A看到B这边连续几个ack都在索要1001,A就知道1001这个数据是丢了,就会重传1001这个数据。而1001-2000重传顺利到达B之后,B索要的就是7001。
上述重传过程中,并没有额外的冗余操作,哪个数据丢就重传哪个,没丢的数据就不需要重传,整个过程都是比较快速的,这就是快速重传,是滑动窗口下超时重传的变种。
也许会有小伙伴想问:如果ack全部都丢了呢?平时丢包率达到10%就已经是非常高的了,丢包率直接100%,此时相当于网线都断了,就无从谈起可靠传输了。
如果通信双方传输的数据量比较小,也不频繁,就仍然是普通的确认应答和普通的超时重传;如果通信双方传输的数据量比较大,也比较频繁,就会进入到滑动窗口模式,按照快速重传的方式进行处理。
通过滑动窗口的方式传输数据,效率会提高;滑动窗口越大,传输效率就越高,那滑动窗口设置的越大就越好吗?答案是否定的。如果传输的速度太快,可能会使接收方处理不过来,此时,接收方就会出现丢包,发送方就得重新传输......TCP前提是可靠性,可靠的基础上再提高传输效率。

5.流量控制(安全机制)

接收端处理数据的速度是有限的。如果发送端发的太快,导致接收端的缓冲区被打满,这个时候如果发送端继续发送,就会造成丢包,继而引起丢包重传等等一系列连锁反应。 因此 TCP 支持根据接收端的处理能力,来决定发送端的发送速度。这个机制就叫做 流量控制( Flow Control)
简单地说,就是站在接收方的角度,反向制约发送方的传输速率,即是发送方发送的速率,不应该超过接收方的处理能力。
A向B发送数据,数据会先到达B的接收缓冲区,B这边的应用程序就会调用read这样的方法,把数据从接收缓冲区中读取出来,进行进一步的处理(数据一旦被read了,就会从接受缓冲区中被删除)。可以把这个看成生产者消费者模型,生产者是A,消费者是B的应用程序,交易场所就是B的接收缓冲区,消费速度就是所谓的"处理能力"。怎样量化衡量处理能力呢?直接通过接收方缓冲区的剩余空间大小,作为衡量处理能力的指标。剩余空间越大,意味着消费速度越快,处理能力就越强;剩余空间越小,就意味着消费速度越慢,处理能力越弱。
接收方每次收到数据之后,都会把接收缓冲区剩余空间大小通过ack返回给发送方,发送方就会按照这个数值来调整下一次的发送速度。
举个栗子:

接收端如何把窗口大小告诉发送端呢?回忆我们的TCP首部中,有一个16位窗口字段,就是存放了窗口大小信息。

那么问题来了,16位数字最大表示65535,那么TCP窗口最大就是65535字节(64kb)么?其实不是的,TCP报头中,选项部分里有一项叫做"窗口扩展因子",通过扩展因子,就可以让窗口大小表示一个更大的值。

6.拥塞控制(安全机制)

流量控制,考虑的是接收方的处理能力,而拥塞控制考虑的不仅仅是接收方,还有通信过程中中间节点的情况。

A发送数据到B,这中间的的转发过程中,任何一个节点处理能力达到上限,都可能对发送方产生影响,甚至可能会影响到可靠传输。

由于中间节点不像接收方一样很容易对它的处理能力进行量化,中间节点结构更复杂,难以直接进行量化,因此就可以使用"实验"的方式来找到合适值。

首先,让A先按照一个比较低的速度发送数据,如果数据传输的非常顺利,没有丢包,就尝试使用更大的窗口,更高的速度进行发送;随着窗口不断增大,达到一定程度,可能中间的节点就会出现丢包,发送方如果发现丢包了,就把窗口大小调小,此时如果发现还是继续丢包,就继续调小;如果不丢包了,就继续尝试增大。在这个过程中,发送方不断调整窗口的大小,逐渐达成"动态平衡"。

这种做法,就相当于把中间节点看成一个整体,通过"实验"的方式,来找到中间节点的瓶颈在哪里。整个"实验"的过曾,可以参考下面这个图。

流量控制和拥塞控制都是在限制发送方的窗口大小,最终实际发送的窗口大小,是取流量控制和拥塞控制中窗口的较小值。

7.延时应答(效率机制)

正常情况下,A把数据传给B,B会立即但会ack给A;但也有时候,A传给B,B等一会再返回ack给A,这就是延时应答。

延时应答的本质也是为了提升传输效率。延时返回ack,给接收方更多的时间来读取接收缓冲区的数据,此时接收方读取数据之后,缓冲区的剩余空间就变大了,这样返回的窗口就变大了。

比如说,初始情况下,接收缓冲区的剩余空间是10kb,如果立即返回ack,那么返回的就是10kb这么大的窗口;如果延时200ms再返回,在这200ms的过程中,接受方的应用程序又读了2kb,此时返回ack,就可以返回12kb的窗口了。而发送方窗口的大小,就是传输效率的关键。窗口越大,传输效率就越高。我们的目的是在保证网络不拥塞的情况下尽量提高传输效率; 那么所有的包都可以延迟应答么?肯定也不是。有两点限制:

1)数量限制:每隔N个包就应答一次;   2) 时间限制:超过最大延迟时间有应答一次;

具体的数量和超时时间,依操作系统不同也有差异;一般 N 2 ,超时时间取 200ms。

8.捎带机制(效率机制)

捎带机制就是在延时应答的基础上,进一步提高传输效率。

网络通信中,通常是这种"一问一答"的通信模型。

这里的request和response跟之前的FIN不一样,这里是业务上的数据。这里的ack也是内核立即返回的,但response是由应用程序代码来返回的,这两者时机是不同的。但是由于TCP引入了延时应答,ack不一定是立即返回的,可能需要等一会,在等一会的过程中,B就正好把response给计算好了.计算好了之后就会返回response,此时,就可以顺便把刚才要返回的ack捎带上一起返回。所以ack和response这两个数据就合并成一个数据了。

本来是要传输两个TCP数据包(封装分成两次发送),通过上述操作就可以把两个包合并成一个,此时就可以得到更高的传输效率了。

9.面向字节流

与UDP面向数据报的通信方式不同,TCP是面向字节流的。面向字节流就会有一个最重要的问题

—粘包问题,粘包问题不是TCP独有的,面向字节流的机制都有类似的情况。而此处的"包"是应用层数据包,如果同时有多个应用层数据包被传输过去,此时就会出现粘包问题。

什么是粘包问题呢?就是两个(多个)数据包粘在一起,没办法区分哪些数据是A数据包的内容,哪些数据是B数据包的内容。为什么会出现粘包这样的情况呢?这就跟接收缓冲区的读取形式有关。

举个简单的栗子:

A向B发送三个数据包,数据分别是aaa,bbb,ccc;这些数据会被放在B的接收缓冲区,再由B使用read之类的方法读取接收缓冲区的数据。但是数据缓冲区的数据是这样存放的:

所以接收缓冲区中,这三个应用层数据包的数据,就是以字节的形式紧紧挨在一起的,而B读取数据的方式是字节流,它可以一次读一个字节,也可以读两个字节,也可以读N个字节......但是最终的目标是为了得到完整的应用层数据包,而B此时根本不知道缓冲区里的数据从哪里到哪里是一个完整的应用层数据包了。

那么该如何解决粘包问题呢?

解决这个问题的核心思路就是:通过定义好的应用层协议,明确应用层数据包之间的边界。基于这个思路,有两种目前常用的方法:

1)引用分隔符

2)引用长度

举个简单的例子说明一下这两个方法是如何应用的:

1)用\n作为分隔符:

A向B传输的数据在B的接收缓冲区中的存放形式如下图:

这个时候B读取数据的时候,就可以一直读取数据,直到读到\n为止。

2)引入长度

A向B传输数据时就将数据的长度一并传输给B

那数据在B接收缓冲区的存放形式就如下图:

这样的话,B读取数据的时候,就可以先读两个字节,得到数据包的长度,再根据这个长度继续读取对应字节的个数。

10.异常情况处理

在使用TCP的过程中也是会出现一些异常情况的,常见的异常情况主要有以下四个:

1)进程崩溃;  2)主机关机(正常流程);  3)主机掉电(非正常流程);   4)网线断开

那我们遇到这些处理时TCP会如何去应对呢?

1).进程崩溃

进程崩溃,就是进程异常终止,这个进程就没有了,那进程描述符也就会释放,此时相当于调用close方法。此时就会触发FIN,对方收到后自然就会返回FIN和ACK。此时就是正常的四次挥手断开连接的流程。所以从这里我们也可以得到一个结论:TCP的连接是可以独立于进程存在,也就是说,如果进程没有了,但TCP的连接不一定就没有了。

2).主机关机(正常流程)

当我们再进行关机的时候,就会先触发强制终止进程操作(相当于上述情况1)。此时就会触发FIN,自然会返回FIN和ACK。但此时不仅仅是进程没了,可能整个系统都关闭了,如果在系统关闭之前,对端返回了FIN和ACK,此时系统还是可以返回ACK,进行正常的四次挥手; 但如果对端返回FIN和ACK时系统已经关闭了,那就无法进行后续的ACK响应。站在对端的角度,对端就以为是自己的FIN丢包了,就会重传FIN,重传几次都没有响应,就会放弃连接,即把对端的信息删除掉。

3).主机掉电(非正常流程)

当主机突然一下就掉电了,此时就是一瞬间的事情,系统根本来不及杀进程,也来不及发送FIN主机就直接停机了。站在对端的角度,对端就不一定知道这个事情,那怎么办?会分为两种情况分别应对:

1)如果对端是在发送数据(接收方掉电),发送的数据就会一直等待ack,就会触发超时重传,然后触发TCP连接重置功能,发起"复位报文段",如果复位报文段(TCP报文六位标志位中的"RST")发出去后还是没有效果,此时就会释放连接。

2)如果对端是在接收数据(发送方掉电),对端还在等待数据到达...但是等了半天也没有消息,此时接收方无法区分时对端没发消息还是挂了。TCP中提供了心跳包机制,就是接收方会周期性的给发送方发起一个特殊的、不携带业务数据的数据包,并期待对方返回一个应答;如果对方没有应答,并且重复多次没有应答,此时接收方就会认为对端挂了,就会单方面的释放连接。

4).网线断开

网线断开其实和刚才的主机掉电情况非常类似。

假设A正在给B发送数据,突然网线断开,A就相当于就会触发超值重传->连接重置->单方面释放连接;

B就会触发心跳包->发现对端一直没有响应->单方面释放连接。

TCP小结

与UDP相比,TCP为什么会这么复杂? 那是因为TCP又要保证了可靠性,同时又尽可能的提高性能。可靠性:

  • 校验和
  • 序列号(按序到达)
  • 确认应答
  • 超时重发
  • 连接管理
  • 流量控制
  • 拥塞控制
提高性能:
  • 滑动窗口
  • 快速重传
  • 延迟应答
  • 捎带应答

以上就是这次介绍的全部内容啦,主要介绍了TCP中十个比较重要的机制,这并不是就说TCP只有这是个机制,还有其他很多机制,只是这里讲述了会比较常用/常见的几种机制,小伙伴们有兴趣的话可以多去了解了解~

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值