欢迎关注个人主页:逸狼
创造不易,可以点点赞吗~
如有错误,欢迎指出~
目录
接着上一篇我们继续谈TCP的核心机制~
TCP的核心机制四=>滑动窗口
可靠传输的代价是 降低了传输的效率,TCP希望能够在可靠传输的基础上也有一个不错的效率,所以引入了滑动窗口.
改进方案:把"发送一个等待一个"改成"发送一批等待一批",把多次等待ack的时间合并成一份时间,批量发送的数据越多,此时效率就可以认为是越高
批量发送的数据,不需要等待的数据量 称为"窗口大小"
遇到丢包如何解决
针对情况1,不需要任何处理
批量发数据,批量ACK,多个ACK只是丢了一部分,不可能全丢
虽然1001ack丢了,但2001到达了,发送方收到2001之后,意味着2001之前的数据都已经收到了(后一个ack能够涵盖前一个ack的意义)
针对情况2:快速识别出哪个数据包丢失,并且针对性的重传,其他顺利到达的数据都无需重传 .这个过程称为"快速重传"
B收到的数据1-1000 和2001-3000,其中1001-2000这个数据丢失,此时B收到2001-3000时返回的ack确认序号是1001,而不是3001(B在向A索要1001的数据),**接下来B收到的3001-4000,4001-5000,....对应的ack确认序号都是1001 ,**A 收到多个1001这样的ack后就意识到1001数据包丢失,A于是就重传1001-2000
TCP的核心机制五=>流量控制
滑动窗口的窗口大小 对于传输数据的性能是直接相关的,但是窗口能无限大吗?
通信是双方的事情,发送方发的快了,也要确保接收方可以处理得过来
这里需要让接收方的处理能力 反向制约 发送方的发送速度,这就是 流量控制
设置窗口大小
如果发送 的速度特别快 ,消费 数据比较慢 ,就会使接收缓冲区装满 ,此时,如果发送方强行发数据 ,就会丢包(被接收方丢弃了)
Linux内核里,就是用**"水位"**这个词来表示接收缓冲区中有多少数据的
- 如果空闲空间越大 ,就可以认为,是应用程序处理的速度比较快 ,就可以让发送方 发快一点,设置一个更大的窗口大小
- 如果空闲空间越小 ,就可以认为,是应用程序处理的速度比较慢, 就可以让发送方 发慢一点,设置一个更小的窗口大小'
TCP中,接收方收到数据时,就会把接收缓冲区剩余空间大小通过ACK数据报 反馈给发送方 ,下一步,发送方就可以依据这个数据来设置发送的窗口大小了
"16位窗口大小" 中的16位表示的范围是64KB,是否意味着发送方窗口大小最大就是64KB?
其中"选项"中可以设置一个特殊选项"窗口扩展因子"
计算公式: 发送方的窗口大小 = 窗口大小 << 窗口扩展因子
其中的 "<<" 表示左移运算(左移1位,表示 "乘2")
流量控制 也不是TCP独有的机制,其他协议也可能会涉及的流量控制(如 数据链路层中有的协议,也支持流量控制)
TCP核心机制六=>拥塞控制
拥塞控制与流量控制相关联,它们都是在对于"可靠传输"进行补充
- 流量控制:站在接收方的视角来限制发送方的速度
- 拥塞控制:站在传输链路层的视角来限制发送方的速度
传输链路中的中间节点情况非常复杂(中间节点非常多;每次走的路线还不一定一样;中间哪个节点出现问题也不好说;可能还有其他设备传输数据经过某个中间节点)
可以通过"做实验"的方式 来找到合适的发送速度
- 先按照一个比较慢的速度发送数据
- 若数据非常畅通,没有丢包, 说明网络上传输数据整体是比较通畅的=>加快传输速度
- 到达一定速度,发现丢包 ,说明网络上可能存在拥堵 => 减慢传输速度
- 减速后,发现不丢包,再加速;....
一直持续动态变化~
流量控制和拥塞控制共同 起作用 限制了发送窗口大小 ,最终实际窗口大小取决于这两个机制得到的发送窗口的较小值
TCP核心机制七=>延时应答
延时应答用于尽可能 降低可靠传输带来的性能影响(提升性能 =>让滑动窗口变大)
在接收方收到数据后 让ack不是立即返回 ,在晚一点的时间内,应用程序 就有机会,读取到缓冲区中更多的数据,进而消费掉更多的数据.在延时返回的ack的窗口大小,大概率要比立即返回的ack的窗口更大
假设让ack不是立即返回,而是100ms之后返回,此时在100ms之内,应用程序可能又消费掉了2kb的数据,此时返回ack携带的窗口大小就是6kb
TCP核心机制八=>捎带应答
捎带应答 是把返回的业务数据和ack 两者合二为一 了, 在延时应答基础上,引入的提升效率的机制
正常情况下,ack和响应 是不同的时机 ,无法合并,但是ack涉及到了**"延时应答",会使ack返回的时间被往后拖** ,这里的延时可能赶上 接下来发送响应数据的操作了,于是就可以在发送响应时把刚才的ack的信息也带上
ack和响应 可以共存
- ack报文不需要载荷,报头中设置ack这一位为1,窗口大小的值,确认序号...
- 响应数据主要设置的是 载荷
TCP核心机制九=>面向字节流
粘包问题
通过面向字节流的方式传输数据 都会涉及到"粘包问题",**TCP中粘的是"携带的载荷"(**应用层数据包)
应用层数据包在TCP的 接受缓冲区中 连成一片黏在一起就称为"粘包问题"
上图中 aaa,bbb,ccc才是完整的"应用层数据包"
解决粘包问题
关键在于明确"包和包之间的边界"
方案1:指定分隔符
适用于文本类的数据,比如约定请求和响应都以 \n结尾.
方案2:指定数据的长度
约定在每个应用层数据包 的开头2或4个字节表示数据包的长度(如果是传输二进制数据,这个方案就很有用了)
TCP核心机制十=>异常情况处理
1.进程崩溃
Java中的体现就是抛出异常,但是没人catch时,最终异常到了jvm这里,jvm进程会直接挂掉
看起来是"崩溃",实际上操作系统会进行"善后"(当进程崩溃时,进程中的PCB就要被回收,PCB中的文件描述符表里对应的所有文件,也会被系统自动关闭,其中针对socket文件,也会触发正常的关闭流程(TCP四次挥手))
2.主机关机(正常流程关机)
正常流程点击关机按钮,此时操作系统就会先干掉所有的进程 ,同时会触发四次挥手
- 四次挥手非常快,四次挥手在关机动作之前完成
- 四次挥手没来得及挥完,关机就完成了
3.主机掉电(拔电源)
接收方掉电
A给B发送数据,B不会再有ACK回应了
A会触发超时重传,重传的数据当然还是没有响应,
反复多次之后,A尝试重置连接(rst)
重置操作也没有ack,A就会单方面释放连接(A把 保存的B的信息删除掉)
发送方掉电
A发着发着不发了 ,B的视角看来,不知道A是挂了 ,还是晚点再发.
此时B就会给A发送一个数据包(探测报文=>不携带业务数据,只是为了触发ACK)
- 如果A返回了ACK ,说明A没有挂
- 如果A没有ACK, 甚至连续多个探测报文都没有ACK ,就可以视为A已经挂了
这个用来探测对方的"生死"的报文称为"心跳包",有周期性的.
TCP内置了心跳包,由于它的周期比较长(秒级-分级)应用程序这一层通常会自行实现一些心跳包,达到更快速的"保活机制"
4.网线断开
和主机掉电是一样的,是两者的复合体
TCP和UDP使用场景对比
TCP和UDP这两个协议并无优劣之分,他们在不同的场景发挥作用
- TCP对于数据需要可靠传输的场景必然是 首选
- UDP对于可靠性要求不高,对于性能要求很高的场景=>分布式系统中,主机之间的通信