【转载】TCP

作者:冠状病毒biss
链接:https://www.nowcoder.com/discuss/444099
来源:牛客网

TCP

TCP 特点

TCP 是面向连接的运输层协议,一个应用进程在向另一个进程发送数据之前,两个进程必须先建立 TCP 连接,发送某些预备报文段,以建立确保数据传输的参数。作为 TCP 连接建立的一部分,连接的双方都将初始化与 TCP 连接相关的许多状态变量。这种连接不是电路交换网络中的端到端电路这种物理连接,而是一种逻辑连接,TCP 报文要先传送到 IP 层加上 IP 首部后,再传到数据链路层,加上链路层的首部和尾部后才离开主机发送到物理层。

TCP 连接提供全双工服务,允许通信双方的应用进程在任何时候都能发送数据。TCP 连接的两端都有各自的发送缓存和接收缓存,用来临时存放双向通信的数据。在发送时,应用程序把数据传送给 TCP 缓存后就可以做自己的事,而 TCP 在合适的时候会把数据发送出去。在接收时,TCP 把收到的数据放入缓存,上层应用程序会在合适的时候读取缓存中的数据。

TCP 连接是点对点的,每一条 TCP 连接只能有两个端点,即只能是单个发送方和单个接收方之间的连接。

TCP 提供可靠的交付服务,通过 TCP 连接传送的数据无差错、不丢失、不重复,并且按序到达。

TCP 是面向字节流的,流是指流入到进程或从进程中流出的字节序列。面向字节流的含义是:虽然应用程序和 TCP 的交互是一次一个数据块,但是 TCP 把应用程序交下来的数据仅仅看成一连串无结构的字节流。TCP 不保证接收方应用程序收到的数据块和发送方应用程序发出的数据块具有对应大小的关系,但是接收方应用程序收到的字节流必须和发送方应用程序发出的字节流完全一样。接收方应用程序必须有能力识别收到的字节流,并把它还原成有意义的应用层数据。


TCP 报文结构

TCP 传送的数据单元是报文段,一个 TCP 报文段分为首部和数据两部分。首部的前 20 个字节是固定的,后面有 4n 字节是根据需要而增加的选项,因此 TCP 首部的最小长度是 20 字节。 TCP 首部的重要字段如下:

源端口和目的端口:各占 2 字节,分别写入源端口号和目的端口号,TCP 的分用功能是通过端口实现的,分用就是指运输层从 IP 层收到发送给各应用进程的数据后,把数据交付给正确的套接字的工作。

序号:占 4 字节。TCP 是面向字节流的,在一个 TCP 连接中传送的字节流中的每一个字节都按顺序编号,首部中的序号字段值指的是本报文段所发送的数据的第一个字节的序号。序号使用 mod2^32^ 计算,每增加到 2^31^-1 后下一个序号就又回到 0。

确认号:占 4 字节,是期望收到对方下一个报文段的第一个数据字节的序号。如果确认号为 N,代表到序号 N-1 为止的所有数据已经正确收到。序号有 32 位长,一般情况下可以保证当序号重复使用时,旧序号的数据早已通过网络到达终点了。

数据偏移:占 4 字节,实际是TCP 报文段的首部长度,指出了 TCP 报文段的数据起始处到 TCP 报文段的起始处的距离。由于首部中有长度不确定的选项字段,因此数据偏移字段是必要的。

标志字段:占 6 位。URG 是紧急标志,URG=1 时告诉系统此报文段中有紧急数据,应尽快传送,而不按照原来的排队顺序传送,和紧急指针配合使用,紧急指针指出了本报文段中紧急数据的字节数和位置。ACK 是确认标志,ACK=1 时表示成功接收了报文段。SYN 是同步标志,在建立连接时用来同步序号,当 SYN=1 而 ACK=0 时,表示一个连接请求报文段,响应时 SYN 和 ACK 都为 1,因此 SYN=1 表示一个连接请求或连接响应报文。FIN 是终止标志,用来释放一个连接,当 FIN=1 时表示报文段发送方的数据已发送完毕,并要求释放连接。PSH 是推送标志,PSH=1 时接收方就不等待整个缓存填满了再向上交付而是尽快交付数据。RST 是复位标志,当 RST=1 时表示 TCP 连接出现了严重错误,必须释放连接再重新建立连接。

接收窗口:占 2 字节,指的是发送本报文段一方的接收窗口,告诉对方从本报文首部的确认号算起允许对方发送的数据量。窗口值是用来限制发送方的发送窗口的,因为接收方的数据缓存空间是有限的。

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


可靠传输协议 ARQ

自动重传请求 ARQ 包括了停止等待协议、回退 N 步协议和选择重传协议,后两种结合了窗口机制,属于连续 ARQ 协议。

停止等待协议

停止等待就是每发送完一个分组就停止发送,等待对方的确认,在收到确认之后再发送下一个分组。停止等待协议包括了三种情况:

1. 无差错情况

A 发送分组 M1,发送完后就暂停并等待 B 的确认。B 收到了 M1 之后就向 A 发送确认,当 A 收到确认之后就再发送下一个分组 M2

2. 出现差错的情况

当 B 收到 M1 时检测出了差错就丢弃了 M1,其他什么也不做,也可能是 M1 在传输过程中丢失了,B 什么都不知道。在这两种情况下 B 都不会发送任何确认信息,解决方法是:A 只要超过一段时间没有收到确认就认为刚才发送的分组丢失了,因而重传前面发过的分组,这就叫超时重传。要实现超时重传,就要在每发送完一个分组时设置一个超时计时器,如果在超时计时器到期之间收到了对方的确认就进行撤销。

有三点需要注意:① A 在发送完一个分组后必须暂时保留已发送分组的副本在超时重传时使用,只有在收到确认后才清除副本。② 分组和确认分组都必须进行编号,这样才能明确是哪一个发送出去的分组进行了确认。③ 超时计时器设置的时间应当比数据在分组传输的平均往返时间稍长一些,如果设置过短会产生不必要的重传,如果设置过长会降低通信效率。

3. 确认丢失和确认迟到

B 发送的对 M1 的确认丢失了,A 在设定的超时重传时间内没有收到确认,并不值得是自己发送的问题还是 B 的问题,因此 A 就会超时重传。假设 B 又收到了重传分组 M1,此时 B 会采取两个行动:① 丢弃这个分组,不向上层交付。② 重新向 A 发送确认。

还有另一种情况就是 B 发送的确认并没有丢失但是迟到了,A 会受到重复的确认,此时 A 会收下并丢弃。通常 A 最终总是可以收到对所有发出的分组的确认,如果 A 不断重传分组但总是收不到确认,就说明通信线路质量太差,不能进行通信。

停止等待协议的优点是简单,但缺点是信道利用率太低。为了提高传输效率,发送方可以不使用停止等待协议,而是采用流水线传输。流水线传输就是发送方可连续发送多个分组,不必每发送完一个分组就停下来等待对方的确认。这样可以使信道上一直有数据不间断地传送,流水线传输中可能会遇到差错,解决差错的两种基本方法是回退 N 步和选择重传。


回退 N 步协议

在回退 N 步即 GBN 协议中,允许发送方发送多个分组而不需要等待确认。在 GBN 中发送方看到的序号可以分为四个范围,已经发送且被确认的的序号、已经发送还未确认的序号、允许发送但还未发送的序号和不允许发送的序号。其中已经发送但还未确认的序号和允许发送但还未发送的序号可以被看作一个长度为 N 的窗口,随着协议的运行该窗口在序号空间向前滑动,因此 GBN 协议也被称为滑动窗口协议。

GBN 发送方必须响应三种类型的事件:

  • 上层的调用

    当上层调用发送方法时,发送方首先检查发送窗口是否已满,即是否有 N 个已发送但未确认的分组。如果窗口未满,则产生一个分组并将其发送并更新相应变量,如果窗口已满,发送方指需将数据返回给上层,隐式说明该窗口已满。实际实现中,发送方更可能缓存而不是立即发送这些数据,或者使用同步机制允许上层在仅当窗口不满时才调用发送方法。

  • 收到一个 ACK

    在 GBN 协议中,对序号为 n 的分组确认采用累积确认的方式,对按序到达的最后一个分组发送确认,表明接收方已经正确接收到序号为 n 的以前且包括 n 在内的所有分组。例如发送了序号为 1~5 的五个分组,除了第三个全部收到了,那么确认序号就是 2。

  • 超时事件

    回退 N 步的名字来源于出现丢失和时延过长时发送方的行为,就像在停止等待协议中那样,如果超时,发送方会重传所有已经发送但还未确认过的分组。如果收到一个 ACK,但仍有已发送但未确认的分组,则计时器也会重新启动。

在 GBN 协议中,接收方丢弃所有失序分组,即使是正确接收的也要丢弃,这样做的理由是接收方必须按序将数据交付给上层。这种做法的优点是接收缓存简单,即接收方不需要缓存任何失序分组。不过丢弃一个正确失序分组的缺点是随后对该分组的重传也许也会错误,而导致更多的重传。


选择重传协议

GBN 协议潜在地允许用多个分组填充流水线,因此避免了停止等待协议中的信道利用问题,但是 GBN 本身也存在性能问题,单个分组的差错就能引起 GBN 重传大量分组,许多分组根本没有重传的必要。随着信道差错率的增加,流水线可能会被这些不必要重传的分组所充斥。

选择重传即 SR 协议,通过让发送方仅重传哪些它怀疑在接收方出错的分组而避免了不必要的重传。这种个别的、按需重传要求接收方逐个地确认正确接收的分组,再次用窗口长度 N 来限制流水线中未完成和未被确认的分组数。与 GBN 不同的是,发送方已经收到了窗口中对某些分组的 ACK。

SR 接收方将确认一个正确接收的分组而不管其是否按需,失序的分组将被缓存直到所有丢失分组都被收到为止,这时才可以将一批分组按序交付给上层。


TCP 可靠原理

TCP 的可靠传输包含很多机制,例如使用检验和来检测一个传输分组中的比特错误、使用定时器来用于超时重传一个分组、使用序号来检测丢失的分组和冗余副本、使用确认来告诉发送方确认的分组信息、使用否定确认来告诉发送方某个分组未被正确接收。

TCP 的发送方仅需维持已发送过但未被确认的字节的最小序号和下一个要发送的字节的序号,从这种角度看 TCP 更像一个 GBN 协议。但是 TCP 和 GBN 有一些显著的区别,许多 TCP 实现会将正确接收但失序的报文段缓存起来。当分组 n 丢失时,GBN 会重传 n 之后的所有分组,但是 TCP 至多只会重传分组 n。对 TCP 提出的一种修改意见是选择确认,它允许 TCP 接收方有选择地确认失序报文段,而不是累积地确认最后一个正确接收的有序报文段,从这个角度看 TCP 又像 SR 协议。因此 TCP 的差错恢复机制是一种 GBN 和 SR 的结合体

除此之外,TCP 还使用流量控制拥塞控制来保证可靠性。

滑动窗口

滑动窗口以字节为单位。发送端有一个发送窗口,窗口中的序号是允许发送的序号,窗口的后沿是已经发送并且确认的序号,窗口的前沿是不允许发送的序号。窗口的后沿可能不动(代表没有收到新的确认),也有可能前移(代表收到了新的确认),但是不会后移(不可能撤销已经确认的数据)。窗口的前沿一般是向前的,也有可能不动(表示没有收到新的请求或对方的接收窗口变小),也有可能收缩,但 TCP 强烈不建议这么做,因为发送端在收到通知前可能已经发送了很多数据,此时如果收缩窗口可能会产生错误。

滑动窗口的状态需要3个指针p1,p2 和 p3。p1 之前的序号表示已经发送并且确认的序号,p1p2 的序号表示已经发送但还没有确认的序号,p2p3 表示允许发送的序号,也叫可用窗口,p1~p3 表示发送窗口,p3 之后的序号表示不可发送的序号。

发送缓存用来暂时存放发送应用程序传给发送方 TCP 准备发送的数据和已经发送但还没确认的数据。接收缓存用来暂时存放按序到达的但尚未被应用程序读取的数据以及未按序到达的数据。

注意三点:① 发送窗口根据接收窗口设置,但并不总是一样大,还要根据网络的拥塞情况调整。② 对于不按序到达的数据,TCP 通常存放在接收窗口,等到字节流缺少的字节收到后再按序交付上层应用程序。③ 接收方必须有累积确认功能,可以减小传输开销,可以在合适的时候发送确认,也可以在自己有数据需要发送时捎带确认。但是接收方不能过分推迟发送确认,不能超过0.5秒。

流量控制

如果某个应用程序读取数据的速度较慢,而发送方发送得太多、太快,发送的数据就会很容易使连接的接收缓存溢出,TCP 为它的应用程序提供了流量控制以消除发送方使接收方缓存溢出的可能性。流量控制是一个速度匹配服务,即发送方的发送速率与接收方的应用程序读取速率相匹配。

TCP 通过让发送方维护一个接收窗口的变量来提供流量控制。通俗地说,接收窗口用于给发送方一个指示,该接收方还有多少可用的缓存空间,因此方法方的发送窗口不能超过接收方给出的接收窗口的数值。因为 TCP 是全双工通信,在连接两端的发送方都各自维护一个接收窗口。

当接收窗口 rwnd 减小到 0 时,就不再允许发送方发送数据了。但是可能存在一种情况,当发生了零窗口报文段不久后,发送方的接收缓存又有了一些存储空间,因此又发生了新的报文说明自己的接收窗口大小,但是这个报文可能会在传输过程中丢失。接收方就会一直等待发送方的非零窗口通知,而发送方也一直在等待接收方发送数组,形成一种死锁的状态。为了解决这个问题,TCP 为每一个连接设有一个持续计时器,只要 TCP 连接的一方收到对方的零窗口通知就启动该计时器,到期后发送一个零窗口探测报文,如果仍为 0 就重新设置计时器的时间,如果对方给出了新的窗口值就可以解决可能出现的死锁问题。

还有一种问题叫做糊涂窗口综合症,当接收方处理接收缓冲区数据很慢时,就会使应用进程间传送的有效数据很小, 极端情况下有效数据可能只有 1 字节但传输开销却有 40 字节(20字节的 IP 头以及 20 字节的 TCP 头) ,导致网络效率极低。为了解决这个问题,可以让接收方等待一段时间,使得接收缓存有足够的空间容纳一个最长报文段或者等到接收缓存已有一半的空闲空间。发送方也不要发送太小的报文,而是把数据积累成足够大的报文或达到接收方缓存空间的一半时才发送。

拥塞控制

网络中对资源需求超过了资源可用量的情况就叫做拥塞。当吞吐量明显小于理想的吞吐量时就出现了轻度拥塞,当吞吐量随着负载的增加反而下降时,网络就进入了拥塞状态。当吞吐量降为 0 时,网络已无法正常工作并陷入死锁状态。拥塞控制就是尽量减少注入网络的数据,减轻网络中的路由器和链路的负担。拥塞控制是一个全局性的问题,它涉及网络中的所有路由器和主机,而流量控制只是一个端到端的问题,是两个端点之间通信量的控制。

根据网络层是否为运输层拥塞控制提供显式帮助可以将拥塞控制的方法区分为两种:端到端拥塞控制和网络辅助的拥塞控制。TCP 使用端到端的拥塞控制,因为 IP 层不会向端系统提供显式的网络拥塞反馈。TCP 所采取的方法是让每一个发送方根据所感知到的网络拥塞程度来限制其向连接发送数据的速率。如果一个 TCP 发送方感知到它到目的地之间的路径上没什么拥塞则会增加发送速率,如果发送方感知到拥塞就会降低其发送速率。限制发送速率是通过拥塞窗口来实现的,它对发送方能向网络中发送流量的速率进行了限制。判断拥塞是通过超时或者连续接收到 3 个冗余 ACK 实现的。

TCP 的拥塞控制算法主要包括了慢启动、拥塞避免和快恢复。慢启动和拥塞避免是 TCP 的强制部分,差异在于对收到的 ACK 做出反应时 cwnd 增加的方式,慢启动比拥塞避免要更快地增加 cwnd 的长度。快恢复是推荐部分,对 TCP 发送方不是必需的。

1. 慢启动

在慢启动状态,拥塞窗口 cwnd 的值以一个 MSS 最大报文段开始并且每当传输的报文段首次被确认就增加一个 MSS。因此每经过一个 RTT 往返时间,拥塞窗口就会翻倍,发送速率也会翻倍。因此 TCP 的发送速率起始很慢,但是在慢启动阶段以指数增长。

结束慢启动有三种情况:① 如果存在一个超时指示的丢包事件,即发生了拥塞,TCP 发送方就会将 cwnd 设置为 1 并重新开始慢启动过程。它还将慢启动阈值设置为 cwnd/2,即检测到拥塞时将慢启动阈值设置为拥塞窗口的一半。② 当拥塞窗口达到慢启动阈值时就会结束慢启动而进入拥塞避免模式。③ 最后一种结束慢启动的方式是,如果检测到三个冗余的 ACK,TCP 就会执行快重传并进入快恢复状态。

2. 拥塞避免

一旦进入拥塞避免状态,cwnd 的值大约是上次遇到拥塞时的值的一半,即距离拥塞可能并不遥远。因此 TCP 无法再每经过一个 RTT 就将 cwnd 的值翻倍,而是采用一种较为保守的方法,每个 RTT 后只将 cwnd 的值增加一个 MSS。这能够以几种方式完成,一种通用的方法是发送方无论何时收到一个新的确认,都将 cwnd 增加一个 MSS。

当出现超时时,TCP 的拥塞避免和慢启动一样,cwnd 的值将被设置为 1,并且将慢启动阈值设置为 cwnd 的一半。

3. 快恢复

有时候个报文段丢失,而网络中并没有出现拥塞,如果使用慢启动算法就会降低传输效率。这时应该使用快重传来让发送方尽早知道出现了个别分组的丢失,快重传要求接收端不要等待自己发送数据时再捎带确认,而是要立即发送确认。即使收到了乱序的报文段后也要立即发出对已收到报文段的重复确认。当发送方连续收到三个冗余 ACK 后就知道出现了报文段丢失的情况,会立即重传并进入快恢复状态。

在快恢复中,会调整慢启动阈值为 cwnd 的一半,并进入拥塞避免状态。


TCP 连接和释放机制

三次握手

TCP 是全双工通信,任何一方都可以发起建立连接的请求,假设 A 是客户端,B 是服务器。

初始 A 和 B 均处于 CLOSED 状态,B 会创建传输进程控制块 TCB 并进入 LISTEND 状态,监听端口是否收到了 TCP 请求以便及时响应。

当 A 要发生数据时就向B发送一个连接请求报文,TCP 规定连接请求报文的 SYN=1,ACK=0,SYN不可以携带数据,但要消耗一个序号,假设此时 A 发送的序号 seq 为 x。发送完之后 A 就进入了 SYN-SENT 同步已发送状态。

当 B 收到 A 的连接请求报文后,如果同意建立连接就会发送给 A 一个确认连接请求报文,其中 SYN=1,ACK=1,ack=x+1,seq=y,ack 的值为 A 发送的序号加 1,ACK 可以携带数据,如果不携带的话则不消耗序号。发送完之后,B进入 SYN-RCVD 同步已接收状态。

当 A 收到 B 的确认连接请求报文后,还要对该确认再进行一次确认,报文的 ACK=1,ack=y+1,seq=x+1,发送后 A 进入 ESTABLISHED 状态,当 B 接收到该报文后也进入 ESTABLISHED 状态,客户端会稍早于服务器端建立连接。

三次握手的原因主要有两个目的,信息对等和防止超时。

从信息对等的角度看,双方只有确定 4 类信息才能建立连接,即 A 和 B 分别确认自己和对方的发送和接收能力正常。在第二次握手后,从 B 的角度看还不能确定自己的发送能力和对方的接收能力,只有在第三次握手后才能确认。

三次握手也是防止失效连接突然到达导致脏连接,网络报文的生存时间往往会超过 TCP 请求超时时间,A 的某个超时连接请求可能会在双方释放连接之后到达 B,B 会误以为是 A 创建了新的连接请求,然后发送确认报文创建连接。因为 A 机器的状态不是 SYN_SENT,所以直接丢弃了 B 的确认数据。如果是两次握手,连接已经建立了,服务器资源被白白浪费。如果是三次握手,B 由于长时间没有收到确认信息,最终超时导致创建连接失败,因此不会出现脏连接。

四次挥手

当 A 已经没有要发送的数据时就会释放连接,会向 B 发送一个终止连接报文,其中 FIN=1,seq=u,u 的值为之前 A 发送的最后一个序号+1。发送完之后进入 FIN-WAIT-1 状态。

B 收到该报文后,发送给 A 一个确认报文,ACK=1,ack=u+1,seq=v,v 的值为 B 之前发送的最后一个序号+1。此时 A 进入了FIN-WAIT-2 状态,B 进入了 CLOSE-WAIT 状态,但连接并未完全释放,B 会通知高层的应用层结束 A 到 B 方向的连接,此时 TCP 处于半关闭状态。

当 B 发送完数据后准备释放连接时,就向 A 发送连接终止报文,FIN=1,同时还要重发ACK=1,ack=u+1,seq=w,seq 不是 v 的原因是在半关闭状态 B 可能又发送了一些数据,之后 B 进入 LAST-ACK 状态。

A 收到连接终止报文后还要再进行一次确认,确认报文中 ACK=1,ack=w+1,seq=u+1。发送完之后进入 TIME-WAIT 状态,等待 2MSL之后进入 CLOSED 状态,B 收到该确认后进入 CLOSED 状态,服务器端会稍早于客户端释放连接。

四次挥手的原因

第一点原因是为了保证被动关闭方可以进入 CLOSED 状态。MSL 是最大报文段寿命,等待 2MSL 可以保证 A 发送的最后一个确认报文能被 B 接收,如果该报文丢失,B 没有收到就会超时重传之前的 FIN+ACK 报文,而如果 A 在发送确认报文后就立即释放连接就无法收到 B 超时重传的报文,因而也不会再一次发送确认报文段,B 就无法正常进入 CLOSED 状态。

第二点原因是 2MSL 时间之后,本连接中的所有报文就都会从网络中消失,可以防止已失效连接的请求数据包与正常连接的请求数据包混淆而发生异常。

除此之外,TCP 还设有一个保活计时器,用于解决客户端主机故障的问题,服务器每收到一次客户的数据就重新设置保活计时器,时间为 2 小时。如果 2 小时内没有收到就间隔 75 秒发送一次探测报文,连续 10 次都没有响应后就关闭连接。

全部评论

相关推荐

点赞 7 评论
分享
牛客网
牛客企业服务