前几天有位读者问我为什么 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