TCP协议梳理
最后更新于
最后更新于
在网络的五层模型中,由下至上分别为:实体层,链接层,网络层,传输层,应用层.
实体层传输0,1构成的电信号
链接层 该层有PPP(点对点协议),Ethernet(以太网)等协议
Ethernet 协议规定一组电信号构成一个数据包,叫做帧(Frame),每一帧分为head和data,head里面包含了发送方mac地址,接收方mac地址等内容
head
固定18字节
data
最小长度46字节
最大长度1500字节
长度称为MTU(Maximum Transmission Unit,最大传输单元)典型值为1500字节.
如果帧长度大于1518字节,需要分成多个帧进行传输,
网络层使用IP协议 包括ipv4协议和ipv6协议
IP协议的数据包叫包(Packet),每个包分为head和data,head里面包含了发送方ip地址,接受方IP地址等内容,如果一个包的长度大于MTU,就需要分成多个帧发送.
ipv4 head
最小长度 20字节
最大长度 60字节
典型值 20字节
ipv4包最大长度 65,535字节
ipv6 head
固定40字节
可通过扩展头部扩展
传输层经常使用tcp协议和udp协议,tcp协议是面向连接的,udp协议是面向无连接的
tcp协议的数据包叫做段(Segment),每个段分为head和data,head里面包含发送方端口和接收方端口等内容,
head
最小长度 20字节
最大长度 60字节
典型值 20字节
data
长度称为MSS,MSS=MTU(Maximum Segment Size)-IP头长度-TCP头长度,典型值为1460
udp协议的数据包叫做数据报(Datagram),每个数据报分为head和data,head里面包含发送方端口和接收方端口等内容
head
固定为8字节
总长度不超过65535字节
应用层协议也分为head和data两部分组成。放在udp或tcp的数据部分,如http,smtp,ftp等
tcp协议的特点相对传输层的另一个协议udp,有如下特点
可靠性
提供可靠的服务。通过序列号、确认应答、重传机制、以及数据完整性检验等技术,确保数据正确且按序到达接收方。
连接性
tcp是面向连接的协议。在数据传输前,必须建立连接(三次握手过程)。数据传输完成后,需要断开连接(四次挥手过程)。
传输效率
由于需要建立连接、确认数据、进行流量控制和拥塞控制等,其头部开销较大(最小20字节),并且在网络条件不佳时重传机制可能导致更高的延迟。
对于需要实时性和不需要很高的可靠性的应用场景,一般避免使用tcp,如流媒体,游戏等
tcp是全双工协议,全双工与半双工,单工通信相对
全双工通信允许数据在两个方向上同时传输
半双工通信允许数据在两个方向上交替传输,但不可同时进行
单工通信则只允许数据在一个方向上传输,没有反向交互的可能。
客户端和服务端都有一个socket套接字,双方都可以通过各自的套接字进行发送和接收消息,socket里面维护了两个缓冲区,一个发送缓冲区,一个接收缓冲区。
发送缓冲区则用于存储待发送的数据。当应用程序调用send()或write()时,数据首先被放入用户空间,然后复制到内核的发送缓冲区中。这个过程确保了数据按照最大报文段长度(MSS)进行分片,并在确认收到ACK之前不会被移除。如果发送缓冲区满了,新的发送请求可能会被阻塞,直到有足够的空间可用
发送缓冲区如图所示
接收缓冲区用于存储从网络接收到的数据包。每当服务器的网卡接收到一个TCP数据包时,该数据包会被复制到内核缓冲区,并最终存放在接收缓冲区中。接收缓冲区的大小取决于应用程序的处理能力和配置参数。如果接收缓冲区满了,而应用程序又没有及时读取数据,这可能导致数据包丢失,并且通过流控通知发送端停止发送
接收缓冲区如图所示
如接收到了1-50,51-100,201-250 3个段,那么连续读取段的最后一个位置是100,接受到的段的最后一个位置是250
tcp_rmem和tcp_wmem
目的是为了防止已经失效的握手请求发送到服务端
假设client发出握手请求a,请求a在某个网络节点中长时间滞留,以致延迟到该连接已经被client释放(如client的connect方法设置了200ms的超时时间,请求a的滞留时间超过200ms)才到达server
假设只有两步握手,server向client发送一个ACK,连接建立,server等待client发送数据,但是client并没有建立新连接,会忽略server的确认,server会一直等待client发送数据,直到超时,造成资源浪费.
采用三次握手,server向client发送一个SYN-ACK,然后等待client回应ACK,因为client不会回应ACK,server就会知道client并没有要求建立连接,
TCP Fast Open (TFO) 是一种旨在加速 TCP 连接建立的协议扩展,TCP 连接需要进行三次握手方可开始传输数据。这使得每个 TCP 连接都需要浪费掉一个 RTT 去建立连接。
客户端发送SYN数据包,该数据包包含Fast Open选项,且该选项的Cookie为空,这表明客户端请求Fast Open Cookie
支持TCP Fast Open的服务器生成Cookie,并将其置于SYN-ACK数据包中的Fast Open选项以发回客户端
客户端收到SYN-ACK后,缓存Fast Open选项中的Cookie。
客户端发送SYN数据包,该数据包包含数据,且该数据包的Fast Open选项包含之前缓存的Cookie
服务端收到Cookie进行校验,如果Cookie有效,服务器将在SYN-ACK数据包中对SYN和数据进行确认,确认中也可以携带数据,,如果无效,服务端将丢弃数据包中包含的数据,仅ACK对应的序列号
客户端将发送ACK确认服务器发回的SYN以及数据
由于这是一项对 TCP 协议的扩展,中间网络设备有可能不支持该特性。比方说有些防火墙(NAT)会直接将带有数据段的 SYN 包认为是非法数据包直接抛弃。导致使用了 TFO 的连接完全无法建立。(如中国的移动数据网络)
为此,操作系统不得不引入一个 blackhole 机制,当对某 IP 以 TFO 进行握手时,如果在一定时间内都没有收到回应,那么就重新尝试以非 TFO 方式进行握手,如果成功,则将该 IP 加入黑名单,之后不再以 TFO 进行握手。
但该机制有两个问题:
可能因为网络正常波动丢包而误判,导致 IP 进入黑名单,后续连接全部丧失 TFO 特性。
有的时候是在特定网络下 TFO 无效(如数据网络),但操作系统的黑名单只记录了目标 IP,所以在切换网络后,依然无法使用 TFO。
SACK在RFC 2018 中定义,SACK可以提高在网络条件不理想时的数据传输效率,允许接收方确认它成功收到不连续的块,以及基础TCP确认的成功收到最后连续字节序号
SACK位于Options选项中
假设发送方发送了序列号为 1-10、11-20、21-30、31-40 和 41-50 的五个数据包。接收方成功接收了序列号为 1-10、21-30 和 41-50 的数据包,但序列号为 11-20 和 31-40 的数据包丢失了。
在传统的 TCP 中,接收方只能发送 ACK 11(表示期待接收序列号为 11 的数据包),发送方需要重新发送序列号为 11 之后所有的数据包。
使用 SACK 时,接收方可以发送一个 ACK 11,但同时在 SACK 选项中指示已经接收到序列号为 21-30 和 41-50 的数据段。发送方收到这个信息后,只需要重传序列号为 11-20 和 31-40 的数据包。
如果中间有数据丢失,即便后面的数据已经接收到,没有SACK机制时,需要重传丢失的数据之后的所有数据,在SACK机制下发送方可以只重传那些未被确认接收的数据块。
在 Linux下,可以通过tcp_sack参数打开SACK功能(Linux 2.4后默认打开)
SACK 的开启过程在 TCP 的三次握手中完成:
SYN 包:在建立连接的初始阶段,客户端发送一个带有 SYN 标志的 TCP 段。在这个段的 TCP 头部选项字段中,客户端可以包含一个 SACK选项,表明它支持 SACK 机制。
SYN-ACK 包:服务器在收到客户端的 SYN 请求后,如果也支持 SACK,则在返回的 SYN-ACK 响应中同样包含 SACK选项,确认双方都支持 SACK 功能。
ACK 包:客户端发送确认包(ACK)给服务器,完成三次握手。此时,如果双方都在 SYN 和 SYN-ACK 包中声明了对 SACK 的支持,那么 SACK 机制即被启用。
D-SACK是SACK的一个扩展,定义在RFC 2883,用来通知发送方收到了哪些重复数据包
如果SACK的第一个段的范围被ACK所覆盖,那么就是D-SACK;
如果SACK的第一个段的范围被SACK的第二个段覆盖,那么就是D-SACK
ACK丢包
sender-> 1-1000 ->reciever -> ACK 1001(丢失) ->sender
sender-> 1001-2000 ->reciever-> ACK 2001(丢失) ->sender
sender-> 1-1000(超时重传) ->reciever-> ACK 2001,SACK 1-1000 ->sender
reordering
如果接收到的数据段不是按照顺序的,即存在一个跳跃(例如,期望的是序号为 51 的数据段,但收到了序号为 101 的数据段),接收方会重复发送最后一个连续接收的数据段的 ACK(51)。发送方收到这样的重复 ACK 可能会认为在此之后的数据段已丢失。
sender-> 1-50 ->reciever -> ACK 51 ->sender
sender-> 51-100(延时) ->reciever
sender-> 101-150 ->reciever -> ACK 51,SACK 101-150 ->sender
sender-> 151-200 ->reciever -> ACK 51,SACK 101-200 ->sender
sender-> 201-250 ->reciever -> ACK 51,SACK 101-250 ->sender
触发快速重传 sender-> 51-100 ->reciever -> ACK 251 ->sender
延时收到 sender-> 51-100 ->reciever -> ACK 251,SACK 51-100 ->sender
TCP 发送方可能会将乱序到达的数据段误解为丢失的数据段。如果发生这种情况,TCP 发送方将重传乱序数据包之前的数据段,并减慢该连接的数据传输速率。通过引入了 D-SACK 选项,发送方知道其初始的丢包判断是错误的,并且可以安全地恢复到之前较高的数据传输速率,而不需要维持基于误判的低速率传输
D-SACK告诉发送端两种可能情况:
收到了重复的数据,数据包没有丢,丢的是ACK包
快速重传算法触发了重传,既不是发出去的包丢了,也不是因为回应的ACK包丢了,而是因为网络延时导致的reordering(网络上出现了先发的包后到的情况)。
当发送方收到三个连续的重复ACK时,它将立即重传未被确认的数据段,而不需要等待数据段的重传计时器超时。
发送方实际需要收到4次重复ACK,第一次ACK为正常的响应ACK,后三次为重复ACK
在超时重传的情况下,如果一个数据段丢失,可能需要等待较长时间(例如,几秒钟)才能重传。快速重传机制允许在较短的时间内(例如,几毫秒)重传丢失的数据段,从而减少延迟并提高数据传输的效率。
假设发送方发送了序列号为 1-10、11-20、21-30、31-40,41-50 共五个数据段。接收方成功接收了序列号为 1-10,21-30,31-40,41-50的数据段,11-20的数据在网络中丢失,接收方在分别收到21-30,31-40,41-50的数据,会重复发送ACK 11,发送方收到三个连续的重复ACK 11,会立即重传11-20的数据
在上方的例子中,如果没有SACK,发送方不知道应该重传11-20,还是后续所有的数据,使用SACK后,发送方知道只有11-20的数据丢失,只重传11-20的数据
RTT: 往返时间,表示一个数据包从发送端到接收端,再返回发送端的时间
RTO: 重传超时时间,在重传数据包之前等待ACK的时间
如果RTO过小,可能导致数据包被误判为丢失,需要频繁重传,导致网络拥塞
如果RTO过大,重发会慢,效率低
因为网络状况是动态变化的,所以RTT和RTO也需要动态变化
定义在RFC793中,计算过程如下:
记录最近几次的RTT值
计算平滑RTT(SRTT): SRTT = α * SRTT + (1 - α) * RTT
其中α是平滑因子,通常取值为0.8或0.9
计算RTO: RTO = min(ubound, max(lbound, β * SRTT))
其中β是放大因子,通常取值为1.3到2.0之间,lbound和ubound是RTO的下限和上限
假设上次SRTT为150ms,最新RTT为200ms
计算SRTT: SRTT = 0.8 * 150ms + 0.2 * 200ms = 160ms
计算RTO: RTO = min(ubound, max(lbound, β * SRTT))
假设ubound为300ms,lbound为100ms,β为1.5
RTO = min(300ms, max(100ms, 1.5 * 160ms)) = 240ms
如果发生了重传, 如何计算RTT
如果第一次ACK丢失,使用t3-t1,偏大
如果第一次ACK是滞留,使用t3-t2,偏小
定义在RFC6289中,是当前TCP协议使用的算法,最大特点是使用一个单独的变量DevRTT计算平滑RTT和RTT的偏差,并使用该变量计算RTO,避免了RTT突然有了一个大幅度波动时,被加权移动平均算法拉低
计算平滑RTT(SRTT): SRTT = SRTT + α (RTT – SRTT)
计算RTT偏差(DevRTT): DevRTT = (1 - β) * DevRTT + β * |SRTT - RTT|
计算RTO: RTO = µ * SRTT + ∂ *DevRTT
一般取值 α=0.125,β=0.25,µ=1,∂=4,各项因子都是经验值
假设上次SRTT为150ms,最新RTT为200ms,上次DevRTT为50ms
计算SRTT: SRTT = 150ms + 0.125 * (200ms - 150ms) = 156.25ms
计算DevRTT: DevRTT = (1 - 0.25) * 50ms + 0.25 * |156.25ms - 200ms| = 37.5ms + 11.25ms = 48.75ms
计算RTO: RTO = 1 * 156.25ms + 4 * 48.75ms = 348.75ms
流量控制用来控制发送方发送数据的速度,使得接收方有足够的时间处理接收到的数据
"窗口"是指发送方可以发送但未被ACK的数据量,窗口大小是动态变化的由接收方告知发送方,通过上述接收和发送缓冲区里面的参数可以得到窗口大小,窗口大小 = 接受缓冲区最大值-接受到的段的最后一个位置-1
每当发送方收到一个ACK,它就会将窗口向前滑动,即将已ack的数据包从窗口中移除,并可以发送新的数据包。
当接收方处理数据缓慢,最后导致接收窗口为0,接收方会在ack中告知发送方接收窗口为0,发送方会停止发送数据,并开启一个定时器(persist timer), 避免因随后的修改接收窗口的数据包丢失使连接的双侧进入死锁,当定时器到期时,发送方会发送一个ZWP包,期待接收方回复一个带着新的接收窗口大小的ack包,一般重试3次,如果仍然为0,发送方一般会发送RST,断开连接
如果接收方在接收窗口满后每次都只能处理一小部分数据,它会ack一系列小的窗口。这被称作愚蠢窗口综合症,因为它在TCP的数据包中发送很少的一些字节,相对于TCP包头是很大的开销。解决这个问题,就要避免对小的window size做出响应,直到有足够大的window size再响应
MSS的典型值为1460字节,如果每次只发送一小部分数据,比如几个字节,相当于一辆客车只运送几个乘客,效率很低,所以需要避免这种情况
接收端使用David D Clark算法:如果收到数据后导致窗口大小小于某个值,直接ack关闭窗口,阻止发送方继续发送数据,等到下面两个条件满足一个
窗口大小>=MSS
接收缓冲区一半为空
发送端使用Nagle算法:下面两个条件满足一个
窗口大小 >= MSS 并且 待发送数据长度 >= MSS
等待时间超过200ms
慢启动是指TCP连接开始时,发送方逐渐增加发送数据量,以观察网络的拥塞情况,避免一开始就发送大量数据导致网络拥塞
当TCP连接开始时,拥塞窗口初始化为一个较小的值,通常是2个MSS的大小
每当收到一个ACK,拥塞窗口就增加一个MSS的大小,这意味着每个往返时间(RTT)内,拥塞窗口大小都会翻倍。这种增长方式是指数的
即 收到ACK时,cwnd = cwnd + 1, 每过一个RTT,cwnd = cwnd * 2
例如:
初始时,cwnd = 1 MSS
第一个RTT后,收到1个ACK,cwnd 变为 2 MSS
第二个RTT后,收到2个ACK,cwnd 变为 4 MSS
第三个RTT后,收到4个ACK,cwnd 变为 8 MSS
以此类推,cwnd 在每个RTT后都会翻倍
为了防止拥塞窗口无限制地增长,TCP还设置了一个慢启动阈值(ssthresh)。通常情况下慢启动阈值的初始值为65535字节,当拥塞窗口的大小增加到这个阈值时,TCP将从慢启动模式切换到拥塞避免模式。
TCP Reno快速恢复算法是在快速重传之后执行的。此算法的目的是减少因为数据包丢失而可能引起的拥塞窗口的剧烈下降,从而避免TCP连接进入慢启动阶段。
在收到快速重传的3个重复ACK后时,在快速重传阶段,cwnd和sshthresh已更新为
cwnd = cwnd /2
sshthresh = cwnd
然后快速恢复算法定义如下:
cwnd = ssthresh + 3 * MSS(3倍MSS对应于收到的三个重复ACK)
收到重复ACK时,cwnd = cwnd + 1
收到新的ACK时,cwnd = ssthrethresh,然后进入拥塞避免
例如:
假设一个 TCP 连接的 cwnd = 12 MSS,ssthresh = 24 MSS。突然之间,发送方收到了三个针对同一个数据包的重复 ACKs。
检测到丢包,触发快速重传,不等待超时。
进入快速恢复,ssthresh 和cwnd都被设置为当前 cwnd 的一半,即 12 MSS / 2 = 6 MSS。cwnd 调整为 ssthresh + 3 = 6 + 3 = 9 MSS。
重传丢失的数据包。
假设在快速恢复期间收到了2个额外的重复 ACK,每收到一个重复 ACK,cwnd 增加1 MSS,因此 cwnd 从9增加到11 MSS。
最后,当发送方收到一个新的 ACK 时,这表明丢失的数据包和后续的一些数据包已经被成功接收。此时,将 cwnd 重置为 ssthresh 的值,即 6 MSS,并结束快速恢复,继续拥塞避免。
TCP BIC(Binary Increase Congestion control)和 TCP CUBIC 是两种较新的TCP拥塞控制算法,主要用于高带宽和高延迟的网络环境(例如宽带互联网连接),这种网络环境通常被称为长肥网络(Long Fat Networks, LFNs)。这两种算法都是为了解决传统TCP拥塞控制算法在这些网络环境下性能不足的问题
这类网络通常在带宽资源充足但信号传输时间较长的场景中出现,例如跨洲或卫星通信。在这种类型的网络中,数据可以以高速率传输,但数据从发送端到接收端的总传输时间(往返时间,RTT)相对较长。
大带宽*延迟积(Bandwidth-Delay Product, BDP) BDP 是网络带宽和往返时间的乘积,用来估算在单个往返时间内能够“在途”的最大数据量。在长肥网络中,这个值通常很大,意味着网络能够在任何时候容纳大量未确认的数据
传统的TCP拥塞控制机制(如TCP Reno)在长肥网络中表现不佳,这些算法通常依赖于丢包作为网络拥塞的信号,因为在高延迟环境中,响应丢包的速度可能不够快,导致带宽未被充分利用。
假设一个跨大西洋的光纤连接,其带宽非常高(例如10 Gbps),而RTT可能高达100ms或更多。在这种情况下,如果使用TCP Reno,网络中的数据包丢失会导致整个窗口大小减半,并且慢启动重新开始。由于高延迟,恢复到最大窗口大小需要更多时间,期间网络带宽被严重浪费。
由于传统算法在遇到丢包时会将拥塞窗口剧烈减半,这导致带宽利用率在遭遇网络波动后不能迅速恢复到最优状态,从而无法充分利用可用的带宽,这种算法的窗口增长策略在逐渐接近网络容量时也过于保守,增长速度慢,导致带宽未被充分利用。
核心思想是在窗口增长阶段使用二分查找的方法来快速逼近最佳的窗口大小。具体来说,TCP BIC在检测到网络拥塞(通常是通过丢包发现)并减小窗口后,会在后续的恢复阶段采用二分法迅速调整窗口大小,以便尽快找到当前网络状态下的最佳窗口值。
窗口减少:当检测到丢包时,TCP BIC 将当前窗口设置为最大窗口值(Wmax),并且将当前窗口值降低β倍(0.2),作为最小窗口值(Wmin)
二分增长:在减小之后,TCP BIC不是简单地线性增加窗口,而是在Wmax和Wmin之间使用一种二分查找策略(快速逼近最佳窗口大小)。这种方法可以使窗口在发生拥塞后更快地恢复,特别适用于大带宽*延迟积(BDP)的网络。
稳定期:当窗口大小接近网络容量时,TCP BIC会细微调整其窗口增长速度,以避免过度波动和进一步的丢包。
假设带宽: 1 Gbps
往返时间(RTT): 100 ms
初始拥塞窗口大小: 1000 个数据包
丢包发生时,窗口大小: 1000 包
立即将窗口减半: 500 包
进入拥塞避免阶段,每个RTT增加1个MSS(最大报文段长度,假设为1460字节)
恢复到原来的窗口大小需要的时间:
(1000 - 500) RTT = 500
500 * 100ms = 50秒
丢包发生时,窗口大小: 1000 包 (Wmax)
将窗口减小到Wmin: 1000 * 0.8 = 800 包
使用二分搜索快速探测:
第1个RTT: 尝试 (1000 + 800) / 2 = 900 包
第2个RTT: 如果成功,尝试 (1000 + 900) / 2 = 950 包
第3个RTT: 如果成功,尝试 (1000 + 950) / 2 = 975 包
第4个RTT: 如果成功,尝试 (1000 + 975) / 2 = 987 包
假设在第4次RTT后找到合适的窗口大小 总恢复时间: 4 RTT = 4 * 100ms = 0.4秒
快速收敛到Wmax在长RTT的情况下,是比较适合的,但是在短RTT的劣网环境下,数据包往返速度快,窗口增长也更快。激进的增窗会引发带宽的争抢,使得拥塞控制不具备公平性
更激进的算法可能会获得不成比例的高带宽份额,违背了TCP拥塞控制的公平性原则。
TCP CUBIC实际上是一种改进的BIC。BIC在短RTT下增窗快,本质是因为其算法依赖RTT。CUBIC的实现方式整体和BIC一样,只不过增窗不再依赖RTT, 是一种基于丢包的拥塞控制算法,它使用一个三次函数来增加和减少拥塞窗口的大小。Cubic 在 Linux 内核 2.6.19 之后被广泛使用,不会在短RTT场景下再有快速增窗的问题了
TCP BBRv3 是由 Google 开发的最新一代拥塞控制算法,它是 BBR 算法的第三个版本。BBRv3 的核心思想是通过测量网络的带宽和往返传播时间(RTT)来控制数据的发送速率,而不是依赖于丢包等传统的拥塞信号。
BBRv3 使用一个基于模型的方法来动态探测和模拟网络路径,主要关注两个参数:瓶颈带宽(网络路径中的最大带宽)和最小RTT
BBRv3 使用带宽、RTT、ECN(显式拥塞通知)和丢包等信号来调整发送速率。
终止连接使用4次挥手,连接的每一端都可以独立的终止,当一端想要终止时,向对侧发送FIN,对侧回复ACK,因此完整的连接终止,需要一对FIN/ACK,分别由两侧端点发出
连接可以工作在TCP半开状态。即一侧关闭了连接,不再发送数据;但另一侧没有关闭连接,仍可以发送数据。已关闭的一侧仍然应接收数据,直至对侧也关闭了连接。
断开过程状态变化所示
确保连接可靠关闭
在TCP连接的终止阶段,双方需要交换FIN和ACK。TIME_WAIT状态确保了最后一个ACK消息被对方成功接收。如果这个ACK消息在网络中丢失,对方将重发FIN消息。处于TIME_WAIT状态的端点仍然可以接收并处理这个重发的FIN消息,并再次发送ACK,确保连接可靠地关闭。
确保老的数据包不会被新连接接收
网络中可能仍然存在延迟的数据包,这些数据包属于刚刚关闭的连接。TIME_WAIT状态持续的时间(通常是MSL的两倍,约为2分钟)足以让这些老的数据包在网络中“死亡”,避免它们被错误地交付到新的同一端口和IP地址组合的连接中。
MSL(Maximum Segment Lifetime) 是指数据段在网络中的最大生存时间,RFC793定义了MSL为2分钟,Linux设置成了30s,参数tcp_max_tw_buckets控制并发的TIME_WAIT的数量,详解见下方参数汇总
例如
客户端 A 与服务器 S 建立了一个 TCP 连接,使用端口对 (IP_A:1234, IP_S:80)。
这个连接传输完数据后开始关闭过程。客户端 A 发送 FIN 包,进入 FIN_WAIT_1 状态。
服务器 S 回应 ACK,进入 CLOSE_WAIT 状态
服务器 S 发送 FIN 包,进入 LAST_ACK 状态。
客户端 A 回应最后的 ACK,进入 TIME_WAIT 状态。
如果没有TIME_WAIT,客户端 A 又立即用相同的端口 1234 与服务器 S 的 80 端口建立新连接。
新连接可能会立即使用相同的端口对 (IP_A:1234, IP_S:80)
如果网络中还存在旧连接的延迟数据包(比如因为网络拥塞或路由变化导致的延迟包),这些包可能会被错误地传递到新连接中
新连接可能会收到不属于它的数据,导致数据混乱或安全问题
如果有了TIME_WAIT,客户端 A 在TIME_WAIT状态结束前无法使用相同的端口 1234 与服务器 S 的 80 端口建立新连接
"粘包"指的是如客户端发送了n次小的数据包,TCP将其合并到了一个TCP段中发送,这就是粘包,"粘包"是应用层协议需要解决的问题
TCP 是一种流协议,没有消息边界,只保证数据可以可靠的,按序到达.
解决办法:
消息定界符
每个消息都有固定长度
消息起始位置增加固定的长度字段,用来表示消息长度
序列化数据,如json协议
http协议是如何处理粘包的
1.协议格式和内容长度指示
HTTP协议有明确的格式,包括请求行/状态行、头部字段和消息体。头部与消息体之间有空行分隔,这种结构化的格式也有助于区分不同的消息。
HTTP响应头中包含 Content-Length 字段,明确指出消息体的长度。例如:
接收方可以根据 Content-Length 的值准确知道应该读取多少字节的数据。
分块传输编码:
允许服务器在不知道整个响应内容大小的情况下开始发送响应。这种方式特别适用于响应体的大小未知或动态生成的内容,如大文件的传输或实时数据流。
每个数据块都以其大小(以十六进制表示)开始,后跟一个回车换行(CRLF),然后是数据本身和另一个 CRLF。传输结束时,发送一个大小为0的块,表示没有更多的数据块。
分块传输编码的响应不能使用 Content-Length
头,因为响应的总长度是未知的。
分块传输在 HTTP/2 中被废弃,因为 HTTP/2 采用了不同的机制来处理相同的问题,即帧的概念。
\r\n\r
表明消息头结束
7\r
:表明接下来的块大小是7字节("Hello, " 的长度)。
Hello, \r
:块内容,紧跟一个 CRLF。
6\r
:表明下一个块的大小是6字节("World!" 的长度)。
World!\r
:第二块的内容,同样跟一个 CRLF。
0\r
:表明后面没有更多的块,块大小为0。
\r
:最后一个 CRLF 作为结束标志。
MPTCP允许在一个TCP连接中同时使用多个网络路径进行数据传输。这种设计使得数据可以通过多个子流(subflow)并行发送,从而提高带宽利用率和传输速度
MPTCP通过在TCP连接的初始握手中交换能力选项来确定双方是否支持MPTCP。一旦建立连接,MPTCP可以创建多个子流,每个子流通过不同的网络路径传输数据。这些子流可以使用不同的IP地址和端口号来标识
带宽利用率提升
MPTCP能够同时利用多条网络路径,从而将数据流量分散到不同的路径上,显著提高整体带宽利用率.
提高传输速度
通过并行传输,MPTCP在高延迟或高丢包率的网络环境中能够提供更快的数据传输速度.
增强可靠性
MPTCP在某一条路径发生故障时,能够自动切换到其他可用路径,确保数据的持续传输,提高了系统的鲁棒性.
负载均衡
MPTCP能够根据网络条件动态调整数据流向,以实现负载均衡,从而优化网络性能.
优化移动性
在移动设备环境中,MPTCP可以平滑地进行网络切换,减少因环境变化导致的连接中断.
Linux 内核版本 5.6 中开始支持多路径 TCP (MPTCP)
Apple 于 2013 年开始支持多路径 TCP (MPTCP,在 iOS 11 中,Apple 通过公共 API 开放了 MPTCP 以供更广泛地使用,到 2021 年 9 月,Apple 已开始支持基于 IPv6 的 MPTCPv1,到 2021 年 10 月,它还扩展了对基于 IPv4 的 MPTCPv1 的支持
golang 1.21开始内置对MPTCP的支持
SourcePort: 16 bits
源端口
Destination Port: 16 bits
目标端口
Sequence Number: 32 bits
序列号
Acknowledgment Number: 32 bits
确认号
Data Offset: 4 bits
表示 TCP头部的长度,数据偏移字段的单位是32位字(4字节)。例如,如果数据偏移字段的值为5,那么TCP头部的长度就是5 × 4 = 20字节。
计算方式
Reserved: 4 bits
保留位:必须为0
RFC 9293 对之前的RFC 793的定义进行了修改,RFC 793中Reserved占用6bits,Control Bits 占用 6 bits,没有CWR和ECE
Control Bits: 8 bits
CWR: Congestion Window Reduced,拥塞窗口减小,为1表示发生了网络拥塞,通知对方主机已经采取措施减少发送窗口的大小,以减缓网络拥塞。
ECE: ECN(ECN-Echo)通常和CWR一起使用。ECN允许网络设备(如路由器)在遇到即将发生的拥塞时,通过修改IP和TCP头部中的ECN字段来通知发送方和接收方,而不是丢弃包。当发送方接收到ECE标志的通知时,它会启动拥塞控制机制,减少拥塞窗口,并在下一个合适的TCP段中设置CWR标志,以告知接收方它已经减少了发送速率。
URG:紧急指针字段,为1表示高优先级数据包
ACK:确认字段,1表示确认号字段有效
PSH:推送功能,为1表示是带有PUSH标志的数据,指示接收方应该尽快将这个报文段交给应用层而不用等待缓冲区装满。
RST:重置连接,为1表示出现严重差错。可能需要重新建立TCP连接。还可以用于拒绝非法的报文段和拒绝连接请求。
SYN:同步序列号,为1表示这是连接请求或是连接接受请求,用于建立连接和使顺序号同步
FIN:为1表示发送方没有数据要传输了,要求释放连接。
Window: 16 bits
接收窗口大小
Checksum: 16 bits
校验和
Urgent Pointer: 16 bits
紧急标志位(URG:
TCP头部的控制位中有一个叫做URG的位,当这个位被设置时,表示该TCP段包含紧急数据。紧急指针字段此时才被视为有效,并用来指示紧急数据的范围。
计算紧急数据的范围:
紧急指针的值加上序列号(该段TCP段的起始序列号)给出了紧急数据的结束位置的序列号。例如,如果序列号是1000,紧急指针是500,那么紧急数据的范围是从序列号1000到1499。
示例
假设有一个TCP段,其序列号为1000,URG标志位被设置,紧急指针值为300。这意味着从序列号1000开始的300字节是紧急数据,紧急数据的范围是1000到1299。接收方的TCP堆栈会特殊处理这300字节的数据,可能会通过不同的机制(如不同的队列或事件)将其传递给应用程序。
Options: variable
由于TCP头部的数据偏移字段最多只能表示60字节的头部长度,所以所有选项的总长度必须在40字节以内(因为基本的TCP头部长度是20字节)。
每个选项的开始是1字节的kind字段,说明选项的类型。
常见的TCP选项
最大报文段长度(MSS): 32 bits
最大报文段长度(4字节,Maximum Segment Size,MSS)通常在建立连接而设置SYN标志的数据包中指明这个选项,指明本端所能接收的最大长度的报文段。通常将MSS设置为(MTU-40)字节,携带TCP报文段的IP数据报的长度就不会超过MTU,从而避免本机发生IP分片。只能出现在同步报文段中,否则将被忽略。
窗口比例(Window Scale):24 bits
取值0-14。用来把TCP的窗口的值左移的位数,使窗口值乘倍。只能出现在同步报文段中,否则将被忽略。这是因为现在的TCP接收数据缓冲区(接收窗口)的长度通常大于65535字节。对于高速或长距离的网络连接特别有用。
时间戳(Timestamps):80bits
用于计算往返时间(RTT)和防止序列号的环绕(当序列号用尽并重新开始时)。
发送端的时间戳: 32 bits, 时间戳回显应答: 32 bits
选择性确认(SACK):
SACK选项的长度取决于包含的SACK块的数量。通常,一个TCP段可以包含多个SACK块。每增加一个SACK块,长度增加8字节。例如:
一个SACK块的情况:
Kind: 1字节
Length: 1字节
SACK Block: 8字节
总计:1 + 1 + 8 = 10字节
两个SACK块的情况:
Kind: 1字节
Length: 1字节
SACK Blocks: 8字节 * 2 = 16字节
总计:1 + 1 + 16 = 18字节
允许接收方指示已经接收到的数据块,而不是仅仅确认最后一个连续的字节
无操作(No-Operation,NOP):8 bits
用于对齐选项,使得后续的选项位于32位的边界上。
结束列表(End of Option List,EOL):8 bits
用于指示选项列表的结束,通常用于填充,以确保TCP头部的总长度是32位字的整数倍。
Padding: variable
由于TCP头部的长度必须是32位字的倍数,填充用于确保这一点。这是因为TCP的数据偏移字段定义了头部的长度,而这个长度包括了基本头部和选项的总和。
somaxconn :
指定系统全局级别上,每个监听socket的最大backlog值。如果你在listen()函数中指定的backlog值超过了somaxconn的设定,那么实际的backlog值将被限制为somaxconn的值。
tcp_max_syn_backlog:
当系统正在处理大量的TCP连接时,这个参数控制着系统为半开(尚未完成三次握手)的TCP连接队列可以保持的最大数量。
tcp_synack_retries:
此参数定义了在放弃响应一个新的连接请求前,内核应该重新发送SYN+ACK包的次数。这个参数对于防止半开连接(SYN Flood 攻击)极为重要。
tcp_abort_on_overflow
当服务器的监听队列溢出时,如果此参数设置为1,系统将向客户端发送RST(重置),而不是忽视接收到的SYN。这有助于客户端更快地了解到服务端的监听队列已溢出。
tcp_rmem
接受缓冲区大小,它有三个值,分别表示最小值、默认值和最大值(单位:字节)。
tcp_wmem
发送缓冲区的大小。它有三个值,对应最小值、默认值和最大值(单位:字节)
tcp_sack
是否启用SACK,1 表示开启,0 表示关闭
tcp_dsack
释放启用D-SACK,1 表示开启,0 表示关闭
tcp_available_congestion_control
显示当前系统支持的拥塞控制算法
tcp_congestion_control
显示当前系统正在使用的拥塞控制算法
tcp_fastopen
0:禁用 TCP Fast Open。
1:允许作为客户端使用 TCP Fast Open。
2:允许作为服务器接受带有 TCP Fast Open 的 SYN 包。
3:允许作为客户端和服务器使用 TCP Fast Open。
tcp_max_tw_buckets
指定系统中TIME_WAIT状态的TCP连接的最大数量。当系统中的TIME_WAIT连接数超过这个值时,新的TIME_WAIT连接将不会被创建,而是被立即关闭。