网络原理之TCP_IP传输层协议,TCP中的主要核心机制(重点)_控制器对tcp保留字段的处理-程序员宅基地

技术标签: udp  网络  JavaEE  tcp/ip  

目录

一. 传输层中的端口号

二. UDP协议

 三. TCP协议

四. TCP中的核心机制 

1. 确认应答

2. 超时重传

3. 连接管理

 建立连接(三次握手)

 断开连接(四次挥手)

4. 滑动窗口 

考虑丢包情况1:ack丢了 

考虑丢包情况2:数据丢了 

5. 流量控制 

6. 拥塞控制

7. 延时应答 

8. 捎带应答

9. 面向字节流

10. 异常情况


一. 传输层中的端口号

通过前面文章的介绍,我们知道了端口号是传输层协议的概念,并且要求:在同一个主机上,一个端口号,不能被多个进程绑定。在传输层中最核心的协议就是: TCP协议 和 UDP协议,在这两个协议的报头中就都会包含源端口和目的端口,是使用两个字节来进行表示的,也就是16个比特位来表示,也就可以得知一个端口号的取值范围是:0~65535,但由于 0~1023 这个范围的端口,是"知名端口号",这些端口号都分配给了一些知名的广泛使用的应用程序了。所以我们日常使用的端口号一般为:1024~65535。

二. UDP协议

在前面的文章中,我们介绍过 UDP协议的特征:无连接;不可靠传输;面向数据报;全双工; 

UDP协议报文结构: UDP报头 + UDP载荷

UDP数据报的结构: UDP会在载荷数据基础上(通过 UDP socket send发送来的数据),再在前面拼装上几个字节的报头,相当于是字符串拼接,但此处的拼接是二进制的拼接,而不是为文本拼接。对于不同的协议来说,功能不同,报头中带有的属性信息也就是不相同的,对于 UDP 来说,报头就是8个字节,如上图所示,分成了四个属性信息。此处也就解决了通信五元组(源端口,目的端口,源IP,目的IP,协议类型)中的两个。

UDP报文长度:两个字节,表示的范围为 0~65535。换算后也就是64KB,因此一个UDP数据报最大可以传输 64KB 的数据。这个数据按现在来讲,是相对较小的,因为如今随便拍一张照片都可以达到几个MB了。那么对于数据报超过64KB的情况,应该怎么处理?

第一种处理方式:需要在应用层,通过代码的方式针对应用层数据报进行手动的分包,拆分成多个包,通过多个UDP数据报进行传输。因此本来只需要send一次数据报,此时就需要send多次了。 

第二种处理方式: 由于第一次处理方式造成的时间成本,测试成本等都大大提高,所以一般会使用 TCP 来代替(具体细节功能后续讲解)。

UDP校验和: 两个字节表示。校验和的作用是验证传输的数据是否是正确的。网络传输,本质是就是光信号和电信号的传输,会容易收到物理环境的影响。在网络传输的过程中,可能会受到一些干扰,在这些干扰下就可能出现 "比特翻转" (1变成0,0变成1)的情况。一旦数据变了,对于数据的含义可能就改变了很多。一个典型的例子就是:程序中经常使用1表示打开,0表示关闭,本来网络数据报是想要实现开启功能,结果因为比特翻转,就变成了关闭。但这样的现象又是客观存在,不可避免的,因此我们能做的就是:及时识别,重发数据。因此就引入了校验和来进行鉴别。

校验和是根据内容进行的一系列数学运算,得到的一个结果。此处数据报报头中的校验和就是针对实际传输的数据进行计算得到的。此时接收方就会根据此计算出来的校验和与 正确的校验和 (是发送方进行计算,发送给接收方的,是不在udp报文中的)进行比较。如果能对上,就说明数据是正确的,如果对不上,数据就出错了。

比较常见的生成校验和算法:

1:CRC:循环冗余校验,把数据的每个字节,循环往上累加,如果累加溢出后,高位就不要了,这种方法虽然简单,但是检验效果不是特别理想:如果数据中同时变动了两个 bit 位,前一个字节少1,后一个字节多1,这样的话,就会出现内容变了,但是CRC不变。

2. MD5:这个是经过一系列复杂的数学运算,得出的结果。得出的结果是定长的 ,无论原始数据多长,得到的MD5值都是固定长度(有4个字节,8个字节...);且原始数据只要变动一个位置,算出来的MD5值差别也会很大,这样就可以让MD5值更分散,冲突概率很小;MD5值是不可逆的,通过原始数据计算得到MD5,理论上再从MD5计算出原始值是无法实现的。(除了一些常见词) 由此也可以看得出MD5的优越性,因此MD5一般除了作为计算校验和,也可以用来计算hash值,密码加密...

 三. TCP协议

在前面的文章中,我们也介绍过 TCP协议的特征:有连接;可靠传输;面向字节流;全双工; 在前面的服务器客户端程序代码中,我们是可以验证出有连接,面向字节流,全双工,这三个特征的,但是可靠传输并没有得到验证。可靠传输是和编码无太大关系的,因为它主要是和TCP内部的机制有关系的,下文将会从内部机制来进行讲解。

TCP协议报文结构:TCP报头(首部)+TCP载荷(数据) 

 源端口号,目的端口号,校验和是和 UDP 2同理的。

选项:这个选项相当于对这个TCP报文的一些属性进行解释说明,这个说明是可有可无的。

4位首部长度:一个TCP报头,长度是可变的(因为选项的存在),并不是像UDP一样固定8个字节。因此,首部长度就描述了TCP报头具体多长,需要注意的是,选项之前的部分是固定长度的(20字节)。所以 (首部长度 - 20字节) 就表示选项的长度。另外需要注意:首部长度是4bit,也就表示0~15,首部长度的单位是4字节。所以如果首部长度是5,表示整个TCP报头是20字节(相当于没有选项),如果首部长度是15,表示整个TCP报头是60字节(选项相当于是40字节)

保留(6位):此处类似于C语言中的保留字,也就是预留了空间。现在虽然没有利用这个空间,但是保不齐以后是需要使用的。要知道,对于网络协议来说,扩展升级,是一件成本极高的事情,例如上文中的UDP报文,我们知道报文长度是2个字节,空间算是很小了,但是为什么不试着把这个空间给改大一些呢?这个方法理论上是可行的,但实际上操作成本极高,因为此时全世界上百亿台机器的操作系统里,都是支持2字节长度的UDP,要想升级,就得让这些设备的操作系统都能够同步升级,支持升级后版本。因此就引入了保留位。

保留位的引入就让升级操作成本降低不少,如果后续TCP有新的功能引入,就可以使用这些保留位字段,此时对于TCP本来的报头结构的影响是比较小的,老的设备即使不升级,也可以兼容。

剩下的一些字段下文根据TCP的一些核心机制进行讲解 

四. TCP中的核心机制 

1. 确认应答

确认应答是实现可靠传输的最核心机制。此处的可靠传输并不是发送方百分之百会把消息发送到接收方, 而指的是尽可能把消息传输过去,同时如果传输不过去,至少也会得知传输失败的消息。

可以理解为:A给B发送了一条消息,B收到消息后会返回给A一个 应答报文(ACK),此时A收到应答报文后,也就知道了A刚刚发送到信息B成功接收到了。但如果隔了一段时间后,还是没有收到 应答报文,那就说明刚刚A发送的消息丢包了,也就是消息没有成功发送出去。类似于:

此处的好呀就相当于是 应答报文,也称为ack,当我收到 "好呀"的时候,我就知道我的信息成功传达了,也就说明短信没有丢包。但是如果隔了很久没有收到回应,说明发的信息大概率就是丢包了。

现在在考虑复杂一点的情况:此处如果连续发送两条信息,网络上是存在 "后发先至" 的问题的,在这个情况下,收到消息的顺序是可能存在变数的。(网络中数据的后发先至,主要是因为两个主机之间,路线存在很多条,数据报1 和 数据报2 走的是不同的路线,此时,这两数据报到达的顺序就存在变数了。)

很明显,此处的应答就错乱了,表示的含义也就出现了歧义了。这种网络后发先至现象是客观存在的,不可避免,因此应答报文到达的顺序也可能发生变动,此时就需要考虑方法来规避这种顺序错乱带来的歧义。

解决上述后发先至问题的方法就是给传输的数据和应答报文进行编号。 

当我们引入序号之后,此时就不再怕顺序错乱了,即使顺序错乱了,也可以通过确认序号来区分当前应答报文是针对哪个数据进行的。                 

 任何一条数据报(包括应答报文)都是有序号的,确认序号就只有应答报文有,因为在普通的报文中确认序号字段里的值并无太大意义。而一条数据报是否是应答报文,取决于黑圈中的 ACK 标志位是否为1,如果是 0 表示不是应答报文。

 TCP的序号的编号

 实际上TCP的序号并不是按照 "一条两条" 这样的方式来编号的,TCP是面向字节流的,TCP的序号也是按照字节来编号的。

假设一条数据是 1000个字节,从1开始编号,此时第一个字节序号就是1,第二个字节的序号就是2...以此类推,但是由于这1000个字节都属于同一个TCP报文,TCP报文就只记录当前的第一个字节的序号作为序号。所以此处TCP报文报头的序号写的就是1。TCP的字节的序号就是依次累加起来的,这个依次累加的过程对于后一条数据来说,起始字节的序号就是上一个数据的最后一个字节的序号。每个TCP数据报报头填写的序号只需要填写TCP数据的头一个字节的序号即可。TCP知道了头一个字节的序号,再根据TCP报文的长度,就很容易知道每个字节的序号。

 TCP的确认序号的取值

 确认序号的取值,是收到的数据的最后一个字节的序号+1。

 此处确认应答的序号,就可以理解为:1.  < 1001 的数据都已经确认收到了。2. A主机接下来应该从 1001 这个序号开始继续发送数据。(B向A索要1001的数据)

 小结:TCP可靠传输能力,最主要就是通过确认应答机制来保证的,通过应答报文,就可以让发送方清楚的知道传输是否成功,进一步引入序号和确认序号,来针对多组数据进行详细的区分。

2. 超时重传

在讨论确认应答时,只是讨论了顺利传输的情况,没有考虑到丢包的情况。

丢包的话,就需要考虑到两种情况了:

1.发的数据丢了;

2. 返回的 ack报文 丢了;

从发送方来看,就是没有收到 ack报文。

丢包是一个概率性事件,而且通常情况下,丢包的概率是比较小的。因此如果重新发一下这个数据报,其实还是有很大概率成功传输的。

因此,TCP就引入了重传机制,在丢包的时候,就要重新再发一次同样数据。那么怎么去判断当前这次传输,是丢包了,还是因为 ack走的慢,正在路上呢?TCP就引入一个时间阈值,发送方发送一个数据后,就会等待ACK,此时开始计时,当超过这个时间阈值后,不管是哪种情况,还没有响应,就重新传输。

解决重复传输问题 

超时重传,就会有一种可能:同样的消息传出两次,如果只是一条消息发出两次,这没什么大问题,但是如果是一条支付请求,那就不是一件小事了。所以就需要去解决这个问题。

TCP对于这种重复数据的传输,是有特殊处理的。保证不会重复的传输同样的数据。TCP 存在一个 "接收缓冲区" 这样的存储空间(接收方操作系统内核的一段内存),每个 TCP 的 socket 对象,都有一个接收缓冲区(其实也有一个发送缓冲区) ,主机B 收到主机A 的数据,其实就是B的网卡读取到数据了,然后把这个数据放到B 的对应的接收缓冲区里。然后在缓冲区中(可以想象成一个阻塞队列),根据数据的序号,TCP 很容易识别当前缓冲区里的这两条数据是否是重复的,如果重复了,就把后来的这份数据丢弃了,保证应用程序调用 read 读取到的数据,一定是不重复的。(后续应用程序使用 getInputStream,进一步的使用read,就是从接收缓冲区读取数据的)

 多次重传问题

重传的数据是否可能又丢包,那必然是会有可能的,因此超时重传是可能重传 N 次的,此处的 N 并非是一个很大的数字。对于重传这个事情,当你重传几次都传不出去,此时继续重传,意义已经不大了。因为一般重传丢包的概率是不大的,多次重传失败,可能就是基础设施的问题了。

假设一次传输丢包的概率是 10%,那么连续两次丢包的概率就是 1%,连续三次丢包的概率就是 0.1%,所以说,连续重传丢包,此时的概率原则上讲,是很低的。如果真的出现了这种情况,也只能说明,此时丢包的概率是远不止 10%的,说明网络大概率是出现了故障。

因此,重传达到一定的次数后,就不会继续重传了,会认为是网络故障。接下来 TCP会尝试重置连接(相当于断开重连),如果重置还是失败,就彻底断开连接。

 在重传的时候,第一次重传和第二次重传,中间的等待时间还不一样,一般来说,重传的轮次越大,这个等待时间也就越大。因为等待时间越大,重传失败的概率就越低,前面重传失败了,也就说明了重传成功的概率并不高(相对而言),所以此时重传的等待时间太快也是白浪费系统资源。

小结:可靠传输是TCP最核心的部分,TCP的可靠传输主要通过 确认应答 + 超时重传 来体现的,其中确认应答描述的是传输顺利的情况,超时重传描述的是传输出问题的情况,这两者互相配合,共同支撑整体的TCP可靠性。

3. 连接管理

TCP建立连接,例如A和B建立连接,A就需要有一个空间存储着B的IP和端口,B就需要有一个空间存储着A的IP和端口。当这两部分信息被维护好了之后,此时连接就有了,同时也把保存这部分信息的空间(数据结构) 称为连接。

进一步,断开连接就是 A和B把自己存储的连接信息(数据结构)删了,连接就断开了。 

管理:就是描述了连接如何创建,如何断开...

 建立连接(三次握手)

 建立连接:通信双方各自要记录对方的信息,彼此之间要互相认同。

建立连接的过程中,把每一次通信,形象的称为一次"握手"。首先A发出建立连接请求,B对此发出应答(ack),B发出建立连接请求,A再对此发出应答(ack)。本质上是四次握手交互,但实际上中间的两次是可以合成一步的,毕竟封装分用一次肯定是要比两次更高效的。所以就有了 "三次握手" 也就是下图中右图的情况。

三次握手的另一个作用:验证通信双方各自的发送能力和接收能力是否正常。

因此,三次握手也在一定程度上保证了TCP传输的可靠性。

 在第一次握手交互的时候(A的建立连接请求),B就知道了A的发送能力正常,B的接收能力正常;第二次握手交互的时候(B的ack+B的建立连接请求),A就知道了A的发送能力正常,接收能力正常B的发送能力正常,接收能力正常;在第三次握手交互的时候(A的ack),B就知道了A的接收能力正常,B的发送能力正常。

因此,在完成三次握手操作之后,也就可以得知通信双方的发送能力和接收能力是否正常了。

补充说明:

TCP中的有连接是指:

1.需要连接先建立好,才可以进行通信;

2.如果连接断了,此时就无法继续通信了;

3.连接建立过程中,通信双方要各自保存好对方的信息的;

而有没有连接,和是否确认应答,没有任何关系。

确认应答体现的是 "可靠传输" 可靠传输和有连接是不相关的。

无连接也是可以做到可靠传输的。例如企业微信,发个消息,不需要建立连接,直接就可以发送,发的消息会有"已读"状态,这就相当于ack了。

因为TCP是有连接的,所以TCP需要能够建立/断开连接,其中建立连接的方式就是三次握手

在上文中提到的建立连接请求,也就是客户端主动给服务器发起的建立连接请求,称为"SYN",同步报文段。

同时TCP也是存在状态的,不同的状态就体现了当前的TCP在干什么,由于此处的状态较复杂,就只简单介绍几种常见状态。

1. LISTEN:服务器的状态,表示服务器已经准备就绪,随时可以有客户端来建立连接了。相当于手机开机,信号良好,随时可以有人打电话过来了;

2. ESTABLISHED:客户端和服务器都有,表示连接建立完成,接下来可以正常通信了。相当于电话拨打过去,对方接通了;

三次握手的意义:

 1. 让通信双方各自建立对对方的"认同";

 2. 验证通信双方各自的发送能力和接收能力是否正常;

 3. 在握手的过程中,双方来协商一些重要的参数;

(TCP通信过程中,有些数据,通信双方要互相同步,此时就需要有这样的交互过程,就恰好利用三次握手的机会,来完成数据的同步。)

 断开连接(四次挥手)

"握手"和"挥手"一样都是形象的叫法,都是客户端服务器之间的数据交互。

四次挥手,和三次握手非常相似,都是通信双方各自向对方发起一个断开连接的请求,再各自给对方一个回应。

FIN:表示断开连接的请求。

那就会有一个问题了,为什么三次握手可以将中间的两次交互合并,而四次挥手不可以将中间的两次交互合并呢?

三次握手中间两次交互能够合并,是因为这两次合并是同一时机的,具体来说,三次握手这三次交互过程,是纯内核中完成的,应用程序是感知不到的,也干预不了,服务器的系统内核收到syn之后,就会立即发出ack,也会立即发出syn,因此就把两次交互一次发送出去。

 而对于四次挥手来说,首先FIN的发起,并不是内核控制的,而是应用程序控制的,调用socket的close方法,或者进程退出,才会触发FIN。ACK则是由内核控制的,是收到FIN之后,立即返回的。这两者之间是会有一个时间差的,因此一般都不会合并同时发送。(如果代码实现使得ACK和FIN之间的时间间隔比较短,此时就有可能系统会把这两个交互合并为一条。)

利用之前写过的TCP服务器客户端程序进行说明:

 四次挥手中的两种重要TCP状态

1. CLOSE_WAIT:出现在被动断开连接的一方,等待关闭。(等待调用close方法关闭socket)应注意:建立连接一定是客户端主动发起请求的,但是断开连接可能是客户端主动提起的,也可能是服务器主动提起的。

2. TIME_WAIT:出现在主动发起断开连接的一方。假设是客户端主动断开连接,当客户端进入 TIME_WAIT 状态的时候,一般都相当于四次挥手已经挥完了。此时这里的TIME_WAIT要保持当前的TCP连接状态,而不是立即释放。原因是:此时最后一个ACK刚刚发出去,还没有到达,万一这个ACK会丢包呢。如果ACK丢包了,服务器会重新发送FIN,因此TIME_WAIT会等待,如果过了一段时间后,也没有收到重传的FIN,此时就认为,最后一个ACK,没丢包,这个时候才会真正去释放。

在三次握手和四次挥手的过程中,同样是存在超时重传的,如果最后一个ACK丢包了,站在服务器的角度来看,服务器不知道是因为 ACK 丢了,还是自己发的 FIN 丢了,所以就统一进行重传处理。

既然服务器可能要重传 FIN,客户端就需要能够针对这个重传的FIN进行ACK响应。因此,TIME_WAIT状态就需要保留一段时间,就是为了能够处理最后一个ACK丢包的情况,能够在收到重传的 FIN之后,进行ACK响应。 

对于TIME_WAIT具体保持多长时间,这里约定一个时间,称为 2MSL。指的是,互联网上,两个节点之间,数据传输消耗的最大时间,如果A 经过了 2MSL后,还没收到重传的 FIN,也就认为是 ACK正常到达了(认为对方没有重传FIN)。

小结:TCP是一个有连接的协议,就需要建立连接,断开连接,其中建立连接的过程是三次握手,断开连接的过程是四次挥手。 

4. 滑动窗口 

确认应答,超时重传,连接管理,都是给 TCP 的可靠性提供的支持。

引入可靠性,实际上是付出了一定代价的,消耗了传输效率,来补全可靠性。因此,虽然UDP没有可靠性,但是其传输效率是要比TCP要高的。

所以就引入了滑动窗口,其本质上就是降低了确认应答,等待ack消耗的时间,来提高传输效率的。(进行IO操作的时候,其实时间成本主要是两个部分:1.等(大部分) 2.数据传输 )

具体的操作也就是:批量发送,批量等待,把多份等待时间,合并成一份。

在保证可靠性的基础上,尽可能的提高传输效率。 

对于基本的确认应答的情况来说,每次发送一条数据,都需要等待 ack 到了,才能发送下一条。 

 而滑动窗口的本质就是不等待的批量发送一组数据,然后使用同一份时间来等待着一组数据的多个ACK。把不需要等待,就能直接发送的数据的最大的量,称为 "窗口大小" ,下图中的窗口大小就是4000。

当批量发送了 窗口大小的数据之后,发送方就要等待ack了。那么发送方什么时候继续往下发送呢?这里并不是说等待所有的ack都到达后才继续发送,而是等到一个ack了,就继续往下发送。 上图中就是只要等到一条ack了,就继续往下发送数据,使得每次等待的ack始终是 4 条。

确认序号的含义:表示该序号往前的所有数据都已经确认到达了。

因此如果收到了 ack 的确认序号为2001,也就表示1~2000的数据已经被确认了,此时就可以发送4000~6000的数据,也就意味着此时等待 ack 的范围是 2001~6000。

考虑丢包情况1:ack丢了 

确认序号的含义:表示该序号往前的所有数据都已经确认到达了。

所以此时是不需要做什么处理的,部分ACK丢了并不要紧,因为可以通过后续的ACK进行确认;例如:确认序号为1001的ack丢了,没关系,确认序号为2001的ack会判定 1~2000 的数据已经被确认。  

 所以实际上,这里的ACK有可能不是老老实实的全部发送的,可能会少发一些ACK,在不影响可靠性的前提下,节省系统资源。而且 ack 不至于都丢完,如果丢包概率很大,也只能说明网络环境已经出现了严重故障了。

考虑丢包情况2:数据丢了 

 

当某一段报文段丢失(1001~2000)之后,发送端会一直收到 1001 这样的ACK,就像是在提醒发送端 "我想要的是 1001 的数据" 一样,来提醒发送方,没有收到 1001 开头的数据。

如果发送端主机连续三次收到了同样一个 "1001" 这样的应答,就会将对应的数据 1001 -
2000 重新发送;
这个时候接收端收到了 1001 之后,再次返回的ACK就是7001了(因为2001 - 7000 接收端
其实之前就已经收到了),被放到了接收端操作系统内核的接收缓冲区中;
此时后面补发丢失的数据,是否会造成数据的顺序错乱呢?答案是不会的,TCP中会有一个接收缓冲区,会在缓冲区中按照序号重新排序的。

ack其实也可以理解成向发送方 "索要" 对应序号的数据。 

上述丢包重传的方式,也称为 "快速重传",也可以视为是超时重传机制,在滑动窗口下的变形。如果当前传输数据密集,按照滑动窗口的方式来传输,此时按照快速重传来处理丢包;如果当前传输密集稀疏,不再按照滑动窗口方式了,就会按照之前的超时重传处理丢包。 

5. 流量控制 

流量控制是一种干预发送的窗口大小的机制。 

滑动窗口,窗口越大,传输效率就越高(一份时间,等的ack就越多)。但是窗口也不能无限大。窗口如果太大,可靠性也就没有了保障了,也会消耗大量的系统资源,而且发送速度过快,接收方也会无法及时处理。 

接收方的处理能力,是一个很重要的约束依据,发送方的发送速度,不能超出接收方的接受速度。流量控制要做的工作,就是根据接收方的处理能力,协调发送方的发送速率。 

那么如何衡量接收方的处理能力呢? 

在前面的叙述中,我们也能知道,接收方会有一个接收缓冲区 。衡量接收方的处理能力的方法就是直接看接收方接收缓冲区的剩余大小。缓冲区剩余空间大,也就说明了此时接收方处理能力较强,处理得快;缓冲区剩余空间小,就说明此时接收方处理能力较弱(相当于发送方的发送速率而言),处理的慢;从而通过得知接收方处理能力的强弱,来作为衡量发送方发送速率的指标。

每次发送方A给接收方B发送一条数据,B就需要算一下缓存空间里的剩余空间,将自己可以接收的缓冲区大小放入 TCP 首部中的 "窗口大小" 字段,通过ACK端通知发送方;发送方就会根据这个窗口大小来确定下一轮发送的窗口大小了。
由于接收方缓冲区剩余空间是一直动态变化的,所以每次返回的ack的窗口大小都在变化。因此,发送方窗口大小不是固定值,而是随着传输的过程处于动态调整的。
接收端一旦发现自己的缓冲区快满了,就会将窗口大小设置成一个更小的值通知给发送端;

发送端接受到这个窗口之后,就会减慢自己的发送速度;

如果接收端缓冲区满了,就会将窗口置为0;这时发送方不再发送数据,但是需要定期发送一
个窗口探测数据段,使接收端把窗口大小告诉发送端。

窗口大小为16位,是否就意味着窗口大小最大就是64KB呢?当然不是的,TCP为了让窗口更大,在选项部分,引入了窗口扩展因子。例如窗口大小为64KB,扩展因子里写了个2,意思就是64KB左移2位,也就变为256KB 了。

在暂停发送的等待过程中,发送方会定期给接收方定期发送 窗口探测报文,这个报文不携带具体业务,只是为了触发ack查询窗口大小。

 6. 拥塞控制

流量控制和拥塞控制共同决定发送方的窗口大小是多少。

流量控制考虑的是接收方的处理能力;

拥塞控制描述的是传输过程中中间节点的处理能力;

发送方按照滑动窗口的方式发送,此处的"窗口大小"描述了发送速率;

窗口大小只是"发送方"的概念,只不过这个窗口大小,是通过接收方的 ack 报头里的窗口大小字段来决定的,从接收方告诉发送方的。

在前面对A的发送速率的分析中,只是考虑了B的处理能力,而没考虑,中间节点的处理能力。对于中间节点来说,它们的处理能力是不好进行直接衡量的。 因此TCP就采取 "实验" 的方式,来测出一个合适的值。(拥塞控制本质上就是通过实验的方式,来逐渐找到一个合适的窗口大小,也就是一个合适的发送速率)

拥塞窗口:尝试以多大的窗口大小进行发送; 

 刚开始的时候,窗口大小为1,以非常慢的速度进行发送数据(此处的1不是1个字节,而是1个单位,一个单位具体代表多少个字节,这里不多过多讨论),发现传输顺利,没丢包,就扩大窗口。初始阶段,由于窗口比较小,每一轮不丢包都会使窗口扩大一倍,呈指数增长。当增长到阈值的时候,就开始线性增长。注意:增长的前提是不丢包。

再接下来,当传输过程中发生网络拥塞了,就说明此时发送的速率已经接近网络的极限了,此时就把窗口大小一下缩成很小的值(重复刚才的指数增长和线性增长的过程),而且阈值也会随之变化。(少量的丢包,我们仅仅是触发超时重传;大量的丢包,我们就认为网络拥塞; )

所以拥塞窗口不是固定数值,而是一直动态变化的,随着时间的推移,逐渐达到一个动态平衡的过程。

 拥塞控制和流量控制的窗口,共同决定了发送方实际的发送窗口,在两者之间取最小值。

7. 延时应答 

延时应答,也是提高效率的机制,也是建立在滑动窗口的基础上的。

滑动窗口的关键,让窗口大一点,传输速率就快一点,因此,可以做到的是,在接收方能够处理的前提下,尽可能把窗口大小放大一点。 

延时:指的就是收到数据后,不是立即就放回ACK了,而是稍微再等等,在这个等待的时间里,接收方的应用程序,就可以把缓冲区里的数据给处理一下,此时接收缓冲区里的剩余空间就大大提高了。 

在实际上,延时应答采取的方式,就是在滑动窗口下,ack不再是每一条数据都返回了。因为前文也说明过,确认序号表示的意思也就是,该序号前的数据已经顺利获取。

 8. 捎带应答

捎带应答,也是一种提高效率的方式,在延时应答的基础上,加上捎带应答。

前文中讲解过,A对B 发起业务请求,ACK是内核立即返回的,但是有时候,在ACK返回后,B也可能需要对A发起业务请求。这两本身是不同的时机的,但是由于TCP 存在上述的延时应答,就导致等待 ACK 的过程中,B就要给A发送业务请求了,就可以让业务请求捎带这个 ACK一起发送出去。

也就是本来是不同时机的两条数据,在延时应答下,可能成为相同时机的,就合并了一起发送了。捎带应答本身说的就是 "能合并" 这件事,延时应答提高了合并的概率。 

9. 面向字节流

面向字节流,就会涉及到一个麻烦事:粘包问题 

接收缓冲区中,其实就是把刚才收到的数据都放在一起,那么当应用程序 read 读取的时候,一次读取N个字节,这就导致可能一次读到的数据是半个应用层数据报,可能是一个应用层数据报,也可能是多个应用层数据报。

所以对于粘包问题,解决方法也很简单,可以对应用层协议进行约定:1. 约定好分隔符;2. 约定好每个包的长度; 

10. 异常情况

异常情况,表示的就是传输过程中出现了不可抗力。 

这里主要分为两种情况: 

1.进程崩溃了,主机关机(按照正常流程关机)

进程没了,对应的 PCB 也就没了,对应的文件描述符表也就释放了,相当于是socket.close(),此时内核会继续完成四次挥手操作,此时其实就是一个正常断开的流程。主机关机的时候,是需要杀死进程的,然后才正式关机,在杀死进程的过程中,就和上述情况是一样的,触发四次挥手。

2. 主机掉电,网线断开 

这种情况下,很显然是来不及进行四次挥手的。

假设是接收方掉电了:

此时发送方仍然在继续发数据,发完数据需要等待ack,但是ack等不到了。从而超时重传,也等不到ack,重传几次,依然没有应答,尝试重置 TCP连接 ,重置失败,从而放弃连接。

走超时重传来放弃连接。

假设是发送方掉电了:

接收方发现,没数据了,此时就不确定是发送方挂了,还是需要准备时间来发送。此时接收方就会先等,然后周期性的给发送方发送一条消息,来确认对方是否还正常工作。这个消息也称为心跳包。心跳包是用来确认通信双方是否处于正常的工作状态中,并不存在业务数据。如果通过心跳包发现对方没有任何回应,也就会断开连接。

通过心跳包来发现连接异常来放弃连接。

通过心跳包你来维护连接这个过程也称为保活机制。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_69417428/article/details/129319636

智能推荐

npm搭建nexus私服_npm login nexus-程序员宅基地

文章浏览阅读249次。代理npm 远程连接超时。## 私服代理配置,先建立blob stores,在建立三个仓库rpositories。后端启动 nexus-3.7.1-02/bin/nexus start。## 2、启动私服:nexus-3.7.1-02/bin/nexus run。淘宝: https://registry.npm.taobao.org。## 3、访问私服 ip:18081、账号admin 密码admin123。## 1、下载nexus-3.7.1-02。启动前修改端口:18081。### 本地上传到私服。_npm login nexus

2022黑龙江最新建筑八大员(材料员)模拟考试试题及答案_料账的试题-程序员宅基地

文章浏览阅读529次。百分百题库提供建筑八大员(材料员)考试试题、建筑八大员(材料员)考试预测题、建筑八大员(材料员)考试真题、建筑八大员(材料员)证考试题库等,提供在线做题刷题,在线模拟考试,助你考试轻松过关。310项目经理部应编制机械设备使用计划并报()审批。A监理单位B企业C建设单位D租赁单位答案:B311对技术开发、新技术和新工艺应用等情况进行的分析和评价属于()。A人力资源管理考核B材料管理考核C机械设备管理考核D技术管理考核答案:D312建筑垃圾和渣土._料账的试题

chatgpt赋能python:Python自动打开浏览器的技巧-程序员宅基地

文章浏览阅读614次。本文由chatgpt生成,文章没有在chatgpt生成的基础上进行任何的修改。以上只是chatgpt能力的冰山一角。作为通用的Aigc大模型,只是展现它原本的实力。对于颠覆工作方式的ChatGPT,应该选择拥抱而不是抗拒,未来属于“会用”AI的人。AI职场汇报智能办公文案写作效率提升教程 专注于AI+职场+办公方向。下图是课程的整体大纲下图是AI职场汇报智能办公文案写作效率提升教程中用到的ai工具。_python自动打开浏览器

Linux中安装JDK-RPM_linux 安装jdk rpm-程序员宅基地

文章浏览阅读545次。Linux中安装JDK-RPM方式_linux 安装jdk rpm

net高校志愿者管理系统-73371,计算机毕业设计(上万套实战教程,赠送源码)-程序员宅基地

文章浏览阅读25次。免费领取项目源码,请关注赞收藏并私信博主,谢谢-高校志愿者管理系统主要功能模块包括页、个人资料(个人信息。修改密码)、公共管理(轮播图、系统公告)、用户管理(管理员、志愿用户)、信息管理(志愿资讯、资讯分类)、活动分类、志愿活动、报名信息、活动心得、留言反馈,采取面对对象的开发模式进行软件的开发和硬体的架设,能很好的满足实际使用的需求,完善了对应的软体架设以及程序编码的工作,采取SQL Server 作为后台数据的主要存储单元,采用Asp.Net技术进行业务系统的编码及其开发,实现了本系统的全部功能。

小米宣布用鸿蒙了吗,小米OV对于是否采用鸿蒙保持沉默,原因是中国制造需要它们...-程序员宅基地

文章浏览阅读122次。原标题:小米OV对于是否采用鸿蒙保持沉默,原因是中国制造需要它们目前华为已开始对鸿蒙系统大规模宣传,不过中国手机四强中的另外三家小米、OPPO、vivo对于是否采用鸿蒙系统保持沉默,甚至OPPO还因此而闹出了一些风波,对此柏铭科技认为这是因为中国制造当下需要小米OV几家继续将手机出口至海外市场。 2020年中国制造支持中国经济渡过了艰难的一年,这一年中国进出口贸易额保持稳步增长的势头,成为全球唯一..._小米宣布用鸿蒙系统

随便推点

js清除HTML的input数据,js 清空 input file 的值的方法-程序员宅基地

文章浏览阅读6.4k次。今天在做一个利用 JS 上传本地图片的小功能时,需要在文件上传成功后,清空 file 类型的 input 元素中所选择的本地文件,以方便二次功能的调用!而这篇文章飞鸟慕鱼博客就来说一说,如果利用 js 来清空一个 file 类型 input 元素的值。js 清空 input file 值的方法方法1:直接将 input file的值改成空字符串;点击我会清空上面input的值function..._js清空file类型的input

stm32H743 使用HAL库SPI读写外部flash失败原因_4线spi读不到flash-程序员宅基地

文章浏览阅读244次。解决办法:__HAL_SPI_ENABLE()放在片选使能前面,__HAL_SPI_DISABLE()放在片选使能后面。HAL_SPI库不能直接用,要修改一下,把SPI_CloseTransfer()函数里面的__HAL_SPI_DISABLE()注释掉。stm32H743生成的spi库,发送接收函数里面有关闭spi接口的函数(__HAL_SPI_DISABLE()),此函数导致始终发生变化,导致读写spiflash失败.发送命令的函数,使用的是HAL_SPI_Transmit();_4线spi读不到flash

Eva.js是什么(互动小游戏开发)-程序员宅基地

文章浏览阅读1.1k次,点赞29次,收藏19次。Eva.js 是一个专注于开发互动游戏项目的前端游戏引擎。:Eva.js 提供开箱即用的游戏组件供开发人员立即使用。是的,它简单而优雅!:Eva.js 由高效的运行时和渲染管道 (Pixi.JS) 提供支持,这使得释放设备的全部潜力成为可能。:得益于 ECS(实体-组件-系统)架构,你可以通过高度可定制的 API 扩展您的需求。唯一的限制是你的想象力!_eva.js

OC学习笔记-Objective-C概述和特点_objective-c特点及应用领域-程序员宅基地

文章浏览阅读1k次。Objective-C概述Objective-C是一种面向对象的计算机语言,1980年代初布莱德.考斯特在其公司Stepstone发明Objective-C,该语言是基于SmallTalk-80。1988年NeXT公司发布了OC,他的开发环境和类库叫NEXTSTEP, 1994年NExt与Sun公司发布了标准的NEXTSTEP系统,取名openStep。1996_objective-c特点及应用领域

STM32学习笔记6:TIM基本介绍_stm32 tim寄存器详解-程序员宅基地

文章浏览阅读955次,点赞20次,收藏16次。TIM(Timer)定时器定时器可以对输入的时钟进行计数,并在计数值达到设定值时触发中断16位计数器、预分频器、自动重装寄存器的时基单元,在 72MHz 计数时钟下可以实现最大 59.65s 的定时,59.65s65536×65536×172MHz59.65s65536×65536×721​MHz不仅具备基本的定时中断功能,而且还包含内外时钟源选择、输入捕获、输出比较、编码器接口、主从触发模式等多种功能。_stm32 tim寄存器详解

前端基础语言HTML、CSS 和 JavaScript 学习指南_艾编程学习资料-程序员宅基地

文章浏览阅读1.5k次。对于任何有兴趣学习前端 Web 开发的人来说,了解 HTML、CSS 和JavaScript 之间的区别至关重要。这三种前端语言都是您访问过的每个网站的用户界面构建块。而且,虽然每种语言都有不同的功能重点,但它们都可以共同创建令人兴奋的交互式网站,让用户保持参与。因此,您会发现学习所有三种语言都很重要。如果您有兴趣从事前端开发工作,可以通过多种方式学习这些语言——在艾编程就可以参与到学习当中来。在本文中,我们将回顾每种语言的特征、它们如何协同工作以及您可以在哪里学习它们。HTML vs C._艾编程学习资料