前言
首先需要明確的是 TCP 是一個可靠傳輸協議,它的所有特點最終都是為了這個可靠傳輸服務。在網上看到過很多文章講 TCP 連接的三次握手
和斷開連接的四次揮手
,但是都太過于理論,看完感覺總是似懂非懂。反復思考過后,覺得我自己還是偏工程型的人,要學習這些理論性的知識,最好的方式還是要通過實際案例來理解,這樣才會具象深刻。本文通過 Wireshark 抓包來分析 TCP 三次握手
和四次揮手
,如果你也對這些理論感覺似懂非懂,那么強烈建議你也結合抓包實踐來強化理解這些理論性的知識。
三次握手
TCP 建立連接的三次握手是連接的雙方協商確認一些信息(Sequence number、Maximum Segment Size、Window Size 等),Sequence number 有兩個作用:一個是 SYN 標識位為 1 時作為初始序列號(ISN),則實際第一個數據字節的序列號和相應 ACK 中的確認號就是這個序列號加 1;另一個是 SYN 標識位為 0 時,則是當前會話的 segment(傳輸層叫 segment,網絡層叫 packet,數據鏈路層叫 frame)的第一個數據字節的累積序列號。Maximum Segment Size 簡稱 MSS,表示最大一個 segment 中能傳輸的信息(不含 TCP、IP 頭部)。Window Size 表示發送方接收窗口的大小。下面看看我在本地訪問博客 mghio 的三次握手過程:
圖中三個小紅框表示與服務器建立連接的三次握手。
- 第一步,client 端(這個示例也就是瀏覽器)發送 SYN 到 server 端;
- 第二步,server 端收到 SYN 消息后,回復 SYN + ACK 到client 端,ACK 表示已經收到了 client 的 SYN 消息;
- 第三步,client 端收到回復 SYN + ACK 后,也回復一個 ACK 表示收到了 server 端的 SYN + ACK 了,其實
到這一步,client 端的 60469 端口已經是 ESTABLISHED 狀態了。
可以看到,其實三次握手的核心目的就是雙方互相告知對象自己的 Sequence number,藍框是 client 端的初始 Sequence number 和 client 端回復的 ACK,綠框是 server 端的初始 Sequence number 和 client 端回復的 ACK。這樣協商好初始 Sequence number 后,發送數據包時發送端就可以判斷丟包和進行丟包重傳了。
三次握手還有一個目的是協商一些信息(上圖中黃色方框是 Maximum Segment Size,粉色方框是 Window Size)。
到這里,就可以知道平常所說的建立TCP連接
本質是為了實現 TCP 可靠傳輸做的前置準備工作,實際上物理層并沒有這個連接在那里。TCP 建立連接之后時擁有和維護一些狀態信息,這個狀態信息就包含了 Sequence number、MSS、Window Size 等,TCP 握手就是協商出來這些初始值。而這些狀態才是我們平時所說的 TCP 連接的本質。因為這個太重要了,我還要再次強調一下,TCP 是一個可靠傳輸協議,它的所有特點最終都是為了這個可靠傳輸服務。
四次揮手
下面再來看看,當關閉瀏覽器頁面是發生斷開連接的四次揮手過程:
相信你已經發現了,上圖抓包抓到的不是四次揮手,而是三次揮手,這是為何呢?
這是由于 TCP 的時延機制(因為系統內核并不知道應用能不能立即關閉),當被揮手端(這里是 server 的 443 端口)第一次收到揮手端(這里是 client 的 63612 端口)的 FIN 請求時,并不會立即發送 ACK,而是會經過一段延遲時間后再發送,但是此時被揮手端也沒有數據發送,就會向揮手端發送 FIN 請求,這里就可能造成被揮手端發送的 FIN 與 ACK 一起被揮手端收到,導致出現第二、三次揮手合并為一次的現象,也就最終呈現出“三次揮手”的情況。
斷開連接四次揮手分為如下四步(假設沒有出現揮手合并的情況):
- 第一步,client 端主動發送 FIN 包給 server 端;
- 第二步,server 端回復 ACK(對應第一步 FIN 包的 ACK)給 client,表示 server 知道 client 端要斷開了;
- 第三步,server 端發送 FIN 包給 client 端,表示 server 端也沒有數據要發送了,可以斷開了;
- 第四步,client 端回復 ACK 包給 server 端,表示既然雙發都已發送 FIN 包表示可以斷開,那么就真的斷開了啊。
下面是 TCP 連接流轉狀態圖(其中 CLOSED 狀態是虛擬的,實際上并不存在),這個圖很重要,記住這個圖后基本上所有的 TCP 網絡問題就可以解決。
其中比較難以理解的是 TIME_WAIT 狀態,主動關閉的那一端會經歷這個狀態。這一端停留在這個狀態的最長時間是 Maximum segment lifetime(MSL)的 2 倍,大部分時候被簡稱之為 2MSL。存在 TIME_WAIT 狀態有如下兩個原因:
- 要可靠的實現 TCP 全雙工連接終止;
- 讓老的重復 segment 在網絡中消失(一個 sement 在網絡中存活的最長時間為 1 個 MSL,一來一回就是 2 MSL);
為什么握手是三次,而揮手是四次?
嘿嘿,這是個經典的面試題,其實大部分人都背過揮手是四次的原因:因為 TCP 是全雙工(雙向)的,所以回收需要四次......。但是再反問下:握手也是雙向的,但是為什么是只要三次呢?
網上流傳的資料都說 TCP 是雙向的,所以回收需要四次,但是握手也是雙向(握手雙方都在告知對方自己的初始 Sequence number),那么為什么就不用四次握手呢?所以凡事需要多問幾個為什么,要有探索和懷疑精神。
你再仔細回看上面三次握手的第二步(SYN + ACK),其實是可以拆分為兩步的:第一步回復 ACK,第二步再發 SYN 也是完全可以的,只是效率會比較低,這樣的話三次握手不也變成四次握手了。
看起來四次揮手主要是收到第一個 FIN 包后單獨回復了一個 ACK 包這里多了一次,如果能像握手那樣也回復 FIN + ACK 那么四次揮手也就變成三次了。這里再貼一下上面這個揮手的抓包圖:
這個圖中第二個紅框就是 server 端回復的 FIN + ACK 包,這樣四次揮手變成三次了(如果一個包算一次的話)。這里使用四次揮手原因主要是:被動關閉端在收到 FIN 后,知道主動關閉端要關閉了,然后系統內核層會通知應用層要關閉,此時應用層可能還需要做些關閉前的準備工作,可能還有數據沒發送完,所以系統內核先回復一個 ACK 包,然后等應用層準備好了主動調 close 關閉時再發 FIN 包。
而握手過程中就沒有這個準備過程了,所以可以立即發送 SYN + ACK(在這里的兩步合成一步了,提高效率)。揮手過程中系統內核在收到對方的 FIN 后,只能 ACK,不能主動替應用來 FIN,因為系統內核并不知道應用能不能立即關閉。
總結
TCP 是一個很復雜的協議,為了實現可靠傳輸以及處理各種網絡傳輸中的 N 多問題,有一些很經典的解決方案,比如其中的網絡擁塞控制算法、滑動窗口、數據重傳等。強烈建議你去讀一下 rfc793 和 TCP/IP 詳解 卷1:協議 這本書。
如果你是那些純看理論就能掌握好一門技能,然后還能舉三反一的人,那我很佩服你;如果不是,那么學習理論知識注意要結合實踐來強化理解理論,要經過反反復復才能比較好地掌握一個知識,講究技巧,必要時要學會通過工具來達到目的。
最后 TCP 所有特性基本上核心都是為了實現可靠傳輸這個目標來服務的,然后有一些是出于優化性能的目的。