您好,登錄后才能下訂單哦!
TCP(Transportation Control Protocol)協(xié)議與IP協(xié)議是一同產(chǎn)生的。事實(shí)上,兩者最初是一個(gè)協(xié)議,后來才被分拆成網(wǎng)絡(luò)層的IP和傳輸層的TCP。我們已經(jīng)在UDP協(xié)議中介紹過,UDP協(xié)議是IP協(xié)議在傳輸層的“傀儡”,用來實(shí)現(xiàn)數(shù)據(jù)包形式的通信。而TCP協(xié)議則實(shí)現(xiàn)了“流”形式的通信。
TCP的內(nèi)容非常豐富。我不能在一篇文章中將TCP講完。這一篇主要介紹TCP協(xié)議的下面幾個(gè)方面:
“流”通信的意義與實(shí)現(xiàn)方式
如何實(shí)現(xiàn)可靠傳輸
“流”通信
TCP協(xié)議是傳輸層協(xié)議,實(shí)現(xiàn)的是端口到端口(port)的通信。更進(jìn)一步,TCP協(xié)議虛擬了文本流(byte stream)的通信。在Linux文本流中我們談到,計(jì)算機(jī)數(shù)據(jù)的本質(zhì)是有序的0/1序列 (如果以byte為單位,就叫做文本流)。計(jì)算機(jī)的功能就是儲(chǔ)存和處理文本流。CPU + memory + 存儲(chǔ)設(shè)備實(shí)現(xiàn)了文本流在同一臺(tái)計(jì)算機(jī)內(nèi)部的加工處理。通過一些IO,比如屏幕和鍵盤,文本流實(shí)現(xiàn)了人機(jī)交互。而進(jìn)一步,如果網(wǎng)絡(luò)通信可在不同計(jì)算機(jī)之間進(jìn)行文本流的交互,那么我們就和整個(gè)計(jì)算機(jī)系統(tǒng)的數(shù)據(jù)處理方式實(shí)現(xiàn)了對(duì)接。
IP協(xié)議(參考TCP/IP協(xié)議詳解03, 05)和UDP協(xié)議采用的是數(shù)據(jù)包的方式傳送,后發(fā)出的數(shù)據(jù)包可能早到,我們并不能保證數(shù)據(jù)到達(dá)的次序。TCP協(xié)議確保了數(shù)據(jù)到達(dá)的順序與文本流順序相符。當(dāng)計(jì)算機(jī)從TCP協(xié)議的接口讀取數(shù)據(jù)時(shí),這些數(shù)據(jù)已經(jīng)是排列好順序的“流”了。比如我們有一個(gè)大文件要從本地主機(jī)發(fā)送到遠(yuǎn)程主機(jī),如果是按照“流”接收到的話,我們可以一邊接收,一邊將文本流存入文件系統(tǒng)。這樣,等到“流”接收完了,硬盤寫入操作也已經(jīng)完成。如果采取UDP的傳輸方式,我們需要等到所有的數(shù)據(jù)到達(dá)后,進(jìn)行排序,才能組裝成大的文件。這種情況下,我們不得不使用大量的計(jì)算機(jī)資源來存儲(chǔ)已經(jīng)到達(dá)的數(shù)據(jù),直到所有數(shù)據(jù)都達(dá)到了,才能開始處理。
“流”的要點(diǎn)是次序(order),然而實(shí)現(xiàn)這一點(diǎn)并不簡單。TCP協(xié)議是基于IP協(xié)議的,所以最終數(shù)據(jù)傳送還是以IP數(shù)據(jù)包為單位進(jìn)行的。如果一個(gè)文本流很長的話,我們不可能將整個(gè)文本流放入到一個(gè)IP數(shù)據(jù)包中,那樣有可能會(huì)超過MTU。所以,TCP協(xié)議封裝到IP包的不是整個(gè)文本流,而是TCP協(xié)議所規(guī)定的片段(segment)。與之前的一個(gè)IP或者UDP數(shù)據(jù)包類似,一個(gè)TCP片段同樣分為頭部(header)和數(shù)據(jù)(payload)兩部分 (“片段”這個(gè)名字更多是起提醒作用:嘿,這里并不是完整的文本流)。整個(gè)文本流按照次序被分成小段,而每一段被放入TCP片段的數(shù)據(jù)部分。一個(gè)TCP片段封裝成的IP包不超過整個(gè)IP接力路徑上的最小MTU,從而避免令人痛苦的碎片化(fragmentation)。
(給文本流分段是在發(fā)送主機(jī)完成的,而碎片化是在網(wǎng)絡(luò)中的路由器完成的。路由器要處理許多路的通信,所以相當(dāng)繁忙。文本流提前在發(fā)送主機(jī)分好段,可以避免在路由器上執(zhí)行碎片化,可大大減小網(wǎng)絡(luò)負(fù)擔(dān))
片段與編號(hào)
TCP片段的頭部(header)會(huì)存有該片段的序號(hào)(sequence number)。這樣,接收的計(jì)算機(jī)就可以知道接收到的片段在原文本流中的順序了,也可以知道自己下一步需要接收哪個(gè)片段以形成流。比如已經(jīng)接收到了片段1,片段2,片段3,那么接收主機(jī)就開始期待片段4。如果接收到不符合順序的數(shù)據(jù)包(比如片段8),接收方的TCP模塊可以拒絕接收,從而保證呈現(xiàn)給接收主機(jī)的信息是符合次序的“流”。
可靠性
片段編號(hào)這個(gè)初步的想法并不能解決我們所有的問題。IP協(xié)議是不可靠的,所以IP數(shù)據(jù)包可能在傳輸過程中發(fā)生錯(cuò)誤或者丟失。而IP傳輸是”Best Effort” 式的,如果發(fā)生異常情況,我們的IP數(shù)據(jù)包就會(huì)被輕易的丟棄掉。另一方面,如果亂序(out-of-order)片段到達(dá),根據(jù)我們上面說的,接收主機(jī)不會(huì)接收。這樣,錯(cuò)誤片段、丟失片段和被拒片段的聯(lián)手破壞之下,接收主機(jī)只可能收到一個(gè)充滿“漏洞”的文本流。
請(qǐng)補(bǔ)上漏洞
TCP的補(bǔ)救方法是,在每收到一個(gè)正確的、符合次序的片段之后,就向發(fā)送方(也就是連接的另一段)發(fā)送一個(gè)特殊的TCP片段,用來知會(huì)(ACK,acknowledge)發(fā)送方:我已經(jīng)收到那個(gè)片段了。這個(gè)特殊的TCP片段叫做ACK回復(fù)。如果一個(gè)片段序號(hào)為L,對(duì)應(yīng)ACK回復(fù)有回復(fù)號(hào)L+1,也就是接收方期待接收的下一個(gè)發(fā)送片段的序號(hào)。如果發(fā)送方在一定時(shí)間等待之后,還是沒有收到ACK回復(fù),那么它推斷之前發(fā)送的片段一定發(fā)生了異常。發(fā)送方會(huì)重復(fù)發(fā)送(retransmit)那個(gè)出現(xiàn)異常的片段,等待ACK回復(fù),如果還沒有收到,那么再重復(fù)發(fā)送原片段… 直到收到該片段對(duì)應(yīng)的ACK回復(fù)(回復(fù)號(hào)為L+1的ACK)。
終于收到ACK的發(fā)送主機(jī)
當(dāng)發(fā)送方收到ACK回復(fù)時(shí),它看到里面的回復(fù)號(hào)為L+1,也就是發(fā)送方下一個(gè)應(yīng)該發(fā)送的TCP片段序號(hào)。發(fā)送方推斷出之前的片段已經(jīng)被正確的接收,隨后發(fā)出L+1號(hào)片段。ACK回復(fù)也有可能丟失。對(duì)于發(fā)送方來說,這和接收方拒絕發(fā)送ACK回復(fù)是一樣的。發(fā)送方會(huì)重復(fù)發(fā)送,而接收方接收到已知會(huì)過的片段,推斷出ACK回復(fù)丟失,會(huì)重新發(fā)送ACK回復(fù)。
通過ACK回復(fù)和重新發(fā)送機(jī)制,TCP協(xié)議將片段傳輸變得可靠。盡管底盤是不可靠的IP協(xié)議,但TCP協(xié)議以一種“不放棄的精神”,不斷嘗試,最終成功。(技術(shù)也可以很勵(lì)志)
面對(duì)“挫折”,TCP協(xié)議的態(tài)度: never give up
TCP協(xié)議和UDP協(xié)議走了兩個(gè)極端。TCP協(xié)議復(fù)雜但可靠,UDP協(xié)議輕便但不可靠。在處理異常的時(shí)候,TCP極端負(fù)責(zé),而UDP一副無所謂的樣子。我們可以順便“黑”一下UDP協(xié)議:
同樣面對(duì)“挫折”,UDP的態(tài)度: who cares…
滑窗
上面的工作方式中,發(fā)送方保持發(fā)送->等待ACK->發(fā)送->等待ACK…的單線工作方式,這樣的工作方式叫做stop-and-wait。stop-and-wait雖然實(shí)現(xiàn)了TCP通信的可靠性,但同時(shí)犧牲了網(wǎng)絡(luò)通信的效率。在等待ACK的時(shí)間段內(nèi),我們的網(wǎng)絡(luò)都處于閑置(idle)狀態(tài)。我們希望有一種方式,可以同時(shí)發(fā)送出多個(gè)片段。然而如果同時(shí)發(fā)出多個(gè)片段,那么由于IP包傳送是無次序的,有可能會(huì)生成亂序片段(out-of-order),也就是后發(fā)出的片段先到達(dá)。在stop-and-wait的工作方式下,亂序片段完全被拒絕,這也很不效率。畢竟,亂序片段只是提前到達(dá)的片段。我們可以在緩存中先存放它,等到它之前的片段補(bǔ)充完畢,再將它綴在后面。然而,如果一個(gè)亂序片段實(shí)在是太過提前(太“亂”了),該片段將長時(shí)間占用緩存。我們需要一種折中的方法來解決該問題:利用緩存保留一些“不那么亂”的片段,期望能在段時(shí)間內(nèi)補(bǔ)充上之前的片段(暫不處理,但發(fā)送相應(yīng)的ACK);對(duì)于“亂”的比較厲害的片段,則將它們拒絕(不處理,也不發(fā)送對(duì)應(yīng)的ACK)。
總有那么幾個(gè)“出格”片段
滑窗(sliding window)被同時(shí)應(yīng)用于接收方和發(fā)送方,以解決以上問題。發(fā)送方和接收方各有一個(gè)滑窗。當(dāng)片段位于滑窗中時(shí),表示TCP正在處理該片段。滑窗中可以有多個(gè)片段,也就是可以同時(shí)處理多個(gè)片段。滑窗越大,越大的滑窗同時(shí)處理的片段數(shù)目越多(當(dāng)然,計(jì)算機(jī)也必須分配出更多的緩存供滑窗使用)。
同時(shí)處理多個(gè)片段
我們假設(shè)一個(gè)可以容納三個(gè)片段的滑窗,并假設(shè)片段從左向右排列。對(duì)于發(fā)送方來說,滑窗的左側(cè)為已發(fā)送并已ACK過的片段序列,滑窗右側(cè)是尚未發(fā)送的片段序列?;爸械钠?比如片段5,6,7)被發(fā)送出去,并等待相應(yīng)的ACK。如果收到片段5的ACK,滑窗將向右移動(dòng)。這樣,新的片段從右側(cè)進(jìn)入滑窗內(nèi),被發(fā)送出去,并進(jìn)入等待狀態(tài)。在接收到片段5的ACK之前,滑窗不會(huì)移動(dòng),即使已經(jīng)收到了片段6和7的ACK。這樣,就保證了滑窗左側(cè)的序列是已經(jīng)發(fā)送的、接收到ACK的、符合順序的片段序列。
對(duì)于接收方來說,滑窗的左側(cè)是已經(jīng)正確收到并ACK回復(fù)過的片段(比如片段1,2,3,4),也就是正確接收到的文本流?;爸惺瞧谕邮盏钠?比如片段5, 6, 7)。同樣,如果片段6,7先到達(dá),那么滑窗不會(huì)移動(dòng)。如果片段5先到達(dá),那么滑窗會(huì)向右移動(dòng),以等待接收新的片段。如果出現(xiàn)滑窗之外的片段,比如片段9,那么滑窗將拒絕接收。
下面一個(gè)視頻中,我嘗試模擬可容納三個(gè)片段的滑窗(固定大小)的工作過程。
http://v.youku.com/v_show/id_XNDg1NDUyMDUy.html
上面的視頻是我用Python和matplotlib包制作的。藍(lán)色點(diǎn)表示片段,紅色點(diǎn)表示ACK。為了說明亂序片段,我故意讓片段和ACK的速度從兩個(gè)值中隨機(jī)選擇。
可以看到,隨著滑窗的滑動(dòng),越來越多的片段被正確的傳送。利用滑窗,我們一定程度上實(shí)現(xiàn)了對(duì)亂序數(shù)據(jù)的緩存。但是,過于亂序的數(shù)據(jù)依然會(huì)被拒絕。我們之前說的stop-and-wait的工作方式,相當(dāng)于發(fā)送方和接收方的滑窗都只能容納一個(gè)片段。
我們將在以后看到,TCP協(xié)議有實(shí)時(shí)調(diào)整滑窗大小的算法,以實(shí)現(xiàn)最優(yōu)效率。
總結(jié)
TCP協(xié)議和UDP協(xié)議走了兩個(gè)極端。TCP協(xié)議復(fù)雜但可靠,UDP協(xié)議輕便但不可靠。在處理異常的時(shí)候,TCP極端負(fù)責(zé),而UDP一副無所謂的樣子。在TCP中,分段和編號(hào)實(shí)現(xiàn)了次序;ACK和重新發(fā)送實(shí)現(xiàn)了可靠性;sliding window則讓上面的機(jī)制更加有效率的運(yùn)行。Never give up,這就是TCP協(xié)議的態(tài)度。
轉(zhuǎn)載:http://www.code123.cc/1282.html
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。