1. UDP 协议结构
报文格式:
UDP 的报文分为报头,正文/载荷(完整的应用层数据包),其中报头部分又分为四个部分,每一个部分都是固定的四个字节,分别存储源端口,目的端口,UDP 报文长度(报文长度 = 报头长度 + 载荷长度),校验和(检验和),每一个部分都是固定的两个字节存储,由于是两个字节存储 UDP 报文长度,所以最大值就是 65535 ,也就是 64KB ,这个时候就会出现一个问题,如果要表示的内容不止是 64KB ,就需要换用 TCP 来表示了
关于校验和:由于网络传输过程中是比较容易出现错误的,传输的电信号/光信号/电磁波等信息容易受到环境的干扰,使这里的传输信号发生转变,校验和的目的就是能够“发现”或者“纠正”这些错误,同时,如果只是发现错误,那么校验和携带的信息就可以很少,如果想要纠正错误,就需要再携带额外的信息(消耗更多的带宽)
在 UDP 协议中使用的简单有效的校验和是 CRC 校验和(循环冗余校验):对 UDP 数据报整个进行遍历,分别取出每一个字节,往一个字节或是两个字节的变量上进行累加,即使溢出之后也继续加,主要关注的是校验和的结果是否会在传输中改变
如果说传输的数据,在网络通信中没有发生任何改变,此时计算出来的就是 checksum1 == checksum2 反之,如果不相等,就代表数据传输中数据发生了改变,就会丢弃这次传输
此外还可能会发生传输过程中校验和的信息也发生改变了,也就是传输过程中校验和变成了 checksum3,此时接收方重新计算校验和得到了 checksum4 ,这种情况下两个校验和大概率是不相等的,所以影响也不大,还有可能出现两组不同的数据计算出相同的校验和,这种概率也是非常低的,所以上面这两种极端情况一般不考虑
MD5 算法:
本质上是一个“字符串 hash 算法”,特点:
- 定长:无论输入多长的字符串,得到的结果都是固定长度(适合做校验算法)
- 分散:输入的内容只要发生一点改变,得到的结果也是相差很大的(适合做哈希算法)
- 不可逆:根据输入的内容计算 md5 对计算机来说是不复杂的,但是如果根据 md5 的值来计算原始值,理论上是不可以的(适合做加密算法)
2. TCP 协议
2.1. 协议结构
2.2. 确认应答
在之前提到过 TCP 的核心机制是确认应答,可以确认对方是否收到数据,在数据传输的过程中,如果有多条请求,并且返回对应的响应,但是此时可能会出现这样的问题:最先发送的请求可能并不会最先收到响应,也就是收到响应的顺序会不一样。
针对这样的问题的解决方案就是给每一个字节都进行编号(TCP 的传输是面向字节流的),并且编号是连续且递增的,按照字节编号这样的机制就称为“TCP 的序号”,在应答报文中,针对之前收到的数据进行对应的编号,称为“TCP 的确认序号”
上面的 32 位序列号和确认序列号就是这种,由于序号是递增的,知道了第一个字节的序号,后续每一个字节的序号都能知道
假如 TCP 发送了的数据标记为了 1~1000,那么确认应答的序号应该是收到的数据最后一个字节序号的下一个序号,也就是1001,表示小于 1001 序号的数据都收到了
并且之后的六位标志位中的第二位(ack)就会设为 1(默认是0)
2.3. 丢包
丢包的原因:
- 数据传输过程中发生了 bit 翻转,收到这个数据的接收方/中间的路由器等,计算校验和发现不匹配,就会把当前数据包丢掉,不再交给应用层
- 数据传输到某个节点(路由器/交换机)时,当前节点负载过高,例如某个路由器单位时间内只能发送n 个包,但是遇到了高峰期,单位时间内需要发送的包超过了 n ,后续传输过来的数据就可能被路由器丢掉了
2.4. 超时重传
TCP 对抗丢包的方法:其实丢包是不可能避免的,TCP 感应到丢包之后就会再重新发一次数据,第二次再发生丢包的概率就会减小很多,TCP 感应丢包是通过应答报文来区分的,收到应答报文之后就说明没有丢包,没有收到应答报文就说明数据丢包了,但是也不能排除当时没收到后续收到了的情况,所以就需要设置一个时间限制,在时间限制内来判断是否丢包,不过还有一个特殊情况:
第一种就是正常的数据没有发送到丢包了,第二种是数据没有丢,但是 ack 丢了,不过无论是哪种情况都会认为是丢包并且进行数据重传,这时就会出现一个问题,第一种情况是没问题的,数据丢了重新传,但是第二种情况数据没有丢,再次发送就意味着主机2收到了两份同样的数据,如果是转账的请求,让你转两次账肯定也不合理
针对上述问题 TCP 也进行了处理,接收方会有一个接收缓冲区,收到的数据会先进入缓冲区中,后续再收到数据就会根据序号在缓冲区中找对应的位置,如果发现当前序号 1~1000 已经存在了,就会把新收到的数据丢弃了,以此来确保读取到的数据是唯一的
重传的时间设定:
这里的时间不是固定的,而是动态变化的,例如发送方第一次重传,超时时间为 t1,如果重传之后仍然没有 ack ,还是继续重传,第二次重传超时时间为 t2,,t2 是大于 t1 的,每多重传一次,超时时间的间隔就会变大
经过一次重传之后,就能让数据到达对方的概率显著提示,反之,如果重传几次都没有顺利到达,说明网络的丢包率已经达到了一个很大的程度
重传也不会无休止的进行,当重传到达一定次数的时候,TCP 就不会尝试重传了,就认为这个链接已经G了,此时先进行“重置/复位 连接”,发送一个特殊的数据包“复位报文”,如果网络恢复了,复位报文就会重置连接,使通信继续进行,如果网络还是有问题,复位报文没有得到回应,此时 TCP 就会单方面放弃连接
确认应答和超时重传这两个核心机制共同构建了 TCP 的“可靠传输机制”
2.5. TCP 的三次握手
三次确保了客户端和服务器之间建立连接,发送不携带业务数据(没有载荷,只有报头)的数据包
客户端发送同步请求,也就是标志位中的第 5 位,然后服务端也回应发送同步信息和确认应答,客户端再确认应答,虽然说看上去是四次交互,但是中间服务器的 syn + ack 合并了,一起发送到客户端,也就是三次握手
TCP 进行三次握手的原因:
- 验证通信路径是否畅通。
- 验证通信双方的发送能力和接收能力是否正常,客户端第一次发送 syn 可以确定客户端的发送能力和服务器的接收能力正常,然后服务器发送 syn + ack 告诉客户端的发送能力和服务器的接收能力正常,然后客户端就知道了自己的接收和发送能力都正常,再发送 ack ,服务器也确认了自己的发送能力正常
- 让通信双方在进行通信之前,对通信过程中需要用到的一些关键参数进行协商(例如确定起始序号,TCP 通信时,起始数据的序号就是通过三次握手协商确定的,每次建立连接 TCP 的其实序号都不同,并且差别很大,这样做是为了避免上一次的数据如果“迷路了”,在下一次 TCP 连接时出现误判,如果发现不是属于此次起始范围的数据就丢弃)
2.6. TCP 的四次挥手
- 客户端完成数据发送任务后,发送一个带有 FIN(终止)标志位的数据包,用来关闭客户端到服务器的数据传送。此时客户端进入 FIN_WAIT_1 状态,表示客户端不再向服务器发送数据,但仍可以接收服务器发送的数据。
- 服务器收到客户端的 FIN 包后,发回一个 ACK 数据包给客户端,确认序号为收到的序号加 1。此时服务器进入 CLOSE_WAIT 状态,表明服务器还有数据可能需要发送给客户端,客户端收到这个 ACK 后进入 FIN_WAIT_2 状态,继续等待服务器的 FIN 报文。
- 当服务器端确定数据已发送完成,则向客户端发送 FIN 报文,告诉客户端自己也要断开连接了,然后服务器进入 LAST_ACK 状态,等待客户端的确认。
- 客户端收到服务器的 FIN 报文后,回复一个 ACK 报文给服务器,确认号为收到的序号加 1,随后客户端进入 TIME_WAIT 状态。服务器收到这个 ACK 报文后,连接正式关闭,进入 CLOSED 状态。客户端在经过 2 倍的 MSL(报文最大生存时间)后,也进入 CLOSED 状态。
状态说明:
LISTEN:服务器进入的状态,服务器把端口绑定好之后相当于进入了该状态,等待客户端发生请求
ESTABLISHED:客户端和服务器都会进入的状态,表示 TCP 已经建立好连接了
CLOSE_WAIT:被动断开连接的一方(先收到 FIN)会进入这个状态,等待代码执行 close 方法
TIME_WAIT:主动断开连接的一方会进入这个状态,按照时间来等待,达到一定时间后等待结束(原因:防止最后一个 ACK 丢包),时间就是 2 倍的 MSL(报文最大生存时间)