计算机网络之运输层

运输层

运输层概述

运输层是作为法律标准的OSI体系结构自下而上的第4层,其主要任务是为相互通信的应用进程提供逻辑通信服务。

进程间基于网络的通信

计算机网络体系结构中的物理层、数据链路层和网络层,它们共同解决了将主机通过异构网络互联起来所面临的问题,实现了主机到主机的通信。然而在计算机网络中实际进行通信的真正实体,是位于通信两端主机中的进程。

如何为运行在不同主机上的应用进程提供直接的逻辑通信服务,就是运输层的主要任务。运输层协议又称为端到端协议。运输层的作用范围是应用进程到应用进程,也称为端到端,即运输层提供进程之间的逻辑通信。

请添加图片描述

请添加图片描述

假设主机A中的应用进程AP1与主机B中的应用进程AP4进行基于网络的通信,主机A中的AP2与主机B中的AP3进行基于网络的通信。
1)主机A中的运输层使用不同的端口对应不同的应用进程,然后通过网络层及其下层传输应用层报文。

2)主机B中的运输层通过不同的端口,将收到的应用层报文交付给应用层中相应的应用进程。

需要注意的是,这里的“端口”并不是看得见、摸得着的物理端口,而是用来区分不同应用进程的标识符。

运输层向应用层实体屏蔽了下面网络核心的细节(例如网络拓扑、所采用的路由选择协议等),它使应用进程看见的就好像在两个运输层实体之间有一条端到端的逻辑通信信道。根据应用需求的不同,因特网的运输层为应用层提供了两种不同的运输层协议:

  • 面向连接的传输控制协议
  • 无连接的用户数据报协议

TCP/IP体系结构运输层中的两个重要协议

网际层为主机之间提供的逻辑通信服务,是一种尽最大努力交付的数据报服务。换句话说,IP数据报在传送过程中有可能出现误码、丢失、重复或失序等传输错误。

对于因特网上的实时音频、视频等多媒体应用,实时性是它们的首要需求,而少量传输错误对播放质量产生的影响较小,可以满足应用需求。

然而,对于因特网中的万维网、文件传输、电子邮件以及电子银行等应用,传输错误可能会造成灾难性的后果。因此,这就需要TCP/IP体系结构的运输层为这类因特网应用提供可靠的数据传输服务。

请添加图片描述

为了满足上述两类不同的因特网应用,TCPIIP体系结构的运输层为其应用层提供了两个不同的运输层协议:

  • 用户数据报协议(UDP)
  • 传输控制协议(TCP)

用户数据报协议

用户数据报协议[RFC768],向其上层提供的是无连接的不可靠的数据传输服务。也就是说,在运输层使用UDP通信的双方,在传送数据之前不需要建立连接。接收方的运输层在收到UDP用户数据报后,不需要给发送方发回任何确认。尽管UDP向其上层提供的是不可靠的数据传输服务,但对因特网上要求实时性的一类应用或某些情况,UDP却是一种最有效的工作方式。

传输控制协议

传输控制协议[RFC793],向其上层提供的是面向连接的可靠的数据传输服务。也就是说,在运输层使用TCP通信的双方,在传送数据之前必须先建立TCP连接(逻辑连接,而非物理连接),然后基于已建立好的TCP连接进行可靠数据传输,数据传输结束后要释放TCP连接。

TCP为了实现可靠数据传输,就必须增加许多措施,例如TCP连接管理、确认机制、超时重传、流量控制以及拥塞控制等,这不仅会使TCP报文段的首部比较大,还要占用许多处理机资源。

请添加图片描述

运输层端口号、复用与分用的概念

运输层端口号

运输层直接为应用进程间的逻辑通信提供服务,它使用端口号来区分不同的应用进程。

运行在计算机上的进程是使用进程标识符(Process Identification,PID)来标识的。然而,因特网上的计算机并不是使用统一的操作系统,而不同操作系统(Windows、Linux、Mac OS)又使用不同格式的进程标识符。为了使运行不同操作系统的计算机的应用进程之间能够进行网络通信,就必须使用统一的方法对TCP/IP体系的应用进程进行标识。

TCP/IP体系结构的运输层使用端口号来标识和区分应用层的不同应用进程。端口号的长度为16比特,取值范围是0~65535,分为两大类:

  • 服务器端使用的端口号,分为两类:

    • 熟知端口号:又称为全球通用端口号,取值范围是0~1023。因特网号码分配管理局IANA将这些端口号分配给了TCP/IP体系结构应用层中最重要的一些应用协议。例如,HTTP服务器端的端口号为80,FTP服务器端的端口号为21和20。与电话通信相比,TCP/IP运输层的熟知端口号相当于所有人都知道的重要电话号码,例如,报警电话110,急救电话120,火警电话119,等等。

    请添加图片描述

    • 登记端口号:取值范围是1024~49151。这类端口号是为没有熟知端口号的应用程序使用的,要使用这类端口号必须在因特网号码分配管理局IANA按照规定的手续登记,以防止重复。例如,Microsoft RDP微软远程桌面应用程序使用的端口号是3389。
  • 客户端使用的短暂端口号:取值范围是49152~65535。这类端口号仅在客户端使用,由客户进程在运行时动态选择,又称为临时端口号。当服务器进程收到客户进程的报文时,就知道了客户进程所使用的临时端口号,因而可以把响应报文发送给客户进程。通信结束后,已使用过的临时端口号会被系统收回,以便给其他客户进程使用。

发送方的复用和接收方的分用

请添加图片描述

1)发送方的某些应用进程所发送的不同的应用报文,在运输层使用UDP协议进行封装,这称为UDP复用;而另一些应用进程所发送的不同的应用报文,在运输层使用TCP协议进行封装,这称为TCP复用。运输层使用端口号来区分不同的应用进程。

2)不管是使用运输层的UDP协议封装成的UDP用户数据报,还是使用TCP协议封装成的TCP报文段,在网际层都需要使用IP协议封装成IP数据报,这称为IP复用。IP数据报首部中协议字段的值用来表明,IP数据报的数据载荷部分封装的是何种协议数据单元,取值为6表示封装的是TCP报文段,取值为17表示封装的是UDP用户数据报。

3)接收方的网际层收到IP数据报后进行IP分用。若IP数据报首部中协议字段的值为17,则把IP数据报的数据载荷部分所封装的UDP用户数据报向上交付给运输层的UDP。若IP数据报首部中协议字段的值为6,则把IP数据报的数据载荷部分所封装的TCP报文段向上交付给运输层的TCP。

4)运输层对UDP用户数据报进行UDP分用,对TCP报文段进行TCP分用,也就是根据UDP用户数据报或TCP报文段首部中的目的端口号,将它们向上交付给应用层的相应应用进程。

UDP和TCP的对比

TCP/IP体系结构的运输层为其应用层提供了两个不同的运输层协议:UDP和TCP。

这两个协议的使用频率仅次于网际层的IP协议。其中,UDP向应用层提供的是无连接的不可靠的数据传输服务,而TCP向应用层提供的是面向连接的可靠的数据传输服务。

TCP连接建立成功后才能基于已建立好的TCP连接进行数据传输。数据传输结束后,必须使用“四报文挥手”来释放TCP连接。

请添加图片描述

UDP和TCP对单播、多播和广播的支持情况

**UDP支持单播、多播和广播。**换句话说,UDP支持“一对一”“一对多”以及“一对全”的通信。

使用TCP协议的通信双方,在数据传输之前必须使用“三报文握手”建立TCP连接。TCP连接建立成功后,通信双方之间就好像有一条可靠的通信信道,通信双方使用这条基于TCP连接的可靠信道进行通信。很显然,TCP仅支持单播,也就是“一对一”的通信。

UDP和TCP对应用层报文的处理:

UDP对应用层报文的处理情况

请添加图片描述

1)发送方的应用进程将应用层报文向下交付给运输层的UDP。

2)UDP直接给应用层报文添加一个UDP首部,使之成为UDP用户数据报,然后进行发送。

3)接收方的UDP收到UDP用户数据报后,去掉UDP首部,将应用层报文向上交付给应用进程。

综上所述,UDP对应用进程交付下来的报文既不合并也不拆分,而是保留这些报文的边界。换句话说,UDP是面向应用报文的。

TCP对应用层报文的处理情况

请添加图片描述

1)发送方的TCP把应用进程交付下来的应用报文,仅仅看作一连串的、无结构的字节流。TCP并不知道这些待传输的字节流的含义,仅将它们编号并存储在自己的发送缓存中。

2)TCP根据发送策略,从发送缓存中提取一定数量的字节,构建TCP报文段并发送。

3)接收方的TCP一方面从所接收到的TCP报文段中取出数据载荷并存储在接收缓存中,另一方面将接收缓存中的一些字节向上交付给应用进程。

综上所述,TCP是面向字节流的,这正是TCP实现可靠传输、流量控制以及拥塞控制的基础。

UDP和TCP对数据传输可靠性的支持

我们知道,TCP/IP体系结构的网际层向其上层提供的是无连接不可靠的数据传输服务。

当运输层使用UDP时,UDP向其上层提供的也是无连接不可靠的数据传输服务。

发送方给接收方发送UDP用户数据报,若传输过程中UDP用户数据报受到干扰而产生误码,接收方UDP可以通过该UDP用户数据报首部中的检验和字段的值,检查出产生误码的情况,但仅仅丢弃该UDP用户数据报,其他什么也不做。

如果发送方给接收方发送的UDP用户数据报被因特网中的某个路由器丢弃了(这可能是由于路由器太忙,或路由器检查出封装该UDP用户数据报的IP数据报的首部出现误码),发送方UDP也不做任何处理,因为UDP向其上层提供的是无连接不可靠的数据传输服务。

综上所述,对于UDP用户数据报出现的误码和丢失等问题,UDP并不关心。基于UDP的这个特点,UDP适用于实时应用,例如IP电话和视频会议等。

尽管TCP/IP网际层中的IP协议向其上层提供的是无连接不可靠的数据传输服务,也就是IP数据报可能在传输过程中出现丢失或误码,但只要运输层使用TCP协议,TCP就可向其上层提供面向连接的可靠的数据传输服务。

使用TCP协议的收发双方,基于TCP连接的可靠信道进行数据传输,不会出现误码、丢失、失序和重复等传输差错。

基于TCP的这个特点,TCP适用于要求可靠传输且对实时性要求不高的应用,例如文件传输和电子邮件等。

UDP首部和TCP首部的对比

请添加图片描述

UDP用户数据报的首部仅有4个字段,每个字段长度为2字节。由于UDP不提供可靠传输服务,它仅仅在网际层的基础上添加了用于区分应用进程的端口,因此它的首部非常简单,仅有8字节。

请添加图片描述

TCP报文段由首部和数据载荷两部分构成。TCP报文段的首部比UDP用户数据报的首部复杂得多,其最小长度为20字节,最大长度为60字节。TCP要实现可靠传输、流量控制、拥塞控制等服务,其首部自然会比较复杂,首部中的字段比较多,首部长度也比较长。

UDP用户数据报首部中的长度字段用于指明UDP用户数据报的长度,而检验和字段用于UDP接收方检查UDP用户数据报在传输过程中是否产生了误码。检验和字段的计算方法有些特殊。在计算检验和时,要在UDP用户数据报之前增加12字节的伪首部

伪首部的第三个字段是全零,第四个字段是IPv4数据报首部中的协议字段的值,对于UDP,此协议字段的值为17。第五个字段是UDP用户数据报的长度。这样的检验和,既检查了UDP用户数据报的源端口号、目的端口号以及UDP用户数据报的数据部分,又检查了IP数据报的源地址和目的地址。接收方收到UDP用户数据报后,仍要加上伪首部来计算检验和。

传输控制协议

传输控制协议(TCP)是TCP/IP体系结构运输层中面向连接的协议,它向其上的应用层提供全双工的可靠的数据传输服务。TCP与UDP最大的区别就是,TCP是面向连接的,而UDP是无连接的。TCP比UDP要复杂得多,除具有面向连接和可靠传输的特性,TCP还在运输层使用了流量控制和拥塞控制机制。

TCP报文段的首部格式

TCP为实现可靠传输而采用了面向字节流的方式。但TCP在发送数据时,是根据发送策略从发送缓存中取出一定数量的字节,并给其添加一个首部使之成为TCP报文段后进行发送。一个TCP报文段由首部和数据载荷两部分构成。

请添加图片描述

源端口字段和目的端口字段

源端口字段占16比特,用来写入源端口号。源端口号用来标识发送该TCP报文段的应用进程。

目的端口字段占16比特,用来写入目的端口号。目的端口号用来标识接收该TCP报文段的应用进程。

序号字段、确认号字段以及确认标志位(ACK)

序号字段占32比特,取值范围是0~232 - 1。当序号增加到最后一个时,下一个序号又回到0。

序号字段的值,用来指出本TCP报文段数据载荷的第一个字节的序号。

确认号字段占32比特,取值范围是0~232 - 1。当确认号增加到最后一个时,下一个确认号又回到0。

确认号字段的值,用来指出期望收到对方下一个TCP报文段的数据载荷的第一个字节的序号,同时也是对之前收到的所有数据的确认。

也就是说,若确认号为n,则表明到序号n-1为止的所有数据都已正确接收,期望接收序号为n的数据。

只有当ACK取值为1时,确认号字段才有效。ACK取值为0时,确认号字段无效。

TCP规定:在TCP连接建立后所有传送的TCP报文段都必须把ACK置1。

数据偏移字段

数据偏移字段占4比特,该字段的取值以4字节为单位,用来指出TCP报文段的数据载荷部分的起始处距离TCP报文段的起始处有多远,这实际上指出了TCP报文段的首部长度。首部固定长度为20字节,因此数据偏移字段的最小值为二进制的0101,加上扩展首部40字节,首部最大长度为60字节,因此数据偏移字段的最大值为二进制的1111。

请添加图片描述

假设某个TCP报文段首部中的数据偏移字段的取值为二进制的0101,那么首部长度就为20字节,因为二进制0101的十进制值是5,而该字段以4字节为单位,因此5乘以4字节等于20字节。

假设另一个TCP报文段首部中的数据偏移字段的取值为二进制的1111,那么首部长度就为60字节,因为二进制1111的十进制值是15,而该字段以4字节为单位,因此15乘以4字节等于60字节。

保留学段

保留字段占6比特,保留为今后使用,目前应置为0。

窗口字段

窗口字段占16比特,取值范围是0~216 - 1,以字节为单位,用来指出发送本报文段的一方的接收窗口的大小,即接收缓存的可用空间大小,这用来表征接收方的接收能力。在计算机网络中,经常用接收方的接收能力的大小来控制发送方的数据发送量,这就是所谓的流量控制。

检验和字段

检验和字段占16比特,用来检查整个TCP报文段在传输过程中是否出现了误码。

与UDP类似,在计算检验和时,要在TCP报文段的前面加上12字节的伪首部。

伪首部的格式与UDP用户数据报的伪首部一样,但应将伪首部的第四个字段的值从17改为6(因为IP数据报首部中的协议字段的值取6时,表明IP数据报的数据载荷是TCP报文段),将第五字段中的UDP长度改为TCP长度。接收方收到此报文段后,仍要加上这个伪首部来计算检验和。

同步标志位

同步标志位(SYN)用于TCP双方建立连接。

1)当SYN=1且ACK=0时,表明这是一个TCP连接请求报文段。

2)对方若同意建立连接,则应在响应的TCP报文段的首部中使SYN=1且ACK=1。

综上所述,SYN为1的TCP报文段要么是一个连接请求报文段,要么是一个连接响应报文段。

终止标志位

终止标志位(FIN)用于释放TCP连接。

当FIN=1时,表明此TCP报文段的发送方已经将全部数据发送完毕,现在要求释放TCP

复位标志位

复位标志位(RST)用于复位TCP连接。

当RST=1时,表明TCP连接中出现严重差错(例如由于主机崩溃或其他原因),必须释放连接,然后再重新建立连接。

RST置1还用来拒绝一个非法的TCP报文段或拒绝打开一个TCP连接。

推送标志位(PSH)

出于效率的考虑,TCP可能会延迟发送数据或向应用程序延迟交付数据,这样可以一次处理更多的数据。但是当两个应用进程进行交互式的通信时,有时在一端的应用进程希望在键入一个命令后立即就能够收到对方的响应。在这种情况下,应用程序可以通知TCP使用推送(PUSH)操作。

发送方TCP把PSH置1,并立即创建一个TCP报文段发送出去,而不需要积累到足够多的数据再发送。

接收方TCP收到PSH为1的TCP报文段,就尽快地交付给应用进程,而不再等到接收到足够多的数据才向上交付。

紧急标志位(URG)和紧急指针字段

当URG取值为1时,紧急指针字段有效;当URG取值为0时,紧急指针字段无效。

紧急指针字段占16比特,以字节为单位,用来指明紧急数据的长度。

当发送方有紧急数据时,可将紧急数据“插队”到发送缓存的最前面,并立刻封装到~个TCP报文段中进行发送。紧急指针会指出本报文段数据载荷部分包含了多长的紧急数据,紧急数据之后是普通数据。

接收方收到紧急标志位为1的TCP报文段,会按照紧急指针字段的值从报文段数据载荷中取出紧急数据并直接上交应用进程,而不必在接收缓存中排队。

选项字段

TCP报文段首部除了20字节的固定部分,还有最大40字节的选项部分。增加选项可以增加TCP的功能。目前有以下选项:

  • 最大报文段长度(Maximum Segment Size,MSS)选项:用来指出的是TCP报文段数据载荷部分的最大长度.

  • 窗口扩大选项:用来扩大窗口,提高吞吐率。

  • 时间戳选项:有以下两个功能:

    • 用于计算往返时间(RTT)

    • 用来处理序号超范围的情况,又称为防止序号绕回

  • 选择确认选项:用来实现选择确认功能。

填充字段

由于选项字段的长度是可变的,因此还需要使用填充字段(填充内容为若干个比特0来确保TCP报文段首部能被4整除。

TCP的运输连接管理

TCP是面向连接的协议,它基于运输连接来传送TCP报文段。TCP运输连接的建立和释放,是每一次面向连接的通信中必不可少的过程。

TCP运输连接有以下三个阶段:

1)建立TCP连接:通过“三报文握手”来建立TCP连接。

2)数据传送:基于已建立的TCP连接进行可靠的数据传输。

3)释放连接:在数据传输结束后,还要通过“四报文挥手”来释放TCP连接。

TCP的运输连接管理就是使运输连接的建立和释放都能正常地运行。

“三报文握手”建立TCP连接

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

  • 使TCP双方能够确知对方的存在。
  • 使TCP双方能够协商一些参数(如最大报文段长度、最大窗口大小、时间戳选项等)。
  • 使TCP双方能够对运输实体资源(如缓存大小、各状态变量、连接表中的项目等)进行分配和初始化。

请添加图片描述

上图给出了两台要基于TCP进行通信的主机,其中一台主机中的某个应用进程主动发起TCP连接,称为TCP客户,另一台主机中被动等待TCP连接的应用进程称为TCP服务器。

可以将TCP建立连接的过程比喻为“握手”。“握手”需要在TCP客户和服务器之间交换三个TCP报文段。最初,两端的TCP进程都处于关闭(CLOSED)状态。

1)TCP服务器进程首先创建传输控制块,用来存储TCP连接中的一些重要信息。之后,TCP服务器进程就进入监听(LISTEN)状态,等待TCP客户进程的连接请求。由于TCP服务器进程是被动等待来自TCP客户进程的连接请求,而不是主动发起的,因此称为被动打开连接。

2)TCP客户进程也要首先创建传输控制块,之后在打算建立TCP连接时向TCP服务器进程发送TCP连接请求报文段,并进入同步已发送(SYN-SENT)状态。

TCP连接请求报文段首部中的同步标志位(SYN)被设置为1,表明这是一个TCP连接请求报文段;

3)TCP服务器进程收到TCP连接请求报文段后,如果同意建立连接,则向TCP客户进程发送TCP连接请求确认报文段,并进入同步已接收(SYN-RCVD)状态。

该报文段首部中的SYN和ACK都设置为1,表明这是一个TCP连接请求确认报文段;

4)TCP客户进程收到TCP连接请求确认报文段后,还要向TCP服务器进程发送一个普通的TCP确认报文段,并进入连接已建立(ESTABLISHED)状态。

该报文首部中的ACK被设置为1,表明这是一个普通的TCP确认报文段;

5)TCP服务器进程收到针对TCP连接请求确认报文段的普通确认报文段后,也进入连接已建立(ESTABLISHED)状态。此时,TCP双方都进入了连接已建立状态,它们可以基于已建立的TCP连接进行可靠的数据传输了。

“四报文挥手”释放TCP连接

请添加图片描述

1)数据传输结束后,TCP通信双方都可以释放TCP连接。

2)假设使用TCP客户进程的应用进程通知其主动关闭TCP连接,TCP客户进程会发送TCP连接释放报文段,并进入终止等待1(FIN-WAIT-1)状态。

该报文段首部中的终止标志位(FIN)和ACK的值都被设置为1,表明这是一个TCP连接释放报文。

3)TCP服务器进程收到TCP连接释放报文段后,会发送一个普通的TCP确认报文段并进入关闭等待(CLOSE-WAIT)状态。

该报文段首部中的ACK的值被设置为1,表明这是一个普通的TCP确认报文段;

TCP服务器进程这时应通知高层应用进程:“TCP客户进程要断开与自己的TCP连接”。此时,从TCP客户进程到TCP服务器进程这个方向的连接就释放了。这时的TCP连接属于半关闭状态,也就是TCP客户进程已经没有数据要发送了,但TCP服务器进程如果还有数据要发送,TCP客户进程仍要接收,也就是从TCP服务器进程到TCP客户进程这个方向的连接并未关闭。半关的状态可能会持续一段时间。

4)TCP客户进程收到该普通的TCP确认报文段后就进入终止等待2(FIN-WAIT-2)状态,等待TCP服务器进程发出的TCP连接释放报文段。若使用TCP服务器进程的应用进程已经没有数据要发送了,应用进程就通知其TCP服务器进程释放连接。由于TCP连接释放是由TCP客户进程主动发起的,因此TCP服务器进程对TCP连接的释放称为被动关闭连接。

5)TCP服务器进程发送TCP连接释放报文段并进入最后确认(LAST-ACK)状态。

该报文段首部中的FIN和ACK的值都被设置为1,表明这是一个TCP连接释放报文段

6)TCP客户进程收到TCP连接释放报文段后,必须针对该报文段发送普通的TCP确认报文段,之后进入时间等待(TIME-WAIT)状态。

7)TCP服务器进程收到该普通的TCP确认报文段后就进入关闭(CLOSED)状态,TCP服务器进程撤销相应的传输控制块。而TCP客户进程还要经过2MSL后才能进入关闭(CLOSED)状态。

MSL的意思是最长报文段寿命(Maximum Segment Lifetime))[RFC793]建议为2分钟。也就是说,TCP客户进程进入时间等待(TIME-WAIT)状态后,还要经过4分钟才能进入关闭(CLOSED)状态。

处于时间等待(TIME-WAIT)状态后要经过2MSL时长,可以确保TCP服务器进程能够收到最后一个TCP确认报文段而进入关闭(CLOSED)状态。

另外,TCP客户进程在发送完最后一个TCP确认报文段后,再经过2MSL时长,就可以使本次连接持续时间内所产生的所有报文段都从网络中消失,这样就可以使下一个新的TCP连接中不会出现旧连接中的报文段。

TCP保活计时器

除时间等待计时器(2MSL计时),TCP还设有一个保活计时器(Keepalive Timer)。

设想以下情况:TCP双方已经建立了连接。后来,TCP客户进程所在的主机突然出现了故障。显然,TCP服务器进程以后就不能再收到TCP客户进程发来的数据。因此,应当有措施使TCP服务器进程不要再白白等待下去。

TCP服务器进程解决上述问题的方法是使用保活计时器,具体如下:

  • TCP服务器进程每收到一次TCP客户进程的数据,就重新设置并启动保活计时器(通常为2小时)。

  • 若保活计时器在定时周期内未收到TCP客户进程发来的数据,则当保活计时器到时后,TCP服务器进程就向TCP客户进程发送一个探测报文段,以后则每隔75秒发送一次。若一连发送10个探测报文段后仍无TCP客户进程的响应,TCP服务器进程就认为TCP客户进程所在主机出了故障,于是就关闭这个连接。

TCP的流量控制

TCP为应用程序提供了流量控制(Flow Control)机制,以解决因发送方发送数据太快而导致接收方来不及接收,造成接收方的接收缓存溢出的问题。

流量控制的基本方法就是接收方根据自己的接收能力(接收缓存的可用空间大小)控制发送方的发送速率。

TCP的流量控制方法

TCP利用滑动窗口机制可以很方便地在TCP连接上实现对发送方的流量控制。

请添加图片描述

如图所示,主机A和B已成功建立了TCP连接,A给B发送数据,B对A进行流量控制。

假设主机A发送的每个TCP数据报文段都携带100字节的数据,在主机A和B建立TCP连接时,B告诉A:“我的接收窗口为400”,因此主机A将自己的发送窗口也设置为400,这意味着主机A在未收到主机B发来的确认时,可将序号落入发送窗口中的全部数据发送出去。

1)主机A将发送窗口内序号为1~100的数据封装成一个TCP报文段发送出去,发送窗口内还有300字节可以发送。

2)主机A将发送窗口内序号为101~200的数据封装成一个TCP报文段发送出去,发送窗口内还有200字节可以发送。

3)主机A将发送窗口内序号为201~300的数据封装成一个TCP报文段发送出去,但该报文段在传输过程中丢失了。

4)主机A还可发送100个字节的数据,即序号落在发送窗口内的301~400号数据。此时主机B给主机A发送累积确认报文段,对主机A之前所发送的201号以前的数据进行累积确认。窗口字段rwnd(也就是主机B的接收窗口)的值被设置为300,可认为现在主机B的接收缓存的可用空间为300字节,而不是之前的400字节,也就是对主机A进行流量控制。

5)主机A收到主机B发来的累积确认报文段后,将发送窗口向前滑动,使已发送并收到确认的数据的序号移出发送窗口,这些数据可从发送缓存中删除了。由于主机B在该累积确认报文段中将自己的接收窗口调整为了300,因此主机A相应地将自己的发送窗口调整为300。这样,主机A的发送窗口内的序号为201~500,其中201~300号是已发送但还未收到确认的数据的序号,因此不能将这些数据从发送缓存删除,因为有可能之后会超时重传这些数据;301~400号字节数据以及401~500号字节数据还未被发送,可被分别封装在一个TCP报文段中发送。

6)主机A将发送窗口内序号301~400号的数据封装成一个TCP报文段发送出去,发送窗口内还有401~500号共100字节可以发送。

7)主机A将发送窗口内序号401~500号的数据封装成一个TCP报文段发送出去。至此,序号落在发送窗口内的数据已经全部发送出去了,不能再发送新的数据了。

请添加图片描述

8)假设此时主机A发送窗口内序号201~300这100字节数据的重传计时器超时了,主机A将它们重新封装成一个TCP报文段发送出去,暂时不能发送其他数据。

9)主机B收到该重传的TCP报文段后,给主机A发送累积确认报文段,对主机A之前所发送的501号以前的数据进行累积确认。另外,主机B在该累积确认报文段中将自己的接收窗口调整为100,这是主机B对主机A进行的第二次流量控制。

10)主机A收到主机B发来的累积确认报文段后,将发送窗口向前滑动,使已发送并收到确认的数据的序号移出发送窗口,这些数据可从发送缓存中删除了。由于主机B在该累积确认报文段中将自己的接收窗口调整为了100,因此主机A相应地将自己的发送窗口调整为100。这样,主机A的发送窗口内的序号为501~600,也就是主机A还可以发送这100字节数据。

11)主机A将发送窗口内序号为501~600号的数据封装成一个TCP报文段发送出去。至此,序号落在发送窗口内的数据已经全部发送出去了,不能再发送新的数据了。

12)主机B给主机A发送累积确认报文段,对主机A之前所发送的601号以前的数据进行累积确认。另外,主机B在该累积确认报文段中将自己的接收窗口调整为0,这是主机B对主机A进行的第三次流量控制。

13)主机A收到主机B发来的累积确认报文段后,将发送窗口向前滑动,使已发送并收到确认的数据的序号移出发送窗口,这些数据可从发送缓存中删除了。由于主机B在该累积确认报文段中将自己的接收窗口调整为了0,因此主机A相应地将自己的发送窗口调整为0。至此,主机A不能再发送普通的TCP报文段了。

14)假设主机B向主机A发送了零窗口的报文段后不久,主机B的接收缓存又有了一些可用空间。于是主机B向主机A发送了接收窗口等于300的报文段。然而,这个报文段在传输过程中丢失了。

15)主机A一直等待主机B发送的非零窗口的通知,而主机B也一直等待主机A发送的数据。如果不采取措施,这种互相等待而形成的死锁局面将一直持续下去。

为了打破上述由于非零窗口通知报文段丢失而引起的双方互相等待的死锁局面,TCP为每一个连接都设有一个持续计时器。只要TCP连接的一方收到对方的零窗口通知,就启动持续计时器。

当持续计时器超时时,就发送一个零窗口探测报文段,仅携带1字节的数据。对方在确认这个零窗口探测报文段时,给出自己现在的接收窗口值。如果接收窗口值仍然是0,那么收到这个报文段的一方就重新启动持续计时器。如果接收窗口值不是0,那么死锁的局面就可以被打破了。

请添加图片描述

TCP的拥塞控制

在某段时间,若对网络中某一资源的需求超过了该资源所能提供的可用部分,网络性能就要变坏,这种情况就叫作拥塞(congestion)。计算机网络中的链路容量(带宽)、交换节点中的缓存和处理机等都是网络的资源。若出现拥塞而不进行控制,整个网络的吞吐量将随输入负荷的增大而下降。

请添加图片描述

具有理想拥塞控制的网络,在吞吐量达到饱和之前,网络吞吐量应等于所输入的负载,故吞吐量曲线是45°的斜线。但当输入负载超过某一限度时,由于网络资源受限,吞吐量就不再增长而保持水平线,也就是吞吐量达到饱和,这就表明输入的负载中有一部分损失掉了。例如,输入到网络中的某些分组被某个节点丢弃了。虽然如此,在这种理想的拥塞控制作用下,网络的吞吐量仍然维持在其所能达到的最大值。

然而,实际网络的情况就很不同了。随着输入负载的增大,网络吞吐量的增长率逐渐减小,也就是在网络吞吐量还未达到饱和时,就已经有一部分的输入分组被丢弃了。当网络的吞吐量明显小于理想的吞吐量时,网络就进入了轻度拥塞的状态。

更值得注意的是当输入负载到达某一数值时,网络的吞吐量反而随输入负载的增大而减小,这时网络就进入了拥塞状态。在无拥塞控制的情况下,当输入负载继续增大到某一数值时,网络的吞吐量就减小为0,此时网络就无法工作了,这就是所谓的死锁。

综上所述,进行拥塞控制是非常有必要的。实际的拥塞控制曲线应该尽量接近理想的

拥塞控制的基本方法

从控制论的角度看,拥塞控制可以分为开环控制和闭环控制两大类。

  • 开环控制方法试图用良好的设计来解决问题,也就是从一开始就保证问题不会发生。一旦系统启动并运行起来了,就不需要中途修正。
  • 闭环控制是一种基于反馈的控制方法,它包括以下三个部分:
    • 监测网络拥塞在何时、何地发生。
    • 把拥塞发生的相关信息传送到可以采取行动的地方。
    • 调整网络的运行以解决拥塞问题。

在因特网中拥塞控制主要采用闭环控制方法。

根据拥塞信息的反馈形式,可将闭环拥塞控制算法分为显式反馈算法和隐式反馈算法。

  • 在显式反馈算法中,从拥塞节点(即路由器)向源点提供关于网络中拥塞状态的显式反馈信息。源站收到后应该降低发送速率。但这些额外注入到网络中的ICMP源点抑制报文有时反而会造成网络更加拥堵。更好的显式反馈算法是,在路由器转发的分组中保留一个字段,用该字段的值表示网络的拥塞状态,而不是专门发送一个通知分组。
  • 在隐式反馈算法中,源点通过对网络行为的观察(例如超时重传或往返时间RTT来推断网络是否发生了拥塞,而无须拥塞节点提供显式反馈信息。TCP采用的就是隐式反馈算法。

TCP的四种拥塞控制方法

TCP的四种拥塞控制方法是慢开始(Slow-Start)、拥塞避免(Congestion Avoidance)快重传(Fast Retransmit)、快恢复(FastRecovery)。

请添加图片描述

发送方要维护一个叫作拥塞窗口(Congestion Window)的状态变量cwnd,其值取决于网络的拥塞程度和所采用的TCP拥塞控制算法,很显然cwnd的值是动态变化的。拥塞窗口cwnd的维护原则是,只要网络没有出现拥塞,拥塞窗口就再增大一些,但只要网络出现拥塞,拥塞窗口就减少一些。

判断网络出现拥塞的依据是,没有按时收到应当到达的确认报文段而产生了超时重传。由于现在的通信线路的传输质量一般都较好,因传输误码而被路由器丢弃分组的概率远小于1%,因此当发送方出现超时重传时,很可能是因为网络中的某个路由器“繁忙”而丢弃了一些分组,这是网络出现拥塞的征兆。

发送方除了要维护拥塞窗口cwnd变量,还要维护**发送窗口(Sender Window)的状态变量swnd。**发送窗口swnd的大小从拥塞窗口cwnd和接收方的接收窗口(Receiver Window)rwnd中取小者。

除拥塞窗口cwnd和发送窗口swnd,发送方还需要维护一个叫作慢开始门限(SSThresh)的状态变量ssthresh:

  • 当cwnd<ssthresh时,使用慢开始算法。
  • 当cwnd>ssthresh时,停止使用慢开始算法而改用拥塞避免算法。
  • 当cwnd=ssthresh时,既可使用慢开始算法,也可使用拥塞避免算法。
慢开始和拥塞避免

请添加图片描述

在TCP双方建立连接时,拥塞窗口cwnd的初始值被设置为1,这是因为主机刚开始发送数据时完全不知道网络的拥塞情况,如果立即把大量的数据都注入网络中,就有可能引起网络拥塞。经验证明,较好的方法是由小到大逐渐增大发送方的拥塞窗口cwnd的数值,直到发生拥塞。另外,还需要设置慢开始门限ssthresh的初始值16。

执行慢开始算法时,发送方每收到一个对新报文段的确认时,就把拥塞窗口cwnd的值加1,然后开始下一轮的传输。

当拥塞窗口cwnd的值增长到慢开始门限ssthresh的值时,就改为执行拥塞避免算法。由于发送方的拥塞窗口cwnd的初始值为1,而发送窗口swnd的值始终等于cwnd的值(假定条件),因此发送方一开始只能发送1个TCP数据报文段。换句话说,cwnd的值是多少,就能发送多少个TCP数据报文段。

在上述例子中,TCP的慢开始算法和拥塞避免算法的执行过程如下:

1)发送方给接收方发送0号数据报文段。接收方收到后,给发送方发回对0号数据报文段的确认报文段。发送方收到该确认报文段后,将cwnd的值加1增大到2。

2)发送方给接收方发送1~2号共2个数据报文段。接收方收到后,给发送方发回对1~2号数据报文段的确认报文段。发送方收到确认报文段后,将cwnd的值加2增大到4。

3)发送方给接收方发送3~6号共4个数据报文段。接收方收到后,给发送方发回对3~6号数据报文段的确认报文段。发送方收到确认报文段后,将cwnd的值加4增大到8。

4)发送方给接收方发送7~14号共8个数据报文段。接收方收到后,给发送方发回对7~14号数据报文段的确认报文段。发送方收到确认报文段后,将cwnd的值加8增大到16。至此,发送方当前的cwnd值已经增大到了ssthresh值。因此要改用拥塞避免算法,也就是每个传输轮次结束后,cwnd的值只能线性加1,而不像慢开始算法那样,每个传输轮次结束后cwnd的值按指数规律增大。

请添加图片描述

5)发送方给接收方发送15~30号共16个数据报文段。接收方收到后,给发送方发回对15~30号数据报文段的确认报文段。发送方收到确认报文段后,将cwnd的值加1增大到17。

6)发送方给接收方发送31~47号共17个数据报文段。接收方收到后,给发送方发回对31~47号数据报文段的确认报文段。发送方收到确认报文段后,将cwnd的值加1增大到18。

7)随着传输轮次的增加,cwnd的值每轮次都线性加1,cwnd的值线性加1增大到了24。

8)发送方给接收方发送171~194号共24个数据报文段。假设这24个数据报文段在传输过程中丢失了几个。这必然会造成发送方对这些丢失报文段的超时重传。发送方以此判断网络可能出现了拥塞,需要调整自己的cwnd值和ssthresh值:

  • 将ssthresh值调整为发生拥塞时cwnd值的一半,对于本例为24/2=12。
  • 将cwnd值减小为1,并重新开始执行慢开始算法。

9)发送方重新开始执行慢开始算法,让cwnd值按指数规律增大。当慢开始算法执行到cwnd值增大到新的ssthresh值12时,就停止使用慢开始算法,转而执行拥塞避免算法。

请添加图片描述

快重传和快恢复

有时个别TCP报文段会在网络中丢失(例如由于误码被路由器丢弃),但实际上网络并未发生拥塞。这将导致发送方超时重传并误认为网络发生了拥塞。

采用快重传算法可以让发送方尽早知道发生了个别报文段的丢失。

所谓快重传,就是使发送方尽快(尽早)进行重传,而不是等超时重传计时器超时再重传。这就要求接收方不要等待自己发送数据时才进行捎带确认,而是要立即发送确认,即使收到了失序的报文段也要立即发出对已收到的报文段的重复确认。发送方一旦收到3个连续的重复确认,就将相应的报文段立即重传,而不是等该报文段的超时重传计时器超时再重传。

请添加图片描述

1)发送方发送1号TCP数据报文段,接收方收到后给发送方发回对1号数据报文段的确认。在该确认报文段到达发送方之前,发送方还可以将发送窗口内的2号数据报文段发送出去。

2)接收方收到2号数据报文段后,给发送方发回对2号数据报文段的确认。在该确认报文段到达发送方之前,发送方还可以将发送窗口内的3号数据报文段发送出去,但该报文段丢失了,接收方自然不会给发送方发回针对该报文段的确认。

3)发送方还可以将发送窗口内的4号数据报文段发送出去。

4)接收方收到4号数据报文段后,发现这不是按序到达的报文段,因此给发送方发回针对2号数据报文段的重复确认,表明:“我现在期望收到的是3号数据报文段,但是我没有收到3号数据报文段,而是收到了未按序到达的数据报文段”。

5)发送方还可以将发送窗口内的5号数据报文段发送出去。

6)接收方收到5号数据报文段后,发现这不是按序到达的数据报文段,因此给发送方发回针对2号数据报文段的重复确认。

7)发送方还可以将发送窗口内的6号数据报文段发送出去。

8)接收方收到6号数据报文段后,发现这不是按序到达的数据报文段,因此给发送方发回针对2号数据报文段的重复确认。

9)至此,发送方会收到3个连续的对2号数据报文段的重复确认,就立即重传3号数据报文段。

10)接收方收到3号数据报文段后,给发送方发回针对6号数据报文段的确认,表明序号到6为止的数据报文段都正确接收了。这样就不会造成对3号数据报文段的超时重传,而是提早进行了重传。

对于个别丢失的报文段,发送方不会出现超时重传,也就不会误认为出现了拥塞而错误地把拥塞窗口cwnd的值减为1。实践证明,使用快重传可以使整个网络的吞吐量提高约20%。

与快重传算法配合使用的是快恢复算法。发送方一旦收到3个重复确认,就知道现在只是丢失了个别的报文段,于是不启动慢开始算法,而是执行快恢复算法:发送方将慢开始门限ssthresh的值和拥塞窗口cwnd的值都调整为当前cwnd值的一半,并开始执行拥塞避免算法。

请添加图片描述

四种算法的解释如下:

1)TCP发送方一开始使用慢开始算法,让拥塞窗口cwnd的值从1开始按指数规律

2)当cwnd值增大到慢开始门限ssthresh值时,停止使用慢开始算法,转而执行拥塞避免算法,让cwnd值按线性加1的规律增大。

3)当发生超时重传时,就判断网络可能出现了拥塞,要采取相应的措施:一方面将sthresh值调整为发生拥塞时cwnd值的一半;另一方面将cwnd值减小为1,并重新开始执行慢开始算法。

4)cwnd值又从1开始按指数规律增大。当增大到新的ssthresh值时,停止使用慢开始算法,转而执行拥塞避免算法,让cwnd值按线性加1的规律增大。

5)当发送方收到3个重复确认时,就进行快重传和快恢复。也就是立刻进行相应数据报文段的重传,并将ssthresh值和cwnd值都调整为当前cwnd值的一半,转而执行拥塞避免算法,让cwnd值按线性加1的规律增大。

请添加图片描述

TCP拥塞控制与网际层拥塞控制的关系

TCP拥塞控制与网际层所采取的策略有着密切的关系。网际层的策略对TCP拥塞控制影响最大的就是IP路由器的IP数据报丢弃策略。

当网络中有大量封装了TCP报文段的IP数据报涌入某个(或某些)路由器并造成路由器进行尾部丢时,这些TCP报文段的多个发送方就会出现超时重传,这会使得许多TCP连接在同一时间突然都进人TCP拥塞控制的慢开始阶段。全局同步使得全网的通信量骤降,而在网络恢复正常后,其通信量又突然增大很多。

为了避免网络中出现全局同步问题,在1998年提出了主动队列管理(Active Queue Managcment,AOM)。所谓“主动”,就是在路由器的队列长度达到某个阈值但还未满时就主动丢弃IP数据报,进而降低发送方的发送速率,因而有可能减轻网络的拥塞程度,甚至不出现网络拥塞。

AQM可以有不同的实现方法,其中曾流行多年的就是随机早期检测(Random Early Detection,RED),也称为随机早期丢弃(Random Early Drop,RED 或 Random Early Discard,RED)。

路由器需要维护两个参数来实现RED:队列长度最小门限和最大门限。当每一个IP数据报到达路由器时,RED就按照规定的算法计算出当前的平均队列长度。

  • 若平均队列长度小于最小门限,则把新到达的IP数据报存入队列进行排队。
  • 若平均队列长度大于最大门限,则把新到达的IP数据报丢弃。
  • 若平均队列长度在最小门限和最大门限之间,则按照某一丢弃概率p把新到达的IP数据报丢弃(这体现了丢弃IP数据报的随机性)

很显然,RED是在路由器的平均队列长度达到一定数值时,也就是出现网络拥塞的早期征兆时,就以概率p丢弃个别IP数据报。让拥塞控制只在个别的TCP连接上进行,因而避免了全局同步问题。

因特网工程任务组IETF曾经推荐在因特网中的路由器使用RED机制[RFC2309],但多年的实践证明,RED的使用效果并不理想。

TCP可靠传输的实现

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

请添加图片描述

在上图中,因特网上的两台主机基于已建立的TCP连接进行通信。为了简单起见,假定数据传输只在一个方向进行,也就是发送方给接收方发送TCP数据报文段,接收方给发送方发送相应的TCP确认报文段。

请添加图片描述

1)假设发送方收到了一个来自接收方的确认报文段。在报文段首部中的窗口字段的值为20,这是接收方表明自己的接收窗口rwnd的尺寸为20字节;确认号字段ack的值为31,这表明接收方期望收到下一个数据的序号是31,而序号到30为止的数据已经全部正确接收了。因此,发送方根据这两个字段的值构造出自己的发送窗口。

2)发送方在没有收到接收方确认的情况下,可以把序号落入发送窗口内的数据依次全部发送出去。凡是已经发送过的数据,在未收到确认之前都必须暂时保留,以便在超时重传时使用。

发送窗口具有前沿和后沿。发送窗口后沿的后面部分,是已发送并已收到确认的数据字节的序号。这些数据字节显然不需要再保存在发送缓存中了,可以将它们删除。发送窗口前沿的前面部分,是当前不允许发送的数据字节的序号。

发送窗口后沿的移动情况有两种可能:

  • 不动:没有收到新的确认,发送窗口的后沿不会移动。
  • 前移:收到新的确认,发送窗口的后沿向前移动。

发送窗口的后沿不可能向后移动。因为不可能撤销掉已收到的确认。

发送窗口前沿的移动情况有三种可能:

  • 前移:通常情况下,发送窗口的前沿是不断向前移动的。
  • 不动:
    • 一种情况是由于没有收到新的确认,接收方通知的窗口大小也没有改变。
    • 另一种情况是收到了新的确认,可向前移动相应位置,但接收方通知的窗口缩小了,前沿应该向后回缩,如果向前移动和向后回缩的尺寸恰好相等,就会使得发送窗口的前沿不动。
  • 向后收缩:这发生在接收方通知的窗口变小了。但TCP标准强烈不赞成这样做,因为很可能发送方在收到这个通知之前,就已经发送了窗口中的许多数据,现在又要收缩窗口,不让发送这些数据,显然就会产生错误。

3)假设发送方将发送窗口内的31~41号数据封装在几个不同的TCP数据报文段中发送出去。此时发送窗口的位置并没有改变,发送窗口内序号31~41的数据已经发送但未收到确认,而序号42~50的数据是允许发送但还未发送的。

可以使用三个指针P1、P2和P3分别指向相应的字节序号:

  • P1指向发送窗口内已发送但还未收到确认的第一个数据的序号。
  • P2指向发送窗口内还未发送的第一个数据的序号。
  • P3指向发送窗口前沿外的第一个数据的序号。

请添加图片描述

这样就可以用P1、P2和P3这三个指针来描述发送窗口的相关信息:

  • 小于P1的就是已发送并已收到确认的部分。
  • 大于等于P3的就是不允许发送的部分。
  • P3减P1可以得出当前发送窗口的尺寸。
  • P2减P1可以得出已发送但尚未收到确认的字节数量。
  • P3减P2可以得出允许发送但当前尚未发送的字节数量(又称为可用窗口或有效窗口)。

请添加图片描述

4)假设发送方之前发送的封装有32和33号数据的报文段到达了接收方。由于数据序号落在接收窗口内,所以接收方接受它们,并将它们存入接收缓存。但是它们是未按序到达的数据,因为31号数据还没有到达。这有可能是丢了,也有可能是滞留在网络中的某处。接收方只能对按序收到的数据中的最高序号给出确认。

因此接收方发出的确认报文段中的确认号仍然是31,也就是期望收到31号数据;窗口字段的值仍是20,表明接收方没有改变自己接收窗口的大小。发送方收到该确认报文段后,发现这是一个针对31号数据的重复确认,就知道接收方收到了未按序到达的数据。

5)现在假设封装有31号数据的报文段到达了接收方。接收方接受该报文段,将其封装的31号数据存入接收缓存。接收方现在可将接收缓存中的31~33号数据一起交付给应用进程,然后将接收窗口向前滑动3个序号,并给发送方发送确认报文段。该确认报文段中窗口字段的值仍为20,表明接收方没有改变自己接收窗口的大小;确认号字段的值为34,表明接收方已经收到序号到33为止的全部数据。

请添加图片描述

6)现在假设又有几个数据报文段到达了接收方,它们封装有37、38以及40号数据。这些数据的序号虽然落在接收窗口内,但它们都是未按序到达的数据,只能先暂存在接收缓存中。假设接收方先前发送的确认报文段到达了发送方。发送方接收后,将发送窗口向前滑动3个序号,发送窗口的尺寸保持不变,这样就有新序号51~53落入发送窗口内,而序号31~33被移出了发送窗口。现在可将31~33号数据从发送缓存中删除了,因为已经收到了接收方针对它们的确认。

请添加图片描述

7)发送方继续将发送窗口内序号42~53的数据封装在几个不同的报文段中发送出去。现在,发送窗口内的序号已经用完了,发送方在未收到接收方发来确认的情况下,不能再发送新的数据。序号落在发送窗口内的已发送数据,如果迟迟收不到接收方的确认,则会产生超时重传。

TCP的选择确认

在之前介绍TCP的快重传和可靠传输时,TCP接收方只能对按序收到的数据中的最高序号给出确认。当发送方超时重传时,接收方之前已收到的未按序到达的数据也会被重传。那么能否设法只传送缺少的数据而不重传已经正确到达(只是未按序到达)的数据呢?

回答是肯定的。TCP可以采用选择确认(Selective ACK,SACK)[RFC2018](建议标准)。

[RFC2018]规定,如果要使用选择确认SACK,那么在建立TCP连接时,就要在TCP首部的选项字段中加上“允许SACK”的选项,而且双方必须事先商定好。如果使用选择确认,那么原来TCP首部中的“确认号字段ack”的用法仍然不变。只是以后在各TCP报文段的首部中都增加了SACK选项,以便报告收到的不连续的字节块的边界。

由于TCP首部选项字段的长度最多只有40字节,而指明一个边界就要用掉4字节(因为序号有32比特,即4字节),因此在选项中最多只能指明4个字节块的边界信息。这是因为4个字节块共有8个边界,因而需要用32个字节来描述。另外还需要两个字节,一个字节用来指明使用了SACK选项,另一个字节用来指明这个选项要占用多少字节。如果要报告5个字节块的信息,那么至少需要42个字节,这就超过了选项字段40字节的最大长度。

相关推荐

  1. 计算机网络网络

    2024-02-06 22:20:02       48 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-02-06 22:20:02       16 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-02-06 22:20:02       16 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-02-06 22:20:02       15 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-02-06 22:20:02       18 阅读

热门阅读

  1. FastAdmin

    2024-02-06 22:20:02       36 阅读
  2. H12-821_315

    2024-02-06 22:20:02       29 阅读
  3. 数组对象过滤

    2024-02-06 22:20:02       32 阅读
  4. k8s的Deployment部署策略线上踩坑

    2024-02-06 22:20:02       26 阅读
  5. 算法.1-三大排序算法-对数器-二分

    2024-02-06 22:20:02       27 阅读
  6. 软件系统架构的演变历史介绍

    2024-02-06 22:20:02       30 阅读
  7. OpenHarmony开源鸿蒙开发之旅

    2024-02-06 22:20:02       33 阅读
  8. 系统架构设计师-21年-上午答案

    2024-02-06 22:20:02       28 阅读
  9. Droppy教程 | 轻量文件共享

    2024-02-06 22:20:02       35 阅读
  10. 【国产MCU】-CH32V307-模拟/数字转换器(ADC)

    2024-02-06 22:20:02       29 阅读
  11. centos找不到新建的硬盘信息

    2024-02-06 22:20:02       33 阅读