运输层

运输层

网络层的上一层就是运输层(也叫传输层)。从 TCP/IP 协议族的名称上,就能看出网络层的 IP 协议和运输层的 TCP 协议的重要性。一般如果需要开发偏底层的网络框架模块,开发人员最好对运输层有深入的了解。


一、运输层简述

在介绍网络层的时候,我们会说通信的两端主体是主机,而实际上从运输层的角度来看,真正进行通信的两个实体实际是主机中的进程,是一台主机的中一个应用进程在与另一台主机中的另一个应用进程进行通信。

运输层和网络层提供的通信
运输层和网络层提供的通信

运输层提供了应用进程之间端到端的逻辑通信。所谓“逻辑通信”是指,从应用层的角度来看,只要应用层把报文交给下面的运输层,运输层就可以把报文传输到接收方的运输层,就好像通信是沿着水平方向直接进行,而实际上数据的传输是经过了多个层级进行的。运输层对上层屏蔽了下面的细节。

运输层为应用进程提供逻辑通信
运输层为应用进程提供逻辑通信

二、TCP 和 UDP

根据不同应用层应用进程的需要,运输层有两种不同运输协议,面向连接的传输控制协议TCP(Transmission Control Protocol) 协议和无连接的用户数据报协议 UDP (User Datagram Protocol)。使用TCP时,进程间传输的数据单元叫 TCP 报文段(segment);使用UDP时,叫用户数据报(User Datagram) 。

采用 TCP 协议时,这种逻辑通信就相当于是一条全双工的可靠信道;UDP协议提供的逻辑通信道是不可靠信道。显然,可靠是需要代价的,为了提供可靠的面向连接的传输,TCP 协议需要占用更多的资源。虽然 UDP 不提供可靠交付,但是某些情况下 UDP 确实最有效的工作方式( Google 制定的 QUIC 就是一种基于 UDP 的低时延的互联网传输协议)。

常见使用UDP和TCP的应用层协议
常见使用UDP和TCP的应用层协议

三、端口和 Socket

1、端口

一个计算机内是可以同时运行多个进程的,那对于网络层来说,如何识别通信的源点和终点—— 不同的进程呢?运输层使用端口(port)来解决这个问题。 应用层的所以进程都通过运输层在传输到IP层,这叫复用;而IP层收到发送给各个应用进程的数据后,又要分别交付给各个软件进程,这叫分用。这时就需要对应用进程进行标示,这个就是端口port。

传送的报文只需要交给主机的某个端口,如何交付给进程由 TCP 和 UDP 来完成。用这种统一的方式,就能对TCP/IP体系的应用进程进行标识。端口号在 TCP 和 UDP 使用16位来标记,其最大值是65535(2^16 – 1 )。

常用应用层协议默认端口
常用应用层协议默认端口

2、Socket

每一个TCP 连接都可以唯一的被通信两端的套接字所确定,端口拼接到 IP 地址即构成了套接字(Socket)。只要使用 TCP/IP 协议通信,应用程序必须使用 Socket,这样才能与操作系统进行交互(系统调用)。套接字的作用更像是运输层和应用层上的应用进程之间的抽象接口

套接字上的进程是受应用程序控制的,而在套接字以下的运输层软件则是计算机操作系统控制。一般来说,应用程序的开发者对套接字以上的应用进程具有完全的控制,而很运输层只有很少控制。

下图是面向连接的 Socket 与系统调用示例图。另外注意 UDP 协议由于是无连接的,不使用Listen 和 accept。

Socket编程和系统调用
Socket编程和系统调用

四、UDP 协议

1、UDP主要特点

跟TCP相比,UDP 只在 IP 数据报服务上增加了很少的功能。其主要特点是:

  • 无连接:发送数据之前不需要建立连接,减少开销和发送数据的时延。
  • 不保证可靠交付:不需要维持复杂的状态关系,只关发送,允许丢包。可以由上层应用进行改进。
  • 面向报文:UDP 不会对上层教下来的报文进行合并和拆分,而是一次交付一个完整报文,报文大小的控制交给上层应用进程来解决。
  • 无拥塞控制:当网络情况不太好时,UDP不会进行速率控制。UDP允许丢弃数据来换取低时延。
  • 首部小:UDP的首部只要8个字节,相对于TCP的20个字节,小了很多。

2、UDP首部格式

UDP 协议的数据报包括首部和数据部分。首部的字段比较简单,共8个字节,4个字段。

UDP协议首部格式
UDP协议首部格式

首部4个字段各占2个字节,分别是源端口、目标端口、长度和检验和。长度是UDP用户数据报的长度,最小值是8(只有首部),检验和用于数据报的差错检测,出错需要丢弃。

从上图中可以看到,UDP协议中有一个伪首部。这个首部不会想下层和上层传递,仅仅是用作计算检验和。IP 数据报中,校验和只检验 IP 数据报的首部,而 UDP 则检查首部加数据部分。

下图是通过wireshark进行的一个 DNS (域名系统)回答报文抓包,DNS 在运输层就是 UDP 协议。

wireshark_UDP实例
wireshark_UDP实例

3、UDP实际应用

基于UDP的各种特点,一般使用场景为在网络情况比较好的内网需要广播应用的场景、处理速度快时延低但是可以接受少数丢包时的场景。常见的 DNS 请求和应答、DHCP 等都是在运输层使用的 UDP 协议。

  • DNS : DNS 请求应答需求简单,不需要复杂的 TCP 协议,同时需要低时延,所以采用 UDP 协议进行请求和应答是很合适的。
  • DHCP : 动态主机设置协议,用于内部网络或ISP自动分配 IP 地址,DHCP 使用在网络比较好的内网中,进行广播通信。
  • QUIC (Quick UDP Internet Connection):Google制定的一种基于UDP的低时延的互联网运输层协议,QUIC 在应用层上实现了重传、拥塞控制等功能,而在运输层不使用耗费资源较多的 TCP。

五、TCP 协议

UDP 是很“乐观”的,相信网络情况都是比较好的,不去考虑网络条件差的问题。而 TCP 设计初认为在复杂网络环境下,丢包、乱序、重传和拥塞都是很常见的情况,为了更好的应对复杂多遍的网络环境,TCP 本身提供了很多功能来应对这些问题,包括可靠传输、流量控制、拥塞控制等,接触这些之前,我们先看看 TCP主要特点和TCP报文段格式**。

1、主要特点

  • 面向连接:应用进程使用 TCP 协议在传输数据前后,需要建立连接和释放连接。类似打电话的拨号和挂断。
  • 点对点通信:TCP 协议提供端到端的通信服务,一个 TCP 连接只能是一对一的。
  • 可靠交付:通过 TCP 传输的数据,最终都是无差错、不丢失、不重复并且按序到达。
  • 全双工通信:TCP 连接的双端都有发送缓冲区和接收缓冲区,用来临时存放双向通信的数据。通信的双方可以同时发送数据。
  • 面向字节流“流”是指流入进程和流出进程的字节序列。TCP 协议把应用进程交下来的数据看成一连串的字节流。TCP 不保证发送进程和接收进程双方数据块一致。接收方的应用进程收到的字节流和发送方发出字节流必须完全一致,并且应用进程需要能够识别字节流,将其还原成有意义的应用层数据。如果发送方向 TCP缓存中发送了10 个数据块,TCP 可以根据网络情况、接收方的处理速率,将这批数据流重新划分或者组合发送。一次发送的数据并没有明显的边界。而 UDP 是面向报文段的,报文长度由应用进程决定。
  • 面向字节流
    面向字节流

2、TCP 报文段的首部格式

虽然 TCP 是面向字节流的,但是其发送的时候仍然是以报文段为单位传输的。TCP 协议的各个功能也体现在了其报文段首部的格式字段中。

TCP报文段的首部格式
TCP报文段的首部格式

 

TCP 报文段首部是固定 20 个字节。我们仍然以 4个字节(32位)为一个分隔进行说明,各个字段的含义如下:

(1)源端口和目的端口

各占 2 个字节,分别写入双方的端口号(0 – 65535)。

(2)报文段序号

4个字节,范围【0 – (2^32 – 1)】共 2^32 个序号,当超过最大序号后,又冲 0 开始。 TCP 连接中传输的每个字节流都按顺序编号,起始序号必须在建立连接时确定。例如,一报文段的序号字段值是301, 而携带的数据共有100字节。这就表明:本报文段的数据的第一个字节的序号是301,最后一个字节的序号是400。然,下一个报文段(如果还有的话)的数据序号应当从 401 开始,即下一个报文段的序号字段值应为 401。这个字段的名称也叫做“报文段序号”。

(3)确认号

占 4 个字节,是期望收到对方下一个报文段的第一个数据字节的序号。若确认号为N,则到 N-1为止所有的字节流都已经正确收到。 的数据的第-一个字节的序号。例如,一报文段的序号字段值是 301, 而携带的数据共有 100 字节。这就表明:本报文段的数据的第一个字节的序号是 301,最后一个字节的序号是 400。显然,下一个报文段(如果还有的话)的数据序号应当从 401 开始,即下一个报文段的序号字段值应为401。这个字段的名称也叫做“报文段序号”。

(4)数据偏移

占 4 位,它指出 TCP 报文段的数据起始处距离 TCP 报文段的起始处有多远。这个字段实际上是指出TCP报文段的首部长度。由于首部中还有长度不确定的选项字段,因此数据偏移字段是必要的。但应注意,“数据偏移”的单位是32位(即以4字节长为计算单位)。由于4位二进制数能够表示的最大十进制数字是15,因此数据偏移的最大值是60字节,这也是 TCP 首部的最大长度(即选项长度不能超过40字节)。
(5)保留:占6位,保留为今后使用,但目前应置为0。下面有6个控制位,用来说明本报文段的性质,它们的意义见下面的(6)~(11)。
(6)紧急URG (URGent)
当 URG=1 时,表明紧急指针字段有效。它告诉系统此报文段中有紧急数据,应尽快传送(相当于高优先级的数据),而不要按原来的排队顺序来传送。例如,用户键盘发送终端命令,这个数据报不应当然排在缓存末尾,使用URG=1 可以让数据插到数据流最前面。配合紧急指针(Urgent Pointer)使用,紧急指针可以指明紧急数据的字节数
(7)确认ACK (ACKnowledgment)

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

(8)推送PSH (PuSH)
当两个应用进程进行交互式的通信时,有时在一端的应用进程希望在键入一个命令后立即就能够收到对方的响应。在这种情况下,TCP就可以使用推送(push)操作。这时,发送方TCP把PSH置1,并立即创建一个报文段发送出去。接收方 TCP 收到 PSH= 1 的报文段,就尽快地(即“推送”向前)交付接收应用进程,而不再等到整个缓存都填满了后再向上交付。虽然应用程序可以选择推送操作,但推送操作很少使用。.
(9)复位RST (ReSeT)
当RST=1时,表明TCP连接中出现严重差错(如由于主机崩溃或其他原因),必须释放连接,然后再重新建立运输连接。RST置1还用来拒绝一个非法的报文段或拒绝打开一个连接。RST也可称为重建位或重置位。
(10)同步SYN (SYNchronization)
在连接建立时用来同步序号。当 SYN=1 而 ACK= 0 时,表明这是一个连接请求报文段。对方若同意建立连接,则应在响应的报文段中使 SYN=1 和ACK=1。因此,SYN 置为 1 就表示这是-一个连接请求或连接接受报文。关于连
接的建立和释放。
(11)终止FIN 
用来释放一个连接。当 FN=1 时,表明此报文段的发送方的数据已发送完毕,并要求释放运输连接。
(12)窗口
占 2 字节。窗口值是 [0, 2^l6- 1] 之间的整数。窗口指的是发送本报文段的一方的接收窗口(而不是自己的发送窗口)。窗口值告诉对方:从本报文段首部中的确认号算起,接收方目前允许对方发送的数据量(以字节为单位)。之所以要有这个限制,是因为接收方的数据缓存空间是有限的。总之,窗口值作为接收方让发送方设置其发送窗口的
依据。这个窗口值是经常动态变化的,发送方需要考虑到窗口大小。

(13)检验和
占2字节。检验和字段检验的范围包括首部和数据这两部分。和 UDP 用户数据报-一样,在计算检验和时,要在TCP报文段的前面加上12字节的伪首部。伪首部的格式与UDP用户数据报的伪首部一样。但应把伪首部第4个字段中的17 改为 6 ( TCP的协议号是6 ),把第5字段中的 UDP 长度改为 TCP 长度。

接收方收到此报文段后,仍要加上这个伪首部来计算检验和。若使用IPv6,则相应的伪首部也要改变。
(14)紧急指针
占2字节。紧急指针仅在URG = 1时才有意义,它指出本报文段中的紧急数据的字节数(紧急数据结束后就是普通数据)。因此,紧急指针指出了紧急数据的末尾在报文段中的位置。当所有紧急数据都处理完时,TCP就告诉应用程序恢复到正常操作。值得注意的是,即使窗口为零时也可发送紧急数据。
(15)选项
长度可变,最长可达40字节。当没有使用“选项“时,TCP的首部长度是 20字节。

3、可靠传输

可靠的基本意思就是不出差错,数据包能完整无误的传输到终点传输层。

最简单的可靠传输方式就是使用停止等待协议,传送一个分组后停止发送,等待收到确认分组后再传递另一个分组。但是由于这种方式不够高效(信道利用率低),现在使用流水线式发送。发送方可以连续发送多个分组,而接收方采用累计确认的方式,对按序到达的最后一个分组发送确认

(1)以字节为单位的滑动窗口

TCP 协议使用字节为单位的滑动窗口协议来实现可靠传输。下图中,为了说明滑动窗口协议原理,我们暂时只考虑 A 向 B 单方向传输数据,A 发送数据,B 发送确认。

发送窗口的位置由窗口前沿和后沿的位置共同确定,正常发送的过程中,数据不断发送和确认时,前沿和后沿都是不断前移的。窗口的大小受到 B 确认报文中窗口数值影响(不能超过 B 的接收能力),另外窗口大小还受到网络拥塞的影响。没有收到 B 的确认时,窗口前沿保持不动,收到确认后,如果收到 B 的接收窗口减小,A 的窗口前沿可能还是不动,而后沿根据确认的需要前移,整个发送窗口减小。

滑动窗口
滑动窗口

上图现假定 A 收到了 B 发来 的确认报文段,其中窗口是20字节,而确认号是31(30以前的数据已经收到了,期望的下一个序号是31),于是 A 构造出发送窗口(暂时不考虑网络拥塞)。

(2)乱序和丢包

“窗口”是一个比较形象的比喻,描述一个窗口需要 3 个指针。

例如下图 P1 、P2、 P3这三个指针指向的几个部分的意义如下:
小于P 1 的是已发送并已收到确认的部分,而大于P 3 的是不允许发送的部分。
P 3 –P 1 =A的发送窗口
P 2 –P 1 =已发送但尚未收到确认的字节数
P 3 –P 2 =允许发送但当前尚未发送的字节数(又称为可用窗口 或有效窗口 )”

A发送11个字节的数据
A发送11个字节的数据

 

现在 A 发送了 11 个字节的数据(31-41),于是发送窗口中有11个字节变成已发送未确认。B收到了部分数据,32和33 ,但是31暂时未收到,B能够给出的确认号仍然是31

可以看到字节到达的顺序乱序了,这个时候 B 的接收缓冲区会将 32 和 33 先缓存起来,等待缺失的数据 31。31有两种情况,一个是彻底丢了,一个是滞留在网络某处。这个时候如何处理?有下面几种方式。

超时重试

如果 31 在 A 的 超时时间内仍未收到确认,A 会自动重传。对于每一个没有收到 ACK的包, 发送方都有一个定时器,超过一定时间就会重新尝试发送。如何来确定重传的时间是个比较复杂的问题。

TCP 采用了一种自适应重传算法。记录每一个报文段的往返时间 RTT(不断变化),进行加权平均另外再根据RTT的波动范围,计算出最终的超时时间。为了让结果更加合理,在计算加权平均RTT结果时,重传的报文不纳入采样中。这个时候重传仍然失败的话,超时时间是不会改变的。上面的算法被再次修正,每遇到超时重传,就将下一次的超时时间间隔设置为更长,一般是两倍时间。

快速重传

超时重传的问题是,超时的周期相对较长,有没有更迅速的方式呢?使用快速重传机制。接收方其实是知道哪个序号的字节丢了。当接收方发现收到的序号大于所期望的序号时,就会检测到数据流中有间隔,于是发送3个冗余的上一个 序号的 ACK,接收方收到后,就会马上重传这个序号,而不是等超时时间到。

选择确认 SACK

发送和接收方可以约定好首部选项字段 ‘SACK’ (Selective Acknowledgment),然后将接收方的目前接收到的缓存地图告知发送方。这样发送方就可以针对性的重复丢失的数据。

4、流量控制

流量控制也是通过滑动窗口来实现的。如果接收方处理速度太慢,可能会导致接收方的缓存没有空间继续接收,进而会造成数据丢失。TCP 协议下,接收方会将确认信息中的窗口字段变小,甚至变成0,从而让发送方降低速率。

发送方的发送窗口是不能超过接收方给出的接收窗口的。因此通过窗口字段,接收方可以控制。

极端情况下,接收窗口可以设置成 0,这个时候发送方就会停止发送数据。

为了防止接收方窗口恢复的报文段丢失,发送方会定时发送 零窗口探测报文段,看是否有机会调整窗口大小。这个时候接收方要注意,不能一空出一点窗口就告诉发送方,因为接收窗口太小(例如紧紧腾出几个字节)的话,发送效率实在太低(首部长度占比高),而且又会马上填满窗口。这种问题叫低能窗口综合征。接收方需要等缓存足够了(比如一半缓冲区)再更新窗口大小。

5、拥塞控制

流量控制是指点对点通信量的控制,是端到端的问题;而拥塞控制则针对的是网络情况,防止网络中路由或链路过载。发送方的发送速率可不能只受接收方的控制,当然还要考虑网络状况。

(1)拥塞

什么是拥塞?当对某一资源(网络资源包括链路带宽、处理机处理和交换节点的缓存等)的需求超过的其能提供范围,网络性能会就会变差,称作拥塞(congestion)。判断网络拥塞的依据就是超时

拥塞的原因往往是多因素的,常见的例如某些路由器的缓存空间不足、处理机速度不够。路由缓存不足时可能会发生分组丢失,这个时候又会导致分组重传加剧网络拥塞

TCP的拥塞控制其实是和网络层有密切关系的。关系在哪?路由器的分组丢弃策略。

简单的丢弃策略就是等缓冲区满之后就丢弃,导致尾部一连串的分组都会被丢弃,而分组一般属于不同连接,这种尾部丢弃策略同时影响多个TCP连接,使得多个TCP连接进去慢开始状态,网络通信量突然大量下降,恢复后又突然上升。这种方式可能会造成拥塞的间歇性出现。

于是出现了主动队列管理AQM(Active Queue Management)。

AQM就是对路由中分组排队进行智能管理而不是简单的进行尾部丢弃。例如当路由的缓存到指定值(通过一定算法算出,而不是到达最大值)就后开始丢弃分组。这样的策略当出现网络拥塞的征兆时,就可以提醒发送方放慢发送速度了。

目前有多重算法来实现AQM,不过都处于实验阶段,还没有哪种实现成为 IETF 标准。

(2)拥塞控制

为了先聚焦在拥塞控制,这里假设接收方缓存足够并且处理能力十分强大,这个时候发送窗口主要由网络的拥塞程度来决定。

TCP进行拥塞控制,也是通过窗口来进行,这个窗口叫做拥塞窗口(cwnd),由发送方维护(其实发送方和接收方是相对的,在数据传输阶段,肯定是双向通信的,两边相对对方即是发送方又是接收方,为了便于理解,这里我们只需要关注一个方向的数据传输即可)。

①慢开始

一开始 TCP 是不知道网络情况的,如果你开始就已大数据量向网络中注入,很可能会加剧网络拥塞,所以需要从小到大逐渐增大发送窗口,这叫慢开始。如果网络情况良好,往返延时低,这个时候为了提高传输效率,需要以指数型增长的形式来加大拥塞窗口。每经历一个传播轮次(从开始发送N个报文到收到了已发送的最后一个字节的确认,这个过程叫做一个传播轮次,经历的时间是往返时间 RTT),cwnd 翻倍。在实际中,接收方没收到一次新报文段的确认,就将拥塞窗口(cwnd)加1,于是拥塞窗口以1、2、4、8这样的指数形式增长,起点低但是增长速率很快。

实际中拥塞窗口(cwnd)大小的单位是字节数,下面为了方面起见我们以报文段的个数作为窗口大小单位。

把窗口的单位改为 报文段的个数 。实际上应当是“拥塞窗口仅增加一个MSS的大小, 单位是字节 ”。在具体实现拥塞避免算法的方法时可以这样来完成:只要收到一个新的确认,就使拥塞窗口cwnd增加(MSS×MSS/cwnd)个字节。例如,假定cwnd等于10个MSS的长度,而MSS是1460字节。发送方可一连发送14600字节(即10个报文段)。假定接收方每收到一个报文段就发回一个确认。于是发送方每收到一个新的确认,就把拥塞窗口稍微增大一些,即增大0.1MSS=146字节。经过一个往返时间RTT(或一个传输轮次)后,发送方共收到10个新的确认,拥塞窗口就增大了1460字节,正好是一个MSS的大小

拥塞窗口的变化情况
拥塞窗口的变化情况

②拥塞避免

拥塞窗口(cwnd)指数型的增长是相当快的,为了防止过大的速率引起拥塞,需要一个阈值,叫做慢开始门限(sshresh),其作用如下:
cwnd < ssthresh时,使用上述的慢开始算法。
cwnd > ssthresh时,停止使用慢开始算法而改用拥塞避免算法。
cwnd =ssthresh时,既可使用慢开始算法,也可使用拥塞避免算法。

拥塞避免 算法的思路是让拥塞窗口 cwnd 缓慢地增大,即每经过一个往返时间RTT就把发送方的拥塞窗口cwnd加1 。这样使得网络可以不容易出现拥塞。

③快重传算法和快恢复

虽然拥塞窗口现在增长很慢,只要它在一直增长但是后面还是会出现拥塞,导致超时出现。这个时候(上图➋位置)又会开始执行慢开始算法,调整门限值ssthresh=cwnd/2=12,同时设置拥塞窗口cwnd=1。当拥塞窗口cwnd=ssthresh=12时(图中的点➌,这是新的 ssthresh值),改为执行拥塞避免算法,拥塞窗口按线性规律增大。

当拥塞窗口cwnd=16时(图中的点➍),出现了一个新的情况,就是发送方一连收到3个对同一个报文段的重复确认(图中记为3-ACK)。有时,个别报文段会在网络中丢失,但实际上网络并未发生拥塞。这个时候不应该进入慢启动阶段,会导致传输效率降低。TCP 认为这种个别丢包是不严重的,采用快重传算法可以让发送方尽早知道发生了个别报文段的丢失。于是不启动慢开始,而是执行快恢复 算法。这时,发送方调整门限值ssthresh=cwnd/2=8,同时设置拥塞窗口cwnd=ssthresh=8(见图中的点➎),并开始执行拥塞避免算法 。

6、连接管理

(1)连接建立-三次握手

TCP 建立连接的过程叫做握手,握手需要在客户和服务器之间交换三个 TCP 报文段。下图是三次握手的过程。

三次握手建立
三次握手建立

A:我是A,请求发送数据。

B:我是B,好的,收到请求,可以发送。

A:收到 B 回复,进入数据发送状态。

客户端状态变化:CLOSED ->SYN-SENT -> ESTABLISHED

客户端状态变化:CLOSED ->LISTEN -> SYN-RCVD -> ESTABLISHED

客户端A 向服务端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状态

为什么不能用两次握手?

为了防止失效的连接请求报文段又传到服务端同时三次握手保证的双方都有数据的请求和应答

假设用两次握手,这样一种情况,A 发送的连接请求报文段延误的很久才到达,中间 A 可能经过的很多次重发。但是 B 以为是 A 重新发出的连接请求,于是发出确认报文段,同意连接,两次握手的情况下,新的连接这个时候已经建立成功了。但其实 A 并没有发出新的请求,不认为连接建立,因此不会向 B 发数据。这个时候 B 的资源就白白浪费了。而对三次握手则没有这个问题。

为什么不用四次握手?

四次以上的握手都是没有必要的,三次的时候已经能够保证双方都有请求和应答了。再多多少次,也不会解决更多问题。

(2)连接释放-四次挥手

四次挥手释放连接
四次挥手释放连接

 

A : 我是 A ,我的数据发送完毕。请求关闭连接

B:收到关闭请求。请等待,我这边数据发送完毕会另行通知。

B:数据已发送完毕,可以关闭。

A:好的。一段时间(2MSL)后我会自动关闭。

数据传输结束后,通信的双方都可释放连接。A 的应用进程先向其 TCP 发出连接释放报文段,并停止再发送数据。A把连接释放报文段首部的终止控制位FIN置1其序号seq=u,它等于前面已传送过的数据的最后一个字节的序号加1。这时A进入FIN-WAIT-1(终止等待1)状态,等待B的确认。

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。然后进入到TIME-WAIT(时间等待)状态。请注意,现在TCP连接还没有释放掉。必须经过时间等待计时器 (TIME-WAIT timer)设置的时间 2MSL 后,A 才进入到 CLOSED 状态。时间 MSL 叫做最长报文段寿命 (Maximum Segment Lifetime),RFC 793建议设为2分钟。实际应用中经常是30s 或 60s

为什么 A 在 TIME-WAIT 状态必须等待 2MSL 的时间呢?

为了防止 B 在超过 2MSL 时间内没有收到 A 最后发出的确认报文。 2MSL 时间可以让 B 超时重传这个 FIN+ACK报文段。如果 A 不等待这段时间,则无法收到 B 的重传,也无法再次发送最后确认,会导致B无法正确进入 CLOSED 状态。

(3)有限状态机

TCP 在请求连接和释放连接过程中,每次发送或接收报文都会伴随着状态的改变。下图中每一个方框即TCP可能具有的状态。粗实线箭头表示对客户进程的正常状态变化,粗虚线箭头表示对服务器进程的正常状态变化,另一种细线箭头表示异常变化

有限状态机
有限状态机

 

六、其他

1、参考

计算机网络(第7版)

极客时间-趣谈网络协议

RFC 5681

发表评论

电子邮件地址不会被公开。 必填项已用*标注