本文原題“你管這破玩意兒叫TCP?”,由閃客sun分享,轉載請聯系作者。
1、引言
網絡編程能力對于即時通訊技術開發者來說是基本功,而計算機網絡又是網絡編程的理論根基,因而深刻準確地理解計算機網絡知識顯然能夯實你的即時通訊應用的實踐品質。
本文風格類似于《網絡編程懶人入門》、《腦殘式網絡編程入門》兩個系列,但通俗又不失內涵,簡潔又不簡陋,非常適合對計算機網絡知識有向往但又有懼怕的網絡編程愛好者們閱讀,希望能給你帶來不一樣的網絡知識入門視角。
本篇將運用通俗易懂的語言,配上細致精確的圖片動畫,循序漸進地引導你理解TCP協議的主要特性和技術原理,讓TCP協議的學習不再如此枯燥和生澀,非常適合入門者閱讀。
本文已同步發布于“即時通訊技術圈”公眾號,歡迎關注。公眾號上的鏈接是:點此進入。
2、系列文章
本文是該系列文章中的第2篇:
本文主要涉及計算機網絡的傳輸層,希望讓TCP協議的學習不再枯燥和生澀。
3、初識傳輸層
你是一臺電腦,你的名字叫 A。
經過上篇《假如你來設計網絡,會怎么做?》的一番折騰,只要你知道另一位伙伴 B 的 IP 地址,且你們之間的網絡是通的,無論多遠,你都可以將一個數據包發送給你的伙伴 B。
上篇中分享的這就是物理層、數據鏈路層、網絡層這三層所做的事情。
站在第四層的你,就可以不要臉地利用下三層所做的鋪墊,隨心所欲地發送數據,而不必擔心找不到對方了。
雖然你此時還什么都沒干,但你還是給自己這一層起了個響亮的名字,叫做傳輸層。
你本以為自己所在的第四層萬事大吉,啥事沒有,但很快問題就接踵而至。
4、問題來了
前三層協議只能把數據包從一個主機搬到另外一臺主機,但是到了目的地以后,數據包具體交給哪個程序(進程)呢?
所以:你需要把通信的進程區分開來,于是就給每個進程分配一個數字編號,你給它起了一個響亮的名字:端口號。
然后:你在要發送的數據包上,增加了傳輸層的頭部:源端口號與目標端口號。
OK,這樣你將原本主機到主機的通信,升級為了進程和進程之間的通信。
你沒有意識到,你不知不覺實現了UDP協議!
當然 UDP 協議中不光有源端口和目標端口,還有數據包長度和校驗值,我們暫且略過。
就這樣,你用 UDP 協議無憂無慮地同 B 進行著通信,一直沒發生什么問題。
但很快,你發現事情變得非常復雜 ... ...
5、丟包問題
由于網絡的不可靠,數據包可能在半路丟失,而 A 和 B 卻無法察覺。
對于丟包問題,只要解決兩個事就好了。
第一個:A 怎么知道包丟了?
答案是:讓 B 告訴 A。
第二個:丟了的包怎么辦?
答案是:重傳。
于是你設計了如下方案:A 每發一個包,都必須收到來自 B 的確認(ACK),再發下一個,否則在一定時間內沒有收到確認,就重傳這個包。
你管它叫停止等待協議。
只要按照這個協議來,雖然 A 無法保證 B 一定能收到包,但 A 能夠確認 B 是否收到了包,收不到就重試,盡最大努力讓這個通信過程變得可靠,于是你們現在的通信過程又有了一個新的特征,可靠交付。
6、效率問題
停止等待雖然能解決問題,但是效率太低了。
A 原本可以在發完第一個數據包之后立刻開始發第二個數據包,但由于停止等待協議,A 必須等數據包到達了 B ,且 B 的 ACK 包又回到了 A,才可以繼續發第二個數據包。這效率慢得可不是一點兩點。
于是:你對這個過程進行了改進,采用流水線的方式,不再傻傻地等。
7、順序問題
但是網路是復雜的、不可靠的。
這導致的問題是:有的時候 A 發出去的數據包,分別走了不同的路由到達 B,可能無法保證和發送數據包時一樣的順序。
對應于我們的例子:在流水線中有多個數據包和ACK包在亂序流動,他們之間對應關系就亂掉了。
如果回到上面的停止等待協議,那么A 每收到一個包的確認(ACK)再發下一個包,那就根本不存在順序問題。但,應該有更好的辦法吧?
是的,更好的辦法就是:A 在發送的數據包中增加一個序號(seq),同時 B 要在 ACK 包上增加一個確認號(ack)。這樣不但解決了停止等待協議的效率問題,也通過這樣標序號的方式解決了順序問題。
而 B 這個確認號意味深長:比如 B 發了一個確認號為 ack = 3,它不僅僅表示 A 發送的序號為 2 的包收到了,還表示 2 之前的數據包都收到了。這種方式叫累計確認或累計應答。
注意:實際上 ack 的號是收到的最后一個數據包的序號 seq + 1,也就是告訴對方下一個應該發的序號是多少。但圖中為了便于理解,ack 就表示收到的那個序號,不必糾結。
8、流量問題
有的時候,A 發送數據包的速度太快,而 B 的接收能力不夠,但 B 卻沒有告知 A 這個情況。
怎么解決呢?
很簡單:B 告訴 A 自己的接收能力,A 根據 B 的接收能力,相應控制自己的發送速率就好了。
B 怎么告訴 A 呢?B 跟 A 說"我很強"這三個字么?那肯定不行,得有一個嚴謹的規范。
于是 B 決定:每次發送數據包給 A 時,順帶傳過來一個值,叫窗口大小(win),這個值就表示 B 的接收能力。
同理:每次 A 給 B 發包時也帶上自己的窗口大小,表示 A 的接收能力。
B 告訴了 A 自己的窗口大小值,A 怎么利用它去做 A 這邊發包的流量控制呢?
很簡單:假如 B 給 A 傳過來的窗口大小 win = 5,那 A 根據這個值,把自己要發送的數據分成這么幾類。
圖片過于清晰,就不再文字解釋了。
當 A 不斷發送數據包時,已發送的最后一個序號就往右移動,直到碰到了窗口的上邊界,此時 A 就無法繼續發包,達到了流量控制。
但是:當 A 不斷發包的同時,A 也會收到來自 B 的確認包,此時整個窗口會往右移動,因此上邊界也往右移動,A 就能發更多的數據包了。
以上都是在窗口大小不變的情況下。而 B 在發給 A 的 ACK 包中,每一個都可以重新設置一個新的窗口大小,如果 A 收到了一個新的窗口大小值,A 會隨之調整。
如果 A 收到了比原窗口值更大的窗口大小,比如 win = 6,則 A 會直接將窗口上邊界向右移動 1 個單位。
如果 A 收到了比原窗口值小的窗口大小,比如 win = 4,則 A 暫時不會改變窗口大小,更不會將窗口上邊界向左移動,而是等著 ACK 的到來,不斷將左邊界向右移動,直到窗口大小值收縮到新大小為止。
OK,終于將流量控制問題解決得差不多了,你看著上面一個個小動圖,給這個窗口起了一個更生動的名字:滑動窗口。
9、擁塞問題
但有的時候,不是 B 的接受能力不夠,而是網絡不太好,造成了網絡擁塞。
擁塞控制與流量控制有些像,但流量控制是受 B 的接收能力影響,而擁塞控制是受網絡環境的影響。
擁塞控制的解決辦法依然是通過設置一定的窗口大小。只不過,流量控制的窗口大小是 B 直接告訴 A 的,而擁塞控制的窗口大小按理說就應該是網絡環境主動告訴 A。
但網絡環境怎么可能主動告訴 A 呢?只能 A 單方面通過試探,不斷感知網絡環境的好壞,進而確定自己的擁塞窗口的大小。
擁塞窗口大小的計算有很多復雜的算法,就不在本文中展開了(有興趣可以深入閱讀《[通俗易懂]深入理解TCP協議(下):RTT、滑動窗口、擁塞處理》)。
假如擁塞窗口的大小為 cwnd,上一部分流量控制的滑動窗口的大小為 rwnd,那么窗口的右邊界受這兩個值共同的影響,需要取它倆的最小值。
窗口大小 = min(cwnd, rwnd)
含義很容易理解:當 B 的接受能力比較差時,即使網絡非常通暢,A 也需要根據 B 的接收能力限制自己的發送窗口。當網絡環境比較差時,即使 B 有很強的接收能力,A 也要根據網絡的擁塞情況來限制自己的發送窗口。正所謂受其短板的影響嘛~
10、連接問題
有的時候,B 主機的相應進程還沒有準備好或是掛掉了,A 就開始發送數據包,導致了浪費。
這個問題在于:A 在跟 B 通信之前,沒有事先確認 B 是否已經準備好,就開始發了一連串的信息。就好比你和另一個人打電話,你還沒有"喂"一下確認對方有沒有在聽,你就巴拉巴拉說了一堆。
這個問題該怎么解決呢?
地球人都知道:三次握手嘛!
- A:我準備好了(SYN)
- B:我知道了(ACK),我也準備好了(SYN)
- A:我知道了(ACK)
A 與 B 各自在內存中維護著自己的狀態變量,三次握手之后,雙方的狀態都變成了連接已建立(ESTABLISHED)。
雖然就只是發了三次數據包,并且在各自的內存中維護了狀態變量,但這么說總覺得太 low,你看這個過程相當于雙方建立連接的過程,于是你靈機一動,就叫它面向連接吧。
注意:這個連接是虛擬的,是由 A 和 B 這兩個終端共同維護的,在網絡中的設備根本就不知道連接這回事兒!
但凡事有始就有終,有了建立連接的過程,就要考慮釋放連接的過程。
這就是網絡編程中耳熟能詳的四次揮手啦!
- A:再見,我要關閉了(FIN)
- B:我知道了(ACK)。給 B 一段時間把自己的事情處理完...
- B:再見,我要關閉了(FIN)
- A:我知道了(ACK)
11、小結一下
以上講述的,就是 TCP 協議的核心思想,上面過程中需要傳輸的信息,就體現在 TCP 協議的頭部,這里放上最常見的 TCP 協議頭解讀的圖。
不知道你現在再看下面這句話,是否能理解:
TCP 是面向連接的、可靠的、基于字節流的傳輸層通信協議。
“面向連接、可靠”,這兩個詞通過上面的講述很容易理解,那什么叫做基于字節流呢?
很簡單:TCP 在建立連接時,需要告訴對方 MSS(最大報文段大小)。
也就是說:如果要發送的數據很大,在 TCP 層是需要按照 MSS 來切割成一個個的 TCP 報文段 的。
切割的時候我才不管你原來的數據表示什么意思,需要在哪里斷句啥的,我就把它當成一串毫無意義的字節,在我想要切割的地方咔嚓就來一刀,標上序號,只要接收方再根據這個序號拼成最終想要的完整數據就行了。
在我 TCP 傳輸這里,我就把它當做一個個的字節,也就是基于字節流的含義了。
12、寫在最后
一提到 TCP,可能很多人都想起被三次握手和四次揮手所支配的恐懼。
但其實你跟著本文中的思路你就會發現,三次握手與四次揮手只占 TCP 所解決的核心問題中很小的一部分,只是因為它在面試中很適合作為知識點進行考察,所以在很多人的印象中就好像 TCP 的核心就是握手和揮手似的。
本文希望你能從問題出發,真正理解 TCP 所想要解決的問題,你會發現很多原理就好像生活常識一樣順其自然,并不復雜,希望你有收獲~
最后,如果對TCP的理解仍存在疑惑,可以繼續閱讀以下精選的資料:
- 《TCP/IP詳解 - 第17章·TCP:傳輸控制協議》(* 推薦)
- 《TCP/IP詳解 - 第18章·TCP連接的建立與終止》
- 《TCP/IP詳解 - 第21章·TCP的超時與重傳》
- 《通俗易懂-深入理解TCP協議(上):理論基礎》(* 推薦)
- 《通俗易懂-深入理解TCP協議(下):RTT、滑動窗口、擁塞處理》
- 《理論經典:TCP協議的3次握手與4次揮手過程詳解》
- 《理論聯系實際:Wireshark抓包分析TCP 3次握手、4次揮手過程》
- 《網絡編程懶人入門(一):快速理解網絡通信協議(上篇)》
- 《網絡編程懶人入門(二):快速理解網絡通信協議(下篇)》(* 推薦)
- 《網絡編程懶人入門(三):快速理解TCP協議一篇就夠》(* 推薦)
- 《腦殘式網絡編程入門(一):跟著動畫來學TCP三次握手和四次揮手》
本文已同步發布于“即時通訊技術圈”公眾號。
▲ 本文在公眾號上的鏈接是:點此進入。同步發布鏈接是:http://www.52im.net/thread-3339-1-1.html
作者:Jack Jiang (點擊作者姓名進入Github)
出處:http://www.52im.net/space-uid-1.html
交流:歡迎加入即時通訊開發交流群 215891622
討論:http://www.52im.net/
Jack Jiang同時是【原創Java
Swing外觀工程BeautyEye】和【輕量級移動端即時通訊框架MobileIMSDK】的作者,可前往下載交流。
本博文
歡迎轉載,轉載請注明出處(也可前往 我的52im.net 找到我)。