系列文章目录
【网络通信基础】网络中的常见基本概念
【网络编程】Java网络编程中的基本概念及实现UDP、TCP客户端服务器程序(万字博文)
【网络原理】UDP协议的报文结构 及 校验和字段的错误检测机制(CRC算法、MD5算法)
文章目录
一、TCP协议的特性
TCP协议段格式
二、TCP协议的相关机制
1. 确认应答
2. 超时重传
一、TCP协议的特性
前面介绍过,TCP(Transmission Control Protocol,传输控制协议)是互联网中的一种有连接的、可靠的、面向字节流、全双工的传输层协议。它是TCP/IP协议族中的一个重要组成部分,用于在网络中可靠地传输数据。
TCP协议是以后工作中最常用到的传输层协议,也是面试最常考的协议,非常非常重要!
TCP协议段格式
TCP协议段(Segment)的格式如下图所示:
由于TCP报头最大长度是60字节,而除选项外,其余报头字段加起来一共是20字节,是固定的。这个选项部分是可选的,因此选项部分为0 ~ 40个字节。
可以看到,TCP报头是比UDP报头复杂很多的。
TCP的内部机制也是很多的,需要了解TCP的这些机制,才能理解报头的含义。
二、TCP协议的相关机制
1. 确认应答
确认应答,可以说是TCP协议用来确保可靠性,最核心的机制。
假设你给“朋友“发了一条短信:
- 当你发出短信后,你可能会期待朋友收到并回复你,以确认他已经收到了你的短信。如果你很长时间没有收到回复,你可能会怀疑短信没有发送成功(前提是朋友一定会回复你的短信)。
- 如果朋友此时收到短信,并给你回复了,你看到回复,就可以知道之前发的短信是一定被正确收到了。在这里,朋友的回复就相当于确认应答。
再假设,你给“朋友”发了两条短信:
- 由于网络传输过程中,经常会出现"后发先至"的情况。导致朋友回复短信的时候,回复短信的顺序和发送短信的顺序是不一致的,就容易发生“答非所问”的情况。
出现后发先至的情况是,由于网络拥塞或者数据包的路由选择引起的。这种情况可能发生在网络中存在多条路径,但是不同路径的延迟不同,导致一些数据包比其他数据包先到达目的地。
为了解决上述问题,TCP就入了序号和确认序号。通过对数据进行编号,应答报文里就告诉发送方,我这次应答的是哪个数据。
而TCP是面向字节流的,以字节为单位进行传输的。因此,TCP的序号和确认序号都是以字节来进行编号的。如下图:
- 假设载荷有1000个字节,一个载荷就有1000个序号,由于序号是连续的,只需要在报头保存第一个字节的序号即可,后续字节的序号都是很容易计算得到的。
- 应答报文中的确认序号,是按照发送过去的最后一个字节的序号+1,来进行设定的。
如上图,当发送第一条数据:
- 数据报头的序号就是1,主机B收到了1 - 1000这些字节数据后,反馈一个应答报文,应答报文中的确认序号的值就是1001
此处1001的含义,有两种理解方式:
- 小于1001的数据,都已经收到了.
- 发送方接下来要给我发的数据是从1001开始的.
确认应答中,通过应答报文来反馈给发送方,当前的数据正确收到了。应答报文,也叫 ACK 报文,ACK => acknowledge 单词的缩写。
ACK报文也就是TCP报头中六位标志位的第二位。平时该位是0,如果当前报文时应答报文,则这一位就是1.
2. 超时重传
超时重传,可以被视为是确认应答机制的一种补充。
发送方发送数据,如果一切顺利,接收方通过 ACK 标志位就可以告诉发送方,当前数据是否成功收到了。
但是,在某些情况下,1. 如果接收方没有及时发送 ACK 确认,2. 或者数据包丢失(丢包),发送方就无法得知数据是否已成功传输(至于为什么会丢包,这里就不细说了)。这时就需要超时重传来补充确认应答机制。
简单来说:
- 发送方发了个数据之后,要等待接收来自接收方的ACK确认。
- 如果等了很久,ACK还没等到,此时发送方就认为数据的传输出现丢包了。
- 当认为丢包之后,就会把刚才的数据包再传输一次(重传)。
- 而这个等待的过程有一个时间阈值(定时器),超过等待时间,就是(超时)。
上面的过程,我们认为没收到ACK就是丢包。实际上,超时重传的触发可能不仅是由于数据包丢失导致的,还可能是由于ACK确认丢失导致的。
不论是数据包丢了,还是ACK丢了,从发送方的角度来看,是区分不了的,都是认为ACK没收到。如下图,对比一下这两种情况:
对于ACK丢失而导致的主机B收到很多重复数据这种情况,这里涉及到的内容也很多:
- TCP socket 在内核中存在接收缓冲区,对于发送方发来的数据,是要先放到接收缓冲区中的。而应用程序调用读方法读数据的操作,其实是读取接收缓冲区的数据。
- 当数据到达接收缓冲区的时候,接收方首先会判定当前缓冲区是否已经有,或者有过这个数据了。
- 如果这个数据已经存在或者存在过,TCP就会直接把重复发来的数据丢弃(去重),这样就能确保应用程序,调用读方法的时候,不会出现重复数据了。
※ 接收缓冲区,除了能进行去重之外,还能够进行排序,对收到的数据按照序号进行排序,确保上层应用程序读到的数据和发送的数据顺序是一致的。
接收方如何判定这个数据是否是"重复数据"?
核心判定依据:前面谈到的数据的序号。
- 数据还在接收缓冲区,还没被读走。此时,拿着新收到的数据的序号,和缓冲区中所有的数据的序号对一下,看看有没有一样的。有一样的就是重复了,就把这个数据丢弃了。
- 数据已经不在接收缓冲区,被应用程序读走了。此时,新来的数据序号是无法从接收缓冲区查到的。但是!应用程序读取数据的时候,是按照序号的先后顺序进行读取的。例如,1-1000,1001-2000,2001-3000。一定是先读序号小的,后读序号大的数据。此时, socket api中就可以记录上次读的最后一个字节的序号是多少。比如,上次读到的最后一个字节的序号是3000,新收到的数据的序号是1001,而这个1001一定是之前已经读过了。这个时候同样可以把这个新的数据包判定为“重复数据”直接丢弃了。
上述这些机制,都是TCP内置的,我们在使用TCP的api的时候,不需要考虑这些。但是学习这些能够让我们了解TCP内部做了哪些事情,从而写出更正确的代码。
TCP为了保证较高性能通信,此处的超时重传也不是无限重传,重传过程也是有一定的策略的。
- 重传次数是有上限的。如果累计到一定次数,还没有收到ACK,TCP认为网络或者对端主机出现异常,强制关闭连接。
- 重传的超时时间也不是固定不变的。随着重传次数的增加,这个超时时间会动态增长。
使用上述策略1的原因:
- 假设,一次网络通信过程中,丢包的概率是10%(已经是一个很大的数字了),包顺利到达的概率是90%
- 如果此时重传了一次,两次都丢包的概率只有10% * 10%,即1%;而两次至少有一次传输成功的概率,就是99%
- 随着重传次数的增加,包能到达接收端的概率也会大大增加,这个概率就非常高了。
如果继续重传,在成功概率这么高的情况下,还是出现丢包的情况,说明当前网络已经出现非常严重的故障了,再重传也意义不大了。因此,就会关闭连接。
使用上述策略2的原因:
结合策略1的分析,数据经过了重传之后还是丢包,大概率是网络出现严重问题了,在没达到重传次数上限之前,重传还是要重传的,但是可以省点力气,少传几次(降低重传频率)。