本篇将详细介绍TCP的知识点和高频的面试题,顺便介绍UDP
TCP的基本认识
TCP的结构
- 序列号:在连接建立的时候,计算机会随机生成一个数字作为序列号的开端,通过sync包发送给目标主机,每一次发送就累加上发送出去的数据字节长度,序列号的作用是解决乱序问题
- 确认应答号:指下一次期望得到的序列号,当发送端接收到确认应答号的话发送端就会知道,确认应答号前面的数据都已经被成功接收了
- 控制位:
- ACK:当该位为1时,确认应答位就生效了,TCP协议规定,除了最初建立连接的sync包以外,ACK位始终为1
- SYNC:当该位为1时,序列号就生效了,此时表示想要建立连接
- RST:当该位为1时,表示TCP连接出现异常,必须强制断开
- FIN:当该位为1时,表示今后不会再有数据发送,希望断开连接
TCP定义
- TCP是面向连接的,一对一的,可靠的,基于字节流的传输层协议
- 面向连接:TCP连接一定是一对一的,不能像UDP一样一对多发送
- 可靠的:无论发生什么情况,TCP可以保证正确的传输信息给接收端
- 基于字节流:传输的消息是没有边界的,意思是无论多大的内容都可以通过TCP协议传输,并且字节流是有序的,TCP传输的数据也可以保证有序
TCP连接定义
TCP连接的建立其实就是需要服务端和客户端在某些信息上面达成共识
- Socket:由IP地址和端口号组成
- 序列号:用来解决乱序问题
- 窗口大小:用来做流量控制
一个TCP连接的确认只需要4个元素,这四个元素被称为TCP的四元组
由此我们可以发现,一个服务器可以建立的TCP的上限和客户端的IP数量以及端口数量有关,理论的上限是2^48次方,但是这是远远无法达到的理论上限值,因为每一个socket都是要占用空间的,在linux内核看来,一切皆文件,socket连接也不例外,这就是一个文件描述符,每一个文件描述符都是需要占用空间的,所以TCP连接的数量是远远无法达到理论值的
TCP和UDP的区别
- UDP的格式非常简单
- 连接:
- TCP是面向连接的,传送数据需要先建立连接
- UDP是不面向连接的,不建立连接就可以直接传输数据
- 服务对象:
- TCP是一对一的,服务的对象只能是服务器和客户端
- UDP是可以支持一对一、一对多、多对多的
- 可靠性:
- TCP是可靠的,可以保证数据正确无误的到达
- UDP是不可靠的,尽最大努力交付
- 拥塞控制、流量控制
- TCP有拥塞控制,当网络拥塞时会调整发送速率
- UDP没有流量控制,网络的拥塞不会影响发送速率
- 首部开销
- TCP的首部开销较大,一般是20字节,如果选项区域被使用了话还会更大
- UDP的首部开销较小,只有固定的8字节
- 传输方式
- TCP是流式传输,没有边界
- UDP是以包为基本单位传输
- 分片不同
- TCP 的数据大小如果大于 MSS 大小,则会在传输层进行分片,目标主机收到后,也同样在传输层组装 TCP 数据包,如果中途丢失了一个分片,只需要传输丢失的这个分片。
- UDP 的数据大小如果大于 MTU 大小,则会在 IP 层进行分片,目标主机收到后,在 IP 层组装完数据,接着再传给传输层,但是如果中途丢了一个分片,则就需要重传所有的数据包,这样传输效率非常差,所以通常 UDP 的报文应该小于 MTU。
TCP连接的建立
三次握手过程
开始时,客户端和服务器都处于 close 状态,但是服务器有一个端口在监听,处于listen状态
连接建立的开始,客户端会初始化一个随机的序列号,将这个序列号填入序列号位置,然后将这个TCP包中的SYN字段设置为1,将之发送给服务端
当服务端接收到sync包后,自己也会随机初始化一个序列号,然后服务端会读取客户端传来的syn的序列号值,将这个值 + 1放置到确认应当位置中,然后将syn位和ACK位设置为1,接下来就会把这个报文发送出去
当客户端接收到来自服务端的第二个报文以后,客户端就会初始化确认应答号为服务端报文序列号+1的值,此时就可以在数据区域放置数据,然后自身的状态处于 Establish 状态,当服务端接收到这个syn + ack报文之后也会处于Establish状态
三次握手的必要性
很多人对于握手为什么需要三次感到疑惑,这里提出比较详尽的解释
- 可以避免历史连接的扰乱
- 一部分的报文可以合并
TCP连接断开
四次挥手过程
四次挥手的必要性
- 四次挥手时发送的fin报文的意思是当前客户端已经不会再发送数据了,但是可以接收数据。当服务端接收到fin报文的时候有可能服务端还需要向客户端发送一些数据,所以不可以将ack报文和fin报文合并,于是握手必须要有四次
重传机制
重传机制是TCP保证可靠性的重要机制,在TCP协议里,当发送端数据到达主机时,接收端主机会返回一个确认应答消息用来表示收到消息。但是网络世界非常纷杂,如果数据包在网络中丢失了,那么就需要依靠重传机制来保障
超时重传
当发送端有在指定时间内没有收到接收端发送来的ACK包时就会出发超时重传机制
这个超时重传的时间设定非常的重要,如果过长,那么就会导致重传时间变得较长,影响资源的利用;如果较短,那么就会发出不必要的包,导致拥塞的网络变得更加拥塞
一般来说,超时重传的时间一般是比RTT稍微多一点
快速重传
快速重传其实用图片的方式非常的好理解
当产生了丢包的时候,由于TCP窗口技术发送端还会继续往后发送大量的包,但是由于中途某一个包丢失了,接收端在接收到数据发现前面丢包了就会发送ACK向发送端再次索要丢失的包,当这个ACK请求被接受了三次的时候就会出发快速重传机制,重新发送被索要的包
快速重传也有需要考究的地方,那就是关于被索要的数据包的后面的数据是否要发送,这是重传一个还是重传所有的问题。为了解决这个问题,于是又提出了SACK方案
SACK重传
SACK的实现其实很简单,就是在返回的TCP头部中加入已经接收到的缓存的信息,这样发送端就可以选择性地发送
滑动窗口
滑动窗口简介
- 我们都知道,TCP协议中发送端和接收端继续交流沟通的方式是你发送一句我应答一句,就像聊天一样。但是,如果交流的双方有一方正忙,那么另一方就必须一直等待,这是很浪费资源的行为,而且这种交互模型在RTT时间很长的情况下是非常低效率的,于是就产生出了窗口技术,发送端可以一次性发送复数的包,这样即使RTT时间较长,也可以比较高效地交互信息
请注意,图中的ACK600丢失了也没有关系,ACK700的到达会让前面的确认都被累计下来
滑动窗口的大小
一般来说,滑动窗口的大小是由接收端确认的 在TCP的头部,有一个字段叫做window,也就是窗口的大小,通过这个字段,发送端可以知道接收端有多少的剩余窗口大小,然后发送端就可以通过剩余窗口的大小来发送数据,注意,发送端发送的数据大小不会超过窗口的大小
为了理解,我们首先来观察发送端发送的TCP包
- 1#的区域表示的内容是已经发送了并且被确认接收的数据
- 2#的区域表示已经发送了但是还没有被确认接收到的数据
- 3#的区域表示未发送但总大小在接收方处理返回内的区域,也就是剩余的窗口大小
- 4#表示还没有发送但是超过了窗口处理范围内的数据
- 在这张图中我们发现,可用窗口的范围变成了0,这是因为发送端一次性把所有能发送的数据都发送完了,此时已经没有能够发送的数据了,发送端会等待接收端的ACK确认
- 当发送端接收到了接收端传回的ACK后就会确认一部分的数据已经被接收到了,此时窗口就会开始滑动起来,把接收到的数据划归到收到确认的区域,然后可用窗口扩展一样的宽度,这样就又有可用的窗口了,又可以发送数据了
接收端的窗口
流量控制
流量控制详细来说非常的复杂,但是我们只需要了解一下,其实简单来说,就是因为虽然有这么大的窗口值,但是接收端不一定能处理的完,于是在双方不断地通信的过程中就会调节窗口的大小,把窗口值不断地变小,当然,也有可能扩大,这里我们放一个图片来方便理解
- 客户端发送 140 字节数据后,可用窗口变为 220 (360 - 140)。
- 服务端收到 140 字节数据,但是服务端非常繁忙,应用进程只读取了 40 个字节,还有 100 字节占用着缓冲区,于是接收窗口收缩到了 260 (360 - 100),最后发送确认信息时,将窗口大小通告给客户端。
- 客户端收到确认和窗口通告报文后,发送窗口减少为 260。
- 客户端发送 180 字节数据,此时可用窗口减少到 80。
- 服务端收到 180 字节数据,但是应用程序没有读取任何数据,这 180 字节直接就留在了缓冲区,于是接收窗口收缩到了 80 (260 - 180),并在发送确认信息时,通过窗口大小给客户端。
- 客户端收到确认和窗口通告报文后,发送窗口减少为 80。
- 客户端发送 80 字节数据后,可用窗口耗尽。
- 服务端收到 80 字节数据,但是应用程序依然没有读取任何数据,这 80 字节留在了缓冲区,于是接收窗口收缩到了 0,并在发送确认信息时,通过窗口大小给客户端。
- 客户端收到确认和窗口通告报文后,发送窗口减少为 0。
拥塞控制
拥塞控制的诞生
- TCP协议是一个无偿博爱的协议,在TCP协议感受到网络拥堵的时候会自动调节自己发送数据的速度和频率,这样做来减缓网络的拥塞程度,和流量控制不同,流量控制关心的是接收端是否能完全接收数据,为此继续的调整;拥塞控制关心的是网络状态,网络状态差的时候就会调整发送频率
- 在前面我们提到了接收窗口,这里我们又提到了拥塞窗口,那么此时,发送窗口的大小就是两者的min()
慢启动
- 慢启动简单的理解就是一开始的时候一点一点的发送数据,然后指数级别增加,不会一上来就大量的发送
- 慢启动也是有阈值的,如果没达到阈值前,都是使用的慢启动算法;如果达到或者超过了阈值,那么就会启动拥塞避免算法
拥塞避免算法
- 当发送包的数量达到或者超过阈值了之后,每次包增加的数量都是线性增加的,这就是拥塞避免算法
拥塞现象的出现
当拥塞现象出现了以后就会再次启动重传机制,一共有两种,超时重传和快速重传,这两种重传带来的变化是不一样的
超时重传
当出现了超时重传的时候,TCP认为大量的包丢失,于是会发生以下变化
- ssthresh设为cwnd/2,
- cwnd 重置为1
快速重传 快速重传发生了之后TCP认为当前只是丢失了一小部分数据,于是有如下变化
- cwnd = cwnd / 2
- ssthresh设为cwnd
接下来就会进入快速恢复算法
快速恢复
快速恢复算法很简单
- 拥塞窗口 cwnd = ssthresh + 3 ( 3 的意思是确认有 3 个数据包被收到了);
- 重传丢失的数据包;
- 如果再收到重复的 ACK,那么 cwnd 增加 1;
- 如果收到新数据的 ACK 后,把 cwnd 设置为第一步中的 ssthresh 的值,原因是该 ACK 确认了新的数据,说明从 duplicated ACK 时的数据都已收到,该恢复过程已经结束,可以回到恢复之前的状态了,也即再次进入拥塞避免状态