UDP
UDP数据报=UDP报头+UDP载荷
UDP的报文格式:
这里的UDP长度,描述了整个UDP数据报,占多少个字节,这里整个UDP长度最多是64kb
在UDP中校验和就是使用CRC的方式来完成的
数据在网络传输中是可能会出现错误的,例如比特翻转(0变成1,1变成0),基于数据内容生成校验和
主机A对数据内容进行校验和后发送给主机B,主机B再对传输来的数据继续进行一遍校验和,如果不相等就舍弃。
CRC算法是一个简单粗暴的计算校验和方式,循环冗余校验。
设定2个字节的变量,把数据的每个字节取出来,往这个变量上进行累加,如果结果溢出超过两个字节,溢出部分就舍弃
TCP
TCP的报文格式:
四位首部长度(是可变长的):指的是报头的长度
如果选项没有,tcp报头的长度就是20字节
如果选项拉满,tcp报头最长为60个字节
4bit表示的数据范围为:0-15,此处首部长度的单位是“4字节”,实际首部长度要在这个数字基础是*4
选项部分最多可能有40个字节
这里的PSH(就是你速度给我会消息,催单用的)
TCP10个比较核心的机制
1.确认应答机制
对于TCP协议来说,可靠传输是一个很重要的问题
我们可以发现在报文中也有一个ack,当这个ack为1时,说明这个是应答报文
但在网络传输中可能会出现后发先至的问题(你如在微信聊天的时候,你发给你的好友两条消息,你的好友会分别回应你这两条消息,但是由于后发先至的问题,你看见的是你的好友对于你的第一条他是第二条回应的)
为了解决这个问题,我们用32位序号和32位确认序号,
由于tcp是根据“字节”来进行编号的
2.超时重传
用来应对网络出现丢包的情况
一般情况下,TCP是通过确认应答来确定数据是否被接收端收到
在前面的确认应答机制中,发送方是通过接受方返回ACK来确定接受端是否收到了数据,假设在发送端发送的数据出现了丢包的情况,那么接受端就不会给发送端发送ACK(正常情况下接受端在收到数据并向发送端发送ACK也是需要时间的,发送端就会进行一定的等待,但是不可能一直等吧,如果这个等待超过了这个时间阈值,那么发送端就会认为发送数据时发生了丢包,发送端就会再将数据重新发送一份给接收端,这就是重传)
接下来我们思考这样一个问题,既然发送的数据会发送丢包,那么接收端在发送ACK的时候会发生丢包吗?
答案是当然会的,如果发送端没有接受到ACK,那么就会认为数据发生了丢包的情况,超过阈值时间后就会再发一次数据给接收端,这时接收端就会有两份一模一样的数据(这样的重传是不允许的,假设你给好朋友转账如果触发重传就会再一次进行转账这是一个严重的问题)但是对于这种情况大佬早已为你解决,去重)
注意:无论tcp底层重传多少次,但核心机制都要保证应用层读到的数据只能有一份
如何进行去重?
发送端在未收到ACK的条件下,会再次向接收端发送一次数据,此时接收端会有两份数据,在接收端会按照序列号来进行去重。
接收端的操作系统内核中,存在一个数据结构“接受缓冲区”类似于阻塞队列,将接受的数据按序列号放入阻塞队列中,在队列中进行判断,判定这个数据是否在队列中存在或是否以前曾出现在队列中,这要出现过这个数据就不会进入队列,直接丢弃(当然在你丢弃的时候,接收端的操作系统内核就知道发生了ack丢包的情况,会再发送一次ack给发送端)这就是一次完整的去重
假设你说我就是想多发一次,不好意思序号是不同的,且序号是协商的不是人为
接受缓冲区除了去重还可以排序,解决在网络传输中先发后至的问题,在接受缓冲区中根据序号的大小进行排序。
上述我们谈到了发送端会在阈值时间内等待接收端发送ACK,那么这个等待时间是多少呢?
这个时间不是一个固定数值而是动态变化的,随着重传次数的增加等待时间也增加。
如果在网络传输中确实出现了严重的故障,重传的很多次还是没有成功,达到一定次数后,就会尝试重置连接,即那六个标志位的RST标志位(复位报文),通信双方清空在这次连接中 的缓冲区的所有数据,重新进行传输。当然这个报文也是要进行传输的,如果出现严重的故障这个报文也是无法发送出去的,此时重置失败,只能断开连接(这个断开连接就是把保存的信息全部删除掉)
综上所述,确认应答机制和超时重传对于可靠性传输是很重要的
3.连接管理
建立连接的流程:三次握手(一定是客户端先发起第一次请求)
断开连接的流程:四次挥手(客户端和服务器都可以发出请求)
建立连接就是通信双方保存对方的信息,完成上述过程要经过三次网络交互
SYN(sychronized)同步 也是TCP报头六位标志位之一
为什么要进行三次握手呢?
首先是为了确保网络传输通道是否畅通(就像地铁的早上第一辆车,查看是否有异常),其次为了保证发送端发送和接收端接受正常(举个例子在打电话有a和b两人,a说话(SYN)后b回应(SYN+ACK)这就让a知道我话筒和听筒都好着,b就知道字节的听筒好着,但是不知道话筒有没有问题,a再回应b(ACK)这时候双方都能确认可以进行正常通话) ,再次在进行连接管理的时候会将tcp报头发送过去,报头包括SYN位为1,还有端口号和ip(肯定要加ip报头才能发送),这样就能让通信双方保留对方的信息 ,还有在这三次握手中还要协商一些必要的参数,比如起始序号,从多少开始然后以字节为单位进行排序
一些特殊情况:比如第一次建立连接所发送的数据报在第一次断开连接后未到达,但是在第二次建立连接后到达了,这种情况下,到达的数据报会直接丢弃,那么我们如何区分这个数据报是不是以前建立连接的报文呢,根据序号就可以判断出来,建立连接时的起始序号时差异很大的
TCP状态
断开连接(四次挥手)
举个例子:假设有两个人a(客户端)和b(服务器) a对b说我要把你删了,b回应知道了,那我也要把你删了,a回应说好。 相互删除,谁怕谁
根据上述图,可以合并为三次挥手吗?答案是可以的,不过得是特殊情况
首先为什么不能合并?
在客户端的FIN发来时,服务器会立即发送一个ACK过去,但在服务器中要应用程序调用close的时候才会给客户端发送FIN两个的时间相隔很久,所以不能合并
在特殊情况下又能合并
在tcp中还有一个机制,”延时应答机制“,我会给你发送ack,但不是这会,这样就可以等待一起发送
四次挥手的重要状态
CLOSE_WAIT:等待应用程序调用close方法
TIME_WAIT:应对最后一个ack丢包这样的场景
客户端在收到服务器发送的fin之后,不能立即释放tcp连接(如果立即释放的,后续一旦对端重传了fin,此时客户端就无法应对了,无法返回ACK)
因此客户端这边就需要一个特殊状态TIME_WAIT状态等待可能到达的fin重传的数据。
TIME_WAIT状态不是持续的,而是有一定时间限制的(等待时间,一般时2MSL),在一定时间内,如果没有收到重传的fin,就默认最后一个ack已经被对方接收,此时TIME_WAIT释放,tcp连接释放
如果服务器出现大量的TIME_WAIT怎么处理?
出现大量TIME_WAIT说明服务器这边触发大量主动断开tcp连接的操作
4.滑动窗口
滑动窗口是一种提高传输效率的机制
这是不引入滑动窗口的数据传输过程
这是引入滑动窗口的数据传输过程
窗口即不等待ack,批量发送数据的过程
滑动窗口中,批量发送的四组数据,不是等待四组数据的ack全部返回后再进行下一组,而是返回一组的ack,继续再发送一组
5.流量控制
流量控制是根据在批量转发数据时,窗口的大小,窗口越大单位内发送的数据就越多,效率就越高,在通常情况下我们肯定是希望单位时间内发送的数据越多越好,但是要保证可靠性,如果发送方太快处理不过来,就有可能引起丢包,这种控制单位时间发送数据的效率以及保证其可靠性的过程叫做流量控制(接收方根据自己的处理速度来约束发送方的速度,使发送数据达到一个平衡)
那么具体如何衡量接受方的速度?
在接收放有一个接受缓冲区(相当于一个阻塞队列),根据这个缓冲区的空闲大小来设置窗口大小,在我们的tcp报头中有16位窗口大小,也就是说就这个16位窗口大小来说最多是64kb,但实际上可以更大,因为在我们的选项中可以设置窗口扩展因子(窗口大小<<窗口扩展因子)
拥塞控制
这也是一个与滑动窗口相配合的机制
流量控制是以接收端的角度去思考的,而拥塞控制则是站在网络节点,在网络传输中,每个网络节点的繁忙程度也各有不同,接受方可以每秒接受很多数据,但是发送的数据就会很有可能卡在半路,发送太快最后引发BUG,这样我们就得去实验:
先将窗口大小以很小的速度进行发送,然后逐渐增加直到出现丢包,然后减小到不丢包了继续增加。
这样我们就可以得到一个合适的窗口大小,不仅仅可以以最快的速度传输,还能保障传输的速度,由于网络是多变的,为了更好的提高数据传输的效率,动态平衡随时调整窗口大小
- 刚开始以比较小的窗口进行数据传输
- 按照指数的方式扩大窗口
- 在指数增长到某个阈值后变成线性增长
- 线性增长到一定程度后出现丢包
- 缩小窗口(方式1:回到刚开始的慢启动。方式2:缩小到一半大小)
7.延时应答
在发送端发送数据后,接受端并没有立刻回应(返回ACK)
延时的目的是为了提高传输效率,通过延时可以给应用程序腾出来更多消费时间,就像你现在直接返会ACK,接受端的接受缓冲区是4kb,你等待一会,接受缓冲区消化一部分后就有4.5kbl,这样他的窗口大小就可以更大,传输效率也就越快了
延时时间的两种方式:1.按照一定的时间来指定延时 2.按照一定的数据量
一般情况下两者结合使用
8.捎带应答
建立在延时应答的基础上,提高效率的
用白话就是说,我看电影,想去上厕所,顺带着倒了杯水。
我们在讲四次挥手的时候,就提到特殊情况下,可以变为三次挥手。就是让延时应答发送端的FIN,然后等待接收端也要给发送端FIN的时候捎带着把应答报文一起发送给发送端
9.面向字节流
在字节流的读取过程中会涉及到一个非常关键的问题:粘包问题(粘的是应用层数据报)
解决方式:分隔符(在1.1中写代码时为什么要加分隔符号的原因)
10.异常问题
1.进程突然崩了
在这种情况下,操作系统任然可以正常回收释放的PCB,释放里面的文件描述符表,虽然进程没了,但操作系统任然管理着tcp的连接,可以正常完成四次挥手断开连接
2.某个主机被关机
关机的时候会弹出让你结束所有你打开的应用进程,然后就是和进程崩了一样的处理。
3.如果直接物理关机
假设接收端掉电,发送端发送数据但结果时得不到回应,重传一定次数后,发送复位报文RST,RST没响应就把他删了
假设发送端掉电,接收端等半天没机发消息,发送个心跳包给接收端问他嗝屁没,嗝屁了就删了所有信息
4.网线断开
发送端发送RST,没回应,删!
接收端发送心跳包,没回应,嗝屁了,删!