大家好,我是小林。
為什麼 TCP 三次握手期間,用戶端和服務端的初始化序列号要求不一樣的呢?
接下來,我一步一步給大家講明白,我覺得應該有不少人會有類似的問題,是以今天在肝一篇!
正文
為什麼 TCP 三次握手期間,為什麼用戶端和服務端的初始化序列号要求不一樣的呢?
主要原因是為了防止曆史封包被下一個相同四元組的連接配接接收。
TCP 四次揮手中的 TIME_WAIT 狀态不是會持續 2 MSL 時長,曆史封包不是早就在網絡中消失了嗎?
是的,如果能正常四次揮手,由于 TIME_WAIT 狀态會持續 2 MSL 時長,曆史封包會在下一個連接配接之前就會自然消失。
但是來了,我們并不能保證每次連接配接都能通過四次揮手來正常關閉連接配接。
假設每次建立連接配接,用戶端和服務端的初始化序列号都是從 0 開始:
圖檔
過程如下:
- 用戶端和服務端建立一個 TCP 連接配接,在用戶端發送資料包被網絡阻塞了,而此時服務端的程序重新開機了,于是就會發送 RST 封包來斷開連接配接。
- 緊接着,用戶端又與服務端建立了與上一個連接配接相同四元組的連接配接;
- 在新連接配接建立完成後,上一個連接配接中被網絡阻塞的資料包正好抵達了服務端,剛好該資料包的序列号正好是在服務端的接收視窗内,是以該資料包會被服務端正常接收,就會造成資料錯亂。
可以看到,如果每次建立連接配接,用戶端和服務端的初始化序列号都是一樣的話,很容易出現曆史封包被下一個相同四元組的連接配接接收的問題。
用戶端和服務端的初始化序列号不一樣不是也會發生這樣的事情嗎?
是的,即使用戶端和服務端的初始化序列号不一樣,也會存在收到曆史封包的可能。
但是我們要清楚一點,曆史封包能否被對方接收,還要看該曆史封包的序列号是否正好在對方接收視窗内,如果不在就會丢棄,如果在才會接收。
如果每次建立連接配接用戶端和服務端的初始化序列号都「不一樣」,就有大機率因為曆史封包的序列号「不在」對方接收視窗,進而很大程度上避免了曆史封包,比如下圖:
相反,如果每次建立連接配接用戶端和服務端的初始化序列号都「一樣」,就有大機率遇到曆史封包的序列号剛「好在」對方的接收視窗内,進而導緻曆史封包被新連接配接成功接收。
是以,每次初始化序列号不一樣能夠很大程度上避免曆史封包被下一個相同四元組的連接配接接收,注意是很大程度上,并不是完全避免了。
那用戶端和服務端的初始化序列号都是随機的,那還是有可能随機成一樣的呀?
RFC793 提到初始化序列号 ISN 随機生成算法:ISN = M + F(localhost, localport, remotehost, remoteport)。
- M是一個計時器,這個計時器每隔4毫秒加1。
- F 是一個 Hash 算法,根據源IP、目的IP、源端口、目的端口生成一個随機數值,要保證 hash 算法不能被外部輕易推算得出。
可以看到,随機數是會基于時鐘計時器遞增的,基本不可能會随機成一樣的初始化序列号。
懂了,用戶端和服務端初始化序列号都是随機生成的話,就能避免連接配接接收曆史封包了。
是的,但是也不是完全避免了。
為了能更好的了解這個原因,我們先來了解序列号(SEQ)和初始序列号(ISN)。
- 序列号,是 TCP 一個頭部字段,辨別了 TCP 發送端到 TCP 接收端的資料流的一個位元組,因為 TCP 是面向位元組流的可靠協定,為了保證消息的順序性和可靠性,TCP 為每個傳輸方向上的每個位元組都賦予了一個編号,以便于傳輸成功後确認、丢失後重傳以及在接收端保證不會亂序。序列号是一個 32 位的無符号數,是以在到達 4G 之後再循環回到 0。
- 初始序列号,在 TCP 建立連接配接的時候,用戶端和服務端都會各自生成一個初始序列号,它是基于時鐘生成的一個随機數,來保證每個連接配接都擁有不同的初始序列号。初始化序列号可被視為一個 32 位的計數器,該計數器的數值每 4 微秒加 1,循環一次需要 4.55 小時。
給大家抓了一個包,下圖中的 Seq 就是序列号,其中紅色框住的分别是用戶端和服務端各自生成的初始序列号。
通過前面我們知道,序列号和初始化序列号并不是無限遞增的,會發生回繞為初始值的情況,這意味着無法根據序列号來判斷新老資料。
不要以為序列号的上限值是 4GB,就以為很大,很難發生回繞。在一個速度足夠快的網絡中傳輸大量資料時,序列号的回繞時間就會變短。如果序列号回繞的時間極短,我們就會再次面臨之前延遲的封包抵達後序列号依然有效的問題。
為了解決這個問題,就需要有 TCP 時間戳。tcp_timestamps 參數是預設開啟的,開啟了 tcp_timestamps 參數,TCP 頭部就會使用時間戳選項,它有兩個好處,一個是便于精确計算 RTT ,另一個是能防止序列号回繞(PAWS)。
試看下面的示例,假設 TCP 的發送視窗是 1 GB,并且使用了時間戳選項,發送方會為每個 TCP 封包配置設定時間戳數值,我們假設每個封包時間加 1,然後使用這個連接配接傳輸一個 6GB 大小的資料流。
32 位的序列号在時刻 D 和 E 之間回繞。假設在時刻B有一個封包丢失并被重傳,又假設這個封包段在網絡上繞了遠路并在時刻 F 重新出現。如果 TCP 無法識别這個繞回的封包,那麼資料完整性就會遭到破壞。
使用時間戳選項能夠有效的防止上述問題,如果丢失的封包會在時刻 F 重新出現,由于它的時間戳為 2,小于最近的有效時間戳(5 或 6),是以防回繞序列号算法(PAWS)會将其丢棄。
防回繞序列号算法要求連接配接雙方維護最近一次收到的資料包的時間戳(Recent TSval),每收到一個新資料包都會讀取資料包中的時間戳值跟 Recent TSval 值做比較,如果發現收到的資料包中時間戳不是遞增的,則表示該資料包是過期的,就會直接丢棄這個資料包。
懂了,用戶端和服務端的初始化序列号都是随機生成,能很大程度上避免曆史封包被下一個相同四元組的連接配接接收,然後又引入時間戳的機制,進而完全避免了曆史封包被接收的問題。
嗯嗯,沒錯。
關注公衆号:「小林coding」 ,回複「我要學習」即可免費獲得「伺服器 Linux C/C++ 」成長路程(書籍資料 + 思維導圖)