网络探索之旅:网络原理
上篇文件讲到了这个网络编程,那么接下来小编就来分享下关于网络原理的一些知识。
对于这些网络原理,我们还得回到TCP/IP五层模型中。
那么首先来分享下关于应用层更多的信息。
应用层
这个应用层,在日常开发上,打最多交道的那一层。
而这里呢,则是涉及到以下这样的情况。
1.使用前辈们创建好的协议,比如常用的HTTP协议
2.自定义应用层协议。
那么对于这个创建好的协议中呢,比如http,我们后续文章再继续分享。
接下来分享的是自定义应用层协议。
这个自定义应用层协议呢,这是个很朴素的事情,并不是很神秘的。
那么它究竟在做什么呢?
1.明确前后端交互中,需要传递哪些信息
2.明确组织信息的格式。
比如举个例子,我们要开发一个校园外卖软件,那么此时呢,打开软件,映入眼帘的就是一串商家列表。
如何让这些信息重现呢?
1.请求
比如用户的ID、用户的位置信息
2.响应
返回的是商家列表
而这些商家列表信息中,此时就有着评分、名字、图片、距离等等这些信息。
那么好,接下来,这么多信息如何进行组织呢?
那么此时,我们就可以使用当前所存在的一些组织信息的方式,但值得注意的是,前后端必须是使用相同方式去组织信息。
比如来个简单粗暴的,行文本来进行组织信息
1.请求:
用户ID和位置信息
1001,E116N40\n
此时我们就约定,使用\n来进行换行
2.响应
商家ID,商家名称,商家图片地址,商家距离,商家评分
2025,杨国富麻辣烫,http://image1.com,西校区左侧靠门口,2km,4.6\n
如若是响应信息过多的话,此时呢,行文本显得臃肿的。
所以对于文本数据的组织方式
市面上还流行着这几种方式
1.xml格式
这样的格式是通过成对的标签表示键值对信息
例如:
<request>
<user>2025</user>
<position>E116N40</position>
</request>
相比之下,比行文本可读性上升不少,但它也有一定的缺点,比如数据较多时,占用网络带宽较多
而且标签也是固定了一套合法标准,不能自己创立
2.JSON格式
它也是使用键值对方式来组织数据
例如:
{
“userID”:2025
“position”:E116N40
}
当然,它优点是很明显的,可读性相比xml,好上不少,再者还能节省不少带宽
由于没有明确数据的绝对格式
所以会出现这样的情况
{“userID”:2025,“position”:E116N40}
如若里面的数据多达20多条的话,看起来也是很费劲的。
3.yml
它是要求数据严格按照某种格式
比如键值对必须独占一行,而且“嵌套”结构,必须通过缩进来表示
例如:
request:
userid:2025
position:E116N40
优点也是很明显的,可读性是很高的。
这些文本格式,可读性相对来说是挺高的,但也是牺牲了性能,即占用带宽较大
所以除了这样的,市面上还有一个二进制的格式
Google protobuffer
它是以二进制格式来组织数据的,显然,对于这个二进制,人是难以阅读的,所以就牺牲了可读性。
但是它会缩减传输数据的格式,带宽消耗少,带来的是,效率越高。
ok,讲到这里,应用层就告一段落先,
接下来再来分享下,传输层中TCP和UDP的相关东西吧
传输层
讲到传输层,那必定是要不开一个东西,端口号
端口号
前面讲过,端口号是一个整数,用来区分不同进程的,同一时刻,同一个机器,同一个协议,一个端口号,只能被一个进程绑定。
但是一个进程是可以绑定多个端口号的。
端口号是通过两个字节无符号整数来表示,范围是0~65535
所以对于这么多端口号,我们是不是可以随意使用呢?
当然不行,比如0端口号,是一个随机设定空闲端口号,即在开发过程中,有时会利用端口号0来测试如何由操作系统自动分配端口,或者验证某种情况下端口的可用性等。
那么还有一部分端口号,称为知名端口号
比如1~1023
这些端口,在互联网历史发展中,已经被预定好了,所以开发过程中避开这些端口号
对于知名端口号,那么有哪些常见的呢?
那么对于剩下1024~65535也是分为了两钟类型
1024~49151:注册端口,分配给特定的应用程序或服务
49152~65535:动态/私有端口,用于临时分配
其实端口还是有好几种类型的,在业务角度上
1.业务端口
比如编写一个服务器的时候,肯定是需要绑定一个端口和客户端进行交互
2.管理端口
比如在服务运行当中,相对服务器的配置进行修改,或者修改服务器某个功能
3.调试端口
服务器会绑定一个端口,然后实现相关的打印,关键变量的逻辑,客户端发送对应的调试请求等功能
ok,那么关于端口的相关知识就讲到这里,接下来小编来分享下关于UDP的进一步知识。
UDP
世界上,存在着很多互联网协议,对于众多协议,我们主要关注它、学习它“数据格式”、“报文格式”就差不多了。
对于UDP来说,它的报文格式如下

这里值得注意的是,为了方便看报文格式,这里主动换行了但实际下确实没有换行的

显然,对于这些名字显得有些陌生,那么小编接下一一解释看
首先要解释的是,为什么都叫做16位呢?
这是因为,源端口、目的端口、UDP长度、校验和它们的容量大小是两个字节的,所以是叫16位。
所以总共加起来就是8个字节,那么之间是没有分隔符的,是通过大小来区分,哪个是哪个。
源端口、目的端口:
而对于源端口、目的端口就不必多说了,就像是一个发件人、一个是收件人信息那样。
UDP长度:
这个长度指的是报文长度
而整个报文长度=报头长度+载荷长度
那么问题又来了,此时如若报文长度过长,超出两个字节怎么办呢?
此时也不必担心,比如可以提前通过应用层协议,由程序将数据进行切片
甚至可以让网络层的IP协议中,兜底,让它来进行切片传输也是可以的。
载荷:
从应用层传递下来的数据,这些数据被封装到UDP数据报中,通过网络进行传输。
校验和:
讲这个校验和之前,我们先要知道的是一个前提
就是网络传输环境中是非常复杂的,电信号/光信号/电磁波等等这些因素,也是会受到环境印象
从而导致传输信号发生改变,最后甚至导致内容也发现改变。
所以校验和的出现是为了“发现”或者说去“纠正”这些错误。
所以这个报文中,引入这个校验和,加入额外信息,来去做这个事。
这些个额外信息,这些额外信息,就是我们要生成的校验和。
对于校验和的生成,其实有很多的算法,比如CRC算法,和UDP目前使用的1的补码加法。
以1的补码加法为例
即将整个报文所有数据视为十六位整数,如若总长度不是偶数,那么此时在末尾添加一个填充字节,使得数据对齐到16位边界
然后十六位中的整数逐一相加,如若超过了16位,发生溢出,将溢出部分加回到低16位上即可。
对最终累加结果取反,即计算1的补码,得到校验和值。
假设此时发送方得到一个校验值checksum1,然后发送到接收方后,接收方也计算得到一个校验值checksum2,那么此时进行比对,不相等,说明数据发生了改变,所以就要丢掉这个数据包。
还有一种极端情况就是,发送方的校验和checksum1—>checksum3被修改了,那么此时接收方计算得到校验和checksum4,checksum3 很大程度上和checksum4是不相等的,此时呢,依旧丢弃这个数据包即可。
对于UDP为什么不使用这个CRC算法呢?
这是因为CRC算法是一个基于多项式除法,其中的数据视为一个大的二进制数,将这个二进制数除以一个固定的多项式所得到的余数。
整体来说效率较低,不如1的补码加法较为方便。
回顾UDP的特点
无连接、不可靠、面向数据报,全双工。
对于无连接、面向数据报、全双工这几点,前面的文章代码可以说是体会到了。
而不可靠,这个点,还是值得说一说的
首先这个不可靠,体现在哪里呢?
1.无连接
不像其他的协议先进行握手,直接向服务器发送数据,而此时接收方可能不知道要有数据到达,所以可能会导致数据丢失/
2.不保证数据送达
数据发送对方过后,即使是某些数据在网络中丢失了,发送方并没有意识的,因为接收方没有给反馈。
3.不保证顺序
UDP对数据报的到达顺序不做保证,数据报可能经过不同的路径到达目的地,导致数据到达顺序是没有保证,而且UDP也没有对其进行正确处理
4.发送数据量不做限制
UDP中,对发送的数据速率不做限制,意味着发送方可以无限制速率发送数据,不考虑对方是否能够承受这些数据,进而在某些高负载情况下,导致网络拥堵,加重网络丢包概率。
到这里,对于UDP的相关知识就分享到这。
接下来到另外一位“人物”
TCP
我们之前讲过,当前的主流的互联网协议是TCP/IP,所以由此可见,TCP可谓是重中之重。
所以下面就会以较长边幅来分享下TCP相关知识
同样的,与UDP一样,先学习它的报文格式。

由这幅图可见,TCP报文格式可谓是有些复杂的。
那么接下来一一讲解下各部分意思。
16位源端口:发送方的端口号
16位目的端口:接收方的端口号
32位序号:发送的数据字节流中的起始字节编号
32位确认序号:期望收到下一个字节编号
4位首部长度:tcp报文段头部长度,以4字节为单位,(比如,传入的数据是0011,十进制是3,乘以,得出是12个字节)
保留六位:目前暂未使用,通常设置为0,后续扩展预留空间
标志为:
1.URG(Urgent):若紧急指针有效,表示有紧急数据优先处理
2.ACK(Acknowledgment):若确认序号有效,表示当前报文段包含确认信息
3.PSH(Push):提示接收方尽快将数据传递到上层应用
4.RST(Reset):复位连接,用于异常情况下的连接重置
5.SYN(Synchronize):同步序号,用于建立连接时初始化序号
6.FIN(Finish):结束连接,表示发送发送方已完成数据发送,请求关闭连接
16位窗口大小:接收方的接收窗口大小,表示接收方可以接收的数据量,用于流量控制
16位检验和:对TCP报文段进行校验的值,检测数据传输过程中是否出现数据错误
16位紧急指针:指向紧急数据最后一个字节所在的位置,需要配合URG标志位进行使用。
选项:可选字段,用于提供额外的功能或信息,比如时间戳,用于优化传输性能
数据:传输应用层包装好的数据。
对于报文格式中,这么多内容来说,确实对于刚接触网络的来说,确实有些懵懵的
比如标志位、序号、确认序号、窗口大小等等。
所以为了对此有更好的解释,这得和TCP核心机制息息相关
回到TCP的特点:有连接、可靠传输、面向字节流、全双工。
有连接、面向字节流、全双工这些东西在代码层面上我们也是可以感受到po
而这个最核心机制可靠传输,则是在系统层面中全做好了,所以我们对此感受也是没那么深。
现在来讲解下TCP中的
核心机制
核心机制一
确认应答
简单来说就是对方是否收到信息了。
举个例子
有一天小编想约朋友吃饭:

此时,我们就涉及到了报文结构中的ACK,返回的信息称为ACK报文。
单纯使用这个应答,其实还是存在着一定的问题。
比如

显然情况1才是我们正常的情况,情况二就出现矛盾了。
那么为什么会出现情况2这个呢?
这是因为,数据包中在网络传输过程中,可能会因为某些原因而”绕路“
导致先到的数据包,而后到了,这就是所谓的“后发先至”。
所以为了解决情况出现的问题,可以使用序号来进行区分。
那么这是如何进行区分的呢?
正是基于TCP面向字节流的特点,所以传输数据的时候,每个字节都会对应一个编号,而这个编号是递增的。
然后这些字节的编号,就称为TCP的序号。
从而引入报文结构中的了32位序号和32位确认序号
32位序号:描述载荷部分,第一个字节序号是多少。
32位确认序号呢?
就是希望收到下一个字节的序列号
比如举个例子

那么此时,这个1001会通过应答报文返回,而这个应答报文中
标志位:ACK,这个位置会设置为1。
对于普通报文来说
标志位:ACK,这个位置会设置为0
这些标志位的设定,还表示了,普通报文中的序号才是有效的,确认序号是无效的
而应答报文中,普通序号和确认序号是有效的。
还有一点值得注意的是,32位的序号,最大可表示4G多的数据,也表明了,序号可以编写到很长。
那么是不是很大的东西,超过4G了就不能传输呢?
当然不是,这些数据很大的话,此时TCP分割成小的块的数据包进行传输
然后这些数据包传输到接收方后,接收方根据这些序号进行自动拼装。
那么回到先发后至的问题。
假设发送了序号为1~1000、1001~2000、2001~3000的数据包
此时只有1001~2000、2001~3000先到了,1~1000还没有到,此时应用层不会立即读取数据内容
而是先放到操作系统内核的缓冲区中,存储起来,等到第一段数据到了,此时应用层才会读取数据。
ok,解释完这个问题,那么另一个问题又来了?
网络中传输的数据一定就会到达接收方吗?
很显然,并不能,毕竟我们网络环境是非常复杂的,出现的问题也是难以预测的,所以会产生丢包问题。
对于丢包问题产生,原因可以举以下几个例子
1.数据传输过程中,由于网络环境发生改变,导致传输数据内容发生改变,接收方收到数据后进行校验和检验,不通过,丢弃该数据包
2.数据包在传输到某个节点的时候,该节点(路由器/交换机),这个节点负载太高了,所以也会丢弃该数据包
那么对于TCP来说如何去处理这个问题呢?
显然,对于网络丢包问题,TCP是不能百分百避免的。
所以只能通过感知数据是否丢包,丢包了,那就进行重新发送一份。
对于这个操作,我们就引入了第二个核心机制:
超时重传
TCP需要通过应答报文来区分是否是丢包了,如若是收到了应答报文,此时就是没有丢包,
没有收到就是说明数据丢包了。
收到和没有收到,这些个过程也是需要时间来验证的。
这个时间验证,就是发送方发送数据后,会给出一个超时时间
在这个时间范围之内,如若没有收到ACK,说明数据包丢失了。
那么还有一些特别情况,由于网络的延时,某个数据包在超时时间之外到了接收方这里了。
但此时已经是触发了超时重传了,所以发送方再次发送了一份数据到接收方这边,
接收方此刻就有了两份相同的数据,这如何处理呢?
TCP会这样子做:收到的数据会放到缓冲区中,然后根据序号进行排序数据,
再收到一份数据的时候,此时再去缓冲区中进行插入数据,发现这份数据是存在了,所以这份数据是会被丢弃的,从而保证应用层读取的数据是唯一的。
那么这个超时重传的时间设定是固定了嘛?
实际情况是发送方第一次重传的时候,超时时间是t1,重传过后,任然是没有ACK返回,此时还会继续重传,此时的重传时间是t2,而这个t2 往往是 大于 t1的,以此类推
后续t2超时时间过后,还是没有收到数据,此时呢,继续重传
重传几次后还没有收到ACK后,就说明,对面接收方大概率是掉线了,此时TCP就会单方面放弃连接,并清除对方的信息。
这就是TCP的超时重传。
以上这两个核心机制超时重传和确认应答构成了TCP可靠性最核心的因素。
那么上诉的超时重传中,它的重要前提是双方的连接起来才行。
所以接下来介绍,TCP核心机制3
连接管理
这里就设计到两大类
1.建立连接
2.断开连接
那么现在来分别讲一讲
比如建立连接这里
我们常用三次握手来形象的描述这个过程
比如:

首先这里要值得说的是
syn代表同步的意思,
A发给B,说明A想保存B的信息
B返回A,说明B也想保存A的信息
这样就构成了双向操作。
但不是说三次握手?为什么会有四条横线呢?
这是因为这里小编主动分开来了,其实中间两次操作是可以合并起来,这是因为,收到syn后,系统内核会立即发送ack和syn(服务器资源充足情况下),所以当作一次发送的
例如:

当作一次发送,此时这样效率会高些。
那么这个就是三次握手了,而这个过程也是可以用一些通俗的话来表示的。
A想与B建立连接,
此时发送了一个syn
然后B收到了,B此时知道A要与它建立连接,B也答应了,那么此时A是不知道的
所以要返回一个ack和syn,当A收到B返回的信息的时候,A知道B是同意了
但是此时B又不知道A会不会“反悔”,此时A再次发送一个syn告诉B,我已经知道并保存了
当B再次收到这个syn的时候,此时就是三次握手建立成功了。
那么对于三次握手,来说,它的意思何在呢?
1.要验证通信链路是否正常,正常了,后面才会进行业务数据的发送。
2.了解通信双方各自的发送能力和接收能力是否都正常
3.让通信双方进行业务数据传输之前,对通信中一些参数做出协商
比如,数据包中的数据起始序号就是在此时确定。
而每一次重新连接的时候,数据包中的起始序号和前面的相比相差就很大。
这是因为,避免新一次的连接的时候,某些旧数据包因为网络延时,现在才到达,
此时呢,相差较大的起始序号就可以起到区别作用,就可以把它给丢弃掉。
最后,还要值得值得说明的是,这些syn、ack报文,不承载业务数据。
那么到这里,三次握手算是分享到这
接下来到四次挥手
过程如下

那么此时此刻,为什么不可以ACK和FIN不可以合并了呢?
其实是可以合并的,但也只是小概率情况下,
这是因为
当服务器收到FIN后,确实是第一次时间返回的是ACK,但服务器返回的FIN触发,是由应用程序来决定的,当应用程序调用close()方法后,进程才会退出,对应的,FIN也是随之发送。
所以也可以推出这一点,ACK和FIN之间是有时间差值在的。
这里要值得说明的是,断开连接不仅是客户端主动,服务器也是可以主动断开连接
上面的是小编画的草图
接下来看看较为真实的连接图吧

图片来源:https://testerhome.com/topics/24906
这幅图中左边还有右边是TCP连接和断开时的一些状态词,那么接下来介绍这些个状态词吧
LISTEN:
这是服务器进入的状态,说明此时服务器已经绑定好端口了,可以随时迎接客户端。
SYN_SENT:
客户端发送了一个syn报文段,等待服务器端SYN_ACK响应。
SYN_RCVD:
表示服务器端收到了报文段,并且已经发送syn、ack作为响应,但没有收到客户端最终的ACK确认
ESTABLISHED:
表示连接已经成功创立,双方可以相互发送数据。
FIN_WAIT_1:
主动关闭方已经发送FIN报文段,等待对方的ACK,或者同时带有ACK和FIN的响应
FIN_WAIT_2:
主动关闭方已经收到了对方关于FIN的ACK确认,正在等待对方的FIN报文
CLOSE_WAIT:
被动断开连接这一方,进入这个状态
表示,收到了主动方发送的FIN,等待应用程序调用close()方法
如若此时,被动方这边收到了大量的CLOSE_WAIT状态,说明此时被动方这边可能出现了代码BUG或者是没有正确关闭close()。
TIME_WAIT:
这是主动断开连接,会进入的状态,这是会等待一段时间后,连接才会释放。
为什么不直接释放连接?
这是因为,防止最后一个ack报文段丢失。
数据包在网络中丢失情况是不可控的,所以,设置这个TIME_WAIT。
如若是直接断开连接了,但是此时最后ack丢包了,那么被动那一方会主动再重传一个FIN
但此时主动方已经断开连接了,所以被动方是不可能收到这个ack了。
这个TIME_WAIT存在时间,会设置为一个最大消耗时间(数据在网络中传输)。
这个时间,一般也是比较充裕的。
ok,那么这个连接管理分享到这里。
接下来讲讲这个核心机制4
滑动窗口
上诉三个机制呢,确实是为的TCP的可靠传输提供了不少支撑
但这个可靠传输也是要牺牲代价的,那就是效率,这个效率肯定是比不上UDP的
所以为了进行适当补救,我们就引入了滑动窗口。
那什么又是滑动窗口呢?
举个例子
我们没有引入滑动窗口之前,发送数据是发送一条数据,那就等待一条数据
那么现在我们是发送一批数据等待一批数据即可,不需要单个单个等待了
此时,我们批量发送数据越大,可以说明效率越高
这个发送数据的量称作为“窗口”
那么为什么叫做滑动的呢?
比如现在有1~1000、1001~2000、2001~3000、3001~4000
此时这批数据发送到对方了
那么发送方是收到第一个1~1000的ack,就立即发送下一个数据呢?,还是继续等待剩下的ack呢?
那当然是继续立即发送这一个数据4001~5000。
那么此时1001~2000、2001~3000、3001~4000、4001~5000新的一批数据了,窗口大小不变
窗口内容却变了,就像是移动到下一个数据,所以这就是滑动窗口。
那么滑动窗口听上去就挺不错的,那么是否是没有坏处呢?
显然,还是会有个老问题,就是丢包
那么这个丢包问题,其实也是要分这两种的
1.数据包传送过了,但是ACK丢了
还是这一批数据:1001~2000、2001~3000、3001~4000、4001~5000
此时,1001~2000数据的ack丢了,那么此时咋办呢?
那么TCP不会做任何处理,这是因为
网络中虽然存在丢包的可能性,但不可能百分之百丢包,而且是在正常连接的情况下
所以此时2001~3000、3001~4000、4001~5000中,哪一个的ack传输成功了
代表1001~2000这个数据也是收到了
之前讲过,序号是依次递增的,所以假设收到4001~5000这个ack,确实是说明了1001~2000这个数据收到了。
2.数据包直接就丢掉了

此时此刻,当1001~2000的数据包没有收到,此时后续传入2001~3000等等这些数据包
返回的ack会向主动方索求之前的1001~2000
那么对于A来说,若连续收到这个1001~2000这个ack后,意识到了1001~2000这个数据包丢失了
所以此时就会立即发送这个1001~2000这个数据包
而因为2001~3000后续的那些数据包是发送过了,再加上这个1001~2000数据包,补齐了信息
那么下一个ack应该就是7001,所以代表之前的所有数据都收到了。
而对于丢失数据包后短时间发送对应的索求信息,然后主动方根据这个信息,进行针对性的发送数据
这个操作就是快速重传。
那么这个快速重传是可以和滑动窗口搭配一起使用的,这样效率又是一个提升,在数据量大的时候。
那么这个滑动窗口就讲到这,接下来分享第五个核心机制
流量控制
上一个机制讲到这个滑动窗口,这个滑动窗口可以把一批批数据进行发送,从而达到提高效率。
那么一批批数据中,每一批数据的“窗口”都可以很大吗?
显然是不行,你发送方要考虑对方的接收能力大小。
所以此时,就引入了流量控制,对这个滑动窗口进行控制。
比如

那么它是如何告知对方的呢?
窗口大小是通过ack报文告知对方的,它可以携带当前窗口大小信息,发聩到发送方
,那么下一步发送方根据这个报文,来调整窗口大小。
那么这个ack报文中就可以使用这个16位窗口大小来设置信息,去告知发送方
那么值得注意的是,16位的窗口大小,最大是不是只能发送64kb呢?
那当然不是的,那么在它报文结构中,它选项这里可以设置一个特殊选项
——窗口扩展因子
此时呢,16位的窗口大小 << 窗口扩展因子,就是它总的告知量,并不是只能发送64kb
注意,<<代表的是左移,类似于乘法的意思。
那么这个流量控制也是一直的呢?
其实也不是的
比如,当前接收方的缓冲区满了,暂时不需要发送数据过来处理,此时呢,就会把当前的ack报文中窗口大小设置为0,告诉发送方,缓冲区满了
当发送方接收后,就开始等待,等待接收方处理数据后,再次发送数据
那么,这个等待,他也不是“死等”的,
它会定期发送一个零窗口探测报文。
就是发送一个不带业务数据的ack报文,去探测对方的缓冲区是否已经有空余了,如若有了
那么会在返回的ack中,更新窗口大小,此时发送方就可以继续发送数据了。
那么单纯发送方“努力”,接收方呢?
此时,这个接收方消耗一定的缓冲区内容后,也会发送一个窗口更新,这样功能的数据包给到发送方。那么发送方收到后,又可以继续发送数据了。
那么值得注意的是,这个流量控制机制,不是TCP特有的,比如其他层,数据链路层中,某些协议也是支持流量控制的。
流量控制分享完,接下来分享核心机制六
拥塞控制
这个拥塞控制是站在传输链路角度去考虑的,去限制发送方的速度。
就是,在不断传输数据的时候呢,某些传输链路节点(路由器/交换机),它们此时的负荷很高了,如若各个节点再加大数据传输,此时,某些节点就会丢包。
那么此时该如何改善呢?
其实前辈们早就考虑到了,通俗的来说
1.先按照一定较小的速率进行发送数据
2.如若此时发送数据的时候,很顺畅,没有丢包,所以此时说明传输数据整体是畅通的,就加快传输速度。
3.加大到一定的速率后,发现又有丢包了,此时呢,说明网络链路中,出现拥堵了,所以再次降低传输数据的速率
4.减速后,发现又畅通,再次加快速率
如此往复,就可以缓解这个传输链路中的拥堵问题
那么上面的文字解释,可以由这幅图片来形象描述

图片来源:https://zhuanlan.zhihu.com/p/144273871 不过这里要值得注意的是
出现网络丢包了,那么进行降速率的时候,是降到底,还是降到新的阈值上呢?
这是这是关乎到两种方案
1.经典方案
降到非常低的阈值,然后进行指数增长,再进行线性增长
2.现代方案
降到一个新的阈值,之后开始进行线性增长。
ok,到这里呢,对于这个拥塞控制也讲的差不多了,接下来分享的是核心机制七
延时应答
这个延时应答,也是提高效率的机制之一
那么它是有助于流量控制的
比如举个例子

那么这个返回ack是带着更大的ack窗口,那么这个也是有一定概率,并不是总是这样
这是因为,要取决于
1.应用层代码如何写,是不是持续读数据?
2.在这个延时时间内,是不是会有新的数据传输过来。
ok,那么这个就是延时应答,接下来分享核心机制八,
捎带应答
那么这个捎带应答,简单来说就是把非业务数据报文和业务数据报文合并在一起发送到对方。
举个例子
比如收到一个请求后,此时呢,要返回一个ack报文和一个业务数据报文,
这个ack报文同时涉及到另一个事“延时应答”,那么此时,没有立刻返回,而此时,业务数据在这个延时时间内完成了计算,要返回了,所以此时,ack报文信息,就不用单独返回了,就直接合并在业务数据中直接返回即可。
那这里要值得注意的是,ack报文主要是设置一些确认序号这些操作的,而业务数据报文,则主要返回载荷数据,此时呢,两个是可以合并在一起的。
那么这个捎带应答也分享到这,接下来分享的是核心机制九
面向字节流
那么在前面那一篇文章,其实也是感受到了
就是发送和接收数据的时候,以字节为单位进行发送和读取。
那么在这个层面下,其实会引出这样的一个问题
粘包问题

因为数据包接收到缓冲区中,所以数据堆到一起,显得数据“粘”在一起了。
显然,我们读取数据为aaa、bbb、ccc才对
那么,对于这个如何解决呢?
1.指定分隔符
这个方案对适用于文本数据
比如,使用\n,或者其他一些废弃使用ascll码等等都可以
2.指定数据长度
这个方法适合于二进制数据
此时约定每个应用层数据包,开头的2 / 4个字节,表示数据包长度
那么以上就是对于核心机制九的分享,接下来分享核心机制10
异常处理
在这个机制上呢,这里会引入好几个场景,从而分享一些解决方法
1.进程崩溃
此时进程崩溃,在Java领域体现,一般就是抛出异常,但是没有人进行catch,最后异常交给jvm兜底,此时jvm无法处理,就”寄“了
那么此时,操作系统就在做善后
因为一个进程,进程中的PCB就会被系统回收,而PCB中文件描述表对应的所有文件,也会被系统自动关闭,针对socket文件中,tcp也会正常进行四次挥手
2.主机关机
这里的主机关机,说的是正常关机状态下
那么此时,系统也是正常干掉所有进程,同时正常进行四次挥手
但还会存在另外一种情况
比如四次挥手没有来得及,此时主机就关机了

3.主机掉电
主机掉电的意思是,把电源给拔了
这也分为接收方断电和发送方断电两个情况
1.发送方断电
此时发送方断电,接收方不会立即知道这个情况
已经等待ack报文回复
那么此时如若开启了keep-alive选项,那么接收方等待一段时间没有收到消息,就会发送一个探测报文,进行探测,如若进行连续探测,还没有回复,此时认为发送方已经掉线了
不开启这个选项的话,那么接收等待发送方数据,然后触发超时重传,然后达到上限,再然后删除已保存对端的信息。
对于这个进行发送探测报文,这个行为,叫做保活机制,发送的报文叫做”心跳包“
2.接收方断电
此时呢,发送方不会立即知道这个接收方断电了
那么依然等待ack报文回复
如若没有,触发超时重传,达到最大上限,此时呢,就会认为连接不可用,删除已保存的对端信息。
4.网线断开
此时情况和主机掉电差不多

到这里,核心机制十也讲完
到这里,TCP的常见机制也讲的 差不多了。
完!