导图社区 TCP协议详解
详细的介绍了TCP的报文格式和特性,然后讲解了什么是TCP连接建立,连接断开,SYN洪水攻击,什么是RTT,RTO,什么是超时重传,什么是快速重传。滑动窗口怎么实现流量控制,什么是窗口死锁等。内容详细,通俗易懂。但是TCP东西还有很多,需要你们自己去检索。
编辑于2021-07-15 17:22:13详细的介绍了TCP的报文格式和特性,然后讲解了什么是TCP连接建立,连接断开,SYN洪水攻击,什么是RTT,RTO,什么是超时重传,什么是快速重传。滑动窗口怎么实现流量控制,什么是窗口死锁等。内容详细,通俗易懂。但是TCP东西还有很多,需要你们自己去检索。
前人栽树,后人乘凉!自己结合大佬的图解书籍,总结包括HTTP的发展历史,HTTP1.0 1.1 2.0 3.0以及各种TLS算法握手深度解析,每个HTTP版本详解,优劣,以及如何优化。关于HTTP的方方面面都有所涉及,现在我来栽树,希望能看到的朋友可以一起学习,无畏艰苦,共同进步。本思维导图也是本人第一次使用这款软件第一次制作,内容上理解有限;如有不对,欢迎指导。
社区模板帮助中心,点此进入>>
详细的介绍了TCP的报文格式和特性,然后讲解了什么是TCP连接建立,连接断开,SYN洪水攻击,什么是RTT,RTO,什么是超时重传,什么是快速重传。滑动窗口怎么实现流量控制,什么是窗口死锁等。内容详细,通俗易懂。但是TCP东西还有很多,需要你们自己去检索。
前人栽树,后人乘凉!自己结合大佬的图解书籍,总结包括HTTP的发展历史,HTTP1.0 1.1 2.0 3.0以及各种TLS算法握手深度解析,每个HTTP版本详解,优劣,以及如何优化。关于HTTP的方方面面都有所涉及,现在我来栽树,希望能看到的朋友可以一起学习,无畏艰苦,共同进步。本思维导图也是本人第一次使用这款软件第一次制作,内容上理解有限;如有不对,欢迎指导。
TCP
TCP的基本认识
TCP头格式
序列号: 在初始建立连接的时候;由发送方计算机基于时钟 + 标识本次TCP连接的四元组的HASH值来计算出随机初始值ISN;然后每发送一次报文;序列号就加上本次报文携带的字节数的大小。
接收⽅可以去除᯿复的数据;
接收⽅可以根据数据包的序列号按序接收;
可以标识发送出去的数据包中, 哪些是已经被对⽅收到的;
可以解决网络包乱序的问题
确认号: 表示下一次期望收到数据的序列号;接收方也可以认为在确认号之前的数据都已被正确处理。用来解决丢包等问题。
数据偏移: 标识TCP报文中数据的偏移量;同时也代表了TCP报文的包头大小
URG: 紧急字段;当该位置1时,表示紧急指针字段有效。
ACK: 确认;该位置1时,确认号字段有效。只有第一次握手的时候ACK为0;其余时候必须置1。
PSH: 推送;该位置1的时候;接收方不用等缓存区满就可以直接交付给应用层。
RST: 复位;该位置1的时候;表示此连接出现严重问题;需要断开重新建立。
SYN :同步;该位置1的时候;表示发送方请求建立连接。
FIN: 终止; 该位置1的时候;表示发送方结束自己的发送窗口;之后将不再发送数据。
窗口大小:表示发送方自己接收窗口大小;告诉对方应该发多大的数据。
校验和: 对整个TCP报文的校验;包括TCP头部和数据部分。
紧急指针: 指出TCP报文数据中需要紧急处理的字节数。之后的为普通数据。
什么是TCP?
TCP是一个传输层协议;基于字节流的提供可靠传输面向连接的的传输协议。
什么是TCP连接?
用于保证可靠性和流量控制等状态信息的集合;包括SOCKET;序列号;窗口大小。
如何确定一条TCP连接?
一个四元组确定一条TCP连接。
TCP和UDP之间有什么区别呢? 分别应用在什么场景?
TCP用于在想要保证可靠性;通信质量要求高的通信中;
UDP用于对时延敏感;不知道对方IP;对可靠没有要求的通信中。
为什么TCP只有头长度而UDP只有包长度?
因为TCP报文中有选项字段,大小不固定,且在IP报文中有标识IP包头长度和包长度的字段。就可以计算出TCP包长度。所以没有必要去再用一个字段去标识TCP包长度。而UDP首部大小固定8字节;所以一个包长度字段来标识就可以。
TCP连接的建立
TCP三次握手建立连接。需要注意:只能在第三次握手才可以携带传输的数据;前两次握手不可以携带数据。
为什么需要三次握手才能建立连接;不是二次四次? 其实也就是为什么最少要三次才可以初始化TCP连接。
为了避免旧的重复历史连接初始化(最主要原因)
当客户端发送一个SEQ=90的建立连接的报文;但是被网络阻塞了;然后超过RTO之后;发送方重新发送一个SEQ=100的建立连接的报文。但是呢这个时候SEQ为90的报文先到达了接收端;接收方响应一个SEQ为Server_ISN;ACK =91的报文。但是接收方根据上下文比较;他应该收到的确认报文ACK应该为101;所以这个时候发送RST复位报文;断开旧的历史连接。等确认号为101的报文收到;在响应一个确认号为SERVER_ISN+1的报文;至此三次握手建立连接
只有三次握手才可以根据上下文同步序列号
第一次握手客户端发送自己连接的ISN;然后需要服务器响应确认;并且服务器也需要将自己报文的ISN发送给客户端;同时也需要客户端响应一个确认。这样一来一回才可以确保可靠同步双方的初始序列号。之后在本次TCP连接中的通信都会交互双方的序列号。
避免资源浪费
如果是两次握手建立连接。那么 每次客户端发送SYN报文请求建立连接时;如果这个时候客户端的SYN报文被网络阻塞;超过RTO就会再次发送一个新的SYN报文。而这个时候被阻塞的报文陆陆续续到达服务器;服务器因为缺少了客户端的确认机制;每次接收到客户端的SYN报文就会建立一个连接。这样就会造成大量无用冗余的连接被建立。非常的浪费资源。
为什么客户端的ISN和服务器的ISN不一样
如果一个旧的连接被重用了;这个时候要是由之前被网络阻塞了。现在要是之前的连接被重用,而发送了一个与之前被阻塞的序列号相同的数据报;接收方就不知道这个是旧数据还是新发送的数据。要是旧的数据被新得连接接收了,会产生严重的数据错乱。所以客户端和服务器在连接初始的时候需要初始化不同的ISN;主要是为了通信双⽅能够根据序号将不属于本连接的报⽂段丢弃。
为什么有IP分层,那么为什么TCP层还需要MSS呢??
因为如果将TCP数据完全交给IP层进行分片;我们都知道当上三层数据超过以太网MTU的时候;需要进行分片。但是IP层不保证数据包的可靠交付;所以在TCP提供可靠传输的机制。如果一个IP分片在传输过程中丢包;那么需要重传所有分片的TCP数据。所以为了效率;TCP在建立连接的时候需要协商双方的MSS。TCP携带的数据要是超过MSS就会分片;所以分片完在IP层封装的时候也不会超过MTU大小。
什么是SYN攻击;如何避免SYN攻击?
我们都知道;在TCP建立连接的时候需要三次握手建立连接。当客户端发送SYN报文之后就会进入SYN_SENT状态;服务器收到并响应之后变成SYN_RCVD状态等待客户端响应最后握手的确认报文。但是我们客户端现在是黑客主机;我们不对服务器响应的报文回ACK包;所以久而久之;服务器的未连接队列就会被占满;这样服务器就不能再对之后接收到的正规的请求连接的报文进行响应。
如何避免攻击SYN攻击?
先看一下完整的流程
方法一:通过修改内核参数;控制队列大小和当队列满应该做什么处理
当网卡接收数据的速率大于应用处理数据的速率的时候;会有一个队列来保存这些数据。可以修改此队列的最大值。
可以修改SYN_RCVD状态的最大连接数。
当未连接队列满的时候;对新的连接请求直接响应RST报文。
方法二:当未连接队列满的时候;服务器收到新的SYN报文则不进入未连接队列。服务器计算一个Cookie值再以SYN+ACK的响应报文发送给客户端;若收到客户端的ACK报文则会校验报文的合法性,要是合法直接放入Accept队列;应用层调用accept() socket接口。
TCP连接的断开
TCP四次挥手过程和状态的变迁
TCP四次握手断开链接。当客户端向服务器发送一个FIN报文的时候;客户端就进入了FIN_WAIT_1状态;表示客户端断开自己的发送窗口;表示已传输完数据;期望断开连接。服务器收到之后立即响应一个ACK确认报文;这个时候服务器变成了CLOSED_WAIT;表示自己可能还有数据没有发送结束;这时如果服务器数据发送结束;发送FIN置1的报文且自己状态变成LAST_ACK;断开服务器自身的发送窗口;客户端收到FIN报文立即响应ACK报文。这时客户端变成最后的状态TIME_WAIT状态;注意客户端TIME_WAIT状态会持续2MSL的时间后才会变成CLOSE状态。
为什么是四次分手?
因为两种情况:1 因为客户端发送完FIN报文只是代表断开自己的发送窗口;但是自己还有接收窗口;还可以接受处理数据。 2: 服务器收到客户端发送的FIN报文之后必须需要响应一个ACK报文,并断开自己的接收窗口。但可能服务器还存在数据没有发送结束;客户端接收窗口也没有关闭。当服务器发送结束之后会向客户端发送FIN报文;表示自己结束数据发送;断开自己的发送窗口;同样的客户端也需要响应ACK报文;同时在等待2MSL时间后断开自己的接收窗口。
为什么请求建立方会需要TIME_WAIT状态?
首先注意:只有主动断开连接的一方才会有TIME_WAIT状态。
1:防止旧的四元组的连接旧的数据包被收到
如果服务器向客户端发送了一个属于本连接的数据包;但是被网络阻塞了。这时客户端向服务器发送FIN断开连接请求;双方四次分手将这个连接断开了。但是被阻塞的数据包还停留在网络中;这时如果这个四元组连接被双方复用了;旧的被阻塞的数据包到达了客户端;是可能会被客户端成功处理的;这个就会造成严重的数据混乱。
2:为了让客户端最后相应的ACK报文被服务器成功收到;保证接收方的成功关闭连接。
如果这个时候客户端的TIME_WAIT时间过短或没有这个状态;那么客户端发送的ACK确认报文可能在还没到达服务器时客户端就处于CLOSE状态了。如果这个时候ACK包被阻塞了;服务器没有收到;但是这个时候客户端已经处于关闭状态了;无法再发送ACK包;所以服务器这个时候只能处于LAST_ACK状态;这个状态无法建立新的连接;这个端口资源就被一直占用了;如果这个状态太多了的话;就会极大地影响服务器对客户端提供服务。
为什么TIME_WAIT状态最起码需要2MSL时间?
首先我们要知道什么是MSL? MSL是 Maximum Segment Lifetime英文的缩写;表示报文最大生存时间。但我们知道TCP基于IP协议的,IP数据包里面存在TTL值;但是不同的是TTL的单位是路由跳数;经过一个路由TTL就会减一;但是MSL单位是时间;报文一旦超过MSL就会被网络中丢弃。所以理论上MSL> TTL
TIME_WAIT状态需要持续2MSL时间比较合理的解释是: 在双方四次分手断开连接过程中;客户端在断开自己发送窗口的之前可能向服务器发送了数据报文;但是呢发送的数据报文可能被网络阻塞了,服务器接收到可能会处理并向客户端响应,这样一来一回也就2MSL时间; 服务器也有可能发送了但被网络阻塞的数据包;为了防止四元组连接在断开后被复用,这些旧的数据包可能会被双方接收并处理;从而造成严重的数据错乱。
最后注意一个东西: TIME_WAIT状态只有是客户端在四次分手过程中收到服务器发送的FIN报文之后才会变成TIME_WAIT状态;并且时间刷新为2MSL。
TCP重传机制
超时重传
超时重传是在发送出一个TCP数据包的时候就设置一个定时器;要是在这个定时器内没有收到对方的ACK确认包就重新发送这个TCP数据包。
说到超时重传我们就不得不提到RTT和RTO,RTT是包的往返时延;表示数据包一来一回的时间;而RTO表示超时重传时间;一般来说RTO是动态计算出来的;随着网络的拥塞情况来滑动的计算出;略大于RTT。要是发生一次超时重传的情况;TCP就会把RTO设置为上一次的两倍;要是超过两次重传那么就说明网络出现问题,这时不应该进行数据包的转发。
快速重传
超时重传有一个缺点就是: 我们在网络阻塞或者数据包丢失需要进行数据重传的时候;需要等RTO超时才可以进行重传。需要等待较长的时间;所以我们需要快速重传;注意:快速重传不以时间为驱动;而是以数据为驱动进行重传。
发送⽅发出了 1,2,3,4,5 份数据: 第⼀份 Seq1 先送到了,于是就 Ack 回 2。结果 Seq2 因为某些原因没收到,Seq3 到达了,于是还是 Ack 回 2;后⾯的 Seq4 和 Seq5 都到了,但还是 Ack 回 2,因为 Seq2 还是没有收到; 发送端收到了三个 Ack = 2 的确认,知道了 Seq2 还没有收到,就会在定时器过期之前,重传丢失的 Seq2。 最后,收到了 Seq2,此时因为 Seq3,Seq4,Seq5 都收到了,于是 Ack 回 6 。
但是呢快速重传还不够完善;有一个很大的问题: 就是在发送过程中Seq2丢包了;虽然3 4 5都到达了接收端;但是这些包的ACK确认包里面确认号都是2;所以这个时候虽然会进行快速重传,但是不知道重传谁,是只重传2还是重传3 4 5?其实看情况两种都有可能,所以快速重传试一把双刃剑;存在这样的问题。为了解决这个问题;我们引出了SACK技术。
SACK
Selective Acknowledgment 选择性确认
SACK技术是在TCP选项中增加一个SACK字段;里面包含了已经缓存的数据信息;通过接收方向发送方发送;当发送方连续收到多个TCP包里面的确认号相同就触发快速重传;通过SACK字段知道那些数据被缓存了;那些数据是缺失的。所以在快速重传的时候只重传没有缓存的数据就可以了。但是必须双方都得支持SACK这个技术。
D-SACK
Duplicate SACK 重复选择性确认; 用来告诉发送方哪些数据被重复接收。
两种数据重复的情况
一种情况是发送方发送的TCP数据包没有丢被接收方正确处理;但是接收方相应的ACK报文在网络中丢失了。这个时候超过了RTO之后;发送方重传这个数据包;重传的数据包到达接收方之后;接收方相应的ACK包会在选项中SACK字段中携带数据已被缓存的信息;告诉发送方你重传的数据我已经缓存过了;这个是重复的数据。这时发送方就会知道不是他发送的TCP包丢了,是发送方响应的ACK丢了。
另一种情况是发送方向接收方发送的TCP报文没有丢;只是被网络阻塞了。然后当连续收到多个ACK相同的回包时直接触发快速重传机制,重传刚才被阻塞的TCP包。但是在重传后,之前被阻塞的数据包到达了接收方且被正确处理缓存起来。这个时候刚才重传的相同数据的报文到达了接收方;接收方响应D-SACK是刚才被阻塞的数据字节数;告诉发送方这个数据他已经缓存过;现在重传的是重复的。
TCP滑动窗口
为什么需要滑动窗口?
因为在TCP在连接建立后收发数据;不可能是发送一个数据包就需要接收方回一个确认包。这样通信效率非常的低下。所以出现了窗口值;窗口值就是无需等待确认;就可以发送的最大的数据量。窗⼝的实现实际上是操作系统开辟的⼀个缓存空间,发送⽅主机在等到确认应答返回之前,必须在缓冲区中保留已发送的数据。如果按期收到确认应答,此时数据就可以从缓存区清除。这里存在累计确认机制;发送方发送一整个窗口的数据;只需要收到ACK为最后一个数据+1;则就认为这个窗口的数据被正确处理。
窗口值大小由哪一方决定的?
发送方发送TCP报文;报文中会有一个字段window;里面的值就是发送方的窗口值。表示发送方告诉接收方他自己还有多大的缓冲区可以存放数据;让发送方一次发送一窗口的数据,不会导致接收方发送的数据接收方处理不过来。
发送方滑动窗口
发送方在操作系统开辟一个缓存空间;里面存放准备发送的数据。然后会发送一整个发送窗口的数据;这些数据全部发送后;每收到多少个字节的数据;窗口就往后滑动几个字节。之前已经被确认的数据就可以在缓冲区中被清除。
这里需要了解一下程序是怎么表示发送方每个部分的 这里引入两种指针:绝对指针(指特定的序列号)和相对指针(需要做偏移)
SND_WND 表示发送方的发送窗口大小;由接收方接收窗口来指定的。
SND_UNA 绝对指针 ;指向发送过但是还没有收到确认的第一个字节数。
SND_NXT 绝对指针; 指向未发送但总大小还在接收方处理能力的第一个字节
相对指针指向#4;是需要做滑动的部分;是SND_UNA+SND_WND计算出来的
接收方滑动窗口
当接收方收到数据之后;会在自己的缓冲区中缓存发送方发送的一阵个窗口数据。每响应几个字节的ACK之后;接收方的窗口就往后滑动几个字节。前面的数据就可以被应用调用。
接收方滑动窗口每个部分怎么表示的
RCV_WND 表示接收方的接收窗口大小,会通告给发送方来告知自己的处理能力。
RCV_NXT 表示期望发送方发送的下一个包的序列号大小;指向#3的第一个字节。
相对指针;也是指向未收到但超过处理能力的第一个字节;是用来滑动的部分。
接收窗口和发送窗口是相等的吗?
并不是完全相等的;首先发送方的发送窗口是取决于对方的TCP报文的窗口值的;发送方的接收窗口是自己接收缓存数据的能力的;并通过TCP报文通知对方。 这里说的是发送方发送窗口和接收方接收窗口的大小关系。应该说是约等的; 因为接收窗口并不是一成不变的;如果接收方应用读取数据的速度很快;接收方的接收窗口是会逐渐增大的。并且通过之后的TCP报文通知发送方;但是这个是有延迟的。所以说发送方发送窗口和接收方接收窗口的大小是约等于的关系。
TCP流量控制
什么是流量控制?
发送方发送数据不可能无脑的发送;如果一次发送的数据太多接收方处理不过来就会触发重传机制;所以TCP提供一种机制;让发送方根据接收发的实际处理能力去发送数据;这就是TCP的流量控制。
怎么实现双方的流量控制的? 以客户端为接收方;服务器为发送方为例:
1. 客户端向服务端发送请求数据报⽂。这⾥要说明下,本次例⼦是把服务端作为发送⽅,所以没有画出服务端的接收窗⼝。 2. 服务端收到请求报⽂后,发送确认报⽂和 80 字节的数据,于是可⽤窗⼝ Usable 减少为 120 字节,同时SND.NXT 指针也向右偏移 80 字节后,指向 321,这意味着下次发送数据的时候,序列号是 321。 3. 客户端收到 80 字节数据后,于是接收窗⼝往右移动 80 字节, RCV.NXT 也就指向 321,这意味着客户端期望的下⼀个报⽂的序列号是 321,接着发送确认报⽂给服务端。 4. 服务端再次发送了 120 字节数据,于是可⽤窗⼝耗尽为 0,服务端⽆法再继续发送数据。 5. 客户端收到 120 字节的数据后,于是接收窗⼝往右移动 120 字节, RCV.NXT 也就指向 441,接着发送确认报⽂给服务端。 6. 服务端收到对 80 字节数据的确认报⽂后, SND.UNA 指针往右偏移后指向 321,于是可⽤窗⼝ Usable增⼤到 80。 7. 服务端收到对 120 字节数据的确认报⽂后, SND.UNA 指针往右偏移后指向 441,于是可⽤窗⼝ Usable增⼤到 200。 8. 服务端可以继续发送了,于是发送了 160 字节的数据后, SND.NXT 指向 601,于是可⽤窗⼝ Usable 减少到 40。 9. 客户端收到 160 字节后,接收窗⼝往右移动了 160 字节, RCV.NXT 也就是指向了 601,接着发送确认报⽂给服务端。 10. 服务端收到对 160 字节数据的确认报⽂后,发送窗⼝往右移动了 160 字节,于是 SND.UNA 指针偏移了160 后指向 601,可⽤窗⼝ Usable 也就增⼤⾄了 200。
操作系统缓冲区与滑动窗口的关系
我们之前说了窗口其实就是在操作系统内存中开辟一个缓冲区就来接收存放数据。既然是由操作系统开辟的;那么操作系统就可以调整缓冲区的大小;也就是可以调整窗口的大小。在应用调取数据不及时的时候;窗口的大小就会被调整。
我们以客户端为发送方; 服务器为接收方;发送窗口和接收窗口大小为360;我们这里举两个例子来看一下操作系统怎么影响窗口的
例子1: 当接收方服务繁忙;应用读取缓存数据不及时的时候 1. 客户端发送 140 字节数据后,可⽤窗⼝变为 220 (360 - 140)。 2. 服务端收到 140 字节数据,但是服务端⾮常繁忙,应⽤进程只读取了 40 个字节,还有 100 字节占⽤着缓冲。 区,于是接收窗⼝收缩到了 260 (360 - 100),最后发送确认信息时,将窗⼝⼤⼩通告给客户端。 3. 客户端收到确认和窗⼝通告报⽂后,发送窗⼝减少为 260。 4. 客户端发送 180 字节数据,此时可⽤窗⼝减少到 80。 5. 服务端收到 180 字节数据,但是应⽤程序没有读取任何数据,这 180 字节直接就留在了缓冲区,于是接收窗⼝收缩到了 80 (260 - 180),并在发送确认信息时,通过窗⼝⼤⼩给客户端。 6. 客户端收到确认和窗⼝通告报⽂后,发送窗⼝减少为 80。 7. 客户端发送 80 字节数据后,可⽤窗⼝耗尽。 8. 服务端收到 80 字节数据,但是应⽤程序依然没有读取任何数据,这 80 字节留在了缓冲区,于是接收窗⼝收缩到了 0,并在发送确认信息时,通过窗⼝⼤⼩给客户端。 9. 客户端收到确认和窗⼝通告报⽂后,发送窗⼝减少为 0。
例子2: 当服务器系统资源紧张;而且服务繁忙应用读取缓存数据不及时操作系统对窗口的影响。 1. 客户端发送 140 字节的数据,于是可⽤窗⼝减少到了 220。 2. 服务端因为现在⾮常的繁忙,操作系统于是就把接收缓存减少了 120 字节,当收到 140 字节数据后,⼜因为应⽤程序没有读取任何数据,所以 140 字节留在了缓冲区中,于是接收窗⼝⼤⼩从 360 收缩成了 100,最后发送确认信息时,通告窗⼝⼤⼩给对⽅。 3. 此时客户端因为还没有收到服务端的通告窗⼝报⽂,所以不知道此时接收窗⼝收缩成了 100,客户端只会看⾃⼰的可⽤窗⼝还有 220,所以客户端就发送了 180 字节数据,于是可⽤窗⼝减少到 40。 4. 服务端收到了 180 字节数据时,发现数据⼤⼩超过了接收窗⼝的⼤⼩,于是就把数据包丢失了。 5. 客户端收到第 2 步时,服务端发送的确认报⽂和通告窗⼝报⽂,尝试减少发送窗⼝到 100,把窗⼝的右端向左收缩了 80,此时可⽤窗⼝的⼤⼩就会出现诡异的负值。 所以,如果发⽣了先减少缓存,再收缩窗⼝,就会出现丢包的现象。 为了防⽌这种情况发⽣,TCP 规定是不允许同时减少缓存⼜收缩窗⼝的,⽽是采⽤先收缩窗⼝,过段时间再减少缓存,这样就可以避免了丢包情况。
窗口关闭;存在可能导致的问题:死锁现象
当接收方应用调取资源过慢;接收窗口变为0;通告发送方让发送方窗口变为0。发送窗口为0时;窗口关闭,不在发送数据。过段时间之后;服务器应用调取资源结束窗口增大,接收方发送一个非0窗口; 这个时候非0窗口的通告在网络中被阻塞了或者丢失了;那接收方就会一直等待发送数据。这种一直等待的过程就叫做窗口死锁现象。
TCP用什么机制解决死锁现象?
TCP中发送方接收到0窗口;窗口关闭后。会立即启动持续定时器,持续定时器过时会发送窗口探测报文(一般接收方发送三次窗口探测报文,每一次探测报文持续30-60秒,要是三次探测报文收到的响应都是0窗口;则发送方直接发送RST断开连接);对方在收到窗口探测报文之后;将自己接收窗口通告给对方。 如果对方通告的接收窗口仍然为0;则从接收到报文开始重新开启持续计时器。 如果对方通告的接收窗口非0,则直接打破死锁现象。
什么是糊涂窗口综合征?
我们知道在接收方系统繁忙;应用无法及时调用缓存的数据;接收方的窗口值就会逐渐减小;如果这个时候接收方只有几字节大小的窗口;并通告给发送方;发送方也会将自己的发送窗口缩小为几字节;然后发送几字节的数据,但我们知道IP+TCP头部最起码都40字节;用40字节的包头去封装几字节的数据;这明显是糊涂的;所以称这种现象为糊涂窗口综合征。 也就是接收方通告小窗口;发送方发送小数据。
怎么解决糊涂窗口综合征??
1 :让接收方不通告小窗口 就是当接收方窗口值小于MSS和缓存/2 中的最小值时;就向发送方通告窗口大小为0的包;然后接收方关闭发送窗口。 当接收方应用将缓存数据处理后;当接收方窗口大于MSS或者接收方缓存空间有一半可以使用时;就可以向发送方通告非0窗口;让发送方打开窗口。
2: 让发送方不发送小数据 使⽤ Nagle 算法,该算法的思路是延时处理,它满⾜以下两个条件中的⼀条才可以发送数据:要等到窗⼝⼤⼩ >= MSS 或是 数据⼤⼩ >= MSS 或者 收到之前发送数据的 ack 回包;只要没满⾜上⾯条件中的⼀条,发送⽅⼀直在囤积数据,直到满⾜上⾯的发送条件。 另外,Nagle 算法默认是打开的,如果对于⼀些需要⼩数据包交互的场景的程序,⽐如,telnet 或 ssh 这样的交互性⽐较强的程序,则需要关闭 Nagle 算法。 可以在 Socket 设置 TCP_NODELAY 选项来关闭这个算法(关闭 Nagle 算法没有全局参数,需要根据每个应⽤⾃⼰的特点来关闭)
TCP拥塞控制
为什么需要拥塞控制 ?不是已经有流量控制了吗?
首先TCP流量控制是提高TCP通信效率的同时避免发送方一次发送太大窗口的数据把接收方的缓冲区填满。 但是我们说TCP是可靠的;所以我们不得不考虑网络是否发生阻塞。如果网络拥塞;这个时候我们一窗口一窗口的发送数据就会导致数据丢包;然后触发超时重传;网络拥塞情况越来越严重。所以我们把TCP设计成无私的传输协议;在网络发生拥塞时,减少我们传输数据的大小。 所以说;拥塞控制也是TCP保证可靠性的机制。
什么是拥塞窗口? 和发送窗口有什么关系?
在网络发生拥塞时;TCP要减少自己的数据发送量。但是我们前面在滑动窗口提到,发送窗口约等于接收窗口;这时实现流量控制。 但是我们如果要保证足够的可靠性;我们不仅要进行流量控制;我们也要进行网络拥塞控制。 所以我们定义了拥塞窗口;拥塞窗口是根据网络情况来动态变化的,网络情况好就变大,网络发生拥塞就变小。 但拥塞窗口和发送窗口之间有什么关系呢? 其实发送窗口 =min(CWND拥塞窗口,SWND发送窗口)之间的最小值。
如何知道网络发生拥塞?
其实很简单;只要发送方在RTO时间内没有接受到发送出去的数据包的正确ACK确认包就认为网络发生拥塞。
拥塞控制有哪些算法?
慢启动
顾名思义,慢启动就是慢慢启动。核心思想:CWND从初始化之后发送方发送数据包开始;每收到一个ACK拥塞窗口就增加1。从右边的举例就可以看出,我们假设从开始的时候CWND大小为1;就说明可以发送一个MSS,发送了一个数据之后,收到一个ACK,CWND变为2;然后又发送两个数据,又收到两个ACK;以此类推,可以看出CWND以指数的形式增长。
那CWND增长到什么时候是个头呢?
有⼀个叫慢启动⻔限 ssthresh (slow start threshold)状态变量。大小一般为65535字节。
当CWND逐渐增长,大小增长到ssthresh之前,用慢启动来控制拥塞; 当增长到ssthresh之后,就启动拥塞避免算法。
拥塞避免
当CWND超过初始ssthresh之后;就进入拥塞避免算法。CWND每收到一个ACK增加1/CWND。 这个时候从慢启动的指数增长变为线性增长。这个时候也是一直增长;只不过增长速度没有那么快了。当这样一直增长;直到窗口足够大,出现网络阻塞,开始出现丢包,触发TCP的重传机制,则会进入拥塞发生算法。 举例:现在假定初始ssthresh为8。
拥塞发生
我们知道重传机制分为两类,不同的重传机制有着不同的拥塞发生算法
超时重传
ssthresh变为CWND的一半,CWND变为1。从此时重新开始慢启动。
快速重传
当接收⽅发现丢了⼀个中间包的时候,发送三次前⼀个包的ACK,于是发送端就会快速地重传,不必等待超时再重传。TCP 认为这种情况不严重,因为大部分没丢,只丢了⼀⼩部分,则 ssthresh 和 cwnd 变化如下: cwnd = cwnd/2 ,也就是设置为原来的⼀半; ssthresh = cwnd ; 进⼊快速恢复算法
快速恢复
我们之前说到CWND一直增大,直到网络发生拥塞,然后发送方收到三个ACK包且确认号都相同,触发快速重传。然后进入快速恢复。
正如前⾯所说,进⼊快速恢复之前, cwnd 和 ssthresh 已被更新了: cwnd = cwnd/2 ,也就是设置为原来的⼀半; ssthresh = cwnd ; 然后,进⼊快速恢复算法如下: 拥塞窗⼝ cwnd = ssthresh + 3 ( 3 的意思是确认有 3 个数据包被收到了); 重传丢失的数据包; 如果再收到重复的 ACK,那么 cwnd 增加 1; 如果收到新数据的 ACK 后,把 cwnd 设置为第⼀步中的 ssthresh 的值,原因是该 ACK 确认了新的数据,说明从 duplicated ACK 时的数据都已收到,该恢复过程已经结束,可以回到恢复之前的状态了,也即再次进⼊拥塞避免状态;
拥塞控制过程图总浏览