🔥博客主页: 【小扳_-CSDN博客】**
❤感谢大家点赞👍收藏⭐评论✍**
1.0 TCP 协议概述
** TCP 协议在传输层,TCP 是传输层协议的一种,传输层中的协议不单单只有 TCP 或者 UDP 。当应用层将数据打包后交给传输层时,如果选择使用 TCP 协议,传输层会在数据包上加上 TCP 报头;如果选择使用 UDP 协议,传输层会在数据包上加上 UDP 报头。**
1.1 TCP 协议格式
** 1)源端口号:表示数据从哪个应用程序发送出来。**
** 2)目的端口号:表示数据要发送到哪个目的应用程序中。**
** 3)32 位序号:TCP 协议中的序号是记录字节流中第一个字节的编号。每个 TCP 报文段都有一个序号字段,用来标识该报文段中第一个字节在整个字节流中的位置。接收端根据序号对接收到的数据进行排序和重组,确保数据的有序传输和可靠性。**
** 简单来说,发送方会对发送的每一个字节进行排序编号,一个字节一个编号。比如,发送从编号为 1 到编号为 1000 的字节,那么此时的 TCP 报头序号中就会记录编号 1 。**
** 4)32 位确认序号:当接收方接收到了 TCP 的数据包,那么就会响应一个确认序号。比如,发送方:发送从编号为 1 到编号为 1000 的字节。接收方接收:该数据包之后,会给发送方一个响应 1001 ,所以该响应的序号就是确认序号。**
** 总结一下,确认序号是指下一个期望接收的字节序号,用来告知发送方接收方已经成功接收到的数据包的序号。**
** 5)4 位头部长度:又称为 4位 TCP 报头长度,表示该 TCP 头部有多少个 32 位 bit (有多少个 4 字节),所以 TCP 头部最大长度是 15 * 4 = 60 个字节长度。**
** 6)6 位保留:保留的是头部长度,所以以后 TCP 所需要的报头不够时,那么就可以从保留的空间中给报头长度进行扩展,不过保留的最大只有 6 个 bit 。**
** 7)6 位标志位:**
** URG:紧急指针是否有效。**
** ACK:确认号是否有效,当接收方接收到数据时,就会发送 ACK 信号进行响应。**
** PSH:提示接收端应用程序立刻从 TCP 缓冲区把数据读走,接收方中会有一个容器进行存储数据,该容器相当于是一个优先级阻塞队列(缓冲区)。**
** PST:对方要求重新建立连接,我们把携带 RST 标识的称为复位报文段。**
** SYN:请求建立连接。**
** FIN:请求断开连接。**
** 8)16 位窗口大小:发送方根据接收方返回的窗口大小来控制发送数据的速度,以避免发送过多数据导致网络拥塞。如果接收方的窗口大小为0,发送方将停止发送数据,直到接收方的窗口大小变为大于0为止。可以实现流量控制和拥塞控制,从而保证数据传输的可靠性和效率。**
** 9)16 位校验和:发送方填充,CRC 校验,接收方进行校验,若校验不通过,则认为数据有问题。此处校验和不光包含 TCP 首部,也包含 TCP 数据部分。**
** 10)16 位紧急指针:标识哪部分是紧急数据。**
2.0 TCP 协议的特性
** TCP 重要的特性:确认应答、超时重传、连接管理、滑动窗口、流量控制、拥塞控制、延迟应答、捎带应答、粘包、异常情况等特性来确保 TCP 可靠性和高的性能。**
2.1 确认应答
** 接收方在接收到数据包后会发送确认应答给发送方,确保发送方知道数据包已经成功到达。通过 TCP 报头中的序号和确认序号来实现该机制,当发送方发送的字节,报头中会记录第一个字节的序号,当接收方接收到该数据时,会给发送方响应一个确认序号。表示:接收方已经接收到了数据了,期待下一个数据,下一个数据从第几个字节开始。**
** 简单来说:每一个 ACK 都带有对应的确认序号,意思是告诉发送者,我已经收到了哪些数据;下一次你从哪里开始发。**
如图:
2.2 超时重传
** 当主机 A 发送数据,一段时间后,没有接收到主机 B 的响应,就会触发超时重传。**
** 分两种情况:**
** 1)主机 A 发送的数据,因为某个原因导致在中间丢包了,那么这种情况,确实要通过超时重传:主机 A 重新发送数据到主机 B 中。这种情况没有太大问题。**
** 2)主机 A 发送的数据,主机 B 接收到了,当主机 B 发送 ACK 进行响应,那么 ACK 因某个原因导致丢包了,那么主机 A 过一段时间,没有接收到响应,就会触发超时重传,这可能就会有问题了,本来主机 B 已经接收到了数据,主机 A 触发了超时重传,又继续给主机 B 发送相同的数据。**
** 因此主机 B 会收到很多重复数据,那么 TCP 协议需要能够识别出哪些包是重复的包,并且把重复的丢弃掉,这时候我们可以利用前面提到的序列号,就可以很容易做到去重的效果。**
2.2.1 超时的时间如何确定?
** 最理想的情况下,找到一个最小的时间,保证“确认应答一定能在这个时间内返回”,但是这个时间的长短,随着网络环境的不同,是有差异的。**
** 如果超时时间太长,会影响整体的重传效率;**
** 如果超时时间太短,有可能会频繁发送重复的包;**
** 因此 TCP 为了保证无论在任何环境下都能比较高性能的通信,会动态计算这个最大超时时间。累计到一定的重传次数, TCP 认为网络或者对端主机出现异常,强制关闭连接。**
2.3 连接管理
** 正常情况下:TCP 通过三次握手建立连接、通过四次挥手断开连接。**
2.3.1 三次握手
** 具体流程:**
** 主机 A 要想跟主机 B 建立连接,首先主机 A 会发送带有 SYN 信号的 TCP 给到主机 B ,来请求建立连接。seq 是序列号,发送的数据的第一个字节序号为 x 。当主机 A 发送完请求连接时,处于 SYNSENT 状态,该状态表示:等待响应。**
** 此时主机 B 正在出于监听状态,监听来自客户端的连接请求。所以 LISTEN 状态通常是服务器在等待客户端连接时的初始状态。**
** 当主机 B 接收到主机 A 发送的请求连接信息时,主机 B 会响应带有 ACK 信号的 TCP ,代表已经接收到数据了,确认学号 ack 为 x + 1,表示期待接收下一个数据为:x + 1,同时还带有 SYN ,表示主机 B 请求与主机 A 建立连接。当主机 B 发送完 SYN ,处于 SYNRCVD 状态,该状态表示:服务器已经接收到客户端发送的 SYN 包,并已经发送了自己的 SYN 包作为响应,表示同意建立连接。**
** 当主机 A 接收到了主机 B 发送的 TCP 包时,会发送一个带有 ACK 信号的 TCP,表示:已经接收到数据了,seq 序列号:记录发送数据中的第一个字节序号为 x + 1。**
** 最后,双方就成功建立了连接,都处于 ESTABLISHED 状态。在 ESTABLISHED 状态下,双方可以互相发送数据,进行通信和交换信息。**
建立连接的意义:
** 1)投石问路,确认当前通信路径是否通畅。**
** 2)确保双方都具备接收数据的能力。**
** 3)协商参数,通信双方共同确认一些通信中的必备参数数值,比如协商序列号。**
2.3.2 四次挥手
** 具体流程:**
** 客户端想要断开与服务器的连接,首先,客户端会发送带有 FIN 信号的 TCP 包给服务器。**
** 服务器先会响应带有 ACK 信号的 TCP 包给客户端,表示:服务器已经接收到数据了。此时服务器可能还没有接收完数据,需要继续接收数据,还不能断开连接。服务端此时处于 CLOSEWAIT 状态。等待数据接收完毕了,手动调用 close() 方法,服务端会主动发送带有 FIN 信号的 TCP 给客户端。**
** 最后客户端接收到服务端发送的 TCP 包后,就代表着可以正式断开连接了,因此客户端发送带有 ACK 信号的 TCP ,服务端此时处于 LASTACK 状态,接收最后一个 ACK 信号。当服务端接收完毕之后,就会处于 CLOSE 状态,服务端就成功断开连接了。**
** 对于客户端来说还需要等待 2MSL ,而不是立马像服务端直接结束。这是因为,万一客户端发送的 ACK 在中间丢包了,就需要触发超时重传,所以需要等待一段时间,来确保 ACK 成功发送到服务器中。**
2.3.3 为什么是 TIME_WAIT 的时间是 2 MSL ?
** 1)MSL 是 TCP 报文的最大生存时间,因此 TIME_WAIT 持续存在 2 MSL 的话,就能保证在两个传输方向上的尚未接收或者迟到的报文都已经消息。 **
** 2)也是理论上保证最后一个报文可靠到达(假设最后一个 ACK 丢失,那么服务器会再重发一个 FIN ,这时虽然客户端的进程不在了,但是 TCP 连接还在,仍然可以重发 LAST_ACK)**
** 2.3.4 服务器出现大量的 CLOSE_WAIT 是什么导致的?**
** 一般而言,对于服务器上出现大量的 CLOSE_WAIT 状态,原因就是服务器没有正确的关闭 socket.close() ,导致四次挥手没有正确完成,这是一个 BUG ,只需要加上 close 即可解决问题。 **
2.4 滑动窗口
** 对于每一个发送的数据段,都要给一个 ACK 确认应答,收到 ACK 后再发送下一个数据,这样做有一个比较大的缺点,即使性能较差,尤其是数据往返的时间较长的时候。**
** 对于这种情况,就引入了滑动窗口。TCP 报头上的窗口大小值的是,无需等待确认应答而可以继续发送数据的最大值。**
举个例子:
** 窗口大小为 4 ,发送前四个数据的时候,不需要等待任何 ACK ,直接发送;收到第一个 ACK 后,滑动窗口向后移动,继续发送第五个数据;依此类推;**
** 这样就有效提高效率了,当发送数据的时候,同时也等待着下一个 ACK 。当速度够快时,看起来就像一下子移动 4 格。**
** 操作系统内核为了维护这个滑动窗口,需要开辟发送缓冲区来记录还有哪些数据没有应答;只有确认应答过的数据,才能从缓冲区删掉。**
** 窗口越大,则网络的吞吐率就越高。**
2.4.1 遇到丢包情况该 TCP 该如何进行重传?
** 这里分两种情况进行讨论。**
** 1)数据包已经抵达,只是 ACK 被丢失了。**
** 这种情况下,部分 ACK 丢了并不要紧,因为可以通过后序的 ACK 进行确认。因为只要后序的 ACK 返回了,就代表着前面的数据包都接收完毕了,只是 ACK 丢了仅此而已。**
** 2)出现数据包丢了的情况。**
** 当某一段数据丢失之后,发送端会一直收到 1001 这样的 ACK ,就像在提醒发送端“我想要的是 1001 一样”;**
** 如果发送端主机连续三次接收到 1001 这样的应答,那么发送端就会在缓冲区中拿到相应的数据重新发送。**
** 这个时候接收端收到 1001 之后,再次返回的 ACK 就是 7001 了,接收端其实之前就已经接收到,被放到接收缓冲区中;这种机制被称为“高速重发控制”(也叫“快重发”)。**
2.5 流量控制
** 接收端处理数据的速度是有限的,如果发送端发的太快,导致接收端的缓冲区被打满,这时候如果发送端继续发送,就会造成丢包,继而引起丢包重传等等一系列连锁反应。**
** 因此 TCP 支持根据接收端的处理能力,来决定发送端的发送速度,这个机制就叫做流量控制。**
** 接收端将自己可以接收的缓冲区大小放入 TCP 首部中的“窗口大小”字段,通过 ACK 端通知发送端。窗口大小字段越大,说明网络的吞吐量越高。接收端一旦发现自己的缓冲区快满了,将会将窗口大小设置成一个更小的值通知给发送端。发送端接收到这个窗口之后,就会减慢自己的发送速度。如果接收端缓冲区满了,就会将窗口置为 0,这时发送方不再发送数据,但是需要定期发送一个窗口探测数据段,使接收端把窗口大小告诉发送端。**
2.6 拥塞控制
** TCP 引入慢启动机制,先发送少量的数据,探探路,摸清当前的网络拥堵状态,再决定按照多大的速度传输数据。**
** 一开始的时候会慢启动,窗口大小一般从 1 开始往上增加,这是因为刚开始发送数据时,不清楚具体的网络环境状况,则窗口需要根据实际的网络情况来增加。一开始,发送方每接收到一个 ACK ,窗口就会 +1 ,指数式增长,到一定的阈值之后,按照线形方式增长。**
** 1)TCP Reno 版本**
** 一开始的阈值为 16 ,超过这个数值,就会以线性方式继续增长,当收到 3 个重复 ACK 执行快重传时,说明此时网络拥塞,那么就需要将窗口大小就会恢复到拥塞的一半 12 。继续以线性的方法继续增长。依次类推,最后会达到一个稳定的窗口大小情况。**
** 2)TCP Tahoe 版本**
** 当接收到多个快重传时,首先窗口会重新从 1 开始往上增加,没接收到一个 ACK 就会窗口大小 +1 ,且阈值变成拥塞时窗口大小的一半。当窗口大小增加到 12 前,以指数方式增长,一旦超过 12 ,以线性方法继续增长,以此类推。**
** 每次发送数据包的时候,将拥塞窗口和接收端主机反馈的窗口大小做比较,取较小的值作为实际发送的窗口。**
2.7 延迟应答
** 在TCP通信中,接收端收到数据后会发送确认应答,确认已经接收到数据。如果接收端立即发送确认应答,那么发送端会根据确认应答的窗口大小来继续发送数据。如果确认应答的窗口大小比较小,发送端发送的数据量就会受限,影响了传输效率。**
** 而如果接收端延迟发送确认应答,等待一段时间后再发送,那么确认应答的窗口大小可能会更大。这样一来,发送端可以发送更多的数据,提高了网络传输效率。因为窗口越大,发送端就可以发送更多的数据,从而提高了网络吞吐量。**
** 发送窗口大小受到接收方的缓冲区剩余大小和延迟应答策略的双重影响。接收方的缓冲区大小决定了最大的发送窗口大小,而延迟应答可以在这个范围内动态调整确认应答的窗口大小。**
2.7.1 所有的包都可以延迟应答吗?
** 肯定不是。**
** 1)数量限制:每隔 N 个包就应答一次。一般 N 取 2 。**
** 2)时间限制:超过最大延迟时间就应答一次。超时间取 200 ms 。**
2.8 捎带应答
** 捎带应答是指在发送 TCP 报文时,如果接收方正好有数据要发送给发送方,那么可以将这些数据捎带在 TCP 报文的 ACK(确认)中一起发送,而不需要额外发送一个单独的数据包。**
** 这种捎带应答的机制可以提高网络的利用率和效率,减少网络传输时延。当接收方收到数据后,如果刚好有数据要发送给发送方,可以直接在 ACK 中回复,并携带需要发送的数据,这样可以减少网络传输的次数,提高网络的吞吐量。**
** 需要注意的是,捎带应答只能在 TCP 中使用,而且只能在接收方发送 ACK 时才能进行,发送方不能在发送数据时捎带应答。**
2.9 粘包问题
** 首先明确,粘包问题中的“包”,是指的应用层的数据包。在 TCP 的协议头中,没有如同 UDP 一样的“报文长度”这样的字段,但是有一个序号这样的字段。**
** 站在传输层的角度,TCP 是一个一个报文过来的,按照序号排好放在缓冲区中。**
** 站在应用层的角度,看到的只是一串连续的字节数据。**
** 那么应用程序看到了这么一连串的字节数据,就不知道从哪个部分开始到哪个部分,是一个完整的应用层数据包。**
2.9.1 如何避免粘包问题呢?
** 归根结底就是一句话,明确两个包之间的边界。**
** 对于定长的包,保证每次都按固定大小读取即可。**
** 对于变长的包,可以在报头位置,约定一个包总长度的字段,从而就知道了包的结束位置。**
** 对于边长的包,还可以在包和包之间使用明确的分隔符。**
2.9.2 UDP 会存在粘包问题吗?
** UDP 是一种无连接的传输协议,不会对数据包进行拆分和合并,因此不存在 TCP 中的粘包问题。每个 UDP 数据包都是独立的,不会因为传输过程中被合并或拆分而导致粘包问题。**
2.10 异常情况
** 1)进程终止:进程终止会释放文件描述符,操作系统仍然可以发送 FIN ,和正常关闭没有什么区别。当一个端口上的所有数据都发送完毕后,操作系统会自动发送一个 FIN 段,用来通知另一端数据发送完毕并准备关闭连接。**
** 2)机器重启:和进程终止的情况相同。**
** 3)机器掉电/网络断开:接收端认为连接还在,一旦接收端有写入操作,接收端发现连接已经不在了,就会进行 RESET ,即使没有写入操作,TCP 自己也会内置一个保活定时器,会定期询问对方是否还在,如果对方不在,也会把连接释放。**
版权归原作者 小扳 所有, 如有侵权,请联系我们删除。