TCP首部格式?
为什么需要 TCP 协议? TCP 工作在哪一层?
什么是 TCP ?
- 面向连接:只能一对一,不能一对多。
- 可靠的:无论网络链路中出现了怎样的链路变化,都可以保证一个报文一定能够到达接收端。
- 字节流:需要指出消息的边界,否则接收端无法读出有效用户信息。
什么是 TCP 连接?
如何唯一确定一个 TCP 连接呢?
- 源地址
- 源端口
- 目的地址
- 目的端口
有一个 IP 的服务端监听了一个端口,它的 TCP 的最大连接数是多少?
最大 TCP 连接数 = 客户端的 IP 数 X 客户端的端口数
UDP 和 TCP 有什么区别呢?分别的应用场景是?
为什么 UDP 头部没有首部长度字段,而 TCP 头部有首部长度字段呢?
原因是 TCP 有可变长的选项字段,而 UDP 首部长度是不会变化的,无需多一个字段去记录UDP的首部长度。
为什么 UDP 头部有包长度字段,而 TCP 头部则没有包长度字段呢?
TCP 和 UDP 可以使用同一个端口吗?
可以,TCP 和 UDP 在内核中是两个完全独立的软件模块,TCP 和 UDP 各自的端口号是相互独立的,如 TCP 有一个 80 号端口,UDP 也可以有一个 80 号端口,二者并不冲突。
TCP 三次握手过程是怎样的?
- 一开始,客户端和服务端都处于 CLOSE 状态。先是服务端主动监听某个端口,处于 LISTEN 状态。
- 客户端会随机初始化序号(client_isn),同时将 SYN 标志位置为 1 ,表示 SYN 报文。接着把第一个 SYN 报文发送给服务端,表示向服务端发起连接,该报文不包含应用层数据,之后客户端处于 SYN-SENT 状态。
- 服务端收到客户端的 SYN 报文后,首先服务端也随机初始化自己的序号(server_isn),然后把 TCP 首部的确认应答号字段填入client_isn + 1 , 接着把 SYN 和 ACK 标志位置为 1 。最后把该报文发给客户端,该报文也不包含应用层数据,之后服务端处于 SYN-RCVD 状态。
- 客户端收到服务端报文后,还要向服务端回应最后一个应答报文,首先该应答报文 TCP 首部 ACK 标志位置为 1 ,其次确认应答号字段填入 server_isn + 1 ,最后把报文发送给服务端,这次报文可以携带客户到服务端的数据,之后客户端处于 ESTABLISHED 状态。
- 服务端收到客户端的应答报文后,也进入 ESTABLISHED 状态。
在 Linux 下可以通过 netstat -napt 命令查看 TCP 的连接状态。
为什么是三次握手?不是两次、四次?
三个原因:
- 防止历史连接的建立(主要原因)
- 帮助双方同步初始化序列号
- 减少双方不必要的资源开销
- 两次握手:无法防止历史连接的建立,会造成双方资源的浪费,也无法可靠的同步双方序列号
- 四次握手:三次握手就能保证建立可靠连接,所以不需要使用更多的通信次数
在两次握手的情况下,服务端在收到 SYN 报文后,就进入 ESTABLISHED 状态,意味着这时可以给对方发送数据,但是客户端此时还没有进入 ESTABLISHED 状态,假设这次是历史连接, 客户端判断到此次连接为历史连接,那么就会回 RST 报文来断开连接,但如果在此之前服务端就建立了历史连接,这样就会导致服务端白白浪费一个资源。
四次握手其实也能够可靠的同步双方的初始化序号,但没必要;两次握手只保证了一方的初始序列号能被对方成功接收,没办法保证双方的初始序列号都能被确认接收。
3.避免资源浪费
为什么每次建立 TCP 连接时,初始化的序列号都要求不一样呢?
- 为了防止历史报文被下一个相同四元组的连接接收(主要方面);
- 为了安全性,防止黑客伪造的相同序列号的 TCP 报文被对方接收;
初始序列号 ISN 是如何随机产生的?
- M 是一个计时器,这个计时器每隔 4 微秒加 1。
- F 是一个 Hash 算法,根据源 IP、目的 IP、源端口、目的端口生成一个随机数值。
既然 IP 层会分片,为什么 TCP 层还需要 MSS 呢?
第一次握手丢失了,会发生什么?
第二次握手丢失了,会发生什么?
当第二次握手丢失了,客户端和服务端都会发生超时重传。
第三次握手丢失了,会发生什么?
当第三次握手丢失了,服务端会发生超时重传。
注:ACK 报文是不会有重传的,当 ACK 丢失了,就由对方重传对应的报文。
什么是 SYN 攻击?如何避免 SYN 攻击?
- 半连接队列,也称 SYN 队列(收到 SYN 但没有收到 ACK)
- 全连接队列,也称 accept 队列(收到 ACK)
避免方法:
- 增大 TCP 半连接队列;
- 开启 tcp_syncookies;
- 减少 SYN+ACK 重传次数
TCP 四次挥手过程是怎样的?
- 客户端打算关闭连接,此时会发送 FIN 报文,之后客户端进入 FIN_WAIT_1 状态
- 服务端收到该报文后,就向客户端发送 ACK 应答报文,接着服务端进入 CLOSE_WAIT 状态
- 客户端收到服务端的 ACK 应答报文后,之后进入 FIN_WAIT_2 状态
- 等待服务端处理完数据后,也向客户端发送 FIN 报文,之后服务端进入 LAST_ACK 状态
- 客户端收到服务端的 FIN 报文后,回一个 ACK 应答报文,之后进入 TIME_WAIT 状态
- 服务端收到了 ACK 应答报文后,就进入了 CLOSE 状态,至此服务端已经完成连接的关闭
- 客户端在经过 2MSL 一段时间后,自动进入 CLOSE 状态,至此客户端也完成连接的关闭
为什么挥手需要四次?
双方都可以主动断开连接,只有主动断开连接的一方,才有TIME_WAIT 状态。
第一次挥手丢失了,会发生什么?
客户端超时重传。如果达到最大重传次数,还没有收到服务端的 ACK,则自动进入 CLOSE 状态
第二次挥手丢失了,会发生什么?
第三次挥手丢失了,会发生什么?
服务端超时重传。
第四次挥手丢失了,会发生什么?
服务端超时重传。
为什么 TIME_WAIT 等待的时间是 2MSL?
为什么需要 TIME_WAIT 状态?
- 防止历史连接中的数据,被后面相同四元组的连接错误的接收
- 保证被动关闭连接的一方,能被正确的关闭
TIME_WAIT 过多有什么危害?
如何优化 TIME_WAIT?
- 复用处于 TIME_WAIT 的 socket 为新连接所用。
- 当系统中处于 TIME_WAIT 的连接超过最大值时,系统就会将后面的 TIME_WAIT 连接状态重置。
- 跳过 TIME_WAIT 状态,当调用 close 后,会立即发送一个 RST 标志给对端,直接关闭。
服务器出现大量 TIME_WAIT 状态的原因有哪些?
- HTTP 没有使用长连接
- HTTP 长连接超时
- HTTP 长连接的请求数量达到上限
服务器出现大量 CLOSE_WAIT 状态的原因有哪些?
如果已经建立了连接,但是客户端突然出现故障了怎么办?
TCP 存在保活机制,超过保活时间,会定期发送探测报文,如果连续几个都没回应,则断连。
如果已经建立了连接,但是服务端的进程崩溃会发生什么?
TCP 的连接信息是由内核维护的,所以当服务端的进程崩溃后,内核需要回收该进程的所有 TCP连接资源,于是内核会发送第一次挥手 FIN 报文,后续的挥手过程也都是在内核完成,并不需要 进程的参与,所以即使服务端的进程退出了,还是能与客户端完成 TCP 四次挥手的过程。
服务端没有listen,客户端发起连接,会发生什么?
服务端回复 RST 报文。
listen 时候参数 backlog 的意义?
通常认为 backlog 是 accept 队列。
accept 发生在三次握手的哪一步?
客户端 connect 成功返回是在第二次握手,服务端 accept 成功返回是在三次握手成功之后。
没有 accept,能建立 TCP 连接吗?
没有 listen,能建立 TCP 连接吗?
重传机制
- 超时重传
在发送数据时,设置一个定时器,当超过指定时间后,没有收到对方的应答报文,就重传。
- 快速重传
发送端连续收到三个 ACK,就立即重传,不用等待定时器超时。
- SACK
选择性确认,在 TCP 头部选项字段中加入 SACK,它可以将已收到的数据的信息发送给发送方,这样发送方就可以知道哪些数据收到了,哪些数据没收到,这样就可以只重传丢失的数据。
- D-ACK
滑动窗口
窗口大小就是指无需等待确认应答,而可以继续发送数据的最大值。
通常窗口的大小是由接收方的窗口大小来决定的。
流量控制
可以让发送方根据接收方的实际接收能力控制发送的数据量。
窗口关闭
如果窗口大小为 0 ,会阻止发送方给接收方传递数据,直至窗口变为非 0 为止。
糊涂窗口综合征
如果接收方腾出几个字节并告诉发送方现在有几个字节的窗口,而发送方会义无反顾地发送这几个字节,这就是糊涂窗口综合症。
如何避免?
需要接收方满足不通告小窗口给发送方同时发送方开启 Nagle 算法。
- 注:如果接收方不满足不通告小窗口给发送方,即使开了 Nagle 算法,也无法避免糊涂窗口综合征,如果对端 ACK 回复很快的话,Nagle 算法就不会拼接太多的数据包,这种情况下依然会有小数据包的传输,网络总体利用率依然很低。
Nagle 算法的思路是延时处理,只有满足下面两个条件中的任意一个条件,才可以发送数据
- 条件一:要等到窗口大小 >= MSS 并且 数据大小 >= MSS ;
- 条件二:收到对端 ACK;
setsockopt(sock_fd, IPPROTO_TCP, TCP_NODELAY, (char *)&value, sizeof(int));
拥塞控制
- 慢启动
- 拥塞避免
- 拥塞发生
- 快速恢复
慢启动:
慢启动算法,发包的个数是指数性的增长。
当 cwnd < ssthresh 时,使用慢启动算法。当 cwnd >= ssthresh 时,就会使用拥塞避免算法。
拥塞避免:
由慢启动的指数增长变为线性增长,达到一定程度,网络发生拥塞,触发重传机制,进入拥塞发生算法。
拥塞发生:
- 超时重传
- 快速重传
发生超时重传的拥塞发生算法:
- ssthresh 设为 cwnd/2;
- cwnd 恢复到初始值;
- cwnd = cwnd/2;
- ssthresh = cwnd;
- 进入快速恢复算法;
如何优化TCP?
TCP 三次握手性能提升
- 客户端优化:修改 SYN 重传次数。
- 服务端优化:修改 SYN+ACK 重传次数;修改半连接队列大小;修改全连接队列大小;当半连接队列满了开启 syncookie 功能;当全连接队列满了用 RST 通知客户端建立连接失败。
- 绕过三次握手。
如何绕过三次握手?
- 客户端发送 SYN 报文,该报文包含 Fast Open 选项,且该选项的 Cookie 为空,这表明客户端请求 Fast Open Cookie;
- 支持 TCP Fast Open 的服务器生成 Cookie,并将其置于 SYN-ACK 数据包中的 Fast Open 选项以发回客户端;
- 客户端收到 SYN-ACK 后,本地缓存 Fast Open 选项中的 Cookie。
- 客户端发送 SYN 报文,该报文包含数据以及此前记录的 Cookie;
- 支持 TCP Fast Open 的服务器会对收到 Cookie 进行校验:如果 Cookie 有效,服务器将在 SYN-ACK 报文中对 SYN 和数据进行确认,服务器随后将数据递送至相应的应用程序;如果 Cookie 无效,服务器将丢弃 SYN 报文中包含的数据,且其随后发出的 SYN-ACK 报文将只确认 SYN 的对应序列号;
- 如果服务器接受了 SYN 报文中的数据,服务器可在握手完成之前发送数据,这就减少了握手带来的 1 个 RTT 的时间消耗;
- 客户端将发送 ACK 确认服务器发回的 SYN 以及数据,但如果客户端在初始的 SYN 报文中发送的数据没有被确认,则客户端将重新发送数据;
TCP 四次挥手性能提升
- 主动方的优化:修改 FIN 报文重传次数;调整 TIME_WAIT 状态的上限个数,一旦超过,直接释放;复用处于 TIME_WAIT 状态的连接(只适用于客户端);调整 TIME_WAIT2 状态的时间(只适用于 close 关闭的连接);调整孤儿连接(调用 close 关闭的连接)的上限个数(只适用于 close 关闭的连接),一旦超过,将不再走四次挥手,直接发送 RST 复位关闭。
- 被动方的优化:如果出现大量 CLOSE_WAIT 状态,从程序中找原因(考虑未调用 close 函数);修改 FIN 报文重传次数;
TCP 传输数据的性能提升
扩大窗口大小;调整发送缓冲区范围;调整接收缓冲区范围;打开接受缓冲区动态调节;调整内存范围。
如何理解 TCP 是面向字节流的协议?
如何解决粘包?
粘包问题的出现是因为不知道用户消息的边界,如果知道边界在哪,接收方就可以通过边界来划分出有效的用户消息。
解决:
- 固定长度的消息
- 特殊字符作为边界
- 自定义消息结构(由包头和数据组成,包头大小固定,包头中包含数据长度)
SYN 报文什么时候情况下会被丢弃?
- 开启 tcp_tw_recycle 参数,并且在 NAT 环境下,造成 SYN 报文被丢弃
- TCP 两个队列满了(半连接队列和全连接队列),造成 SYN 报文被丢弃
Linux 提供了两个系统参数来快速回收处于 TIME_WAIT 状态的连接,这两个参数默认关闭:
- net.ipv4.tcp_tw_reuse,如果开启该选项的话,客户端(连接发起方) 在调用 connect() 函数时,如果内核选择到的端口已经被相同四元组的连接占用的时候,就会判断该连接是否处于 TIME_WAIT 状态,如果该连接处于 TIME_WAIT 状态并且 TIME_WAIT 状态持续的时间超过了 1 秒,那么就会重用这个连接,然后就可以正常使用该端口了。所以该选项只适用于连接发起方。
- net.ipv4.tcp_tw_recycle,如果开启该选项的话,允许处于 TIME_WAIT 状态的连接被快速回收;
使这两个选项生效的前提是要打开 TCP 时间戳(默认开启),即 net.ipv4.tcp_timestamps=1
第一种场景,对于服务器来说,如果同时开启了recycle 和 timestamps 选项,则会开启一种称之为per-host 的 PAWS 机制。
什么是 PAWS 机制?
tcp_timestamps 选项开启之后, PAWS 机制会自动开启,它的作用是防止 TCP 包中的序列号发生绕回。在开启 tcp_timestamps 选项情况下,一台机器发的所有 TCP 包都会带上发送时的时间戳,PAWS 要求连接双方维护最近一次收到的数据包的时间戳(Recent TSval),每收到一个新数据包都会读取数据包中的时间戳值跟 Recent TSval 值做比较, 如果发现收到的数据包中时间戳不是递增的,则表示该数据包是过期的,就会直接丢弃这个数据包。
当开启 recycle 和 timestamps 选项时,就会开启一种叫 per-host 的 PAWS 机制。 perper-host 是对相同的 IP 做 PAWS 检查,而不是对 TCP 四元组进行检查。当客户端 A 通过 NAT 网关和服务器建立 TCP 连接,然后服务器主动关闭并且快速回收 TIME-WAIT 状态的连接后,客户端 B 也通过 NAT 网关和服务器建立 TCP 连接,注意客户端 A 和 客户端 B 因为经过相同的 NAT 网关,所以是用相同的 IP 地址与服务端建立 TCP 连接,如果客户端 B的 timestamp 比客户端 A 的 timestamp 小,那么由于服务端的 per-host 的 PAWS 机制的作用, 服务端就会丢弃客户端主机 B 发来的 SYN 包。
tcp_tw_recycle 在 Linux 4.12 版本后,直接取消了这一参数。
已建立连接的TCP,收到SYN会发生什么?
问题:一个已经建立的 TCP 连接,客户端中途宕机了,而服务端此时也没有数据要发送,一直处于 Established 状态,客户端恢复后,向服务端建立连接,此时服务端会怎么处理?
1. 客户端的 SYN 报文里的端口号与历史连接不相同
- 如果客户端恢复后,发送的 SYN 报文中的源端口号跟上一次连接的源端口号不一样,此时服务端会认为是新的连接要建立,于是就会通过三次握手来建立新的连接。
- 如果客户端恢复后,发送的 SYN 报文中的源端口号跟上一次连接的源端口号一样(注意此时的 SYN 报文是乱序的,因为 SYN 报文的初始化序列号是一个随机数),服务端会回复一个携带了正确序列号和确认号的 ACK 报文,这个 ACK 被称之为 Challenge ACK。 接着,客户端收到这个 Challenge ACK,发现确认号并不是自己期望收到的,于是就会回 RST 报文,服务端收到后,就会释放掉该连接。
如何关闭一个 TCP 连接?
要伪造一个能关闭 TCP 连接的 RST 报文,必须同时满足以下两个条件:
- 四元组相同
- 序列号是对方期望的
- tcpkill 工具只能用来关闭活跃的 TCP 连接,无法关闭非活跃的 TCP 连接,因为 tcpkill 工具是等双方进行 TCP 通信后,才去获取正确的序列号,如果这条 TCP 连接一直没有任何数据传输,则永远获取不到正确的序列号。
- killcx 工具可以用来关闭活跃和非活跃的 TCP 连接,因为 killcx 工具是主动发送 SYN 报文,这时对方就会回复 Challenge ACK ,然后 killcx 工具就能从这个 ACK 获取到正确的序列号。
四次挥手中收到乱序的 FIN 包会如何处理?
在 FIN_WAIT_2 状态时,如果收到乱序的 FIN 报文,那么就被会加入到乱序队列,并不会进入到 TIME_WAIT 状态。 等再次收到前面被网络延迟的数据包时,会判断乱序队列有没有数据,然后会检测乱序队列中是否有可用的数据,如果能在乱序队列中找到与当前报文的序列号保持序列号连续的报文,就会看该报文是否有 FIN 标志,如果发现有 FIN 标志,这时才会进入 TIME_WAIT 状态。
在 TCP 正常挥手过程中,处于 TIME_WAIT 状态的连接,收到相同四元组的 SYN 后会发生什么?
- 如果客户端的 SYN 的「序列号」比服务端「期望下一个收到的序列号」要大,并且SYN 的「时间戳」比服务端「最后收到的报文的时间戳」要大。那么就会重用该四元组连接,跳过 2MSL 而转变为 SYN_RECV 状态,接着就能进行建立连接过程。
- 如果客户端的 SYN 的「序列号」比服务端「期望下一个收到的序列号」要小,或者SYN 的「时间戳」比服务端「最后收到的报文的时间戳」要小。那么就会再回复一个第四次挥手的 ACK 报文,客户端收到后,发现并不是自己期望收到确认号,就回 RST 报文给服务端。
在 TIME_WAIT 状态,收到 RST 会断开连接吗?
- 如果 net.ipv4.tcp_rfc1337 参数为 0,则提前结束 TIME_WAIT 状态,释放连接(默认情况)
- 如果 net.ipv4.tcp_rfc1337 参数为 1,则会丢掉该 RST 报文
TCP 连接,一端断电和进程崩溃有什么区别?
在客户端主机宕机后,服务端向客户端发送的报文会得不到任何的响应,在一定时长后,服务端就会触发 超时重传机制,重传未得到响应的报文。服务端重传报文的过程中,客户端主机重启完成后,客户端的内核就会接收重传的报文,然后根据报文的信息传递给对应的进程:
- 如果客户端主机上没有进程绑定该 TCP 报文的目标端口号,那么客户端内核就会回复 RST 报文,重置该 TCP 连接;
- 如果客户端主机上有进程绑定该 TCP 报文的目标端口号,由于客户端主机重启后,之前的 TCP 连接的数据结构已经丢失了,客户端内核里协议栈会发现找不到该 TCP 连接的 socket 结构体, 于是就会回复 RST 报文,重置该 TCP 连接。
只要有一方重启完成后,收到之前 TCP 连接的报文,都会回复 RST 报文,以断开连接。
这种情况,服务端超时重传报文的次数达到一定阈值后,内核就会判定出该 TCP 有问题,然后通过 socket 接口告诉应用程序该 TCP 连接出问题了,于是服务端的 TCP 连接就会断开。
拔掉网线后, 原本的 TCP 连接还存在吗?
- 在客户端拔掉网线后,如果服务端发送了数据报文,那么在服务端重传次数没有达到最大值之 前,客户端就插回了网线,那么双方原本的 TCP 连接还是能正常存在,就好像什么事情都没有发生。
- 在客户端拔掉网线后,如果服务端发送了数据报文,在客户端插回网线之前,服务端重传次数 达到了最大值时,服务端就会断开 TCP 连接。等到客户端插回网线后,向服务端发送了数据, 因为服务端已经断开了与客户端相同四元组的 TCP 连接,所以就会回 RST 报文,客户端收到后就会断开 TCP 连接。至此, 双方的 TCP 连接都断开了。
- 如果双方都没有开启 TCP keepalive 机制,那么在客户端拔掉网线后,如果客户端一直不插回网线,那么客户端和服务端的 TCP 连接状态将会一直保持存在。
- 如果双方都开启了 TCP keepalive 机制,那么在客户端拔掉网线后,如果客户端一直不插回网 线,TCP keepalive 机制会探测到对方的 TCP 连接没有存活,于是就会断开 TCP 连接。而如果在 TCP 探测期间,客户端插回了网线,那么双方原本的 TCP 连接还是能正常存在。
tcp_tw_reuse 为什么默认是关闭的?
- 历史 RST 报文可能会终止后面相同四元组的连接,因为 PAWS 检查到即使 RST 是过期的,也不会丢弃。
- 如果第四次挥手的 ACK 报文丢失了,有可能被动关闭连接的一方不能被正常的关闭。
HTTPS 中 TLS 和 TCP 能同时握手吗?
- 客户端和服务端都开启了 TCP Fast Open 功能,且 TLS 版本是 1.3;
- 客户端和服务端已经进行一次通信;
客户端和服务端同时支持 TCP Fast Open 功能的情况下,在第二次以后的通信过程中,客户端可以绕过三次握手直接发送数据,而且服务端也不需要等收到第三次握手后才发送数据。如果 HTTPS 的 TLS 版本是 1.3,那么 TLS 过程只需要 1-RTT。 因此如果 TCP Fast Open + TLSv1.3 情况下,在第二次以后的通信过程中,TLS 和 TCP 的握手过程是可以同时进行的。 如果基于 TCP Fast Open 场景下的 TLSv1.3 0-RTT 会话恢复过程,不仅 TLS 和 TCP 的握手过程是可以同时进行的,而且 HTTP 请求也可以在这期间内一同完成。
TCP Keepalive 和 HTTP Keep-Alive 是一个东西吗?
这两个是完全不一样的东西
- HTTP 的 Keep-Alive 也叫 HTTP 长连接,该功能是由「应用程序」实现的,可以使得用同一个 TCP 连接来发送和接收多个 HTTP 请求/应答,减少了 HTTP 短连接带来的多次 TCP 连接建立和释放的开销。
- TCP 的 Keepalive 也叫 TCP 保活机制,该功能是由「内核」实现的,当客户端和服务端长达一定时间没有进行数据交互时,内核为了确保该连接是否还有效,就会发送探测报文,来检测对方是否还在线,然后来决定是否要关闭该连接。
TCP 协议有什么缺陷?
1.升级 TCP 的工作很困难:
TCP 协议是在内核中实现的,应用程序只能使用不能修改,对于升级内核要涉及底层软件和运行库的更新,非常麻烦。
2.TCP 建立连接的延迟:
4.网络迁移需要重新建立 TCP 连接:
如何基于 UDP 协议实现可靠传输?
QUIC 协议实现可靠传输
在UDP头部和HTTP之间加入两层头部,分别是Packet Header 和 QUIC Frame Header,Packet Header 细分为两种,Long Packet Header 用来首次建立连接,Short Packet Header 用于日常数据传输,QUIC首次也需要三次握手,主要为了协商出 ID。Short Packet Header中的 Packet Number 是每个报文独一无二的编号,它是严格递增的,如果 Packet N 丢失了,重传的编号就不是N了,而是一个比N大的值。
Packet Number 单点递增两个好处:
- 更加精准计算 RTT,没有TCP重传的歧义性问题(计算RTT的起始时间应该从首次发送开始还是从超时重传时开始呢?);
- 支持乱序确认(不必像TCP一样必须有序确认,只要有新数据确认,无论之前是否丢包,当前窗口都会右移)
既然 Packet Number 是严格递增的,那么重传数据包与丢失数据包的编号并不一致,那么如何确定包中的内容是否相同呢?
所以引入QUIC Frame Header,通过比较两个数据包的 Stream ID 和 Offset,如果一致,则内容相同。后续再根据这两个字段进行数据包的正确组装。
总结:QUIC 通过单向递增的 Packet Number,配合 Stream ID 与 Offset 字段信息,可以支持乱序确认而不影响数据包的正确组装,摆脱了TCP必须按顺序确认应答,解决TCP因丢包导致后续其他数据包阻塞。
QUIC是如何解决队头阻塞问题?
在http那篇文章中,介绍到了hhtp/1.1解决了hhtp/1.0的发送端队头阻塞,但没有解决接收端队头阻塞,hhtp/2.0利用Stream解决了http层面的队头阻塞,但存在TCP层队头阻塞问题(hhtp/2.0多个Stream共用一个滑动窗口,当数据丢失,滑动窗口无法移动,此时会阻塞所有http请求)
对于QUIC,它给每个Stream都分配了一个独立的滑动窗口,这样使得一个TCP连接上的多个Stream之间相互独立,解决了队头阻塞问题。
总结:通过Stream解决http层的队头阻塞,通过给每个Stream分配一个滑动窗口解决了TCP层队头阻塞问题。
QUIC如何做流量控制?
QUIC实现了两种级别的流量控制,分别是Stream和Connection两种级别:
- Stream级别的流量控制:Stream可以被认为是一条HTTP请求,每个Stream都有独立的滑动窗口,所以每个Stream都可以做流量控制。
- Connection流量控制:限制连接中所有Stream相加的总字节数,防止发送方超过连接的缓冲量。
QUIC对拥塞算法改进
QUIC处在应用层,可以根据不同的应用设置不同的拥塞控制算法,而TCP拥塞控制是基于内核和操作系统支持的,对系统所有应用都生效,无法针对性设置。
QUIC更快建立连接
QUIC使用TLS1.3,只需一个RTT就可以同时完成建立连接与密钥协商,在第二次时,数据包可以同时和QUIC握手信息(连接信息+TLS信息)一起发送,达到0RTT的效果。
QUIC如何迁移连接?
QUIC通过连接ID确定连接,客户端和服务端可以各自选择一组ID来标记自己,即使后面网络发生变化(如4G切换到WIFI)导致IP发生变化,仍可以无缝复用原连接。
TCP和UDP可以使用同一个端口吗?
TCP和UDP能绑定同一个端口吗?
可以。传输层有两个传输协议分别是TCP和UDP,在内核中是两个完全独立的模块,当主机收到数据包以后,可以根据IP包头的协议号确定是TCP还是UDP,进而决定交给哪个模块(TCP/UDP)处理,送给TCP/UDP的模块会根据端口号确定给哪个应用进程处理。(TCP、UDP的端口号之间也相互独立,如TCP 80 端口和UDP 80 端口并不冲突)
多个TCP进程可以绑定绑定同一个端口吗?
如果两个 TCP 服务进程同时绑定相同的IP地址和端口号,那么执行bind的时候会出错,错误为“Address already in use”;如果绑定端口相同,但IP地址不同,那么执行bind不会报错。
如何解决服务端重启,报错“Address already in use”的问题?
客户端的端口可以重复使用吗?
多个客户端可以bind同一个端口吗?
客户端TCP连接的TIME_WAIT状态过多,会导致端口耗尽而无法建立新的连接吗?
如何解决客户端 TCP 连接 TIME_WAIT 过多,导致无法与同一个服务器建立连接的问题?
用了 TCP 协议,数据一定不会丢吗?
不一定。可以通过ping和mtr进行查看,ping只能查看是否发生丢包,无法查看具体哪个节点发生丢包,而mtr可以查看具体那个节点发生丢包。
TCP四次握手可以变成三次吗?
怎么关闭 TCP 延迟确认机制?