【Java】传输层协议TCP

传输层协议TCP

TCP报文格式

此处源端口和目的端口很好理解

首部长度

首部长度指的是TCP报文头部中的一个字段,它用于指示TCP报文头部占用了多少个四字节

此处的单位是四字节,把首部长度的数字*4才是真正的报头长度

TCP报头最大长度为60字节,TCP报头的前20个字节,是固定的,使TCP报头的最短长度为20字节

需要通过首部长度,来确认报头到哪里就结束了,载荷数据是从哪里开始的

保留位

在设计TCP时,虽然此处不需要,但是先占好了位置,如果后面还需要增加其他的内容,可以使用保留位中的空间,但是现在还没有使用

32位序列号和32位确认应答号

32位序列号:主机A为每个发送出去的段生成一个唯一的序列号,表示该段的序号。每个数据段都有一个独立的序列号,确保主机B可以将它们正确地排序并重组为原始数据流。主机A在发送数据段时会将该序列号附加在数据段的头部。

32位确认应答号:主机B在确认每个正确接收的数据段时,会将该段的序列号(确认应答号)放在确认段的头部中。这样,主机A就可以根据确认应答号了解哪些数据段已经正确到达目标主机。

标记

ACK

ACK(Acknowledgement)是TCP协议中的一个标志位,用于确认接收方已经成功接收到发送方传输的数据。

在TCP协议中,每当接收方成功接收并正确处理了一个TCP报文段后,它会向发送方发送一个带有ACK标志位的确认报文段,表示已经成功接收到数据。这个确认报文段中的ACK字段会包含一个序号,表示接收方期望下一个数据报文段的序号。

通过ACK标志位,发送方可以得知哪些数据已经被接收方成功接收,从而可以继续发送下一个数据报文段。如果发送方在一定时间内没有收到接收方的ACK确认,它会认为数据丢失,触发重传机制,确保数据的可靠性。

需要注意的是,ACK只表示数据是否被接收方正确接收,而不保证数据的正确性或顺序。TCP协议通过序号和确认机制来保证数据的可靠传输和顺序恢复。

总结起来,ACK(Acknowledgement)是TCP协议中的一个标志位,用于确认接收方已经成功接收到发送方传输的数据。通过ACK标志位,发送方可以获取接收方的确认,以实现数据的可靠传输和顺序恢复

SYN

SYN(Synchronize)是TCP协议中的一个标志位,用于建立连接时进行同步和协商。

在TCP的三次握手过程中,SYN标志位扮演重要的角色。具体的连接建立过程如下:

客户端发送一个带有SYN标志位的TCP报文段给服务器,表示客户端请求建立连接。这个报文段也包含一个初始序号(ISN),用于序列号的初始化。

服务器接收到客户端发送的SYN报文段后,发送一个带有SYN和ACK标志位的报文段给客户端,表示已经收到了请求,并同意建立连接。同时,服务器会选择一个初始序号作为响应的序列号。

客户端收到服务器发送的带有SYN和ACK标志位的报文段后,发送一个带有ACK标志位的报文段给服务器,表示确认收到了服务器的响应。这个报文段中的序号字段会设置为服务器发送的序列号加1,表示客户端期望下一个数据报文段的序号。

通过这样的三次握手过程,双方建立了初始的序列号并同步了连接的状态,可以开始进行数据的传输。

需要注意的是,SYN标志位仅在建立连接时使用,而不在数据传输过程中使用。它用于同步双方的状态和序列号,并确保连接的可靠性。

总结起来,SYN(Synchronize)是TCP协议中的一个标志位,用于建立连接时进行同步和协商。通过三次握手过程,客户端和服务器使用SYN和ACK标志位进行状态和序列号的同步,确保连接的可靠性和正确性。

FIN

2023/9/8 20:49:14

FIN(Finish)是TCP协议中的一个标志位,用于表示发送方或接收方希望关闭连接。

当一方发送了带有FIN标志位的TCP报文段时,它表示该方已经完成了数据的发送,并且不再发送任何数据。另一方接收到带有FIN标志位的报文段后,会发送一个带有ACK标志位的确认报文段作为回应,表示已经收到了关闭请求。

在完全关闭连接之前,最终会有双方各发送一个带有FIN标志位的报文段,以确保双方都同意关闭连接。这个过程称为"四次握手"(Four-way Handshake)。

具体的关闭过程如下:

发送方发送一个带有FIN标志位的报文段,表示它已经完成了数据的发送。

接收方收到带有FIN标志位的报文段,发送一个带有ACK标志位的报文段作为确认。

接收方发送一个带有FIN标志位的报文段,表示它也已经完成了数据的发送。

发送方收到带有FIN标志位的报文段后,发送一个带有ACK标志位的报文段作为最终的确认。

在这个过程完成后,双方都认为连接已经关闭,不再进行数据传输。然后,操作系统会释放相关的资源,结束连接。

总结起来,FIN(Finish)是TCP协议中的一个标志位,用于表示发送方或接收方希望关闭连接。通过四次握手过程,双方发送带有FIN和ACK标志位的报文段来协商和确认关闭连接,并释放相关资源。

RST

RST(Reset)是TCP协议中的一个标志位,用于重置连接或表示异常情况。

当一方收到一个带有RST标志位的TCP报文段时,它会立即终止当前的连接,并不回复任何确认。这种情况通常发生在以下几种情况下:

对方端口未打开或不可达:如果发送方向一个未打开或不可达的端口发送数据,接收方会发送一个带有RST标志位的报文段作为响应,告知发送方连接无法建立。

非法连接请求:当接收方收到一个不合法或非法的连接请求时,它可能会发送一个带有RST标志位的报文段来拒绝连接。

意外的连接中断:在某些情况下,如网络故障、硬件故障或防火墙策略等,TCP连接可能会意外中断。接收方可能会发送一个带有RST标志位的报文段来终止连接并清理相关资源。

需要注意的是,RST标志位表示连接的重置,不同于FIN标志位用于正常关闭连接。RST标志位的使用通常表示异常情况或错误,而不是正常的连接终止过程。

总结起来,RST(Reset)是TCP协议中的一个标志位,用于重置连接或表示异常情况。当接收方收到带有RST标志位的报文段时,它会立即终止连接并不回复任何确认。RST标志位通常用于拒绝不合法的连接请求或表示连接的意外中断。

URG

URG(Urgent Pointer)是TCP协议中的一个特殊标志,用于指示传输中的紧急数据。当设置了URG标志的TCP报文到达接收方时,接收方会根据URG指针的值来判断报文中的哪部分数据是紧急数据,并优先处理。

URG标志在TCP头部的标志字段中,用于标识TCP报文的特殊属性。当设置了URG标志后,TCP报文头部的URG字段中的值就会指示紧急数据的末尾位置。

传输紧急数据时,通常需要与"紧急指针"(Urgent Pointer)一起使用。紧急指针是一个16位的字段,它表示了紧急数据在整个TCP数据流中的偏移量。接收方根据紧急指针的值来决定从何处开始处理紧急数据。紧急指针的值是相对于序列号字段的偏移量,指示了紧急数据的结束位置。

需要注意的是,URG标志和紧急指针并不会自动触发紧急数据的处理,而是提供了一种通知机制,告知接收方有关紧急数据的信息。接收方可以根据URG标志和紧急指针的值来实现相应的处理逻辑,例如及时处理紧急数据或给予其特殊的处理优先级。

总之,URG标志和紧急指针是TCP协议中用于传输紧急数据的机制。它们提供了一种通知机制,使发送方能够告知接收方某部分数据是紧急的,并根据紧急指针的值进行相应的处理。

PSH

PSH(Push)是TCP协议中的一个标志位,用于指示发送方应该立即将缓冲区中的数据发送给接收方,而不需要等待缓冲区填满或等待定时器超时。

当设置了PSH标志的TCP报文到达接收方时,接收方会告知操作系统立即将接收到的数据交给上层应用进行处理,而不是等待更多的数据到达或等待缓冲区填满。这样可以确保及时将数据交付给上层应用,提高实时性和响应速度。

PSH标志在TCP头部的标志字段中,用于表示TCP报文的特殊属性。当设置了PSH标志位后,TCP协议栈会根据该标志位来触发数据的立即发送。

PSH标志通常与应用层的数据交互相关。当上层应用发送的数据需要及时到达目的地或进行实时处理时,可以设置PSH标志,以便及时交付数据。

需要注意的是,PSH标志仅仅是一个请求标志,它只是向接收方发出请求,要求接收方尽快将数据交给上层应用。接收方可以根据自身的策略和条件来决定是否立即响应该请求。

总结起来,PSH(Push)标志是TCP协议中的一个标志位,用于指示发送方应该立即将缓冲区中的数据发送给接收方,而不需要等待缓冲区填满或等待定时器超时。它可以提高数据传输的实时性和响应速度。

16位窗口大小

十六位窗口大小是指TCP协议中用来控制数据流量的窗口大小字段的长度。在TCP头部中,窗口大小字段占据了16个比特位(16个二进制位),因此被称为十六位窗口大小。

窗口大小表示接收方能够接收到的数据量,在TCP连接中起到流量控制的作用。发送方根据接收方提供的窗口大小信息,调整发送的数据量,以确保不会导致接收方缓冲区溢出或网络拥塞。

由于十六位窗口大小字段长度有限,其最大值为65535(2^16-1),通过使用这个字段,TCP连接可以控制数据的传输速度,避免过多的数据积压和丢失。

需要注意的是,由于十六位窗口大小的限制,对于高带宽和高延迟的网络连接,窗口大小可能会成为瓶颈,限制了数据传输的速度。为了解决这个问题,TCP引入了拥塞窗口等机制,以支持更大的窗口大小和更高的传输性能。

总结起来,十六位窗口大小是TCP协议中用来控制数据流量的窗口大小字段的长度,占据16个比特位。窗口大小表示接收方能够接收到的数据量,用于流量控制。它具有最大值为65535的限制,对于高带宽和高延迟的网络连接可能会成为瓶颈。

16位校验和

23/9/8 20:53:41

16位校验和是一种用于数据完整性检验的简单校验算法。它将待发送的数据按照固定大小(通常以16位为单位)进行分组,并计算每个分组中所有比特的和,得到一个16位的校验和值。

具体计算步骤如下:

将待发送的数据进行分组,每组16位(2个字节)。

对每个分组内的所有比特进行求和,忽略溢出的进位。

将求和结果取反,得到最终的16位校验和。

在发送数据时,发送方会将原始数据和对应的校验和一起发送给接收方。接收方在接收到数据后,对接收到的每个分组进行相同的校验和计算,并将计算结果与接收到的校验和进行比较。如果两者一致,则说明数据在传输过程中没有发生错误;如果不一致,则说明数据可能发生了错误或损坏。

16位校验和是一种简单的校验算法,能够检测出一部分错误,如单比特错误或少量的传输错误。然而,它并不能检测到所有可能的错误,因此在实际应用中,常常使用更复杂的校验算法,如CRC(循环冗余校验)等,以提高数据完整性的检测能力。

总结起来,16位校验和是一种简单的校验算法,用于检测数据的完整性。它将待发送的数据按照16位为单位进行分组,计算每个分组中所有比特的和,并将结果取反作为校验和。接收方在接收到数据后,进行相同的计算,并与发送方提供的校验和进行比较,以判断数据是否发生错误或损坏。然而,16位校验和仅能检测部分错误,而不能覆盖所有可能的错误。

16位紧急指针

16位紧急指针是TCP协议头部中的一个字段,用于指示数据流中的紧急数据的位置。紧急指针字段占据了16个比特位(16个二进制位),因此被称为16位紧急指针。

在TCP连接中,紧急数据通常用于实时通信或某些应用层协议中需要优先处理的数据。通过设置紧急指针,发送方可以向接收方传达数据流中哪些部分是紧急数据,接收方可以据此优先处理这些数据。

当发送方发送具有紧急指针的数据时,接收方会根据紧急指针来识别紧急数据的边界。对于紧急指针之前的数据,接收方会按照正常顺序进行处理;而对于紧急指针之后的数据,则可能会被接收方提前处理,以满足紧急数据的要求。

需要注意的是,紧急指针所指示的位置是相对于当前序列号的偏移量,而不是绝对位置。发送方和接收方需要按照TCP流状态保持一致,以正确解读紧急指针字段。

总结起来,16位紧急指针是TCP协议头部中的一个字段,用于指示数据流中的紧急数据的位置。发送方可以设置紧急指针来传达紧急数据的位置,接收方可以据此优先处理紧急数据。紧急指针是相对于当前序列号的偏移量,需要发送方和接收方在TCP流状态中保持一致,以正确解读该字段。

选项

TCP协议头部中的选项字段用于在TCP报文中传递一些可选的、与特定功能或参数相关的信息。选项字段可以包含多个选项,每个选项由一个字节的类型字段和一个字节的长度字段组成。

选项字段是可变长度的,每个选项的长度由长度字段指定。常见的选项包括窗口扩大因子、时间戳、最大报文段长度(MSS)等。

窗口扩大因子选项允许接收方通知发送方它可以支持的窗口大小,以便发送方根据该信息调整发送窗口的大小。

时间戳选项用于对报文进行时间戳标记,帮助测量往返时间(RTT)和计算报文的时序。

最大报文段长度(MSS)选项用于告知对方本地所能接受的最大报文段长度,以便发送方在传输数据时进行分段。

除了上述常见选项外,还有一些其他的选项,如选择确认选项(SACK)、拥塞控制选项等,用于实现更高级的TCP功能和性能优化。

选项字段在TCP报文头部中是可选的,不是所有的TCP报文都会使用选项字段。如果使用了选项字段,其总长度必须是32位(4字节)的倍数。

总结起来,选项字段是TCP报文头部中的可选字段,用于传递一些与特定功能或参数相关的信息。选项字段可以包含多个选项,每个选项由一个字节的类型字段和一个字节的长度字段组成。常见的选项包括窗口扩大因子、时间戳、最大报文段长度等,用于实现不同的TCP功能和性能优化。选项字段的长度必须是32位的倍数。

TCP特点

有连接,可靠传输,面向字节流,全双工

可靠传输实现机制-确认应答

上述图片展示了TCP传输的确认应答机制,但是为什么要给每一个应答编号呢?

因为此时会出现后发先至的问题

假设我们顺序发送了TCP1和TCP2,接收端在接收到TCP1和TCP2后发送了TCP1的应答和TCP2的应答,但是由于一些原因,使TCP2 的应答先于TCP1的应答,此时会产生错误
此时的解决方法就是给应答增加一个确认序号,确认序号的数值,就是收到的最后一个字节的编号再+1

我们通过ACK标记来确认是普通报文,还是返回的应答报文

ACK是0表示这是一个普通报文,此时只有32位序号是有效的,32位确认序号是无效的

ACK是1表示这是一个应答报文,这个报文的序号和确认序号都是有效的
确认报文的序号和正常报文的序号,之间没有关联关系,各自论各自的

确认应答是TCP保证可靠性的最核心的机制,而不是其他

超时重传

在网络上很可能出现发一个数据,然后丢了,这种现象就叫丢包,因为数据在网络上传输会经过很多路由器和交换机,他们是交通枢纽,每个时间通过的数据量也不确定,如果有一个设备太繁忙了,后面的数据等太久了,就可能会被丢弃,此时就会出现丢包

如果真的出现丢包这样的现象,就要通过超时重传这样的机制来确保数据能够正确传输,超时重传,相当于针对确认应答机制,进行重要的补充

针对于TCP,丢包会出现两种情况,一种是发送的数据丢包,一种是返回的确认应答丢包


作为数据的发送方来说,他并不知道是上述的两种情况中的哪一种,此时既然无法区分,数据源在没有等到应答报文,等待一段时间后,就会将数据重新发送一分给接收方

针对于第一种情况,数据本身并没有发送到接收方,此时发送方再重传一份数据,是没有任何问题的,但是如果是第二种情况,实际上发送方的数据已经传输到接收方,此时如果再发送一份数据,此时就会出现数据重复的问题

作为接收方,收到数据后,需要对数据进行去重,把重复的数据丢弃掉,保证应用程序,调用read的时候,读到的数据不会重复,那么如何进行去重呢?

我们可以直接使用TCP的序列号来作为判定依据,TCP会在内核中,给每个socket对象都安排一个内存空间,相当于一个队列,被称为'接收缓冲区',收到的数据,都会放到接收缓冲区里,并且按照序号排列号顺序了,此时就可以很容易找到此时新收到的数据是否重复了

此时的有序排列,是非常有意义的,这里也解决了在确认应答机制中的后发先至问题,此时就算出现了先发后至问题,应用程序这里读到的数据,仍然是有序的

在数据被读取后,数据就会被默认从队列中删除,此时是一个生产者消费者模型,

接收缓冲区数据有序还可以保证在数据在被读取后删除,此时发送端再发送一条数据,此时就可以通过序号大小来判断此时这个数据是否是被读取到了,就可以实现去重的问题

发送方等待的超时时间是可变的,可以配置的,此时的超时时间并不是一个固定的值,而是会随着超时轮次的增加,而在进一步增加
随着超时重传的轮次增加,等待的时间也会越来越长,正常来说,在第二次重传就会有极大的概率重传成功,如果一个数据经过好几次都没有传输过去,说明大概率是网络出现了很大的问题,此时重传次数到达一定程度,此时就会尝试重置TCP连接,对应TCP报文中的RST字段,此时这个数字为1,就表明要尝试重置TCP连接

如果此时网络已经出现了严重故障,复位操作也没有成功,最终只能放弃连接,就会把自身保存的对端信息就删除掉了

连接管理机制

三次握手

三次握手是客户端服务器建立连接的一个过程

首先客户端向服务器发起一个请求,表示我想要和你建立连接

服务器表示收到,然后返回我也想和你建立连接的一个响应

客户端此时也返回一个收到,此时连接就建立完成了

分别站在客户端和服务器的角度,两者都向对方发起了想和你建立连接的申请,并且都能够得到对方的回应,此时连接就可以建立

SYN(Synchronize)是TCP连接建立过程中的一个标志位,用于同步序列号和进行初始握手。

在TCP三次握手中,SYN标志位被用作连接请求方发送的报文段的标识。发送方设置SYN标志位为1,表示请求建立连接,并携带自己的初始序列号。

具体的握手过程如下:

  1. 客户端向服务器发送一个包含SYN标志位的SYN报文,其中序列号用来标识客户端的起始数据字节序号。
  2. 服务器在接收到客户端的SYN报文后,会发送一个包含SYN和ACK标志位的报文,其中SYN标志位仍然为1,表示同意建立连接,ACK标志位为1,确认收到了客户端的SYN请求,并携带自己的初始序列号。
  3. 客户端收到服务器的报文后,发送一个ACK报文,其中ACK标志位为1,表示确认收到了服务器的同意建立连接的报文,并携带服务器的初始序列号+1。

这样,通过三次握手,双方完成了连接的建立,可以开始进行数据传输。

此处建立连接的过程使用TCP三次握手,主要是为了保证网络通信顺畅,并且验证发送和接收方设备都能够正常运行

为什么必须是三次握手而不是四次或者两次?

两次:两次握手虽然可以验证完成通信能力的验证,但是服务器端并没有接收到客户端发来的ACK应答报文,所以服务器并不知道验证通过的信息

四次:四次是可以完成任务,但是他是将服务器端发送的信息拆分成两次,是没有必要的,拆分成两次,降低了效率

四次挥手

四次挥手是通信双方断开连接的过程

三次握手,必然是由客户端发起的,但是四次挥手则不一定,但大部分还是客户端主动断开连接

这里假设还是客户端发起断开连接的申请

首先客户端发送给服务器断开连接的申请,服务器立马返回一个ACK报文,表示收到

但是这里的断开连接和返回ACK并不是一起的,而是根据服务器端代码的执行逻辑在彻底断开连接

此时客户端在给服务器返回一个ACK,连接彻底关闭


FIN也是六个标志位中的其中一个,FIN为1,表示这是一个结束报文

具体而言,TCP四次挥手的过程如下:

  1. 第一次挥手(FIN+ACK):

    客户端发送一个标志位包含FIN(结束)标志和ACK(确认)标志的报文,表示客户端已经没有数据需要发送,并请求关闭连接。此时客户端进入FIN_WAIT_1状态。

  2. 第二次挥手(ACK):

    服务器收到客户端的FIN报文后,发送一个仅包含ACK标志的报文作为回应,表示它已经收到了客户端的关闭请求。此时服务器进入CLOSE_WAIT状态,等待自己的数据发送完毕。

  3. 第三次挥手(FIN+ACK):

    当服务器所有的数据都发送完毕后,会发送一个标志位包含FIN和ACK的报文,表示服务器也没有数据需要发送,并准备关闭连接。此时服务器进入LAST_ACK状态。

  4. 第四次挥手(ACK):

    客户端收到服务器的FIN报文后,发送一个ACK报文作为回应,表示已经确认服务器的关闭请求。此时客户端进入TIME_WAIT状态。

最后,服务器在收到客户端的确认后,会关闭连接,此时服务器进入CLOSED状态,而客户端则等待一段时间后关闭连接,进入CLOSED状态。

为什么必须是四次挥手,而不是三次

因为和三次挥手不同,服务器向客户端返回应答是,两次不一定能够合并,第一次ACK返回的时候,是瞬发的,收到FIN后,就会立马返回ACK,而第二次返回FIN时,需要调用close方法,或者进程结束,才会发送

特殊情况

  1. 如果服务器始终没有进行close,客户端的连接就始终不关闭吗?

此时服务器的TCP状态,就处于CLOSE_WAIT状态,站在服务器的角度,虽然这里的连接没有关闭,但是这个链接其实已经不能正常使用了

如果此时对socket进行读操作,如果数据还没读完,可以正常读,读完了就返回EOF

如果此时针对socket进行写操作,就会直接触发异常

站在客户端的角度,如果迟迟收不到对方的FIN,会进行等待,如果一直等不到,就会单方面放弃链接

  1. 如果在三次握手和四次挥手的过程中,出现了丢包该如何处理?

这里也是涉及到超时重传机制,三次握手和四次挥手也是带有重传机制的,如果连续重传多次,此时仍会单方面释放连接

  1. 丢失第一条 FIN 报文:如果客户端发送的第一条 FIN 报文丢失,服务器将无法收到关闭连接的请求,客户端会等待超时并重新发送第一条 FIN 报文。

  2. 丢失第二条 ACK 报文: 在服务器确认收到客户端的关闭请求后,发送的 ACK 报文如果丢失,客户端将一直等待服务器的确认,不会进入CLOSE_WAIT 状态。

  3. 丢失第三条 FIN 报文: 如果服务器发送的第三条 FIN 报文丢失,客户端将无法接收到服务器的关闭请求,服务器会等待超时并重新发送第三条FIN 报文。

  4. 丢失第四条 ACK 报文: 在客户端收到来自服务器的关闭请求后,发送的 ACK 报文如果丢失,客户端将一直等待服务器的确认,不会进入TIME_WAIT 状态。

滑动窗口

刚才我们讨论了确认应答策略,对每一个发送的数据段,都要给一个ACK确认应答。收到ACK后再发送下一个数据段。这样做有一个比较大的缺点,就是性能较差。尤其是数据往返的时间较长的时候。.
每次发送数据之后,为了保证可靠性,都要消耗大量的时间来等待ACK,使用滑动窗口就可以缩短上述时间

一次性发出一组数据,发送这一组数据的过程中,不需要等待ACK,此时就相当于一份等待时间等四个ACK,把一次发送多少数据,不用等ACK这样的大小称为窗口

窗口越大,此时批量发送的数据就越多,效率就越高

但是窗口不能无限大,如果是无限大,相当于完全不必等ACK,此时就和不可靠传输差不多

滑动窗口是一个形象的比喻,实际上本质就是批量发送数据,这样可以缩短等待时间,比之前能提升一定的效率但是效率仍没有UDP高

按照滑动窗口的方式来批量发送数据,万一丢包了怎么办?

此时分为两种情况:一种是;数据报丢失,一种是返回的ACK丢失

  1. ACK丢失

    ACK如果出现丢失,不用做任何处理,也是正确的,除非是所有ACK都丢了(出现了重大网络故障),否则,只是丢了一部分ACK,对于可靠传输,没有任何影响
    因为只要有一个ACK到达,就说明这个ACK之前的数据就全部到达了

  2. 数据丢失

    如图所示,在1001-2000这段数据丢失后,服务器回一直返回1001这个序号,当返回的ACK连续几次都是1001的话,客户端就知道,1001-2000这段数据丢包了,此时在重新传输1001-2000这段数据,在这之前客户端发送的2001-3000,3001-4000...6001-7000这些数据,就会放在接收方的缓存区里

    如果接收缓冲区,这一块少了元素,返回的ACK就会始终索要1001-2000这个数据,一旦这个数据被补上了,此时1001-2000后面的数据就不必重新传输了,这样的操作被称为快速重传,相当于用最小的成本,完成了重传的工作

流量控制

滑动窗口,窗口越大,传输效率越高,但窗口也不能无限大,如果窗口太大了,就可能使接收方处理不过来了,或者是使传输的中间链路出现处理不过来,这样就会出现丢包,此时窗口大不仅没有提高效率,反而还影响了效率

流量控制,就是避免让窗口太大,导致接收方处理不过来

接收端将自己可以接收的缓冲区大小放入 TCP 首部中的 "窗口大小" 字段,通过ACK端通知发送端;

窗口大小字段越大,说明网络的吞吐量越高;

接收端一旦发现自己的缓冲区快满了,就会将窗口大小设置成一个更小的值通知给发送端;发送端接受到这个窗口之后,就会减慢自己的发送速度;

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

接收端如何把窗口大小告诉发送端呢?
回忆我们的TCP首部中,有一个16位窗口字段,就是存放了窗口大小信息;
那么问题来了,16位数字最大表示65535,那么TCP窗口最大就是65535字节么?
实际上,TCP首部40字节选项中还包含了一个窗口扩大因子M,实际窗口大小是窗口字段的值左移 M 位

拥塞控制

总的传输效率,是一个不同效应,取决于最短板

虽然TCP有了滑动窗口这个大杀器,能够高效可靠的发送大量的数据。但是如果在刚开始阶段就发送大量的数据,仍然可能引发问题。

因为网络上有很多的计算机,可能当前的网络状态就已经比较拥堵。在不清楚当前网络状态下,贸然发送大量的数据,是很有可能引起雪上加霜的。
TCP引入慢启动机制,先发少量的数据,探探路,摸清当前的网络拥堵状态,再决定按照多大的速度传输数据;

此处引入一个概念程为拥塞窗口

发送开始的时候,定义拥塞窗口大小为1;

每次收到一个ACK应答,拥塞窗口加1;

每次发送数据包的时候,将拥塞窗口和接收端主机反馈的窗口大小做比较,取较小的值作为实际发送的窗口;

像这样的窗口的增长速度,是指数级别的,慢启动只是初始满,但是增长速度非常快


拥塞控制和流量控制,共同限制了滑动窗口机制,可以使滑动窗口,能够在可靠性的前提下,提高传输效率了

延迟应答

延迟应答的作用主要是,在条件允许的基础上,尽可能地提高窗口的大小

假设接收端缓冲区为1M。一次收到了500K的数据;如果立刻应答,返回的窗口就是500K;但实际上可能处理端处理的速度很快,10ms之内就把500K数据从缓冲区消费掉了;

在这种情况下,接收端处理还远没有达到自己的极限,即使窗口再放大一些,也能处理过

来;

如果接收端稍微等一会再应答,比如等待200ms再应答,那么这个时候返回的窗口大小就是1M;

一定要记得,窗口越大,网络吞吐量就越大,传输效率就越高。我们的目标是在保证网络不拥塞的情况下尽量提高传输效率

捎带应答

在延迟应答的基础上,我们发现,很多情况下,客户端服务器在应用层也是 "一发一收" 的。意味着客户端给服务器说了 "How are you",服务器也会给客户端回一个 "Fine, thank you";

那么这个时候ACK就可以搭顺风车,和服务器回应的 "Fine,thank you" 一起回给客户端


延迟ACK发送的时机,尽可能和响应一起返回给客户端,这也是为什么四次挥手有的时候也可以是三次挥手

数据报从两个合并成一个,效率会有明显的提升,主要是因为这里每次传数据都是需要封装分用,能合并的原因,一方面是时机上是可以同时的,另一方面是ACK数据本身不需要携带载荷,和正常的数据也不冲突,完全就可以让一个数据包,即能携带在和数据,又能带有ACK的信息

面向字节流

在面向字节流这样的情况下,会产生一些其他的问题

粘包问题

这里粘的是应用层数据报

通过tcp read或者write的数据,都是tcp报文的载荷,也就是应用层数据

发送方一次性是可以发送多个应用层数据报的,但是接收的时候,如何区分,从哪里到哪里是一个完整的应用数据报,如果没设计好,设计方就很难区分

此处正确的做法是合理的设计应用层协议
应用层协议中,引入分隔符,区分包与包之间的边界
应用层协议中,引入包长度,也能区分包之间的边界

粘包问题不仅仅是tcp才有的,只要是面向字节流的也会有相同的问题,解决方案也相同,要不引入分隔符,要么使用长度

xml/json是通过分隔符来区分包的

protobuffer是通过长度来区分的

TCP异常情况处理

进程崩溃

进程结束表示着PCB没了,文件描述符表也就被释放了,相当于调用了socket.close()

此时崩溃的这一方就会发出FIN,进一步触发四次挥手,此时链接就被正常释放了,此时tcp的处理和进程正常退出,没啥区别

主机关机

正常关机,就会尝试杀死所有进程,此时和进程崩溃的处理方式是相同的

主机关机有一定的时间,如果在这个时间内,四次挥手可能正好结束,但是如果没有挥手结束,也没有问题,此时通过超时重传机制,在重传几次都没有应答以后,就自动单方面释放自己的连接信息了

主机掉电

主机掉电分为两种情况,第一种是发送方主机掉电,一种是接收方主机掉电

接收方掉电

此时情况比较简单,如果是接收方掉电,发送方发送的数据没有收到ACK,此时就会触发超时重传,如果重传仍然失败,就会触发复位报文RST,尝试重新连接,重置操作仍然失败,此时就会单方面释放连接了

发送方掉电

此时的情况就比较复杂
此时接收方一直在等待发送方的消息,此时发送方突然就不发送消息了,此时接收方不知道发送方是暂时不发数据,还是一直就不发了,此时接收方就会阻塞等待

此时就涉及到心跳包,接收方虽然是只需要接收消息,但是也会周期性的给对方发送一个不携带任何业务数据(载荷)的TCP数据报,发起这个数据报的目的,就是为了触发ACK,就是确认一下,发送方是否正常工作,如果此时不能正常返回ACK,说明接收方已经不能发送数据了

相关推荐
suweijie7682 小时前
SpringCloudAlibaba | Sentinel从基础到进阶
java·大数据·sentinel
公贵买其鹿3 小时前
List深拷贝后,数据还是被串改
java
xlsw_6 小时前
java全栈day20--Web后端实战(Mybatis基础2)
java·开发语言·mybatis
神仙别闹7 小时前
基于java的改良版超级玛丽小游戏
java
黄油饼卷咖喱鸡就味增汤拌孜然羊肉炒饭8 小时前
SpringBoot如何实现缓存预热?
java·spring boot·spring·缓存·程序员
暮湫8 小时前
泛型(2)
java
超爱吃士力架8 小时前
邀请逻辑
java·linux·后端
南宫生8 小时前
力扣-图论-17【算法学习day.67】
java·学习·算法·leetcode·图论
转码的小石8 小时前
12/21java基础
java
李小白668 小时前
Spring MVC(上)
java·spring·mvc