《计算机网络》 读书笔记(四) 运输层

29 篇文章 0 订阅
12 篇文章 12 订阅


1. 运输层

          运输层是整个网络体系结构中的关键层次。运输层中最重要的协议包括: UDPTCP。TCP比UDP复杂的多,它是面对连接的可靠服务,包括了流量控制、拥塞控制等一些特殊机制。


1.1 运输层协议概述

1.1.1 进程之间的通信

          从通信和信息处理的角度看, 运输层向它上面的应用层提供通信服务,它属于面向通信部分的最高层,同时也是用户功能中的最低层。当网络的边缘部分中的两个主机使用网络的核心部分的功能进行端到端的通信时,只有主机的协议栈才有运输层,而网络核心部分中的路由器在转发分组时都只用到下三层的功能。
          从IP层来说,通信的两端是两个主机。IP数据报的首部明确标志了这两个主机的IP地址。但"两个主机之间的通信"这种说法还不够清楚。这是因为,真正进行通信的实体是主机中的进程,是这个主机中的一个进程和另一个主机中的一个进程在交换数据。因此严格来说,两个主机进行通信就是两个主机中的 应用进程互相通信。IP协议虽然能把分组送到目的主机,但是这个分组还停留在主机的网络层而没有交付给主机中的应用进程。从运输层看, 通信的真正端点并不是主机而是主机中的进程。也就是说, 端到端的通信是应用进程之间的通讯。
          运输层有一个很重要的功能-- 复用(multiplexing)和分用(demultiplexing)。这里的"复用"是指在发送方的不同的应用进程都可以使用同一个运输层协议传送数据(当然要加上适当的首部),而"分用"是指接收方的运输层在剥去报文的首部后能够把这些数据正确的交付到目的应用进程。
          从这里可以看出网络层和运输层之间有明显的区别。网络层是为主机之间提供逻辑通信,而运输层为应用进程之间提供端到端的逻辑通信。
          运输层会对收到的报文进行差错检测。而在网络层,IP数据报首部中的检验和字段,只检验首部是否出现差错而不检查数据部分。
          根据应用程序的不同需求,运输层需要有两种不同的运输协议,即 面向连接的TCP和无连接的UDP
           运输层向高层用户屏蔽了下面网络核心的细节(如网络拓扑、所采用的路由选择协议等), 它使应用进程看见的就是好像在两个运输层实体之间有一条端到端的逻辑通信信道,但这条逻辑通信信道对上层的表现却因运输层使用的不同协议而有很大的差别。当运输层 采用面对连接的TCP协议时,尽管下面的网络是不可靠的(只提供尽最大努力的服务),但这种逻辑通信信道就相当于 一条全双工的可靠信道。但当运输层采用 无连接的UDP协议时,这种逻辑通信信道仍然是一条 不可靠信道

1.1.2 运输层协议分类

          运输层协议包括两种,分别是:
           用户数据报协议UDP(user datagram protocol)
           传输控制协议TCP(transmission control protocol)
          两个对应的报文分别被称为TCP报文段(segment)和UDP用户数据报。


           UDP在传输数据之前,不需要先建立连接。远地主机的运输层在收到UDP报文后,不需要给出任何确认。虽然UDP不是可靠交付,但在某些情况下UDP却是最有效的工作方式。
           TCP则提供面对连接的服务。在传送数据前,必须首先建立起连接,数据传送结束后要释放连接。TCP不提供广播和多播服务。由于TCP提供可靠的、面对连接的运输服务,因此不可避免地增加了许多的开销,如确认、流量控制、计数器以及连接管理等。着不仅使协议数据单元的首部增大很多,还要占用许多的处理机资源。
          下图给出了一些应用和应用层协议主要使用的运输层协议(UDP和TCP)。



1.1.3 运输层的端口

          前面提到运输层具有分用和复用功能。应用层所有的应用进程都可以他那个过运输层在传达到IP层,这是复用。运输层从IP层收到数据报后必须交付给指定的应用进程,这是分用。因此,给应用层的每个应用进程赋予一个非常明确的标志至关重要。
          解决这个问题的方法是在运输层使用的协议端口号(protocol prot number),或者通常称为端口(port)。这就是说,虽然通信的终点是应用进程,但我们只要把传送的报文交到目标主机的某一个合适的目标端口,剩下的工作(即最后交付给目的进程)就由TCP来完成。
          这里需要注意的是,这种在协议栈层间的抽象的协议端口是软件端口,和路由器或交换机上的硬件端口是完全不同的概念。硬件端口是不同硬件设备进行交互的接口,而软件端口是应用层的各种协议进程与运输实体进行层间交互的一种地址。不同系统具体实现端口的方法可以是不同的(这取决于操作系统)。
          TCP/IP的运输层用一个16位端口号来标志一个端口。 端口号只具有本地意义,它只是为了标志本计算机应用层中的各个进程在和运输层交互时的层间接口。在因特网不同计算机中,相同的端口号是没有关联的。16位的端口号可以允许有65535个不同的端口号,这个数据对于一个计算机来说是足够用的。运输层的端口号分为以下两大类:
           1. 服务器端使用的端口号
          这里又分为两类,最重要的一类称为熟知端口号(well known port number)或系统端口号,数值为0-1023。下面给出了一些常用的熟知端口号:
          另外一类称为登记端口号,数值在1024 - 49151.这类端口号是为没有熟知端口好的应用程序使用的。
           2. 客户端使用的端口号
          数值为49152 - 65535。这类端口号仅在客户进程运行时才动态选择,因此又称为短暂端口号。这类端口号是留给客户进程选择暂时使用的。通讯结束后,使用过的客户端口号就不复存在。



2. UDP协议

2.1 UDP概述

          用户数据报协议UDP只是在IP的数据报服务至上增加了很少的一点功能,这就是复用和分用的功能以及差错检测的功能。UDP的主要特点是:
           1. UDP无连接的,即发送数据之前不需要建立连接(当然发送数据结束时也没有连接可以释放),因此减少了开销和发送数据之前的时延。
           2. UDP使用 尽最大努力交付,即不保证可靠交付,因此主机不需要维持负责的连接状态表。

          3. UDP面向报文的。发送方的UDP对应用程序交下来的报文,在添加首部后就向下交付给IP层。UDP对应用层交下来的报文,既不合并,也不分拆,而是保留这些报文的边界。这就是说,应用层交给UDP多长的报文,UDP就照样发送,即一次发送一个报文。在接受方的UDP,对IP层交上来的UDP用户数据报,在去除首部后就原封不动的交付给上层的应用进程。也就是说,UDP一次交付一个完整的报文。因此,应用程序必须选择合适大小的报文。若报文太长,UDP把它交给IP层后,IP在传送时可能要进行分片,这会降低IP层的效率。反之,若报文太短,UDP把它交给IP层后,会使IP数据报的首部的相对长度太大,这也会降低IP层的效率。UDP协议是面向报文的,它与应用层和IP层的关系见下图:


           4. UDP没有拥塞控制,因此网络出现的拥塞不会使源主机的发送速率降低。这对某些实时应用是很重要的。很多的实时应用(如IP电话、实时视频会议等)要求源主机以恒定的速率发送数据,并且允许在网络发生拥塞时丢失一些数据,但却不允许数据有太大的时延。UDP刚好适合这种要求。
           5. UDP支持一对一、一对多、多对一和多对多的交互通讯
           6. UDP的首部开销小,只有8个字节,比TCP的20个字节的首部要短。
          虽然某些实时应用需要使用没有拥塞的UDP,但当很多的源主机同时都向网络发送高速率的实时视频时,网络就有可能发生拥塞,结果大家都没法正常接收。因此,不使用拥塞控制的UDP可能会引起网络产生严重的拥塞问题。
          还有一些使用UDP的实时应用,需要对UDP的不可靠的传输进行适当的改进,以减少数据的丢失。在这种情况下,应用进程本身可以在不影响应用的实时性的前提下,增加一些提高可靠性的措施,如采用前向纠错或重传已丢失的报文。


2.2 UDP首部格式

          用户数据报UDP有两个字段:数据字段和首部字段。首部字段很简单,只有8个字节,由四个字段组成, 每个字段的长度都是两个字节。各字段意义如下:
           1. 源端口  源端口号。在需要对方回信时选用。不需要时刻选用全0.
           2. 目的端口  目的端口号。这在终点交付报文时必须要使用到。
           3. 长度    UDP用户数据报的长度,其最小值是8(仅有首部)。
           4. 检验和  检测UDP用户数据报在传输中是否出错。有错就丢弃。详细见下图示例:


          当运输层从IP层收到UDP报文时,就根据首部中的目的端口,把UDP数据报通过相应的端口,上交最后的终点---应用程序。
          如果接受方UDP发现收到的报文中的目的端口号不正确(即不存在对应于该端口好的应用程序),就丢弃该报文,并由ICMP发送"端口不可达"差错报文给发送方。
          UDP用户数据报首部中检验和的计算方法有些特殊。在计算检验和时,要在UDP用户数据报之前增加12个字节的伪首部。所谓 "伪首部"是因为这种伪首部并不是UDP用户数据报的真正的首部。只是在计算检验和时,临时添加在UDP用户数据报前面,得到的一个临时的UDP用户数据报。检验和就是按照这个临时的UDP用户数据报计算的。伪首部既不向下传送也不向上递交,而仅仅是为了计算检验和。

          UDP计算检验和的方法和计算IP数据报首部检验和的方法相似。但不同的是:IP数据报的检验和只检验IP数据报的首部,但 UDP的检验和是把首部和数据部分一起都检验
          下面是一个计算UDP检验和的例子:




3. TCP协议

3.1 传输控制协议TCP概述

          TCP协议最主要的特点:
          1. TCP是 面向连接的运输层协议。这就使说,应用程序在使用TCP协议之前,必须先建立TCP连接。在传送数据完毕后,必须释放已经建立的TCP连接。这就是说,应用进程之间的通讯好像在打电话:通话前要先拨号建立连接,通话结束后要挂机释放连接。
          2. 每一条TCP连接只能有两个 端点(endpoint),每一条TCP连接只能是 点对点的(一对一).
          3. TCP提供 可靠交付的服务。也就是说,通过TCP连接传送的数据,无差错、不丢失、不重复、并且按序到达。
          4. TCP提供 全双工通信。TCP允许通信双方的应用进程在任何时候都能发送数据。TCP连接的两端都设有发送缓存和接受缓存,用来临时存放双向通信的数据。在发送时,应用程序在把数据传送给TCP的缓存后,就可以做自己的事,而TCP在合适的时候把数据发送出去。在接受时,TCP把收到的数据放入缓存,上层的应用进程在合适的时候读取缓存中的数据。
          5. 面向字节流。 TCP中的 "流"指的是流入到进程或从进程流出的字节序列。"面向字节流"的含义是:虽然应用程序和TCP的交互是一次一个数据块(大小不等),但TCP把应用程序交下来的数据看成仅仅是 一连串的无结构的字节流。TCP并不知道所传送的字节流的含义。TCP不保证接收方应用程序所收到的数据块和发送方应用程序所发出的数据块具有对应大小的关系(例如,发送方应用程序交给发送方的TCP共10个数据块,但接受方的TCP可能只用了4个数据块就把收到的字节流交付给了上层的应用程序)。但接收方应用程序收到的字节流必须和发送方应用程序发出的字节流完全一样。当然,接受方的应用程序必须有能力识别收到的字节流,把它还原成有意义的应用层数据。
          TCP和UDP在发送报文时所采用的方式是完全不同的。TCP对应用进程一次把多长的报文发送到TCP的缓存中是不关心的。TCP根据对方给出的窗口值和当前网络拥塞的程度来决定一个报文段应包含多少个字节(UDP发送的报文长度是应用进程给出的)。如果应用进程传送到TCP缓存的数据块太长,TCP就可以把它划分短一些再传送。如果应用进程一次只发来一个字节,TCP也可以等待积累有足够多的字节后再构成报文段发送出去。


3.2 TCP的连接

          TCP把连接作为 最基本的抽象。TCP的许多特性都与TCP是面向连接的这个基本特性有关。
          TCP连接的端点是 套接字(socket)或插口。根据RFC793的定义:端口号 拼接到(contatenated with)IP地址即构成了套接字。因此套接字的表示方法是在点分十进制的IP地址后面写上端口号,中间用冒号或逗号隔开。
           每一条TCP连接唯一地被通讯两端的两个端点(即两个套接字)所确定。即:

          TCP连接::={socket1,socket2}={(IP1:PORT1),{IP2:PORT2}}


3.3 TCP可靠传输的实现

          为了讲述TCP的可靠传输, 假定数据传输只在一个方向进行,即A发送数据,B给出确认。这样的好处是使讨论只限于两个窗口,即发送方A的发送窗口和接受方B的接受窗口。可靠传输的原理可以看 <《计算机网络》 读书笔记(一) 可靠传输的工作原理>。TCP的可靠传输可以看成可靠传输原理的一个应用。

3.3.1 以字节为单位的滑动窗口

          TCP的滑动窗口是以字节为单位的。如下图。


          现假定A收到了B发来的确认报文段,其中窗口是20(字节),而确认号是31(这表明B期望收到的下一个序号是31,而序号30为止的数据已经收到了)。根据这两个数据,A就构造出自己的发送窗口。

          发送窗口表示:在没有收到B的确认情况下,A可以连续把窗口内的数据都发送出去。凡是已经发送过的数据,在未收到确认之前都必须暂时保留,以便在超时重传时使用。
          发送窗口里面的序号表示允许发送的序号。显然,窗口越大,发送方就可以在收到对方确认之前连续发送更多的数据,因而可能获得更高的传输效率。但接受方必须来得及处理这些收到的数据。
          发送窗口后沿的后面部分表示已发送且已收到了确认。这些数据显然不需要再保留了。而发送窗口前沿的前面部分表示不允许发送的,因为接受方都没有为这部分数据保留临时存放的缓存空间。
          发送窗口的位置由窗口前沿和后沿的位置共同确定。发送窗口后沿的变化情况有两种可能,即不动(没有收到新的确认)和前移(收到了新的确认)。发送窗口后沿不可能向后移动,因为不能撤销掉已收到的确认。发送窗口前沿通常是不断向前移动,但也有可能不动。这对应于两种情况:一是没有收到新的确认,对方通知的窗口大小也不变;二是收到了新的确认但对方通知的窗口缩小了,使得发送窗口前沿正好不动。
          发送窗口前沿也可能 向后收缩。这发生在对方通知的窗口缩小了。但TCP的标准 强烈不赞成这么做。因为很可能发送方在收到这个通知以前已经发送了窗口中许多数据,现在又要收缩窗口,不让发送这些数据,这样就会产生一些错误。

          现在假定A发送了序号为31~41的数据。见下图。


          这时,发送窗口位置并未改变,但发送窗口内靠后面有11个字节(灰色小方框表示)表示已发送但未收到确认。而发送窗口内靠前面的9个字节(42~50)是允许发送但尚未发送的。

          从以上所述可以看出,要描述一个发送窗口的状态需要三个指针:P1,P2和P3。指针都指向字节的序号。这三个指针指向的几个部分的意义如下:
          小于P1的是已经发送并已收到确认的部分,而大于P3的是不允许发送的部分。
          P3-P1=A的发送窗口(又称为 通知窗口)
          P2-P1=已经发送但尚未收到确认的字节数
          P3-P2=允许发送但尚未发送的字节数(又称为 可用窗口或有效窗口)

          再看一下B的接受窗口。B的接受窗口大小是20.在接受窗口外面,到30号为止的数据是已经发送过确认,并且已经交付给主机了。因此在B可以不再保留这些数据。接收窗口内的序号(31~50)是允许接受的。在上图中,B收到了序号为32和33的数据。这些数据没有按序到达,因为序号31的数据没有收到(也许丢失了,也许滞留在网络中的某处)。          请注意,B只能对按序收到的数据中最高序号给出确认,因此B发送的确认报文段中确认好仍然是31(即期望收到的序号),而不能是32或33.

          现在假定B收到了序号为31的数据,并把序号为31~33的数据交付给主机,然后B删除这些数据。接着把接受窗口向前移动3个序号(见上图),同时给A发送确认,其中窗口值仍为20,但确认号是34.这表明B已经收到了到序号33为止的数据。我们注意到,B还收到了序号为37,38和40的数据,但这些都没有按序到达,只能先暂存在接受窗口中。A收到B的确认后,就可以把发送窗口向前滑动3个序号,但指针P2不动。可以看出,现在A的可用窗口增大了,可发送的序号范围是42~53.
          A在继续发送完序号42~53的数据后,指针P2向前移动和P3重合。发送窗口内的序号都已用完,但还没有再收到确认(见下图)。


          由于A的发送窗口已满,可用窗口已减小到零,因此必须停止发送。请注意,存在下面这种可能性,就是发送窗口内所有的数据都已正确到达B,B也早已发送了确认。但不幸的是,所有这些确认都滞留在网络中。在没有收到B的确认时,A不能猜测:"或许B收到了吧"。为了保证可靠传输,A只能认为B还没有收到这些数据。于是,A在经过一段时间后(由超时计时器控制)就重传这部分数据,重新设置超时计数器,直到收到了B的确认为止。如果A收到确认号落在发送窗口内,那么A就可以使发送窗口继续向前滑动,并发送新的数据。


          下图描述了发送方维持的发送缓存和发送窗口,以及接受方维持的接受缓存和接收窗口。

                                         
          先来看发送方的情况.发送缓存用来暂时存放:
          1.发送应用程序传送给发送方TCP准备发送的数据。
          2.TCP已发送出但尚未收到确认的数据。
          发送窗口通常只是发送缓存的一部分。已被确认的数据应当从发送缓存中删除,因此发送缓存和发送窗口的后沿是重合的。发送应用程序最后写入发送缓存的字节减去最后被确认的字节,就是还保留在发送缓存中的被写入的字节数。发送应用程序必须控制写入缓存的速率,不能太快,否则发送缓存就会没有存放数据的空间。

          再看一下接收方的情况。接收缓存用来暂时存放:
          1.按序到达的、但尚未被接收应用程序读取的数据;
          2.未按序到达的数据。
          如果收到的分组被检测出有差错,则要丢弃。如果接受应用程序来不及读取收到的数据,接收缓存最终就会被填满,使接收窗口减小到零。反之,如果接受应用程序能够及时从接受缓存中读取收到的数据,接收窗口就可以增大,但最大不能超过接受缓存的大小。在上图中,还指出了下一个期望收到的字节号。这个字节号也就是接收方给发送方的报文段的首部中的确认号。


          根据以上所讨论的,强调一下三点:
          第一,虽然A的发送窗口是根据B的接收窗口设置的,但在同一时刻,A的发送窗口并不总是和B的接收窗口一样大。这是因为通过网络传送窗口值需要经历一定的时间滞后(这个时间还是不确定的)。另外,发送方A还可能根据网络当时的拥塞情况适当减小自己的发送窗口数值。
          第二,对于不按序到达的数据应如何处理,TCP标准并无明确规定。如果接收方把不按序到达的数据一律丢弃,那么接收窗口的挂历会比较简单,但这样对网络资源的利用不利(因为发送方会重复传输较多的数据)。因此TCP通常对不按序到达的数据是先临时存放在接受窗口中,等到字节流中所缺少的字节收到后,再按序交付给上层的应用进程
          第三,TCP要求接收方必须有累积确认的功能,这样可以减小传输开销。接受方可以在合适的时候发送确认,也可以在自己有数据要发送时把确认信息顺便捎带上。但需要注意两点。第一,接受方不应过分推迟发送确认,否则会导致发送方不必要的重传,这反而浪费了网络的资源。TCP标准规定,确认推迟的时间不应超过0.5秒。若收到一连串具有最大长度的报文段,则必须每隔一个报文段就要发送一个确认。第二,捎带确认实际上并不经常发生,因为大多数应用程序不同时在两个方向上发送数据。


3.3.2 超时重传时间的选择

          重传机制是 TCP 中最重要和最复杂的问题之一。TCP 每发送一个报文段,就对这个报文段设置一次计时器。只要计时器设置的重传时间到但还没有收到确认,就要重传这一报文段。重传的时间选择概念上简单,但工程实现上确实是TCP最复杂的问题之一。

          其原因是由于往返时延的方差很大, TCP 的下层是一个互联网环境,IP 数据报所选择的路由变化很大。因而运输层的往返时间的方差也很大。详见下图。



          那TCP协议中超时计时器的超时重传时间究竟设置为多大呢?这可以通过以下几种方法来计算:

           加权平均往返时间的计算
          TCP 保留了 RTT 的一个 加权平均往返时间 RTTS(这又称为平滑的往返时间)。第一次测量到RTT样本时,RTTS 值就取为所测量到的 RTT样本值。以后每测量到一个新的RTT 样本,就按下式重新计算一次RTTS:
          新的 RTTS = (1 - a) * (旧的 RTTS) + a *(新的 RTT 样本)
          式中,0<= a < 1。若 a 很接近于零,表示 RTT 值更新较慢。若选择 a 接近于 1,则表示 RTT 值更新较快。(RFC 2988 推荐的 a 值为 1/8,即 0.125)

          超时重传时间 RTO (RetransmissionTime-Out) 的计算
          (1) RTO 应略大于上面得出的加权平均往返时间RTTS 。
          (2) RFC 2988 建议使用下式计算 RTO:
                   RTO = RTTS + 4 *RTTD     
          (3) RTTD是 RTT 的偏差的加权平均值。
          (4) RFC 2988 建议这样计算 RTTD。第一次测量时, RTTD 值取为测量到的 RTT 样本值的一半。在以后的测量中,则使用下式计算加权平均的 RTTD:
新的 RTTD= (1 - b) *(旧的RTTD)+ b *|RTTS - 新的 RTT 样本|                 
           式中,b 是个小于 1 的系数,其推荐值是 1/4,即 0.25。

           往返时间的测量

          往返时间的测量相当复杂,如下图所示:


          在上图中,TCP 报文段 1 没有收到确认。重传(即报文段 2)后,收到了确认报文段 ACK。但 如何判定此确认报文段是对原来的报文段 1 的确认,还是对重传的报文段 2 的确认? 

          下面这些算法提供了一下解决方法。
           Karn 算法 
          在计算平均往返时间 RTT 时,只要报文段重传了,就不采用其往返时间样本。这样得出的加权平均平均往返时间 RTTS 和超时重传时间 RTO 就较准确。 

          修正Karn 算法 

          报文段每重传一次,就把 RTO 增大一些,如下公式所示:
          新的 RTO=y*(旧的 RTO)             式中,系数y的典型值是 2 。

          当不再发生报文段的重传时,才根据报文段的往返时延更新平均往返时延 RTT 和超时重传时间 RTO 的数值。
          实践证明,这种策略较为合理。 

3.3.2 选择确认 SACK (Selective ACK) 

       当接收方收到了和前面的字节流不连续的两个字节块。如果这些字节的序号都在接收窗口之内,那么接收方就先收下这些数据,但要把这些信息准确地告诉发送方,使发送方不要再重复发送这些已收到的数据。详细可见 <3.7  TCP报文段的首部格式>

          RFC 2018 的规定 
          (1) 如果要使用选择确认,那么在建立 TCP 连接时,就要在 TCP 首部的选项中加上“允许 SACK”的选项,而双方必须都事先商定好。
          (2) 如果使用选择确认,那么原来首部中的“确认号字段”的用法仍然不变。只是以后在 TCP 报文段的首部中都增加了 SACK 选项,以便报告收到的不连续的字节块的边界。
          (3) 由于首部选项的长度最多只有 40 字节,而指明一个边界就要用掉 4 字节,因此在选项中最多只能指明 4 个字节块的边界信息。


3.4 TCP的流量控制

          所谓的流量控制就是让发送方的发送速率不要太快,让接收方来得及接收。利用滑动窗口机制可以很方便的在TCP连接上实现对发送方的流量控制。TCP的窗口单位是字节,不是报文段,发送方的发送窗口不能超过接收方给出的接收窗口的数值。


          如上图所示,说明了利用可变窗口大小进行流量控制。设主机A向主机B发送数据。在建立连接时,B告诉了A:”我的接收窗口rwnd=400“。因此, 发送方的发送窗口不能超过接收方给出的接收窗口的数值。需要注意的是,TCP的 窗口单位是字节,不是报文段。也就是说在连接建立时,双方确定的窗口值是400。再设每一个报文段为100字节长,序号的初始值为seq=1,图中的箭头上面大写ACK,表示首部中的却认为为ACK,小写ack表示确认字段的值。
          接收方的主机B进行了三次流量控制。第一次把窗口设置为rwind=300,第二次减小到rwind=100最后减到rwind=0,即不允许发送方再发送过数据了。这种使发送方暂停发送的状态将持续到主机B重新发出一个新的窗口值为止。
          假如,B向A发送了零窗口的报文段后不久,B的接收缓存又有了一些存储空间。于是B向A发送了rwind=400的报文段,然而这个报文段在传送中丢失了。A一直等待收到B发送的非零窗口的通知,而B也一直等待A发送的数据。这样就死锁了。为了解决这种死锁状态,TCP为每个连接设有一个 持续计时器。只要TCP连接的一方收到对方的零窗口通知,就启动持续计时器,若持续计时器设置的时间到期,就发送一个 零窗口探测报文段(仅携带1字节的数据),而对方就在确认这个探测报文段时给出了现在的窗口值。
          TCP报文段发送时机的选择
           TCP报文段发送时机主要有以下几种选择途径。
           1)TCP维持一个变量,它等于最大报文段长度MSS,只要缓存中存放的数据达到MSS字节就组装成一个TCP报文段发送出去
           2)由发送方的应用程序指明要求发送报文段,即TCP支持的推送操作
          3)是发送方的一个计时器期限到了,这时就把当前已有的缓存数据装入报文段发送出去。



3.5 TCP的拥塞控制

          拥塞控制的原理
          在某段时间,若对网络中的某一资源的需求超过了该资源所能提供的可用部分,网络的性能就要变化,这种情况叫做 拥塞
          网络拥塞往往是由许多因素引起的,简单的提高节点处理机的速度或者扩大结点缓存的存储空间并不能解决拥塞问题。例如
           1. 当某个结点缓存容量扩展到非常大,于是凡到达该结点的分组均可在结点的缓存队列中排队,不受任何限制。由于输出链路的容量和处理机的速度并未提高,因此在这队列中的绝大多数的分组在排队等待时间会大大增加,结果上层软件只好把他们进行重传。
          因此,问题的是指往往是整个系统的各个部分不匹配,只有各个部分平衡了,问题才会得到解决。
           2.拥塞控制和流量控制的差别
           所谓拥塞控制就是防止过多的数据注入到网络中,这样可以使网络中的路由器或链路不致过载。拥塞控制所要做的都有一个前提,就是 网络能承受现有的网络负荷。 拥塞控制是一个 全局性的问题
           流量控制往往指的是点对点通信量的控制,是个 端到端的问题。流量控制所要做的就是控制发送端发送数据的速率,以便使接收端来得及接受。
           3.拥塞控制设计
          拥塞控制是很难设计的,因为它是一个动态的问题,许多情况下,甚至正式拥塞控制机制本身成为引起网络性能恶化甚至死锁的原因。从控制理论的角度来看拥塞控制这个问题,可以分为开环控制和闭环控制两种方法。开环控制就是在设计网络时事先将有关拥塞发生的所有因素考虑周到,一旦系统运行起来就不能在中途改正。
          闭环控制是基于反馈环路的概念,包括如下措施:
          1)监测网路系统以便检测拥塞在何时何地发生
          2)把拥塞发生的信息传送到可采取行动的地方
          3)调整网络系统的行动以解决出现的问题。
           4.拥塞控制方法
          因特网建议标准RFC2581定义了进行拥塞控制的四种算法,即慢开始(Slow-start),拥塞避免(Congestion Avoidance)快重传(Fast Restrangsmit)和快回复(Fast Recovery)。我们假定
          1)数据是单方向传送,而另外一个方向只传送确认
          2)接收方总是有足够大的缓存空间,因为发送窗口的大小由网络的拥塞程度来决定。
 
          --慢开始和拥塞避免
          发送报文段速率的确定,既要根据接收端的接收能力,又要从全局考虑不要使网络发生拥塞,这由接收窗口和拥塞窗口两个状态量确定。接收端窗口(Reciver Window)又称通知窗口(Advertised Window),是接收端根据目前的接收缓存大小所许诺的最新窗口值,是来自接收端的流量控制。拥塞窗口cwnd(Congestion Window)是发送端根据自己估计的网络拥塞程度而设置的窗口值,是来自发送端的流量控制。
          慢启动原理:
          1)当主机开始发送数据时,如果立即将较大的发送窗口的全部数据字节都注入到网络中,那么由于不清楚网络的情况,有可能引其网络拥塞
          2)比较好的方法是试探一下,即从小到达逐渐增大发送端的拥塞控制窗口数值
          3)通常在刚刚开始发送报文段时可先将拥塞窗口cwnd设置为一个最大报文段的MSS的数值。在每收到一个对新报文段确认后,将拥塞窗口增加至多一个MSS的数值,当rwind足够大的时候,为了防止拥塞窗口cwind的增长引起网络拥塞,还需要另外一个变量---慢开始门限ssthresh
          拥塞控制具体过程为:
          1)TCP连接初始化,将拥塞窗口设置为1
          2)执行慢开始算法,cwind按指数规律增长,知道cwind == ssthress开始执行拥塞避免算法,cwnd按线性规律增长
          3)当网络发生拥塞,把ssthresh值更新为拥塞前ssthresh值的一半,cwnd重新设置为1,按照步骤(2)执行。
          --快重传和快恢复
          一条TCP连接有时会因等待重传计时器的超时而空闲较长的时间,慢开始和拥塞避免无法很好的解决这类问题,因此提出了快重传和快恢复的拥塞控制方法。
          快重传算法并非取消了重传机制,只是在某些情况下更早的重传丢失的报文段(如果当发送端接收到三个重复的确认ACK时,则断定分组丢失,立即重传丢失的报文段,而不必等待重传计时器超时)。慢开始算法只是在TCP建立时才使用
          快恢复算法有以下两个要点:
          1)当发送方连续收到三个重复确认时,就执行“乘法减小”算法,把慢开始门限减半,这是为了预防网络发生拥塞。
          2)由于发送方现在认为网络很可能没有发生拥塞,因此现在不执行慢开始算法,而是把cwnd值设置为慢开始门限减半后的值,然后开始执行拥塞避免算法,是拥塞窗口的线性增大。


3.6 TCP的运输连接管理

          TCP是面向连接的协议。运输连接是用来传送TCP报文的。TCP运输连接的建立和释放是每一次面向连接的通讯中必不可少的过程。因此,运输连接就有三个阶段,即:连接建立、数据传送和连接释放。因此,运输连接就有三个阶段,即: 连接建立、数据传送和连接释放。运输连接的管理就是使运输连接的建立和释放都能正常的进行。
          在TCP连接建立时,要解决以下3个问题:
          1. 要使每一方能够确知对方的存在。
          2. 要允许双方协商一些参数(如最大窗口值、是否使用窗口扩大选项和时间戳选项以及服务质量等)
          3. 能够对运输实体资源(如缓存大小、连接表中的项目等)进行分配。
          TCP连接的建立采用客户服务器方式。主动发起连接建立的应用进程叫做 客户(client),而被动等待连接建立的应用进程叫做 服务器(server)


3.6.1 TCP的连接建立


          上图画出了TCP的建立连接的过程。假定主机A运行的是TCP客户程序,而B运行TCP服务器程序。最初两端的TCP进程都处于CLOSED(关闭)状态。图中的主机下面的方框分别是TCP进程所处的状态。需要注意的是, A主动打开连接,而 B被动打开连接
          B的TCP服务器进程先创建 传输控制块TCB(Transmission Control Block),准备接受客户进程的连接请求。然后服务器进程就处于LISTEN(收听)状态,等待客户的连接请求。如有,即做出相应。
          A的TCP客户进程也是首先 创建控制模块TCB,然后向B发出连接请求报文段,这时首部中的同步位SYN=1,同时选择一个初始序号seq=x。TCP规定,SYN报文段(即SYN=1的报文段)不能携带数据,但要 消耗掉一个序号。这时,TCP客户端进程进入SYN-SENT(同步已发送)状态。
          B收到连接请求报文段后,如同意建立连接,则向A发送确认。在确认报文段中应把SYN位和ACK位都置1,确认号是ack=x+1,同时也为自己选择一个初始序号seq=y。这里需要注意,这个报文段也不能携带数据,但 同样要消耗掉一个序号。这时TCP服务器进程进入SYN-RCVD(同步收到)状态。
          TCP客户进程收到B的确认后,还要向B给出确认。确认报文段的ACK置1,确认号ack=y+1,而自己的序号seq=x+1。TCP的标准规定,ACK报文段可以携带数据。但 如果不携带数据则不消耗序号,在这种情况下,下一个数据报文段的序号仍是seq=x+1。这时,TCP连接已经建立,A进入ESTABLISHED(已建立连接)状态。
          当B收到A的确认后,也进入ESTABLISHED状态。
          上面给出的连接建立过程叫做 三次握手(three-way handshake),或 三次联络
          为什么A还要发送一次确认呢?这主要是为了防止已失效的连接请求报文段突然又传送到了B,因而产生错误。
          所谓 "已失效的连接请求报文段"是这样产生的。考虑一种正常情况。A发送连接请求,但因连接请求报文丢失而未收到确认。于是A再重传一次连接请求。后来收到了确认,建立了连接。数据传输完毕后,就释放了连接。A共发送了两个连接请求报文段,其中第一个丢失,第二个到达了B。没有"已失效的连接请求报文段"。
          现假定出现一种异常情况,即A发出的第一个连接请求报文段并没有丢失,而是在某些网络结点长时间滞留了,以致延误到连接释放以后的某个时间才到达B。本来这是一个早已失效的报文段。但B收到此失效的连接请求报文段后,就误认为是A又发出一次新的连接请求。于是就向A发出确认报文段,同意建立连接。假定不采用三次握手,那么只要B发出确认,新的连接就建立了。
          由于现在A并没有建立连接的请求,因此不会理睬B的确认,也不会向B发送数据。但B却以为新的运输连接已经建立了,并一直等待A发来数据。B的许多资源就这样白白浪费了。
          采用三次握手的办法可以防止上述现象的发生。例如在刚才的情况下,A不会向B的确认发出确认。B由于收不到确认,就知道A并没有要求建立连接。


3.6.2 TCP的连接释放


          TCP连接释放过程比较复杂,这里仍然结合双方状态的改变来阐述连接释放的过程。
          数据传输结束后,通信的双方都可以释放连接。现在A和B都处在ESTABLISHED状态。A的应用进程先向其TCP发出连接释放报文段,并停止再发送数据,主动关闭TCP连接。A把连接释放报文段首部的FIN置1,其序号seq=u,它等于前面已传送过的数据的最后一个字节的序号加1.这时A进入 FIN-WAIT-1(终止等待1)状态,等待B的确认。请注意,TCP规定,FIN报文段即使不携带数据,它也消耗掉一个序号。
          B收到连接释放报文段后即发出确认,确认号是ack=u+1,而这个报文段自己的序号是v,等于B前面已传送过的数据的最后一个字节的序号加1.然后B就进入 CLOSE-WAIT(关闭等待)状态。TCP服务器进程这是应通知高层应用进程,因而从A到B这个方向的连接就释放了,这时的TCP连接处于 半关闭(half-close)状态,即A已经没有数据要发送了,但B若发送数据,A仍要接收。也就是说,从B到A这个方向的连接并未关闭。这个状态可能会持续一些时间。
          A收到来自B的确认后,就进入 FIN-WAIT-2(终止等待2)状态,等待B发出的连接释放报文段。
          若B已经没有要向A发送的数据,其应用进程就通知TCP释放连接。这时B发出的连接释放报文段必须使FIN=1.现假定B的序号为w(在半关闭状态B可能又发送了一些数据)。B还必须重复上次已发送过的确认号ack=u+1.这时B就进入 LAST-ACK(最后确认状态),等待A的确认。
          A在收到B的连接释放报文段后,必须对此发出确认。在确认报文段中把ACK置1,确认号ack=w+1,而自己的序号是seq=u+1(根据TCP标准,前面发送过的FIN报文段要消耗一个序号)。然后进入到 TIME-WAIT(时间等待)状态。请注意,现在TCP连接还没有释放掉。必须经过 时间等待计时器(TIME-WAIT timeer)设置的时间2MSL后,A才进入到CLOSED状态。时间MSL叫做 最长报文段寿命(Maximum Segment Lifetime),RFC793建议设为2分钟。但这完全是从工程上考虑,对于现在的网络,MSL=2分钟可能太长了一些。因此TCP允许不同的实现可根据具体情况使用更小的MSL值。因此,从A进入到TIME-WAIT状态后,要经过4分钟才能进入到CLOSED状态,才能开始建立下一个新的连接。当A撤销相应的传输控制块TCB后,就结束了这次的TCP连接。
          为什么A在TIME-WAIT状态必须等待2MSL的时间呢?有下面两个理由。
          第一,为了保证A发送的最后一个ACK报文段能够到达B。这个ACK报文段有可能丢失,因而使处在LAST-ACK状态的B收不到对已发送的FIN+ACK报文段的确认。B会超时重传这个FIN+ACK报文段,儿A就能在2MSL时间内收到这个重传的FIN+ACK报文段。接着A重传一次确认,重新启动2MSL计时器。最后,A和B都正常进入到CLOSED状态。如果A在TIME-WAIT状态不等待一段时间,而是在发送完ACK报文段后立即释放连接,那么就无法收到B重传的FIN+ACK报文段,因而也不会再发送一次确认报文段。这样,B就无法按照正常步骤进入CLOSED状态。
          第二,防止上一节提到的"已失效的连接请求报文段"出现在本连接中。A在发送完最后一个ACK报文段后,在经过时间2MSL,就可以使本连接持续的时间内所产生的所有报文段都从网络中消失。这样就可以使下一个新的连接中不会出现这种旧的连接请求报文段。
          B只要收到了A发出的确认,就进入CLOSED状态。同样,B在撤销相应的传输控制块TCB后,就结束了这次的TCP连接。需要注意的是,B结束TCP连接的时间要比A早一些。
          上述的TCP连接释放过程是四次握手,但也可以看成是两个二次握手。
          除时间等待计时器外,TCP还设有一个 保活计时器(keepalive timer)。设想有这样的情况:客户已主动与服务器建立了TCP连接。但后来客户端的主机突然出现故障。显然,服务器以后就不能再收到客户发来的数据。因此,应当有措施是服务器不要再白白等待下去。这就是使用保活计时器。服务器每收到一次客户端数据,就重新设置保活计时器,时间的设置通常是两小时。若两小时没有收到客户端的数据,服务器就发送一个探测报文段,以后则每个75分钟发送一个。若一连发送10个探测报文段后仍无客户端的相应,服务器就认为客户端出现了故障,接着就关闭了这个连接。

3.6.3 TCP的有限状态机

          为了更清晰的看出TCP连接的各种状态之间的关系,下图给出了TCP的有限状态机。图中每个方框即TCP可能具有的状态。每个方框中的大写英文字符串是TCP标准所使用的TCP连接状态名。状态之间的箭头表示可能发生的状态变迁。箭头旁边的字,表明引起这种变迁的原因,或表明发生状态变迁后又出现什么动作。请注意图中有三种不同的箭头,粗实线箭头表示对客户端进程的正常变迁。粗虚线箭头表示对服务器进程的正常变迁。另一种细线箭头表示异常变迁。



3.7  TCP报文段的首部格式

          TCP虽然是面向字节流的,但TCP传送的数据单元却是报文段。一个TCP报文段分为首部和数据两部分,而TCP的全部功能都体现在它首部中各自段的作用。因此,只有弄清TCP首部各字段的作用才能掌握TCP的工作原理。
          TCP报文段首部的前20个字节是固定的,后面有4N字节是根据需要而增加的选项(N是整数)。因此TCP首部的最小长度是20字节,见下图报文格式。

          首部固定部分各字段的意义如下:
           1.源端口(Source Port):

          16位的源端口字段包含初始化通信的端口号。源端口和IP地址的作用是标识报文的返回地址。


          2.目的端口(Destination Port):

          16位的目的端口字段定义传输的目的。这个端口指明接收方计算机上的应用程序接口。


          3.序列号(Sequence Number):

          占4个字节。序号范围是[0, 2^32-1],共2^32(即42 8496 7296)个序号。序号增加到2^32-1后,下一个序号就又回到0。也就是说,序号使用 mod 2^32运算。TCP是面向字节流的。在一个TCP连接中传送的字节流中的每一个字节都按顺序编号。整个要传送的字节流起始序号必须在连接时建立。首部中的序号字段则指的是本报文段所发送的数据的第一个字节的序号。例如,一个报文段的序号字段值是 301 ,而携带的数据共有100字节。这就表明:本报文段的数据的第一个字节序号是301,最后一个字节的序号是400。显然,下一个报文段(如果还有的话)的数据序号应当从401开始,即下一个报文段的序号字段值应为401。这个字段的名称也叫做“报文段序号”。


          4.确认号:

          占4个字节,是期望收到对方的下一个报文段的第一个数据字节的序号。例如,B正确收到了A发送过来的一个报文段,其序号字段值是501,而数据长度是200字节(序号501--700),这表明B正确收到了A发送的序号700为止的数据。因此,B期望收到A的下一个序号是701,于是B在发送给A的确认报文段中把确认号置为701。请注意,现在的确认号不是501,也不是700,而是701。
          总之,应当记住: 
          若确认号=N,则表明:到序号N-1为止的所有数据都已正确收到。
          由于序号字段有32位长,可对4GB的数据进行编号。在一般情况下可保证当序号重复使用时,旧序号的数据早已通过网络到达终点了。


          5.数据偏移:

          占4位,它指出TCP报文段的数据起始处距离TCP报文段的起始处有多远。这个字段实际上是指出TCP报文段的首部长度。由于首部中还有长度不确定的选项字段,因此数据偏移字段是必要的。但请注意,“数据偏移”的单位是32位字(即以4字节的字为计算单位)。由于4位二进制能够表示的最大十进制数字是15,因此数据偏移的最大值是60字节,这也是TCP首部的最大长度(即选项长度不能超过40字节)。


          6.保留:

          占6位,保留为今后所用,但目前应置为0.
          下面有6个控制位说明本报文段的性质,它们的意义见下面的(7)~(12)。


          7.紧急URG(URGent): 

          当URG=1时,表明此报文应尽快传送,而不要按原来的排队顺序来传送。与“紧急指针”字段配合使用,紧急指针指出在本报文段中的紧急数据的最后一个字节的序号,使接收方可以知道紧急数据共有多长;


          8. 确认ACK(ACKnowlegment):

          仅当ACK=1时确认号字段才有效。当ACK=0时,确认号无效。TCP规定,在连接建立后所有传送的报文段都必须把ACK置1。


          9. PSH(Push Function,推功能):

          这个标志表示Push操作。当两个应用进程进行交互式的通信时,又是在一端的应用进程希望在键入一个命令后立即就能够收到对方的响应。在这总情况下,TCP就可以使用推送(push)操作。这时,发送方TCP把PSH置1,并立即创建一个报文段发送出去。接受方TCP收到PSH=1的报文段,就尽快地(即"推送"向前)交付给接受应用进程,而不再等到整个缓冲都填满了后再向上交付。
          虽然应用程序可以选择推送操作,但推送操作还是很少使用。


          10.复位比特RST:

          当RST=1时,表明TCP连接中出现严重差错(如由于主机奔溃或者其他原因),必须释放连接。复位比特还用来拒绝一个非法的报文段或拒绝打开一个连接。RST也可称为重建位或重置位。


          11.同步SYN(SYNchronization):

          在连接建立时用来同步序号。当SYN=1而ACK=0,表明这是一个连接请求报文段。若对方同意建立连接,则应在响应的报文段中使SYN=1和ACK=1。因此,SYN置为1就表示这是一个连接请求或连接接受报文。


          12. 终止比特FIN:

          用来释放一个连接。当FIN=1时,表明此报文段的发送端的数据已发送完毕,并要求释放运输连接。


          13. 窗口:

           占2字节。窗口值是[0, 2^16-1]之间的整数。窗口指的是发送本报文段的一方的接受窗口(而不是自己的发送窗口)。窗口值告诉对方:从本报文段首部中的确认号算起,接受方目前允许对方发送的数据量。之所以要有这个限制,是因为接收方的数据缓冲空间是有限的。总之,窗口值作为接受方让发送方设置其发送窗口的依据
          例如,设确认号是701,窗口字段是1000.这就表明,从701算起,发送此报文段的一方还有接受1000个字节数据(字节序号是701~1700)的接受缓存空间。
          总之,应当记住:
          窗口字段明确指出了现在允许对方发送的数据量。窗口值是经常在动态变化着的。


          14. 检验和
          占 2 字节。检验和字段检验的范围包括首部和数据这两部分。在计算检验和时,要在 TCP 报文段的前面加上 12 字节的伪首部。


          15. 紧急指针
          占 16 bit。紧急指针仅在URG=1时才有意义,它指出本报文段中的紧急数据的字节数(紧急数据结束后就是普通数据)。因此紧急指针指出了紧急数据的末尾在报文段中的位置。当所有紧急数据都处理完后,TCP就告诉应用程序恢复到正常操作。值得注意的是,即使窗口为0时,也可以发送紧急数据。


          16. 选项
          长度可变,最长可达40字节。当没有使用选项时,TCP的首部长度是20字节。
          a) 选项 MSS
          TCP最初只规定了以一种选项,即最大报文段长度MSS(Maximum Segment Size)。MSS是每一个TCP报文段中的数据字段的最大长度。数据字段加上TCP首部才等于整个TCP报文段。所以MSS并不是整个TCP报文段的最大长度,而是"TCP报文段长度减去TCP首部长度"。
          之所以要规定一个最大报文段长度MSS。并不是考虑接受方的接受缓存可能放不下TCP报文段中的数据。实际上,MSS与接受窗口没有关系。我们知道,TCP报文段的数据部分,至少要加上40字节的首部(TCP首部20字节和IP首部20字节,这里还没有考虑首部中的选项部分),才能组装成一个IP数据报。若选择较小的MSS长度,网络的利用率就会降低。设想在极端的情况下,当TCP报文段只含有1字节的数据时,在IP层传输的数据报的开销至少有40字节。这样,对网路的利用率就不会超过1/41.到了数据链路层还要加入一些开销。但反过来,若TCP报文段非常长,那么在IP层传输时就可能要分解成多个短数据报文片。在终点要把收到的各个短数据报文装配成原来的TCP报文段。当传输出错时还要进行重传。这些也都会使靠小增大。
          因此,MSS应尽可能大些,只要在IP传输时不需要再分片就行。由于IP数据报所经历的路径是动态变化的,因此在这条路径上确定的不需要分片的MSS,如果该走另一条路径就可能需要进行分片。因此最佳的MSS是很难确定的。在连接建立的过程中,双方都把自己能够支持的MSS写入这一字段,以后就按照这个数值传送数据,两个传送方向可以有不同的MSS值。若主机未填写这一项,则MSS的默认值是536字节长。因此,所有在因特网上的主机都应能接受的报文段长度是536+20(固定首部长度)=556字节。
         b) 选项  窗口扩大选项
          窗口扩大选项是为了扩大窗口。TCP首部中的窗口字段长度是16位,因此最大的窗口大小是64K字节。虽然这对早期的网络是足够的,但对于包含卫星信道的网络,传播时延和宽带都很大,要获得高吞吐率需要更大的窗口大小。
          窗口扩大选项占3字节,其中有一个字节表示移位值S。新的窗口值等于TCP首部中的窗口位数从16增到到(16+S),这相当于把窗口值向左移动S位后获得实际的窗口大小。移位值允许使用的最大值是14,相当于窗口最大值增大到2^(16+14) - 1 = 2^30 -1。
          窗口扩大选项可以在双方初始建立TCP连接时进行协商。如果连接的某一端实现了窗口扩大,当它不再需要扩大窗口时,可发送S=0的选项,使窗口大小回到16.
          c) 选项  时间戳
          时间戳选项占10字节,其中最主要的字段时间戳值字段(4字节)和时间戳回送回答字段(4字节)。时间戳选项有以下两个功能:
          第一,用来计算往返时间RTT。发送方在发送报文段把当时时间的时间值放入时间戳字段,接受方在确认该报文段时把时间戳字段复制到时间戳回送回答字段。因此,发送方在收到确认报文后,可以准确的计算出RTT来。
          第二,用于处理TCP序号超过2^32的情况,这又称为防止序号绕回PAWS(Protect Against Wrapped Sequence numbers)。我们知道,序号只有32位,而每增加2^32个序号就会重复使用原来用过的序号。当使用高速网络时,在一次TCP连接的数据传输中序号很可能会被重复使用。例如,若用1GB/S的速度发送报文段,则不到35秒钟数据字节的序号就会重复。为了使接受方能够把新的报文段和迟到很久的报文段区分开,可以在报文段中加上这种事件戳。
          d) 选项  选择确认(SACK)
          连续ARQ协议接受端通常采用的是累计确认的方法。累计确认方法有缺点也有优点。优点是简单,即使确认丢失也不必重传。缺点是不能向发送方反映接受方已经正确接受的所有分组的信息。
          选择确认是累计确认外的另一种选择。它的好处是,只当报文缺失或错误时,只需要发送方发送错误或缺失的报文段。


(版权所有,转载时请注明作者和出处  http://blog.csdn.net/arau_sh/article/details/10439891

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值