溫故而知新

上一章節(jié)我們了解到了http的請(qǐng)求鏈接組成以及基本發(fā)展歷程,今天我們來說說數(shù)據(jù)傳輸?shù)囊恍┕适拢?/p>
通過本章節(jié),我們將明白:為什么說TCP是一個(gè)「面向連接的」、「可靠的」、「基于字節(jié)流的」傳輸層協(xié)議。
傳輸層的誕生
下三層「物理層、數(shù)據(jù)鏈路層、網(wǎng)絡(luò)層」為我們提供了一種保障,只要兩個(gè)站點(diǎn)之間的網(wǎng)絡(luò)是通暢的,那么我就可以從一端發(fā)送數(shù)據(jù)到另一端,這三層居功至偉,是網(wǎng)絡(luò)傳輸?shù)?b>基石。
假設(shè)在客戶端和下三層之間添加一層,只負(fù)責(zé)轉(zhuǎn)發(fā)客戶端的請(qǐng)求和服務(wù)器的響應(yīng)。我們稱之為「?jìng)鬏攲印埂?/p>
傳輸層站在第四層,利用下三層所做的鋪墊,隨心所欲地發(fā)送數(shù)據(jù),而不必?fù)?dān)心找不到對(duì)方了。
此時(shí),傳輸層相當(dāng)于什么也沒做,只是負(fù)責(zé)轉(zhuǎn)發(fā),起到一個(gè)中介的作用,但是問題來了:
問題一:數(shù)據(jù)包來自何處,又去往哪里
下三層協(xié)議只能把數(shù)據(jù)包從一個(gè)主機(jī)搬到另外一臺(tái)主機(jī),但是,到了目的地以后,數(shù)據(jù)包具體交給哪個(gè)程序(進(jìn)程)呢?
所以,需要把通信的進(jìn)程區(qū)分開來,于是就需要給每個(gè)進(jìn)程分配一個(gè)數(shù)字編號(hào)「端口號(hào)」。
并在要發(fā)送的數(shù)據(jù)包上,增加了傳輸層的頭部,「源IP」、「源端口號(hào)」與「目標(biāo)IP」「目標(biāo)端口號(hào)」。
OK,數(shù)據(jù)包就知道自己從哪里來,到哪里去了。這樣就將原本主機(jī)到主機(jī)的通信,升級(jí)為了進(jìn)程和進(jìn)程之間的通信。
就這樣,數(shù)據(jù)傳輸層又成了數(shù)據(jù)包的搬運(yùn)工。但很快,有新的問題出現(xiàn)了......
問題二:丟包啦......
由于網(wǎng)絡(luò)的不可靠,數(shù)據(jù)包可能在半路丟失,而 A 和 B 卻無法察覺。
對(duì)于丟包問題,只要解決兩個(gè)事就好了。
第一個(gè),A 怎么知道包丟了?
答案:讓 B 告訴 A
第二個(gè),丟了的包怎么辦?
答案:重傳
于是有了新的方案,A 每發(fā)一個(gè)包,都必須收到來自 B 的確認(rèn)(ACK),再發(fā)下一個(gè),否則在一定時(shí)間內(nèi)沒有收到確認(rèn),就重傳這個(gè)包。
這就是停止等待協(xié)議。只要按照這個(gè)協(xié)議來,雖然 A 無法保證 B 一定能收到包,但 A 能夠確認(rèn) B 是否收到了包,收不到就重試,盡最大努力讓這個(gè)通信過程變得可靠,于是你們現(xiàn)在的通信過程又有了一個(gè)新的特征,可靠交付。
問題三:停止等待引發(fā)的效率低下
停止等待雖然能解決問題,但是效率太低了,A 原本可以在發(fā)完第一個(gè)數(shù)據(jù)包之后立刻開始發(fā)第二個(gè)數(shù)據(jù)包,但由于停止等待協(xié)議,A 必須等數(shù)據(jù)包到達(dá)了 B ,且 B 的 ACK 包又回到了 A,才可以繼續(xù)發(fā)第二個(gè)數(shù)據(jù)包,這效率慢得可不是一點(diǎn)兩點(diǎn)。
于是你對(duì)這個(gè)過程進(jìn)行了改進(jìn),采用流水線的方式,不再傻傻地等。
問題四:復(fù)雜網(wǎng)絡(luò)/流水線引發(fā)的順序問題
但是網(wǎng)路是復(fù)雜的、不可靠的。
有的時(shí)候 A 發(fā)出去的數(shù)據(jù)包,分別走了不同的路由到達(dá) B,可能無法保證和發(fā)送數(shù)據(jù)包時(shí)一樣的順序。
首先我們要明確,如果是依靠停止等待協(xié)議,是沒有亂序一說的。
在流水線中有多個(gè)數(shù)據(jù)包和ACK包在亂序流動(dòng),他們之間對(duì)應(yīng)關(guān)系就亂掉了。
難道還回到停止等待協(xié)議?A 每收到一個(gè)包的確認(rèn)(ACK)再發(fā)下一個(gè)包,那就根本不存在順序問題。應(yīng)該有更好的辦法!
A 在發(fā)送的數(shù)據(jù)包中增加一個(gè)序號(hào)(seq),同時(shí) B 要在 ACK 包上增加一個(gè)確認(rèn)號(hào)(ack),這樣不但解決了停止等待協(xié)議的效率問題,也通過這樣標(biāo)序號(hào)的方式解決了順序問題。
而 B 這個(gè)確認(rèn)號(hào)意味深長(zhǎng):比如 B 發(fā)了一個(gè)確認(rèn)號(hào)為 ack = 3,它不僅僅表示 A 發(fā)送的序號(hào)為 2 的包收到了,還表示 2 之前的數(shù)據(jù)包都收到了。這種方式叫累計(jì)確認(rèn)或累計(jì)應(yīng)答。
注意,實(shí)際上 ack 的號(hào)是收到的最后一個(gè)數(shù)據(jù)包的序號(hào) seq + 1,也就是告訴對(duì)方下一個(gè)應(yīng)該發(fā)的序號(hào)是多少。但圖中為了便于理解,ack 就表示收到的那個(gè)序號(hào),不必糾結(jié)。
問題五:流量問題「請(qǐng)求過快,服務(wù)器忙不過來」
有的時(shí)候,A 發(fā)送數(shù)據(jù)包的速度太快,而 B 的接收能力不夠,但 B 卻沒有告知 A 這個(gè)情況。
怎么解決呢?
同上述確認(rèn)機(jī)制不同,確認(rèn)機(jī)制是B告訴A數(shù)據(jù)包是否收到了,在這里,B并不是因?yàn)槭詹坏綌?shù)據(jù)包,而是接受的速度同A發(fā)送的速度不匹配,造成B壓力過大,最終崩潰。這個(gè)時(shí)候的讓B告訴A自己的能力,否則就真的得累死了。
于是 B 決定,每次發(fā)送數(shù)據(jù)包給 A 時(shí),順帶傳過來一個(gè)值,叫窗口大小(win),這個(gè)值就表示 B 的接收能力。同理,每次 A 給 B 發(fā)包時(shí)也帶上自己的窗口大小,表示 A 的接收能力。
B 告訴了 A 自己的窗口大小值,A 怎么利用它去做 A 這邊發(fā)包的流量控制呢?
很簡(jiǎn)單,假如 B 給 A 傳過來的窗口大小 win = 5,那 A 根據(jù)這個(gè)值,把自己要發(fā)送的數(shù)據(jù)分成這么幾類。
1、A會(huì)將數(shù)據(jù)包分成已發(fā)送成功和未發(fā)送兩撥。當(dāng)A收到B發(fā)送過來的窗口大小win = 5 的時(shí)候,A將從已發(fā)送成功的最后一個(gè)位置開始,發(fā)送B指定的窗口大小的數(shù)量的數(shù)據(jù)包,并將狀態(tài)更新成已發(fā)送未確認(rèn)或未發(fā)送可發(fā)送;
2、發(fā)送數(shù)據(jù)包,更新數(shù)據(jù)包狀態(tài),已發(fā)數(shù)據(jù)包更新成已發(fā)送未確認(rèn);
有點(diǎn)像分頁。
當(dāng) A 不斷發(fā)送數(shù)據(jù)包時(shí),已發(fā)送的最后一個(gè)序號(hào)就往右移動(dòng),直到碰到了窗口的上邊界,此時(shí) A 就無法繼續(xù)發(fā)包,達(dá)到了流量控制。
但是當(dāng) A 不斷發(fā)包的同時(shí),A 也會(huì)收到來自 B 的確認(rèn)包,此時(shí)整個(gè)窗口會(huì)往右移動(dòng),因此上邊界也往右移動(dòng),A 就能發(fā)更多的數(shù)據(jù)包了。
以上都是在窗口大小不變的情況下,而 B 在發(fā)給 A 的 ACK 包中,每一個(gè)都可以重新設(shè)置一個(gè)新的窗口大小,如果 A 收到了一個(gè)新的窗口大小值,A 會(huì)隨之調(diào)整。
如果 A 收到了比原窗口值更大的窗口大小,比如 win = 6,則 A 會(huì)直接將窗口上邊界向右移動(dòng) 1 個(gè)單位。
如果 A 收到了比原窗口值小的窗口大小,比如 win = 4,則 A 暫時(shí)不會(huì)改變窗口大小,更不會(huì)將窗口上邊界向左移動(dòng),而是等著 ACK 的到來,不斷將左邊界向右移動(dòng),直到窗口大小值收縮到新大小為止。
OK,終于將流量控制問題解決得差不多了,你看著上面一個(gè)個(gè)小動(dòng)圖,給這個(gè)窗口起了一個(gè)更生動(dòng)的名字,滑動(dòng)窗口。
問題六:擁塞問題
但有的時(shí)候,不是 B 的接受能力不夠,而是網(wǎng)絡(luò)不太好,造成了網(wǎng)絡(luò)擁塞。
擁塞控制與流量控制有些像,但流量控制是受 B 的接收能力影響,而擁塞控制是受網(wǎng)絡(luò)環(huán)境的影響。
擁塞控制的解決辦法依然是通過設(shè)置一定的窗口大小,只不過,流量控制的窗口大小是 B 直接告訴 A 的,而擁塞控制的窗口大小按理說就應(yīng)該是網(wǎng)絡(luò)環(huán)境主動(dòng)告訴 A。
但網(wǎng)絡(luò)環(huán)境怎么可能主動(dòng)告訴 A 呢?只能 A 單方面通過試探,不斷感知網(wǎng)絡(luò)環(huán)境的好壞,進(jìn)而確定自己的擁塞窗口的大小。
假如擁塞窗口的大小為 ?cwnd,上一部分流量控制的滑動(dòng)窗口的大小為 rwnd,那么窗口的右邊界受這兩個(gè)值共同的影響,需要取它倆的最小值。
窗口大小 = min(cwnd, rwnd)
含義很容易理解,當(dāng) B 的接受能力比較差時(shí),即使網(wǎng)絡(luò)非常通暢,A 也需要根據(jù) B 的接收能力限制自己的發(fā)送窗口。當(dāng)網(wǎng)絡(luò)環(huán)境比較差時(shí),即使 B 有很強(qiáng)的接收能力,A 也要根據(jù)網(wǎng)絡(luò)的擁塞情況來限制自己的發(fā)送窗口。正所謂受其短板的影響嘛~
問題七:連接問題
我們先來看一個(gè)動(dòng)圖
有的時(shí)候,B 主機(jī)的相應(yīng)進(jìn)程還沒有準(zhǔn)備好或是掛掉了,A 就開始發(fā)送數(shù)據(jù)包,導(dǎo)致了浪費(fèi)。
這個(gè)問題在于,A 在跟 B 通信之前,沒有事先確認(rèn) B 是否已經(jīng)準(zhǔn)備好,就開始發(fā)了一連串的信息。就好比是老師在辛苦講課,下面的同學(xué)在睡覺開小差,導(dǎo)致老師的課都白講了......
該怎么讓資源更好的利用起來呢,杜絕浪費(fèi):
三次握手建立連接
實(shí)現(xiàn)A和B的正常通信,主要有兩點(diǎn):
1、證明A到B的通信正常;
2、證明B到A的通信正常;
想要實(shí)現(xiàn)著兩個(gè)功能,需要怎么做呢?
1、A向B發(fā)送連接請(qǐng)求,說明要建立連接了;(第一次握手)
2、B回復(fù)A,你的消息我收到了;(第二次握手)
3、B向A發(fā)送請(qǐng)求,你看看我發(fā)的消息能收到不;(第三次握手);
4、A向B發(fā)送請(qǐng)求,你的消息我收到了;(第四次握手)
綜上,是通過四次握手實(shí)現(xiàn)鏈接的,但是由于確認(rèn)消息體積很小,可以將第二步和第三步合并成一步,B發(fā)送A鏈接的同時(shí)將確認(rèn)消息捎帶上,這就成了三次握手。

A -> B [SYN] Seq=0 Win=64240 Len=0
????????????????????????MSS=1460 WS=256
B - >A [SYN, ACK] Seq=0 Ack=1 Win=29200 Len=0
????????????????????????MSS=1424 WS=512
A -> B [ACK] Seq=1 Ack=1 Win=132352 Len=0
四次揮手?jǐn)嚅_鏈接

B -> A [FIN, ACK] Seq=8 Ack=4096 Win=37888 Len=0
A -> B [ACK] Seq=4096 Ack=9 Win=132352 Len=0
A -> B [FIN, ACK] Seq=4096 Ack=9 Win=132352 Len=0
B -> A [ACK] Seq=4096 Ack=9 Win=37888 Len=0
同建立連接不同的是,B在請(qǐng)求斷開連接的時(shí)候有肯能A還有沒有發(fā)送的數(shù)據(jù),所以握手的確認(rèn)消息不能捎帶,所以需要四次握手。
小結(jié)
? ? ?TCP 在建立連接時(shí),需要告訴對(duì)方 MSS(最大報(bào)文段大?。┮簿褪钦f,如果要發(fā)送的數(shù)據(jù)很大,在 TCP 層是需要按照 MSS 來切割成一個(gè)個(gè)的TCP 報(bào)文段?的。切割的時(shí)候不管你原來的數(shù)據(jù)表示什么意思,需要在哪里斷句啥的,我就把它當(dāng)成一串毫無意義的字節(jié),在我想要切割的地方咔嚓就來一刀,標(biāo)上序號(hào),只要接收方再根據(jù)這個(gè)序號(hào)拼成最終想要的完整數(shù)據(jù)就行了。在我TCP 傳輸這里,會(huì)把它當(dāng)做一個(gè)個(gè)的字節(jié),也就是基于字節(jié)流的含義了。
OK,到這里大家應(yīng)該明白為什么說TCP是面向連接的、可靠的、基于字節(jié)流的傳輸層通信協(xié)議了吧。
如有問題,歡迎留言討論!
附錄
1、TCP協(xié)議頭
2、上述關(guān)鍵名稱的含義
SYN:同步序列編號(hào)(Synchronize Sequence Numbers)。是TCP/IP建立連接時(shí)使用的握手信號(hào)。在客戶機(jī)和服務(wù)器之間建立正常的TCP網(wǎng)絡(luò)連接時(shí),客戶機(jī)首先發(fā)出一個(gè)SYN消息,服務(wù)器使用SYN+ACK應(yīng)答表示接收到了這個(gè)消息,最后客戶機(jī)再以ACK消息響應(yīng)。這樣在客戶機(jī)和服務(wù)器之間才能建立起可靠的TCP連接,數(shù)據(jù)才可以在客戶機(jī)和服務(wù)器之間傳遞。
ACK:(Acknowledge character)即是確認(rèn)字符,在數(shù)據(jù)通信中,接收站發(fā)給發(fā)送站的一種傳輸類控制字符。表示發(fā)來的數(shù)據(jù)已確認(rèn)接收無誤
FIN:關(guān)閉連接的信號(hào)標(biāo)示(https://www.cnblogs.com/borey/p/5626124.html)。
URG:(緊急位)急指針是一個(gè)正的偏移量,和序號(hào)字段中的值相加表示緊急數(shù)據(jù)最后一個(gè)字節(jié)的序號(hào)。TCP的緊急方式是發(fā)送端向另一端發(fā)送緊急數(shù)據(jù)的一種方式。緊急指針指向包內(nèi)數(shù)據(jù)段的某個(gè)字節(jié)(數(shù)據(jù)從第一字節(jié)到指針?biāo)缸止?jié)就是緊急數(shù)據(jù),不進(jìn)入接收緩沖就直接交給上層進(jìn)程,余下的數(shù)據(jù)要進(jìn)入接收緩沖的)
PSH:(push)表示有 DATA數(shù)據(jù)傳輸
RST:表示連接重置
參考文檔:
1、《HTTP權(quán)威指南》
2、圖解TCP