计算机网络 TCP协议简介

392 阅读17分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动

TCP协议简介

一、简介

计算机网络运输层的两个主要协议:

  1. 用户数据报协议UDP (User Datagram Protocol) [RFC 768]
  2. 传输控制协议TCP (Transmission Control Protocol) [RFC 793]

image.png

image.png

二、特点

1. 面向连接

在使用TCP通信时必须先建立连接;通信完毕后必须释放连接。

2. 每一条TCP连接只能有两个端点(点对点通信)

每一条TCP连接被通信的两个端点(两个套接字)唯一确定。

套接字socket = (IP 地址:端口号)

TCP连接:= {socket1, socket2} = {(IP1: port1), (IP2: port2)}

3. 提供可靠交付

通过TCP连接传送的数据,无差错不丢失不重复,并且按序到达。

4. 提供全双工通信

TCP允许通信双方的应用进程在任何时候都能发送数据。

TCP连接的两端都设有发送缓存接收缓存,用来临时存放双向通信的数据。

  • 在发送时,应用程序在把数据传送给TCP的缓存后,就可以做自己的事,而TCP在合适的时候把数据发送出去。
  • 在接收时,TCP把收到的数据放入缓存,上层的应用进程在合适的时候读取缓存中的数据。

5. 面向字节流

TCP中的“流”(stream)指的是流入到进程或从进程流出的字节序列。

面向字节流:虽然应用程序和TCP的交互是一次一个数据块(大小不等),但TCP把应用程序交下来的数据仅仅看成是一连串的无结构的字节流。TCP并不知道所传送的字节流的含义。接收方的应用程序必须有能力识别收到的字节流。

接收方应用程序收到的字节流必须和发送方应用程序发出的字节流完全一样(数据的准确性和完整性)。

即TCP传输的时候只把数据当做是一个二进制字节流,TCP并不知道从哪里到哪里是一个完整的应用层数据,而是由接收方对数据重新组装还原成有意义的应用层数据。

image.png

三、可靠传输的工作原理

理想的传输条件有以下两个特点:

(1)传输信道不产生差错。

(2)不管发送方以多快的速度发送数据,接收方总是来得及处理收到的数据。

1. 停止等待协议

分为3种情况:

  1. 无差错情况

image.png

  1. 出现差错,超时重传

image.png

  1. 确认丢失和确认迟到

image.png

2. 连续ARQ协议

当用到流水线技术的时候,就需要用到连续ARQ协议和滑动窗口协议。

关键词: 滑动窗口累积确认回退N(Go-back-N)

四、可靠传输的实现

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

image.png

发送窗口:在没有收到接收方的确认的情况下,发送方可以把发送窗口内的数据都发送出去。

接收窗口:接收方可以接收的数据的范围,主要用来控制发送方的发送窗口。

发送窗口的后沿

发送窗口的前沿




image.png

要描述一个发送窗口的状态需要三个指针: P1, P2和P3。指针都指向字节的序号。

这三个指针指向的几个部分的意义如下:

小于P1的是已发送并己收到确认的部分

大于P3的是不允许发送的部分

P3-P1 = A的发送窗口

P2-P1 = 已发送但尚未收到确认的字节数

P3-P2 = 允许发送但当前尚未发送的字节数(又称为可用窗口有效窗口)




image.png

若发送方发送窗口已满,可用窗口为0;但是来自接收方的确认都滞留在网络中。在没有收到接收方的确认时,为了保证可靠传输,发送方只能认为接收方没有收到数据。发送方在超时计时器的控制下重传这部分数据,直到收到了接收方的确认。




image.png

发送缓存

发送缓存用来暂时存放:

  1. 发送应用程序传送给发送方TCP准备发送的数据。
  2. TCP已发送出但尚未收到确认的数据。

发送窗口通常只是发送缓存的一部分。
已被确认的数据应当从发送缓存中删除,因此发送缓存和发送窗口的后沿是重合的。

接收缓存

接收缓存用来暂时存放:

  1. 按序到达的、但尚未被接收应用程序读取的数据;
  2. 未按序到达的数据。

如果收到的分组被检测出有差错,则要丢弃。
如果接收应用程序来不及读取收到的数 据,接收缓存最终就会被填满,使接收窗口减小到零。
反之,如果接收应用程序能够及时从 接收缓存中读取收到的数据,接收窗口就可以增大,但最大不能超过接收缓存的大小。

接收窗口的最新数据应该加入到接收缓存中,因此接收缓存和接收窗口的前沿是重合的。

注意点:

  1. 虽然A的发送窗口是根据B的接收窗口设置的,但在同一时刻,A的发送窗口并不总是和B的接收窗口一样大。这是因为通过网络传送窗口值需要经历一定的时间滞后(这个时间还是不确定的)。发送方A还可能根据网络当时的拥塞情况适当减小自己的发送窗口数值。
  2. 对于不按序到达的数据应如何处理,TCP标准并无明确规定。
  3. TCP要求接收方必须有累积确认的功能,这样可以减小传输开销。接收方可以在合适的时候发送确认,也可以在自己有数据要发送时把确认信息顺便捎带上。但是接收方不应过分推迟发送确认,否则会导致发送方不必要的重传,这反而浪费了网络的资源。TCP标准规定,确认推迟的时间不应超过0.5秒。若收到一连串具有最大长度的报文段,则必须每隔一一个报文段就发送一个确认[RFC 1122]。而且捎带确认实际上并不经常发生,因为大多数应用程序很少同时在两个方向上发送数据。
  4. TCP的通信是全双工通信。通信中的每一方都有自己的发送窗口和接收窗口。

2. 超时重传时间的选择

RTT : 报文段的往返时间
RTTs : 加权平均往返时间(又称为平滑的往返时间)
RTO : 超时计时器设置的超时重传时间(RetransmissionTime-Out)
RTTd : RTT的偏差的加权平均值

每当第一次测量到RTT样本时,RTTs值就取为所测量到的RTT样本值。
但以后每测量到一个新的RTT样本,就按下式重新计算一次RTTs:
新的RTTs = (1一a) x (旧的RTTs) + a x (新的RTT样本)

在上式中,0≤a<1。若a很接近于零,表示新的RTTs值和旧的RTTs值相比变化不 大,而对新的RTT 样本影响不大(RTT值更新较慢)。若选择a接近于1,则表示新的 RTTs值受新的RTT样本的影响较大(RTT值更新较快)。已成为建议标准的RFC 6298推荐 的a值为1/8, 即0.125。 用这种方法得出的加权平均往返时间RTTs就比测量出的RTT值 更加平滑。

超时计时器设置的超时重传时间RTO (RetransmissionTime-Out)应略大于上面得 出的加权平均往返时间RTTs。
RFC 6298建议使用下式计算RTO:
RTO = RTTs + 4 x RTTd

而RTTd 是RTT的偏差的加权平均值,它与RTTs 和新的RTT样本之差有关。
RFC6298建议这样计算RTTd:
当第一次测量时,RTTp值取为测量到的RTT样本值的一半。
在以后的测量中,则使用下式计算加权平均的RTTd:
新的RTTd = (1- β) x (旧的RTTd) + β x | RTTs-新的RTT样本 |
这里β是个小于1 的系数,它的推荐值是1/4,即0.25。

为了区分开有效的和无效的往返时间样本,提出了一下算法

Karn算法:根据以上所述,Karn 提出了一个算法:在计算加权平均RTTS时,只要报文段重传了,就不采用其往返时间样本。这样得出的加权平均RTTS和RTO就较准确。

Karn算法修正:报文段每重传一次,就把超时重传时间RTO增大一些。典型的做法是取新的重传时间为旧的重传时间的2倍。当不再发生报文段的重传时,才根据上面给出的式子计算超时重传时间。

总之,Karn算法能够使运输层区分开有效的和无效的往返时间样本,从而改进了往返时间的估测,使计算结果更加合理。

五、流量控制

流量控制:让发送方的发送速率不要太快,要让接收方来得及接收。

原理:滑动窗口机制, 接收窗口

关键词:持续计时器,零窗口探测报文段

image.png

现在我们考虑一种情况。B向A发送了零窗口的报文段后不久,B的接收缓存又有了一些存储空间。于是B向A发送了rwnd = 100的报文段。然而这个报文段在传送过程中丢失了。A一直等待收到B发送的非零窗口的通知,而B也一直等待A发送的数据。如果没有其他措施,这种互相等待的死锁局面将一直延续下去。

为了解决这个问题,TCP为每一个连接设有一个持续计时器(persistence timer)。 只要 TCP连接的一方收到对方的零窗口通知,就启动持续计时器。若持续计时器设置的时间到 期,就发送一个零窗口探测报文段(仅携带1字节的数据),而对方就在确认这个探测报文段时给出了现在的窗口值。 如果窗口仍然是零,那么收到这个报文段的一方就重新设置持续计时器。如果窗口不是零,那么死锁的僵局就可以打破了。

TCP规定,即使设置为零窗口,也必须接收以下几种报文段:零窗口探测报文段确认报文段携带紧急数据的报文段

六、拥塞控制

1. TCP拥塞控制的算法

慢开始(slow-start)、拥塞避免(congestionavoidance)、快重传(fast retransmit)和快恢复(fast recovery)

( 见草案标准RFC 5681)

2. 基础概念

拥塞窗口 cwnd (congestion window)
最大报文段 SMSS (Sender Maximum Segment Size)
慢开始门限 ssthresh

新的RFC 5681把初始拥塞窗口cwnd设置为不超过2至4个SMSS的数值。
具体的规定如下:
SMSS > 2190字节
则设置初始拥塞窗口cwnd = 2 x SMSS字节,且不得超过2个报文段。

(SMSS > 1095字节)且(SMSS ≤ 2190 字节),
则设置初始拥塞窗口cwnd = 3 x SMSS字节,且不得超过3个报文段。

SMSS ≤ 1095字节
则设置初始拥塞窗口cwnd = 4 x SMSS字节,且不得超过4个报文段。

慢开始规定,在每收到一个对新的报文段的确认后,可以把拥塞窗口增加最多一个 SMSS的数值。更具体些,就是
拥塞窗口cwnd每次的增加量 = min(N,SMSS)
其中N是原先未被确认的、但现在被刚收到的确认报文段所确认的字节数。不难看出,当N<SMSS时,拥塞窗口每次的增加量要小于SMSS。

如果用报文段的个数作为窗口大小的单位,发送方每收到一个对新报文段的确认(重传的不算在内)就使发送方的拥塞窗口加1。因此使用慢开始算法后,每经过一个传输轮次(transmission round),拥塞窗口 cwnd 就加倍。

为了防止拥塞窗口cwnd增长过大引起网络拥塞
cwnd < ssthresh 时,使用上述的慢开始算法。
cwnd > ssthresh 时,停止使用慢开始算法而改用拥塞避免算法。
cwnd = ssthresh 时,既可使用慢开始算法,也可使用拥塞避免算法。

image.png

image.png

快重传算法规定,发送方只要一连收到3个重复确认,就知道接收方确实没 有收到报文段M3,因而应当立即进行重传(即“快重传”),这样就不会出现超时,发送方 也不就会误认为出现了网络拥塞。使用快重传可以使整个网络的吞吐量提高约20%。

因此,发送方知道现在只是丢失了个别的报文段。于是不启动慢开始,而是执行快恢复算法。这时,发送方调整门限值ssthresh=cwnd/2,同时设置拥塞窗口 cwnd = ssthresh ,并开始执行拥塞避免算法。

image.png




同时考虑流量控制拥塞控制

发送方窗口的上限值 = Min [ rwnd , cwnd ]

rwnd和cwnd中数值较小的一个,控制了发送方发送数据的速率。




流量控制是一个端到端的问题,是接收端抑制发送端发送数据的速率,以便使接收 端来得及接收。
拥塞控制是一个全局性的过程,涉及到所有的主机、所有的路由器,以及与降低网终传输性能有关的所有因素。

七、TCP连接管理

关键词:三次握手四次挥手时间等待计时器保活计时器

TCP的有限状态机

image.png

1. 连接建立

image.png

注意点:

  • TCP规定,SYN报文段(即SYN= 1的报文段)不能携带数据,但要消耗掉一个序号。



2. 连接释放

image.png

注意点:

  • TCP规定,FIN报文段即使不携带数据,它也消耗掉一个序号。

  • TCP连接必须经过时间等待计时器(TIME-WAIT timer)设置的时间2MSL后,A才进入到CLOSED状态。

  • 时间MSL叫做最长报文段寿命(Maximum Segment Lifetime),RFC 793建议设为2分钟

  • 除时间等待计时器外,TCP还设有一个保活计时器(keepalive timer)。 设想有这样的情 况:客户已主动与服务器建立了TCP连接。但后来客户端的主机突然出故障。显然,服务 器以后就不能再收到客户发来的数据。因此,应当有措施使服务器不要再白白等待下去。这 就是使用保活计时器。服务器每收到一次客户的数据,就重新设置保活计时器,时间的设置 通常是两小时。若两小时没有收到客户的数据,服务器就发送一个探测报文段,以后则每隔 75秒钟发送一次。若-连发送10个探测报文段后仍无客户的响应,服务器就认为客户端出 了故障,接着就关闭这个连接。




3. 几个小问题

  1. 为什么要进行三次握手?

为什么不是两次?

根本原因: 无法确认客户端的接收能力。

分析如下:

如果是两次,你现在发了 SYN 报文想握手,但是这个包滞留在了当前的网络中迟迟没有到达,TCP 以为这是丢了包,于是重传,两次握手建立好了连接。

看似没有问题,但是连接关闭后,如果这个滞留在网路中的包到达了服务端呢?这时候由于是两次握手,服务端只要接收到然后发送相应的数据包,就默认建立连接,但是现在客户端已经断开了。

看到问题的吧,这就带来了连接资源的浪费。

为什么不是四次?

三次握手的目的是确认双方发送接收的能力,那四次握手可以嘛?

当然可以,100 次都可以。但为了解决问题,三次就足够了,再多用处就不大了。


作者:神三元
链接:juejin.cn/post/684490…
来源:稀土掘金\

  1. 为什么要进行四次挥手?

为什么是四次挥手而不是三次?

因为服务端在接收到FIN, 往往不会立即返回FIN, 必须等到服务端所有的报文都发送完毕了,才能发FIN。因此先发一个ACK表示已经收到客户端的FIN,延迟一段时间才发FIN。这就造成了四次挥手。

如果是三次挥手会有什么问题?

等于说服务端将ACKFIN的发送合并为一次挥手,这个时候长时间的延迟(服务端还有数据要处理和传输)可能会导致客户端误以为FIN没有到达客户端,从而让客户端不断的重发FIN


作者:神三元
链接:juejin.cn/post/684490…
来源:稀土掘金\

  1. 为什么在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早一些。

如果不等待会怎样?

如果不等待,客户端直接跑路,当服务端还有很多数据包要给客户端发,且还在路上的时候,若客户端的端口此时刚好被新的应用占用,那么就接收到了无用数据包,造成数据包混乱。所以,最保险的做法是等服务器发来的数据包都死翘翘再启动新的应用。

那,照这样说一个 MSL 不就不够了吗,为什么要等待 2 MSL?

  • 1 个 MSL 确保四次挥手中主动关闭方最后的 ACK 报文最终能达到对端
  • 1 个 MSL 确保对端没有收到 ACK 重传的 FIN 报文可以到达

这就是等待 2MSL 的意义。


作者:神三元
链接:juejin.cn/post/684490…
来源:稀土掘金\

参考资料:

《计算机网络(第7版)-谢希仁》

(建议收藏)TCP协议灵魂之问,巩固你的网路底层基础

字节跳动最爱考的前端面试题:计算机网络基础