网络原理知识(7)

上次我们提到了TCP协议的一些核心机制,讲完了三次握手这个面试最常考的内容,接下来该讲解四次挥手的内容了,当然这仍然是TCP的核心机制三中的一部分。

四次挥手(断开连接)

上次我们说四次挥手中间两次是否能合并,回答是"如能",不能是因为ack是内核控制返回的,内核收到FIN,第一时间返回ack,和你应用程序的代码无关;而第二个fin则是代码中调用socket.close才会触发的(进程结束也能触发)。因此第二个fin的时机与第一个ack的时机很可能不是同一时机。

至于回答"能",这个到后面我们可以了解到,延迟应答,当然到那里就叫"捎带应答"了。

三次握手,一定是客户端主动发起syn(第一次握手一定是客户端开头的);而四次挥手,客户端和服务器都可以主动发起fin(就看是谁先调用close)。

但是,从实践角度来看,还是客户端断开连接的可能性更大,一个餐馆主动把客人撵走,感觉不太合适。

下面是四次挥手的详图:

这里的closewait与timewait的关系是:谁是主动发起fin的一方(不能用客户端、服务器来描述),就会进入到timewait;谁是被动接收fin的一方,就会进入closewait(这里在等待你的应用程序代码来调用close方法)。当然timewait也是为了给最后一个ACK丢包行为,做一个托底。

这里看服务器的代码:

从这里我们也可以看到第一个fin与第二个fin之间有时间差的,不是在第一个ack之后立即执行第二个fin的。

在网络传输中,随时都会丢包,三次握手、四次挥手时也一样。

丢包就需要触发超时重传,在四次挥手中,丢包有好几种情况,如果第一个fin丢了,A就无法在规定时间内拿到ack,于是A会重新发送fin;如果是第一个ack丢了,逻辑同上;如果是第二个fin丢了,B也再次重传就可以了,在第二个fin到达A之前,A这里的连接肯定是存在的,就能够及时的处理ack。

假设出现了这样的情况:

如果B给A发fin,A收到了,在A收到了之后,返回ACK把连接释放了。

可是,在A向B发了ack之后,不管B是否收到了ack,直接把连接释放了,ack也恰好出现了没有被B收到的情况(ack丢包了),此时就无人来处理重传的fin了。

此时A的应对方法是:A这边在收到了fin之后,不能立即释放连接,而是要等一下,等一下对方是否可能重传fin(最后一个ack是可能丢包的)。多等一会,确认对方确实不会重传fin再释放。

那么要等多久呢?

这里的listen、Established、timewait与closewait都是经典的面试题,大家要好好掌握。

核心特性四:滑动窗口

这里听着跟数据结构那里的滑动窗口怎么名字一样?算法中的滑动窗口是出自TCP的。

在确保TCP的可靠性传输的同时,TCP也会付出代价,就是效率降低,肯定效率没有UDP高。

那么,我们引入了滑动窗口来对效率来进行优化。

正常的数据包传输过程,都是发送一个,再有一个确认应答。

这样的效率就很低。

于是,滑动窗口的特点,就是"批量传输":

A的很多个数据包发送给主机B,B再确认应答,前几个数据包都是不等待直接往后发,发到了一定的量之后再等待(当然不能一直发,不然可靠传输无法保证)。

下一组是怎么发的?不是等这一组所有的ack都回来再发第二组,因为这样花费时间很长,而是收到一个ack之后就发下一条,这样A就用一份时间来等多组的ack了(把多组等待ack的时间重叠成一份了)。

那么,就有了这样的滑动窗口:

这里的2001的ack返回给A时,A就会发下一个从5001到6000数据包,滑动窗口右移一格,以此类推,当中间的数据包先返回时,比如一开始的滑动窗口是1到4001,按理来说,应该是1001先返回,但2001的ack先返回了,我们默认将滑动窗口右移两格,将后两个(4001到6000)数据包发送过去。这里是因为序号2001的含义是该序号之前的数据都已经确认收到了,2001ack就能覆盖他前面的(即使还没到)1001的含义。

那么,窗口越大,批量发的数据就越多,效率就越高,但是窗口不能无限大,太大会影响到可靠性。滑动窗口是在可靠传输的基础上提高效率的(当然,引入可靠性,会使效率产生折损,引入滑动窗口,只是让折损更小,效率不可能比UDP还高)。

在滑动窗口的过程中,当然也会丢包。

这里分两种情况:

此时,不用做任何处理,由于有一些ack到达了主机A,后面的ack值自然会覆盖前面的。

下一个就有问题了:

此时,1001~2000的数据包丢了,在A不知道的情况下,继续选择2000之后的数据包进行传输,而后面主机B没有收到数据包1001~2000,会一直返回1001的ack,虽然收到了2001~3000及以后的数据包,但1001~2000丢包了,接收方仍然再索要1001的数据。当主机A发现返回的ack都是1001时,A就意识到了,1001~2000丢包了,于是重新传输1001~2000,当1001~2000传输成功之后,此时的确认序号是7001,因为刚才的2001~7000都收到了,就差1001~2000了,通过重传一下子把缺失的部分补上了,补上之后,继续从7001往后传输就行了。

这里就是快速重传,只是谁丢了,重传谁,其他已经收到的数据无需重传,整个重传过程是很快的(这也是滑动窗口下的超时重传的变种操作)。

这是当时接收缓冲区的情况:

核心特性五:流量控制

滑动窗口,窗口越大,效率就越高,但是不能无限大,太大了就会影响可靠性。而接收方的处理能力是有上限的(和你的代码是有关系的),如果超出了这个上限,就会出现丢包,这里就引出了"流量控制"。

流量控制就是给发送方踩刹车,让发送方发的慢点,可以让接收方根据自身处理数据的速度,反馈给发送方,限制发送方发送的速度,在ack中,依赖一个特殊的属性,就是上图中的"窗口大小"。

"16位窗口大小",表示的是接收方接收缓冲区的剩余空间的大小,填入到这个属性中,发送方会按照这个数字来重新设定发送的滑动窗口大小,因此滑动窗口的大小是动态变化的。由于报头+载荷的长度为64KB,而在其中是否滑动窗口的大小最大数值就是64KB呢?不是的,在"选项"里,有一个特殊的属性------窗口扩展因子。

于是,就有了下图:

当然,在接收缓冲区满了之后,由于A要知道B接收缓冲区啥时候有空余,就会进行"窗口探测",如果B回复说仍然是满的,那么等一会A又会发一个窗口探测,直到窗口缓冲区有空余为止,继续向B发送数据包。

核心特性六:拥塞控制

流量控制是依据接收方处理能力进行限制的,根据接收缓冲区空余空间来衡量;拥塞控制是依据传输链路的转发能力来进行限制的。

如果不好衡量某个设备,就可以把整个通信链路视为整体,通过做实验的方式,找到一个合适的窗口大小。

如图,中间是各种路由器与交换机,数据包都是经由他们传输给接收方的,这里参照"木桶原理",先按照小的窗口(速度)先发送着,如果到时候很顺利,没丢包,就加大速度;出现了丢包,就减小速度,达到一种动态平衡。

拥塞控制与流量控制都能限制发送方的窗口大小,这两个值哪个小,哪个说了算,16位窗口那里就填谁。

下面是拥塞控制图:

一开始慢启动,一开始的窗口指数增长(增速很快),但与此同时要设置一个阈值,防止出现丢包严重的情况,当指数增长第一次大于等于这个阈值时,如果没有丢包,就改为线性增长,直到出现丢包情况,出现了丢包情况,不再像之前一样重新慢启动,而是将窗口大小从阈值开始线性增长,也就跳过了指数增长的阶段,直到丢包,再重复这样的操作。

今天的内容到这里就结束了,下次我们继续。

相关推荐
刘佬GEO1 小时前
线下医美机构做 GEO 的实际价值:从策略到效果拆解
网络·人工智能·搜索引擎·ai·语言模型
前端摸鱼匠1 小时前
【AI大模型春招面试题26】大模型的“上下文窗口”(Context Window)是什么?长度对模型性能的影响?
人工智能·ai·面试·大模型·求职招聘
南境十里·墨染春水1 小时前
linux学习进程 线程同步——读写锁
java·jvm·学习
ZWZhangYu1 小时前
MCP 实战:从协议原理到 Java 自定义工具服务落地
java·开发语言·人工智能
Flittly1 小时前
【SpringSecurity新手村系列】(5)RBAC角色权限与账户状态校验
java·spring boot·笔记·安全·spring·ai
笨蛋不要掉眼泪1 小时前
面试篇-java基础下
java·后端·面试·职场和发展
wechatbot8882 小时前
企业微信 iPad 协议客服机器人自动化管理平台开发指南
java·运维·微信·自动化·企业微信·ipad
weisian1512 小时前
Java并发编程--46-热点Key与大Value:Redis集群中的“定时炸弹”
java·redis·热key·大key
zs宝来了2 小时前
网络篇11-本机网络IO工作原理
服务器·网络·tcp/ip