前言

今天看到一个分享,有讲到使用http请求时踩到了一个坑:服务端 TIME_WAIT 过多导致服务变得不可用。
通过这个案例来复习一下tcp协议,并分析原因和解决问题。

TCP协议

简介

TCP协议面向应用层提供一种面向连接的、可靠的字节流服务,它处于7层网络协议的第4层。

  1. 面向连接意味着两个应用在使用TCP协议交互时要首先建立连接(后续将讲到如何建立连接和关闭连接)
  2. 可靠的服务表明由TCP协议交付给应用的数据是可靠的。可靠性指的是数据能够被 正确地 有序地 传输

协议首部

TCP协议头

协议头部第一行,分别表明了TCP双方的端口号,结合IP层的源、目的地址IP可唯一确定一个TCP连接双方的信息。
第二行表示当前数据包编号,为TCP传输过程中标记数据编号,它用于TCP提供可靠数据传输时保证数据有序的依据
第三行表示对对方数据包的确认号。值为收到的数据编号+1
第四行,前4位表示TCP协议头部长度(限制了头部最长2^4-1字长,1个字长为32bit),6位保留位之后为6位标志位:

  1. URG 紧急指针有效
  2. ACK 确认序号有效
  3. PSH 接收方应尽快将报文段交给应用层
  4. RST 重建连接
  5. SYN 同步序号发起一个连接
  6. FIN 发送端完成发送任务

第四行后16位为窗口大小,用于TCP数据传输过程中拥塞控制
第五行为校验和与紧急指针。
选项中字段:TODO
数据包可能为空

TCP选项

TCP首部可包含选项部分。每个选项若干字节,00、01 为不带len字节的选项。带len字节的选项第一个字节表示类型,第二个字节表示该选项配置的长度len,后续跟len-2个字节的选项值

  1. 00 选项表结束
  2. 01 无操作
  3. 02 xx 最大报文段长度,后续跟着的xx个字节数据表示长度具体值
  4. 08 0a 时间戳,后续4字节时间戳值,4字节时间戳回显应答

TCP连接

连接过程:三次握手

为了建立TCP连接,客户端程序与服务端程序共进行过程:

这个过程共有3次交互,也被叫做3次握手。

TCP三次握手过程

如图,大写 SYN ACK 表示的TCP协议头中的标志位,小写 req,ack表示TCP协议头中的传输数据编号和确认编号(握手过程中syn标志位一直为1)。

首先看连接过程:

  1. 客户端发起数据传输连接请求,seq值为 x
  2. 服务端收到客户端连接请求,回复确认ACK标志位为1,序号ack=x+1,同时发起服务端到客户端的连接请求,seq值位 y(标志位ACK SYN为1,seq值为y,ack值为x+1
  3. 客户端收到确认请求,建立起 客户端-》服务端连接。并回复服务端连接请求,ACK标志位为1,确认序号ack=y+1,当服务端收到确认序号时,建立起 服务端-》客户端 连接

连接过程中的状态变化:

客户端:

  1. 一开始处于CLOSE状态,当主动发起连接请求后,状态变为SYN_SENT
  2. 当服务端收到请求并回复ACK,客户端收到返回包并回复服务端ACK后,状态由SYN_SENT变为ESTABLISHED,客户端-》服务端 连接建立

服务端:

  1. 一开始处于CLOSE状态,当被动接受连接请求后,状态变为 SYN_RCVD
  2. 当客户端收到请求并回复ACK,服务端收到ACK后,状态由 SYN_RCVD变为ESTABLISHED,服务端-》客户端连接建立

至此,全双工的TCP连接建立完成,可以进行数据传输

断开过程:四次挥手

断开一个连接需要经过四次交互,这是因为TCP连接是全双工工作,每个数据传输方向需要单独关闭。
当一方完成数据传输时就可以发送一个FIN来终止这个方向的连接,接收FIN的一端收到信号后不再读数据但是仍可以发送数据,可以等数据发送完毕再发FIN信号进行关闭该方向的连接。

TCP四次挥手过程

断开连接时交互过程:

  1. 客户端数据发送完成后,向服务端发送FIN信号表明结束 C->S方向的连接
  2. 服务端收到FIN信号,回复ACK,客户端收到ACKC->S方向连接正式关闭
  3. 服务端数据发送完成向客户端发送 FIN信号关闭S->C方向连接
  4. 客户端收到FIN信号,回复ACK,服务端收到ACKS->C方向连接正式关闭

断开连接过程中的状态变化:

主动断开方(图中的客户端):

  1. 一开始处于ESTABLISHED状态,当主动发起关闭连接请求后状态变为FIN_WAIT_1(等待对端响应)
  2. 当收到确认响应后,状态由FIN_WAIT_1变为FIN_WAIT_2(等待对端关闭连接)
  3. 当收到对端的关闭请求后,状态由FIN_WAIT_2变为TIME_WAIT(等待2个MSL[最大报文寿命,报文在网络传输过程中最大生存时间]超时)
  4. 当经过2个MSL超时后,状态由TIME_WAIT变为CLOSE

被动断开方(图中的服务端):

  1. 一开始处于ESTABLISHED状态,当收到关闭连接请求后,状态变为CLOSE_WAIT
  2. 当服务端数据传输完毕,向对端发起关闭请求连接后,状态变为LAST_ACK
  3. 当收到对端ACK消息后,状态由LAST_ACK变为CLOSE

主动断开方TIME_WAIT存在的必要性:
如果没有2MSL的TIME_WAIT状态,当主动断开方发送的ACK失败时,另一端会重发FIN请求,而这时,如果原来的连接被复用就会出问题。有了TIME_WAIT状态后,主动断开方的ACK应答如果在网络中丢失,那么另一端会重发FIN请求,它只需再次应答即可。当2个MSL时间后仍未收到重发的请求,则认为应答数据包正确到达对端。

TCP可靠性传输

可靠性保证

  1. 应用数据被分割成TCP认为最合适发送的数据段(区别于UDP数据包长度不变),由TCP传给IP的信息单位成为报文段或段,后续将讲到如何确定报文段的长度。
  2. 当TCP发送一个段后,它会启动一个定时器,等待目的端确认收到报文段。若不能及时收到确认将会重发报文段
  3. TCP收到另一端的数据后,它将发送一个确认,这个确认不是立即发送的,通常推迟几分之一秒(捎带ACK,如果同时需要发送数据给对端,将ACK捎带过去)
  4. TCP将保持它的首部和数据的检验和,这是一个端到端的检验和,目的是检测数据在传输过程中的任何变化。如果收到的段和检验和有差错,TCP将丢弃该段,等待发送端超时重传
  5. IP层交付的数据可能是失序的,TCP层会根据收到的数据重新排序,将数据以正确的顺序交付给上层
  6. IP层有可能收到重复数据,TCP会丢弃掉重复的数据
  7. TCP提供流量控制。TCP的对端都有固定大小的缓冲空间,TCP的接收端只允许另一端发送接收端缓冲区所能容纳的数据,这防止较快主机发送到慢主机时导致缓冲区溢出

最大报文段长度

最大报文段长度(MSS)表示TCP往另一端传输数据的最大块数据的长度。当开始建立连接的时候,连接的双方都会告知各自的MSS。MSS的值在TCP协议头的选项部分。

超时重传

内核参数net.ipv4.tcp_timestamps控制tcp数据包里是否带有timestamp,如果带有,则接收到消息的时候会做时间过滤,不符合的会被丢掉。

拥塞控制

滑动窗口

TCP滑动窗口

报文在发送过程中可能的状态:

  1. 已发送
  2. 可发送,但未发送
  3. 可发送,已发送,但未收到ack
  4. 不可发送
    TCP发送数据的时候,会指定滑动窗口大小,如上图,滑动窗口大小为6,首先,看传输过程中,滑动窗口在3-8报文段时的情况。此时,1,2报文段处于状态①,9之后的报文段处于状态④;在窗口3-8内,都是可发送的报文,有处于状态1、2、3的,当客户端收到报文段5的ack时,3-5报文状态变为1,此时滑动窗口向右滑动3个位置,变成6-11。数据传输的过程中,发送窗口不断随着网络情况调整大小。

拥塞控制算法

拥塞控制主要通过一定的算法,不断修改拥塞窗口大小(cwnd,congestion window)达到目的。主要用到以下几种算法:

  1. 慢启动
  2. 拥塞避免
  3. 快速重传: 当发送数据端连续收到3次同一个数据包ack时,进入快速重传。此时 ssthresh值设为当前cwnd的一半,
慢启动

cwnd从初始值1开始,每收到1个报文段ACK,cwnd增加1.(数据并发发送,在一定时间内如果无丢包,cwnd几乎呈指数级增加。起始值为1,收到这个包的ACK后cwnd+1,下次发送2个数据包,当2个数据包收到ACK后cwnd+2,再下次发送4个数据包,以此类推)
当cwnd值增加到ssthresh大小时,进入拥塞避免。

进入慢启动过程有两种情况:

  1. 刚刚建立连接,开始发送数据: 刚开始时,ssthresh值由连接双方协商,cwnd值为1。然后不断增加cwnd值,到达ssthresh值大小时进入拥塞避免
  2. 链路发生拥塞,超时未收到ack: 发生拥塞时,ssthresh值变为cwnd一半,cwnd值变为1。然后通过慢启动算法不断增加cwnd,到达ssthresh值大小时进入拥塞避免
拥塞避免

当cwnd的值到达慢启动ssthresh的值后,每个RTT(Round Trip Time,往返时延)cwnd+1。在拥塞避免阶段cwnd值线性增加

进入拥塞避免有两种情况:

  1. 由慢启动过程的2中情况转入拥塞避免:在慢启动中已列出2种情况
  2. 由快速重传状态转入拥塞避免:发生快速重传时,ssthresh值大小变为cwnd的一半,cwnd值也变为原来的一半(此时ssthresh与cwnd一样大)。

拥塞避免最后转换为2种状态:

  1. 变为慢启动: 当发生超时时,状态转换为慢启动。
  2. 变为快速重传: 当发生连续收到3个相同ack时,状态转换为快速重传
快速重传

进入快速重传的情况:

  1. 连续收到3次同一个数据包的ack:收到3次ack包很可能是由丢包引起的(在数据接收端,当数据包N丢失后,后续收到N+1,N+2的数据包时都会重复向数据发送端发送对N-1的ACK包。如果2次重复ACK,有可能是数据包乱序到达,3次重复ACK,很可能是丢包,4次,很大可能丢包。但是判断丢包用的重复ack次数值越大,对网络性能影响越大。折中取了3次)
图示
  1. 状态转换
    拥塞控制状态转换
  2. 状态转换实例图
    拥塞控制状态转换实例