1. 三次握手

握手主要流程:
Client -> Server: SYN x = rand()Client Client -> Server: ACK x+1, y+1
注意:只有在一次完整的 roundtrip 之后 Client 才能开始发送数据;Server 则需要收到 ACK x+1, y+1 消息到达后才能开始发送数据。
TCP Fast Open
- 打开此配置时,可以第一条
消息里带上数据,不用等一次完整的 roundtrip
SYN
- 限制:
消息中可带的数据大小有限制
SYN
- 只有特定的几种 HTTP 请求可使用此方式发送
- 只能用于重复连接之前曾经连过的 Server 的情况下
- 配置:
- Server: Linux kernel v4.1+
- Client: iOS 9+/OSX 10.11+ 或他相应的 Linux 版本
- 应用程序打开相关的 socket flag
- 性能提升:
- HTTP网络延迟降低 15%
- 页面加载时间平均降低 10%
- 某些高延迟情况下有 40% 的降低
2. 拥塞控制
1) 限流(Flow control)
- 目的:防止一端发送过多数据使另一端来不及处理
- 机制:
- 客户端、服务端分别告知对方已方接收窗口的大小(rwnd)
- 连接刚建立时,两端的 rwnd 都使用系统默认值
- 每个 ACK 消息都会带上当前最新的 rwnd,使对方可以动态调整发送的数据流量
Window Scaling (RFC 1323)
- 目的:TCP协议只为rwnd预留了16位,使rwnd最大为2^16=65536字节,要想设置超过此值的的窗口大小,需要 Window Scaling
- 实现:在三次握手过程中确定一个左移位数(shift),rwnd 的实际大小为ACK消息中带的16位的值左移 shift 以后得到的值
- 支持 Window Scaling 以后,rwnd 最大支持1G字节
- 主流系统上已默认开启
- 通信信道上的中间结点、路由器、防火墙等可能会禁用此选项
$> sysctl net.ipv4.tcp_window_scaling $> sysctl -w net.ipv4.tcp_window_scaling=1
2) 拥塞控制(Congestion control)
- 目的:防止发送太多数据导致网络来不及处理导致网络拥塞
- 慢启动(Slow-Start)
- Server侧为每个 tcp 连接维护一个拥塞窗口大小(cwnd),其默认的初始值比较小。
- cwnd 不需要通知对端
- 为提高传输效率,可将 cwnd 默认初始值提高到10个 network segments
- 引入规则:传输中未被 ACK 的数据量限额为 MIN(cwnd, rwnd)
- 开始时 cwnd 较小,随后每收到一个 ACK,cwnd += 1,这样下次就可以发送之前两倍的数据量
- 扩展:Slow-Start Restart 机制
- 连接空闲一段时间后会将 cwnd 重设为默认值
- 为防止此机制影响连接的传输性能,可采用下列命令关闭此机制:
$> sysctl net.ipv4.tcp_slow_start_after_idle$> sysctl -w net.ipv4.tcp_slow_start_after_idle=0
- Server侧为每个 tcp 连接维护一个拥塞窗口大小(cwnd),其默认的初始值比较小。
- 拥塞避免(Congestion avoidance)
- 网络条件好的情况下,cwnd会一直倍增,直到其超出接收方系统配置的拥塞阈值窗口大小(ssthres),或出现丢包。此时会触发拥塞避免机制来调整拥塞窗口(cwnd)大小
- 常见算法:
- TCP Tahoe and Reno
- TCP Vegas
- TCP New Reno
- TCP BIC
- TCP CUBIC (Linux上默认)
- Compound TCP (Windows上默认)
- Proportional Rate Reduction for TCP (RFC 6937)
- 目的:加快丢包后的恢复速度
- 性能:在有丢包的连接上平均延时降低3-10%
- Linux 3.2+ 内核上默认使用此拥塞避免算法
3. 队头阻塞
TCP
协议下每个数据包都有唯一的序号,协议能够保证这些包的可靠、按顺序传输。但如果多个包中某个序号比较靠前的在传输过程中发生了丢包,其它序号靠后的包即使已顺利到达,也需要在接收方的缓冲区中等待,直到队头包重传成功才能将报文组装并返回给应用层。
- 队头阻塞发生在
协议层,应用层无法感知,应用层只能感知到总体的传输延迟TCP
- 队头阻塞的负面影响在音、视频应用这种不要求所有包都到达即可正常运行的场景下尤其明显。
4. 四次挥手
Client -> Server: SEQ=x, FIN=1 //Client=FIN_WAIT1Client : SEQ=y, ACK=x+1 //Client=FIN_WAIT2, Server=CLOSE_WAITClient : 可能有 Server 还未发完的数据传送继续进行...Client : SEQ=z, ACK=x+1, FIN=1 //Server=LAST_ACKClient -> Server: SEQ=x+1, ACK=z+1 //Client=TIME_WAIT(2MSL后自动进入CLOSED状态)
参考资料
- https://hpbn.co/building-blocks-of-tcp/