一直以来,对sack都只是有所耳闻,最近产品的一个bug让我比较深入的了解了下。
什么是sack
更准确的说法是s ack,而不是sack,因为全称是selective acknowledgment,翻译过来是选择性确认。tcp头部中的ack确认序列号大家应该都比较了解了,正常情况下确认序列号等于收到的对端报文的序列号+payload length,假如本端发送的确认序列号为x,则说明对端发送的到x为止的数据我都收到了,对端下一个数据包的序列号应也为x。
sack包含两个tcp选项,都放在tcp option中。一个是SACK-Permitted选项,这个选项只会在tcp连接建立的时候使用,也就是双方在三次握手中的前两个包中协商本次连接中是否支持sack;另一个是sack选项,该选项参数告诉对方已经接收到并缓存的不连续的数据块,发送方可根据此信息检查究竟是哪些块丢失,从而发送相应的数据块。受 TCP 包长度限制,tcp option最多只有40字节,一个sack字段的长度一般为10字节,所以TCP包头最多包含四组SACK字段。
基于sack的快速重传
- 超时重传
假如三次握手中出现丢包(还没来得及使用sack),tcp协议会遵循超时重传的原则。tcp协议栈通过某种算法计算出rto,当rto时间内没有收到ack包,则认为丢包,开始超时重传,值得一提的是,超时重传的的时间是按指数递增的,比如第一次是1s,第二次是2s,第三次是4s。 - 快速重传
当TCP 发送端在观测到至少dupthresh(一般为 3)个重复ACK序列号后,即重传可能丢失的数据分组,而不需等待重传计时器超时。sack会选择性确认丢包之后的报文,但是ack不会,所以即使出现丢包触发了sack,ack序列号也不会增加。
开启sack的快速重传如下图所示,红色虚线的包丢失了,客户端收到了连续三个携带sack的ack包,且三个ack包的确认序列号都一样,所以立即重传了100序列号的包。
遇到的问题
最近遇到的一个问题也与sack有关,大概是测试的产品在第三次握手时会对入向数据包tcp头部插入一个字段,所以需要将后续所有入向报文的序列号加上这个字段的长度,然后将出向的确认序列号减去这个字段的长度,这些都没有问题,问题出在入向丢包的时候,会触发服务器的sack,而产品没有对sack的序列号做偏移,导致客户端认为对端确认了还没有发出去的数据,连接出现异常。