为什么TCP建立连接要求发送方和接收方的初始化序列号不一样的呢?

1,276 阅读3分钟

前几天有位读者问我为什么 TCP 建立连接需要三次握手文章中一个问题:

就是他不明白「序列号解决了数据包的缺失和顺序颠倒等问题,但为什么要求发送方和接收方的初始序列号不一样?」

后来,我跟他交流半个小时,终于把他讲明白了。

我觉得应该有不少人会有以下的问题。

1.为什么接收方可以通过序列号对重复的数据包进行去重?

根据上图,发送方向接收方发送seq=1的数据包,接收方收到数据包后,也向发送方发送了ack=seq+1的响应,传输途中发生了丢包的现象。

发送方未收到接收方发送的ACK,在发送方等待了特定时间间隔后依旧没有收到ACK,那么发送方就认为接收方没有收到数据,因此发送方就按照超时重传的方式来处理,如果发生了大量的这样的问题,那么接收方就会收到大量的重复数据。

针对上面出现的问题,TCP需要能够识别出那些重复的包,并把重复的包去掉,这样就必须对包进行去重,可以根据TCP报头中的序列号来进行去重。

2.为什么发送方和接收方的初始化序列号要求不一样的呢?

主要原因是为了防止历史报文被下一个相同四元组的连接接收。

是的,如果能正常四次挥手,由于 TIME_WAIT 状态会持续 2 MSL 时长,历史报文会在下一个连接之前就会自然消失。

但是我们并不能保证每次连接都能通过四次挥手来正常关闭连接。

假设每次建立连接,发送方和接收方的初始化序列号一样,比如都是从 0 开始:

过程如下:

  • 发送方和接收方建立一个 TCP 连接,在发送方发送数据包被网络阻塞了,而此时接收方的进程重启了,于是就会发送 RST 报文来断开连接。

  • 紧接着,发送方又与接收方建立了与上一个连接相同四元组的连接;

  • 在新连接建立完成后,上一个连接中被网络阻塞的数据包正好抵达了接收方,刚好该数据包的序列号正好是在接收方的接收窗口内,所以该数据包会被接收方正常接收,就会造成数据错乱。

可以看到,如果每次建立连接,发送方和接收方的初始化序列号都是一样的话,很容易出现历史报文被下一个相同四元组的连接接收的问题。

那发送方和接收方的初始化序列号都是随机的,那还是有可能随机成一样的呀?

对不起,还真不会。

RFC793 提到初始化序列号 ISN 随机生成算法:ISN = M + F(localhost, localport, remotehost, remoteport)。

  • M是一个计时器,这个计时器每隔4毫秒加1。

  • F 是一个 Hash 算法,根据源IP、目的IP、源端口、目的端口生成一个随机数值,要保证 hash 算法不能被外部轻易推算得出。

可以看到,随机数是会基于时钟计时器递增的,基本不可能会随机成一样的初始化序列号。

参考资料

[^1]Why do we need a 3-way handshake? Why not just 2-way?

networkengineering.stackexchange.com/questions/2…

[^2]rfc793

datatracker.ietf.org/doc/html/rf…