TCP层的简单总结

1,376 阅读13分钟

TCP头部一些字段的意思

image.png 序列号:在建立连接时由计算机生成的随机数作为其初始值,通过 SYN 包传给接收端主机,每发送一次数据,就「累加」一次该「数据字节数」的大小。用来解决网络包乱序问题。

确认应答号:指下一次「期望」收到的数据的序列号,发送端收到这个确认应答以后可以认为在这个序号以前的数据都已经被正常接收。用来解决丢包的问题。

控制位:

  • ACK该位为 1 时,确认应答 的字段变为有效,TCP 规定除了最初建立连接时的 SYN 包之外该位必须设置为 1 。
  • RST:该位为 1 时,表示 TCP 连接中出现异常必须强制断开连接
  • SYN:该位为 1 时,表示希望建立连接,并在其「序列号」的字段进行序列号初始值的设定
  • FIN:该位为 1 时,表示今后不会再有数据发送,希望断开连接。当通信结束希望断开连接时,通信双方的主机之间就可以相互交换 FIN 位为 1 的 TCP 段。

为什么需要tcp协议

IP 层是「不可靠」的,它不保证网络包的交付、不保证网络包的按序交付、也不保证网络包中的数据的完整性。

所以需要在ip上层保证数据包的完整性。它能确保接收端接收的网络包是无损坏、无间隔、非冗余和按序的。

什么是TCP

TCP 是面向连接的、可靠的、基于字节流的传输层通信协议。

  • 面向连接一定是 一对一 才能连接,不能像 UDP 协议可以一个主机同时向多个主机发送消息,也就是一对多是无法做到的;
  • 可靠的:无论的网络链路中出现了怎样的链路变化,TCP 都可以保证一个报文一定能够到达接收端;
  • 字节流:用户消息通过 TCP 协议传输时,消息可能会被操作系统 分组 成多个的 TCP 报文,如果接收方的程序如果不知道「消息的边界」,是无法读出一个有效的用户消息的。并且 TCP 报文是「有序的」,当「前一个」TCP 报文没有收到的时候,即使它先收到了后面的 TCP 报文,那么也不能扔给应用层去处理,同时对「重复」的 TCP 报文会自动丢弃。

对于建立连接的理解

在客户端和服务端维护连接,而建立一定的数据结构来维护双方交互的状态包括Socket、序列号和窗口大小),用这样的数据结构来保证所谓的面向连接的特性。

如何唯一确定一个 TCP 连接

TCP 四元组可以唯一的确定一个连接,四元组包括如下:

  • 源地址
  • 源端口
  • 目的地址
  • 目的端口

源地址和目的地址的字段(32位)是在 IP 头部中,作用是通过 IP 协议发送报文给对方主机。

源端口和目的端口的字段(16位)是在 TCP 头部中,作用是告诉 TCP 协议应该把报文发给哪个进程。

TCP 和 UDP 区别

连接

  • TCP 是面向连接的传输层协议,传输数据前先要建立连接。
  • UDP 是不需要连接,即刻传输数据。

服务对象

  • TCP 是一对一的两点服务,即一条连接只有两个端点。
  • UDP 支持一对一、一对多、多对多的交互通信。

可靠性

  • TCP 是可靠交付数据的,数据可以无差错、不丢失、不重复、按需到达。
  • UDP 是尽最大努力交付,不保证可靠交付数据。

拥塞控制、流量控制

  • TCP 有拥塞控制和流量控制机制,保证数据传输的安全性。
  • UDP 则没有,即使网络非常拥堵了,也不会影响 UDP 的发送速率。

首部开销

  • TCP 首部长度较长,会有一定的开销,首部在没有使用「选项」字段时是 20 个字节,如果使用了「选项」字段则会变长的。
  • UDP 首部只有 8 个字节,并且是固定不变的,开销较小。

传输方式

  • TCP 是流式传输,没有边界,但保证顺序和可靠。
  • UDP 是一个包一个包的发送,是有边界的,但可能会丢包和乱序。

分片不同

  • TCP 的数据大小如果大于 MSS 大小,则会在传输层进行分片,目标主机收到后,也同样在传输层组装 TCP 数据包,如果中途丢失了一个分片,只需要传输丢失的这个分片。
  • UDP 的数据大小如果大于 MTU 大小,则会在 IP 层进行分片,目标主机收到后,在 IP 层组装完数据,接着再传给传输层。

上面这个区别真的是总结的最详细的了。看别人写的都是对象链接,字节流,可靠,拥塞控制,流量控制,一对一。

TCP 和 UDP 应用场景

由于 TCP 是面向连接,能保证数据的可靠性交付,因此经常用于:

  • FTP 文件传输;
  • HTTP / HTTPS

由于 UDP 面向无连接,它可以随时发送数据,再加上UDP本身的处理既简单又高效,因此经常用于:

  • 包总量较少的通信,如 DNS 、SNMP 等;
  • 视频、音频等多媒体通信;
  • 广播通信;

TCP建立连接(三次握手的过程)

每一次握手的确认序号都是上一次对方发送的序列号 + 1。 image.png 过程分析:

  • 服务器端首先监听某个端口号,处于listen状态。
  • 第一次握手后客户端处于syn_sent状态
  • 第二次握手后服务端处于syn_rcvd状态
  • 第三次握手后客户端处于established状态
  • 最后服务端接收到ack报文后处于established状态。

注意:第三次握手是可以携带数据的,前两次握手是不可以携带数据的

为什么需要三次握手

其实我以前也是觉得面试的时候回答出为了确认收发双发都有接收和发送数据的能力即可。再者就是防止已失效的报文重新传到对端,从而造成服务器资源的浪费。

小林coding总结的很详细了。

  • 三次握手才可以阻止重复历史连接的初始化(主要原因)客户端通过确认号判断。如果不对,那么将发送RST来终止连接。
  • 三次握手才可以同步双方的初始序列号
  • 三次握手才可以避免资源浪费

TCP 建立连接时,通过三次握手能防止历史连接的建立,能减少双方不必要的资源开销,能帮助双方同步初始化序列号。序列号能够保证数据包不重复、不丢弃和按序传输。

如果两次握手呢?

如果是两次握手连接,就无法阻止历史连接,那为什么 TCP 两次握手为什么无法阻止历史连接呢?

因为在两次握手的情况下,「被动发起方」没有中间状态给「主动发起方」来阻止历史连接(即发送RST报文),导致「被动发起方」可能建立一个历史连接,造成资源浪费

如果四次握手呢?

三次握手一来一回同步了双方序列号了。三次握手就已经理论上最少可靠连接建立,所以不需要使用更多的通信次数。

TCP断开连接(四次挥手)

双方都可以主动断开连接,断开连接后主机中的「资源」将被释放。

image.png 主动关闭连接的一方,才有 TIME_WAIT 状态。

为什么需要四次才能完成握手,而不是三次

  • 关闭连接时,客户端向服务端发送 FIN 时,仅仅表示客户端不再发送数据了但是还能接收数据。
  • 服务器收到客户端的 FIN 报文时,先回一个 ACK 应答报文,而服务端可能还有数据需要处理和发送,等服务端不再发送数据时,才发送 FIN 报文给客户端来表示同意现在关闭连接。

为什么需要 TIME_WAIT 状态?

主动发起关闭连接的一方,才会有 TIME-WAIT 状态。

需要 TIME-WAIT 状态,主要是两个原因:

  • 防止历史连接中的数据,被后面相同四元组的连接错误的接收。
  • 保证「被动关闭连接」的一方,能被正确的关闭。

为什么 TIME_WAIT 等待的时间是 2MSL?

如果被动关闭方没有收到断开连接的最后的 ACK 报文,就会触发超时重发 FIN 报文,另一方接收到 FIN 后,会重发 ACK 给被动关闭方, 一来一去正好 2 个 MSL。

就是说,如果最后的ack报文丢失,那么另一方发送一个fin报文的时间和丢失的ack报文的最大时间不得超过2msl。

可以看到 2MSL时长 这其实是相当于至少允许报文丢失一次。比如,若 ACK 在一个 MSL 内丢失,这样被动方重发的 FIN 会在第 2 个 MSL 内到达,TIME_WAIT 状态的连接可以应对。

TCP的重传机制

超时重传

TCP 会在以下两种情况发生超时重传:

  • 数据包丢失
  • 确认应答丢失 如何计算这个超时时间呢? 超时重传时间是以 RTO (Retransmission Timeout 超时重传时间)表示。超时重传时间 RTO 的值应该略大于报文往返 RTT 的值

RTT 指的是数据发送时刻到接收到确认的时刻的差值,也就是包的往返时间。

  • 当超时时间 RTO 较大时,重发就慢,丢了老半天才重发,没有效率,性能差;
  • 当超时时间 RTO 较小时,会导致可能并没有丢就重发,于是重发的就快,会增加网络拥塞,导致更多的超时,更多的超时导致更多的重发。

如果超时重发的数据,再次超时的时候,又需要重传的时候,TCP 的策略是超时间隔加倍。

也就是每当遇到一次超时重传的时候,都会将下一次超时时间间隔设为先前值的两倍。两次超时,就说明网络环境差,不宜频繁反复发送。

快速重传

不以时间为驱动,而是以数据驱动重传

快速重传的工作方式是当收到三个相同的 ACK 报文时,会在定时器过期之前,重传丢失的报文段。

滑动窗口

我们都知道 TCP 是每发送一个数据,都要进行一次确认应答。当上一个数据包收到了应答了, 再发送下一个。这种方式的缺点是效率比较低的。

所以就可以使用窗口来存放数据,窗口大小就是指无需等待确认应答,而可以继续发送数据的最大值

窗口的实现实际上是操作系统开辟的一个缓存空间,发送方主机在等到确认应答返回之前,必须在缓冲区中保留已发送的数据。如果按期收到确认应答,此时数据就可以从缓存区清除。

窗口大小由哪一方控制呢?

接收端告诉发送端自己还有多少缓冲区可以接收数据。于是发送端就可以根据这个接收端的处理能力来发送数据,而不会导致接收端处理不过来。

所以,通常窗口的大小是由接收方的窗口大小来决定的

流量控制

TCP 提供一种机制可以让「发送方」根据「接收方」的实际接收能力控制发送的数据量,这就是所谓的流量控制。

拥塞控制

控制的目的就是避免「发送方」的数据填满整个网络。

为了在「发送方」调节所要发送数据的量,定义了一个叫做「拥塞窗口」的概念。

拥塞窗口 cwnd是发送方维护的一个的状态变量,它会根据网络的拥塞程度动态变化的。他的取值是接收方和发送方窗口的最小值。

那么什么时候才能知道网络是否拥塞了呢?

其实只要「发送方」没有在规定时间内接收到 ACK 应答报文,也就是发生了超时重传,就会认为网络出现了拥塞。

拥塞控制主要是四个算法:

慢启动

TCP 在刚建立连接完成后,首先是有个慢启动的过程,这个慢启动的意思就是一点一点的提高发送数据包的数量。

慢启动的算法记住一个规则就行:当发送方每收到一个 ACK,拥塞窗口 cwnd 的大小就会加 1。

慢启动算法,发包的个数是指数性的增长

慢启动什么时候停止呢?

有一个叫慢启动门限 ssthresh (slow start threshold)状态变量。

  • 当 cwnd < ssthresh 时,使用慢启动算法。
  • 当 cwnd >= ssthresh 时,就会使用「拥塞避免算法」

拥塞避免

cwnd >= ssthresh时,就会触发拥塞避免算法,它的规则是:每当收到一个 ACK 时,cwnd 增加 1/cwnd。

拥塞避免算法就是将原本慢启动算法的指数增长变成了线性增长。

当一只这样线性增长时,最后会造成报文丢失,就会触发超时重传机制。这时候就进入到拥塞发生算法阶段。

拥塞发生

当发生了「超时重传」,则就会使用拥塞发生算法。

这个时候,ssthresh 和 cwnd 的值会发生变化:

  • ssthresh 设为 cwnd/2
  • cwnd 重置为 1 (是恢复为 cwnd 初始化值,我这里假定 cwnd 初始化值 1)

这种方式会造成网络的卡顿。

当发生了「快速重传」,也会使用拥塞发生算法。

则 ssthresh 和 cwnd 变化如下:

  • cwnd = cwnd/2 ,也就是设置为原来的一半;
  • ssthresh = cwnd;
  • 进入快速恢复算法

快速恢复

  • 拥塞窗口 cwnd = ssthresh + 3 ( 3 的意思是确认有 3 个数据包被收到了);
  • 重传丢失的数据包;
  • 如果再收到重复的 ACK,那么 cwnd 增加 1;
  • 如果收到新数据的 ACK 后,把 cwnd 设置为第一步中的 ssthresh 的值,原因是该 ACK 确认了新的数据,说明从 duplicated ACK 时的数据都已收到,该恢复过程已经结束,可以回到恢复之前的状态了,也即再次进入拥塞避免状态;

image.png

这里感谢小林coding,让我学到了很多。

大家可以去关注一下小林coding公众号,真的全是干货。