轉(zhuǎn)自原文:TCP、UDP以及TCP滑窗询刹,它們的區(qū)別
注:文中提到TCP傳輸過(guò)程的特點(diǎn)谜嫉,以及“粘包”的原因和解決方案,干貨值得點(diǎn)贊凹联!
我們已經(jīng)講解了物理層沐兰、連接層和網(wǎng)絡(luò)層。最開(kāi)始的連接層協(xié)議種類繁多(Ethernet蔽挠、Wifi住闯、ARP等等)。到了網(wǎng)絡(luò)層,我們只剩下一個(gè)IP協(xié)議(IPv4和IPv6是替代關(guān)系)比原。進(jìn)入到傳輸層(transport layer)插佛,協(xié)議的種類又開(kāi)始繁多起來(lái)(比如TCP、UDP量窘、SCTP等)雇寇。這就好像下面的大樹(shù),根部(連接層)分叉很多蚌铜,然后統(tǒng)一到一個(gè)樹(shù)干(網(wǎng)絡(luò)層)锨侯,到了樹(shù)冠(傳輸層)部分又開(kāi)始開(kāi)始分叉,而每個(gè)樹(shù)枝上長(zhǎng)出更多的樹(shù)葉(應(yīng)用層)冬殃。我們?cè)?a target="_blank" rel="nofollow">網(wǎng)絡(luò)層已經(jīng)看到识腿,通過(guò)樹(shù)干的統(tǒng)一,我們實(shí)現(xiàn)了一個(gè)覆蓋全球的互聯(lián)網(wǎng)絡(luò)(Internet)造壮。然而渡讼,我們可能出于不同的目的利用這張“網(wǎng)”,隨之使用的方式也有所區(qū)分耳璧。不同的傳輸層協(xié)議(以及更多的應(yīng)用層協(xié)議)正是我們使用“網(wǎng)”的不同方式的體現(xiàn)成箫。
傳輸層最重要的協(xié)議為TCP協(xié)議和UDP協(xié)議。這兩者使用“網(wǎng)”的方式走了兩個(gè)極端旨枯。兩個(gè)協(xié)議的對(duì)比非常有趣蹬昌。TCP協(xié)議復(fù)雜,但傳輸可靠攀隔。UDP協(xié)議簡(jiǎn)單皂贩,但傳輸不可靠。其他的各個(gè)傳輸層協(xié)議在某種程度上都是這兩個(gè)協(xié)議的折中昆汹。我們先來(lái)看傳輸層協(xié)議中比較簡(jiǎn)單的UDP協(xié)議明刷。我們將參考許多之前文章的內(nèi)容(協(xié)議森林01,?03,?05)。
UDP(User Datagram Protocol)傳輸與IP傳輸非常類似满粗。你可以將UDP協(xié)議看作IP協(xié)議暴露在傳輸層的一個(gè)接口辈末。UDP協(xié)議同樣以數(shù)據(jù)包(datagram)的方式傳輸,它的傳輸方式也是"Best Effort"的映皆,所以UDP協(xié)議也是不可靠的(unreliable)挤聘。那么,我們?yōu)槭裁床恢苯邮褂肐P協(xié)議而要額外增加一個(gè)UDP協(xié)議呢捅彻? 一個(gè)重要的原因是IP協(xié)議中并沒(méi)有端口(port)的概念组去。IP協(xié)議進(jìn)行的是IP地址到IP地址的傳輸,這意味者兩臺(tái)計(jì)算機(jī)之間的對(duì)話步淹。但每臺(tái)計(jì)算機(jī)中需要有多個(gè)通信通道从隆,并將多個(gè)通信通道分配給不同的進(jìn)程使用(關(guān)于進(jìn)程诚撵,可以參考Linux進(jìn)程基礎(chǔ))。一個(gè)端口就代表了這樣的一個(gè)通信通道广料。正如我們?cè)?a target="_blank" rel="nofollow">郵局和郵差中提到的收信人的概念一樣砾脑。UDP協(xié)議實(shí)現(xiàn)了端口,從而讓數(shù)據(jù)包可以在送到IP地址的基礎(chǔ)上艾杏,進(jìn)一步可以送到某個(gè)端口韧衣。
盡管UDP協(xié)議非常簡(jiǎn)單,但它的產(chǎn)生晚于更加復(fù)雜的TCP協(xié)議购桑。早期的網(wǎng)絡(luò)開(kāi)發(fā)者開(kāi)發(fā)出IP協(xié)議和TCP協(xié)議分別位于網(wǎng)絡(luò)層和傳輸層畅铭,所有的通信都要先經(jīng)過(guò)TCP封裝,再經(jīng)過(guò)IP封裝(應(yīng)用層->TCP->IP)勃蜘。開(kāi)發(fā)者將TCP/IP視為相互合作的套裝硕噩。但很快,網(wǎng)絡(luò)開(kāi)發(fā)者發(fā)現(xiàn)缭贡,IP協(xié)議的功能和TCP協(xié)議的功能是相互獨(dú)立的炉擅。對(duì)于一些簡(jiǎn)單的通信,我們只需要“Best Effort”式的IP傳輸就可以了阳惹,而不需要TCP協(xié)議復(fù)雜的建立連接的方式(特別是在早期網(wǎng)絡(luò)環(huán)境中谍失,如果過(guò)多的建立TCP連接,會(huì)造成很大的網(wǎng)絡(luò)負(fù)擔(dān)莹汤,而UDP協(xié)議可以相對(duì)快速的處理這些簡(jiǎn)單通信)快鱼。UDP協(xié)議隨之被開(kāi)發(fā)出來(lái),作為IP協(xié)議在傳輸層的"傀儡"纲岭。這樣抹竹,網(wǎng)絡(luò)通信可以通過(guò)應(yīng)用層->UDP->IP的封裝方式,繞過(guò)TCP協(xié)議止潮。由于UDP協(xié)議本身異常簡(jiǎn)單窃判,實(shí)際上只為IP傳輸起到了橋梁的作用。我們將在TCP協(xié)議的講解中看到更多TCP協(xié)議和UDP協(xié)議的對(duì)比沽翔。
UDP的數(shù)據(jù)包同樣分為頭部(header)和數(shù)據(jù)(payload)兩部分兢孝。UDP是傳輸層(transport layer)協(xié)議,這意味著UDP的數(shù)據(jù)包需要經(jīng)過(guò)IP協(xié)議的封裝(encapsulation)仅偎,然后通過(guò)IP協(xié)議傳輸到目的電腦。隨后UDP包在目的電腦拆封雳殊,并將信息送到相應(yīng)端口的緩存中橘沥。
來(lái)自wikipedia
上面的source port和destination port分別為UDP包的出發(fā)端口和目的地端口。Length為整個(gè)UDP包的長(zhǎng)度夯秃。
checksum的算法與IP協(xié)議的header checksum算法相類似座咆。然而痢艺,UDP的checksum所校驗(yàn)的序列包括了整個(gè)UDP數(shù)據(jù)包,以及封裝的IP頭部的一些信息(主要為出發(fā)地IP和目的地IP)介陶。這樣堤舒,checksum就可以校驗(yàn)IP:端口的正確性了。在IPv4中哺呜,checksum可以為0舌缤,意味著不使用checksum。IPv6要求必須進(jìn)行checksum校驗(yàn)某残。
端口(port)是伴隨著傳輸層誕生的概念国撵。它可以將網(wǎng)絡(luò)層的IP通信分送到各個(gè)通信通道。UDP協(xié)議和TCP協(xié)議盡管在工作方式上有很大的不同玻墅,但它們都建立了從一個(gè)端口到另一個(gè)端口的通信介牙。
隨著我們進(jìn)入傳輸層,我們也可以調(diào)用操作系統(tǒng)中的API澳厢,來(lái)構(gòu)建socket环础。Socket是操作系統(tǒng)提供的一個(gè)編程接口,它用來(lái)代表某個(gè)網(wǎng)絡(luò)通信剩拢。應(yīng)用程序通過(guò)socket來(lái)調(diào)用系統(tǒng)內(nèi)核中處理網(wǎng)絡(luò)協(xié)議的模塊线得,而這些內(nèi)核模塊會(huì)負(fù)責(zé)具體的網(wǎng)絡(luò)協(xié)議的實(shí)施。這樣裸扶,我們可以讓內(nèi)核來(lái)接收網(wǎng)絡(luò)協(xié)議的細(xì)節(jié)框都,而我們只需要提供所要傳輸?shù)膬?nèi)容就可以了,內(nèi)核會(huì)幫我們控制格式呵晨,并進(jìn)一步向底層封裝魏保。因此,在實(shí)際應(yīng)用中摸屠,我們并不需要知道具體怎么構(gòu)成一個(gè)UDP包谓罗,而只需要提供相關(guān)信息(比如IP地址,比如端口號(hào)季二,比如所要傳輸?shù)男畔?檩咱,操作系統(tǒng)內(nèi)核會(huì)在傳輸之前會(huì)根據(jù)我們提供的相關(guān)信息構(gòu)成一個(gè)合格的UDP包(以及下層的包和幀)。
端口是傳輸層帶來(lái)的最重要的概念胯舷。我們進(jìn)一步了解了UDP協(xié)議刻蚯。如果已經(jīng)掌握了IP協(xié)議,那么UDP協(xié)議就沒(méi)有任何困難可言桑嘶,它只是IP協(xié)議暴露在傳輸層上的接口炊汹。
在TCP協(xié)議中,我們使用連接記錄TCP兩端的狀態(tài)逃顶,使用編號(hào)和分段實(shí)現(xiàn)了TCP傳輸?shù)挠行蛱直悖褂?a target="_blank" rel="nofollow">advertised window來(lái)實(shí)現(xiàn)了發(fā)送方和接收方處理能力的匹配充甚,并使用重復(fù)發(fā)送來(lái)實(shí)現(xiàn)TCP傳輸?shù)目煽啃浴N覀冎恍枰獙CP片段包裝成IP包霸褒,扔到網(wǎng)絡(luò)中就可以了伴找。TCP協(xié)議的相關(guān)模塊會(huì)幫我們處理各種可能出現(xiàn)的問(wèn)題(比如排序,比如TCP片段丟失等等)废菱。最初的TCP協(xié)議就是由上述的幾大塊構(gòu)成的技矮。
TCP(Transportation Control Protocol)協(xié)議與IP協(xié)議是一同產(chǎn)生的。事實(shí)上昙啄,兩者最初是一個(gè)協(xié)議穆役,后來(lái)才被分拆成網(wǎng)絡(luò)層的IP和傳輸層的TCP。我們已經(jīng)在UDP協(xié)議中介紹過(guò)梳凛,UDP協(xié)議是IP協(xié)議在傳輸層的“傀儡”耿币,用來(lái)實(shí)現(xiàn)數(shù)據(jù)包形式的通信。而TCP協(xié)議則實(shí)現(xiàn)了“流”形式的通信韧拒。
TCP協(xié)議是傳輸層協(xié)議淹接,實(shí)現(xiàn)的是端口到端口(port)的通信。
IP協(xié)議(參考協(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ī)資源來(lái)存儲(chǔ)已經(jīng)到達(dá)的數(shù)據(jù)菌湃,直到所有數(shù)據(jù)都達(dá)到了稳懒,才能開(kāi)始處理。
“流”的要點(diǎn)是次序(order)慢味,然而實(shí)現(xiàn)這一點(diǎn)并不簡(jiǎn)單场梆。TCP協(xié)議是基于IP協(xié)議的,所以最終數(shù)據(jù)傳送還是以IP數(shù)據(jù)包為單位進(jìn)行的纯路。如果一個(gè)文本流很長(zhǎng)的話或油,我們不可能將整個(gè)文本流放入到一個(gè)IP數(shù)據(jù)包中,那樣有可能會(huì)超過(guò)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包不超過(guò)整個(gè)IP接力路徑上的最小MTU霞篡,從而避免令人痛苦的碎片化(fragmentation)世蔗。
(給文本流分段是在發(fā)送主機(jī)完成的,而碎片化是在網(wǎng)絡(luò)中的路由器完成的朗兵。路由器要處理許多路的通信污淋,所以相當(dāng)繁忙。文本流提前在發(fā)送主機(jī)分好段余掖,可以避免在路由器上執(zhí)行碎片化寸爆,可大大減小網(wǎng)絡(luò)負(fù)擔(dān))
TCP片段的頭部(header)會(huì)存有該片段的序號(hào)(sequence number)。這樣盐欺,接收的計(jì)算機(jī)就可以知道接收到的片段在原文本流中的順序了赁豆,也可以知道自己下一步需要接收哪個(gè)片段以形成流。比如已經(jīng)接收到了片段1找田,片段2歌憨,片段3,那么接收主機(jī)就開(kāi)始期待片段4墩衙。如果接收到不符合順序的數(shù)據(jù)包(比如片段8)务嫡,接收方的TCP模塊可以拒絕接收,從而保證呈現(xiàn)給接收主機(jī)的信息是符合次序的“流”漆改。
片段編號(hào)這個(gè)初步的想法并不能解決我們所有的問(wèn)題心铃。IP協(xié)議是不可靠的,所以IP數(shù)據(jù)包可能在傳輸過(guò)程中發(fā)生錯(cuò)誤或者丟失挫剑。而IP傳輸是"Best Effort" 式的去扣,如果發(fā)生異常情況,我們的IP數(shù)據(jù)包就會(huì)被輕易的丟棄掉樊破。另一方面愉棱,如果亂序(out-of-order)片段到達(dá)唆铐,根據(jù)我們上面說(shuō)的,接收主機(jī)不會(huì)接收奔滑。這樣艾岂,錯(cuò)誤片段、丟失片段和被拒片段的聯(lián)手破壞之下朋其,接收主機(jī)只可能收到一個(gè)充滿“漏洞”的文本流王浴。
TCP的補(bǔ)救方法是,在每收到一個(gè)正確的梅猿、符合次序的片段之后氓辣,就向發(fā)送方(也就是連接的另一段)發(fā)送一個(gè)特殊的TCP片段,用來(lái)知會(huì)(ACK袱蚓,acknowledge)發(fā)送方:我已經(jīng)收到那個(gè)片段了钞啸。這個(gè)特殊的TCP片段叫做ACK回復(fù)。如果一個(gè)片段序號(hào)為L(zhǎng)癞松,對(duì)應(yīng)ACK回復(fù)有回復(fù)號(hào)L+1爽撒,也就是接收方期待接收的下一個(gè)發(fā)送片段的序號(hào)。如果發(fā)送方在一定時(shí)間等待之后响蓉,還是沒(méi)有收到ACK回復(fù)硕勿,那么它推斷之前發(fā)送的片段一定發(fā)生了異常。發(fā)送方會(huì)重復(fù)發(fā)送(retransmit)那個(gè)出現(xiàn)異常的片段枫甲,等待ACK回復(fù)源武,如果還沒(méi)有收到,那么再重復(fù)發(fā)送原片段... 直到收到該片段對(duì)應(yīng)的ACK回復(fù)(回復(fù)號(hào)為L(zhǎng)+1的ACK)想幻。
當(dāng)發(fā)送方收到ACK回復(fù)時(shí)粱栖,它看到里面的回復(fù)號(hào)為L(zhǎng)+1,也就是發(fā)送方下一個(gè)應(yīng)該發(fā)送的TCP片段序號(hào)脏毯。發(fā)送方推斷出之前的片段已經(jīng)被正確的接收闹究,隨后發(fā)出L+1號(hào)片段。ACK回復(fù)也有可能丟失食店。對(duì)于發(fā)送方來(lái)說(shuō)渣淤,這和接收方拒絕發(fā)送ACK回復(fù)是一樣的。發(fā)送方會(huì)重復(fù)發(fā)送吉嫩,而接收方接收到已知會(huì)過(guò)的片段价认,推斷出ACK回復(fù)丟失,會(huì)重新發(fā)送ACK回復(fù)自娩。
通過(guò)ACK回復(fù)和重新發(fā)送機(jī)制用踩,TCP協(xié)議將片段傳輸變得可靠。盡管底盤是不可靠的IP協(xié)議,但TCP協(xié)議以一種“不放棄的精神”脐彩,不斷嘗試碎乃,最終成功。(技術(shù)也可以很勵(lì)志)
上面的工作方式中丁屎,發(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包傳送是無(wú)次序的,有可能會(huì)生成亂序片段(out-of-order)蓬蝶,也就是后發(fā)出的片段先到達(dá)尘分。在stop-and-wait的工作方式下,亂序片段完全被拒絕丸氛,這也很不效率培愁。畢竟,亂序片段只是提前到達(dá)的片段缓窜。我們可以在緩存中先存放它定续,等到它之前的片段補(bǔ)充完畢,再將它綴在后面禾锤。然而私股,如果一個(gè)亂序片段實(shí)在是太過(guò)提前(太“亂”了),該片段將長(zhǎng)時(shí)間占用緩存恩掷。我們需要一種折中的方法來(lái)解決該問(wèn)題:利用緩存保留一些“不那么亂”的片段倡鲸,期望能在段時(shí)間內(nèi)補(bǔ)充上之前的片段(暫不處理,但發(fā)送相應(yīng)的ACK)黄娘;對(duì)于“亂”的比較厲害的片段峭状,則將它們拒絕(不處理,也不發(fā)送對(duì)應(yīng)的ACK)寸宏。
滑窗(sliding window)被同時(shí)應(yīng)用于接收方和發(fā)送方宁炫,以解決以上問(wèn)題。發(fā)送方和接收方各有一個(gè)滑窗氮凝。當(dāng)片段位于滑窗中時(shí)羔巢,表示TCP正在處理該片段。滑窗中可以有多個(gè)片段竿秆,也就是可以同時(shí)處理多個(gè)片段启摄。滑窗越大幽钢,越大的滑窗同時(shí)處理的片段數(shù)目越多(當(dāng)然歉备,計(jì)算機(jī)也必須分配出更多的緩存供滑窗使用)。
我們假設(shè)一個(gè)可以容納三個(gè)片段的滑窗匪燕,并假設(shè)片段從左向右排列蕾羊。對(duì)于發(fā)送方來(lái)說(shuō),滑窗的左側(cè)為已發(fā)送并已ACK過(guò)的片段序列帽驯,滑窗右側(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ì)于接收方來(lái)說(shuō)汪诉,滑窗的左側(cè)是已經(jīng)正確收到并ACK回復(fù)過(guò)的片段(比如片段1,2谈秫,3扒寄,4),也就是正確接收到的文本流拟烫「帽啵滑窗中是期望接收的片段(比如片段5, 6, 7)。同樣硕淑,如果片段6课竣,7先到達(dá)嘉赎,那么滑窗不會(huì)移動(dòng)。如果片段5先到達(dá)于樟,那么滑窗會(huì)向右移動(dòng)公条,以等待接收新的片段。如果出現(xiàn)滑窗之外的片段迂曲,比如片段9靶橱,那么滑窗將拒絕接收。
利用滑窗路捧,我們一定程度上實(shí)現(xiàn)了對(duì)亂序數(shù)據(jù)的緩存关霸。但是,過(guò)于亂序的數(shù)據(jù)依然會(huì)被拒絕鬓长。我們之前說(shuō)的stop-and-wait的工作方式谒拴,相當(dāng)于發(fā)送方和接收方的滑窗都只能容納一個(gè)片段。
在TCP連接中涉波,我們通過(guò)將ACK回復(fù)“附著”在其他數(shù)據(jù)片段的方式,減少了ACK回復(fù)所消耗的流量炭序。但這并不是全部的故事啤覆。TCP協(xié)議并不是對(duì)每個(gè)片段都發(fā)送ACK回復(fù)。TCP協(xié)議實(shí)際采用的是累計(jì)ACK回復(fù)(accumulative acknowledgement)惭聂。接收方往往利用一個(gè)ACK回復(fù)來(lái)知會(huì)連續(xù)多個(gè)片段的成功接收窗声。通過(guò)累計(jì)ACK,所需要的ACK回復(fù)通潮棵伲可以降到50%见剩。
如下圖所示苍苞,橙色為已經(jīng)接收的片段羹呵。方框?yàn)榛案曰叮翱扇菁{3個(gè)片段太示。
累計(jì)ACK
滑窗還沒(méi)接收到片段7時(shí),已接收到片段8呀非,9镜盯。這樣就在滑窗中制造了一個(gè)“空穴”(hole)速缆。當(dāng)滑窗最終接收到片段7時(shí)艺糜,滑窗送出一個(gè)回復(fù)號(hào)為10的ACK回復(fù)。發(fā)送方收到該回復(fù)翅楼,會(huì)意識(shí)到,片段10之前的片段已經(jīng)按照次序被成功接收管嬉。整個(gè)過(guò)程中節(jié)約了片段7和片段8所需的兩個(gè)ACK回復(fù)蚯撩。
此外,接收方在接收到片斷呀癣,并應(yīng)該回復(fù)ACK的時(shí)候,會(huì)故意延遲一些時(shí)間蹬竖。如果在延遲的時(shí)間里,有后續(xù)的片段到達(dá)列另,就可以利用累計(jì)ACK來(lái)一起回復(fù)了芽腾。
在之前的討論中页衙,我們以片段為單位摊滔,來(lái)衡量滑窗的大小的店乐。真實(shí)的滑窗是以byte為單位表示大小艰躺,但這并不會(huì)對(duì)我們之前的討論造成太大的影響眨八。
發(fā)送方滑窗可以分為下面兩個(gè)部分腺兴。offered window為整個(gè)滑窗的大小。
接收方滑窗可分為三個(gè)部分:
可以看到页响,接收方的滑窗相對(duì)于發(fā)送方的滑窗多了一個(gè)"Received; ACKed; Not Sent to Proc"的部分。接收方接收到的文本流必須等待進(jìn)程來(lái)讀取。如果進(jìn)程正忙于做別的事情辱魁,那么這些文本流即使已經(jīng)正確接收染簇,還是需要暫時(shí)占用接收緩存砾赔。當(dāng)出現(xiàn)上述占用時(shí),滑窗的可用部分(也就是圖中advertised window)就會(huì)縮水青灼。這意味著接收方的處理能力下降暴心。如果這個(gè)時(shí)候發(fā)送方依然按照之前的速率發(fā)送數(shù)據(jù)給接收方,接收方將無(wú)力接收這些數(shù)據(jù)杂拨。
TCP協(xié)議會(huì)根據(jù)情況自動(dòng)改變滑窗大小专普,以實(shí)現(xiàn)流量控制。流量控制(flow control)是指接收方將advertised window的大小通知給發(fā)送方弹沽,從而指導(dǎo)發(fā)送方修改offered window的大小檀夹。接收方將該信息放在TCP頭部的window size區(qū)域:
發(fā)送方在收到window size的通知時(shí)筋粗,會(huì)調(diào)整自己滑窗的大小,讓offered window和advertised window相符炸渡。這樣娜亿,發(fā)送窗口變小,文本流發(fā)送速率降低蚌堵,從而減少了接收方的負(fù)擔(dān)买决。
advertised window大小有可能變?yōu)?,這意味著接收方的接收能力降為0辰斋。發(fā)送方收到大小為0的advertised window通知時(shí)策州,停止發(fā)送。
TCP協(xié)議是一個(gè)可靠的協(xié)議宫仗。它通過(guò)重新發(fā)送(retransmission)來(lái)實(shí)現(xiàn)TCP片段傳輸?shù)目煽啃怨还摇:?jiǎn)單的說(shuō),TCP會(huì)不斷重復(fù)發(fā)送TCP片段藕夫,直到片段被正確接收孽糖。?
TCP片段丟失
接收方(receiver)可以通過(guò)校驗(yàn)TCP片段頭部中checksum區(qū)域來(lái)檢驗(yàn)TCP片段是否出錯(cuò)。我們已經(jīng)接觸過(guò)了IP協(xié)議詳解的checksum算法毅贮。TCP片段的checksum算法與之類似办悟。IP協(xié)議的checksum只校驗(yàn)頭部,TCP片段頭部的checksum會(huì)校驗(yàn)包括IP頭部滩褥、TCP頭部和TCP數(shù)據(jù)在內(nèi)的整個(gè)序列病蛉,確保IP地址、端口號(hào)和其他相關(guān)信息正確瑰煎。如果TCP片段出錯(cuò)铺然,接收方可以簡(jiǎn)單的丟棄改TCP片段,也就相當(dāng)于TCP片段丟失酒甸。
TCP片段包裹在一個(gè)IP包中傳輸魄健。IP包可能在網(wǎng)絡(luò)中丟失。導(dǎo)致IP包丟失的原因可能有很多插勤,比如IP包經(jīng)過(guò)太多的路由器接力沽瘦,達(dá)到hop limit;比如路由器太過(guò)擁擠农尖,導(dǎo)致一些IP包被丟棄析恋;再比如路由表(routing table)沒(méi)有及時(shí)更新,導(dǎo)致IP包無(wú)法送達(dá)目的地卤橄。
下面我們要介紹兩種重新發(fā)送TCP片段的機(jī)制:超時(shí)重新發(fā)送和快速重新發(fā)送绿满。
我們之前已經(jīng)簡(jiǎn)單介紹過(guò)重新發(fā)送的機(jī)制:當(dāng)發(fā)送方送出一個(gè)TCP片段后,將開(kāi)始計(jì)時(shí)窟扑,等待該TCP片段的ACK回復(fù)喇颁。如果接收方正確接收到符合次序的片段漏健,接收方會(huì)利用ACK片段回復(fù)發(fā)送方。發(fā)送方得到ACK回復(fù)后橘霎,繼續(xù)移動(dòng)窗口蔫浆,發(fā)送接下來(lái)的TCP片段。如果直到計(jì)時(shí)完成姐叁,發(fā)送方還是沒(méi)有收到ACK回復(fù)瓦盛,那么發(fā)送方推斷之前發(fā)送的TCP片段丟失,因此重新發(fā)送之前的TCP片段外潜。這個(gè)計(jì)時(shí)等待的時(shí)間叫做重新發(fā)送超時(shí)時(shí)間(RTO, retransmission timeout)原环。
我們剛才介紹了超時(shí)重新發(fā)送的機(jī)制:發(fā)送方送出一個(gè)TCP片段,然后開(kāi)始等待并計(jì)時(shí)处窥,如果RTO時(shí)間之后還沒(méi)有收到ACK回復(fù)嘱吗,發(fā)送方則重新發(fā)送。TCP協(xié)議有可能在計(jì)時(shí)完成之前啟動(dòng)重新發(fā)送滔驾,也就是利用快速重新發(fā)送(fast-retransmission)谒麦。快速發(fā)送機(jī)制如果被啟動(dòng)哆致,將打斷計(jì)時(shí)器的等待绕德,直接重新發(fā)送TCP片段。
由于IP包的傳輸是無(wú)序的摊阀,所以接收方有可能先收到后發(fā)出的片段耻蛇,也就是亂序(out-of-order)片段。亂序片段的序號(hào)并不等于最近發(fā)出的ACK回復(fù)號(hào)胞此。已接收的文本流和亂序片段之間將出現(xiàn)空洞(hole)城丧,也就是等待接收的空位。比如已經(jīng)接收了正常片段5,6,7豌鹤,此時(shí)又接收亂序片段9。這時(shí)片段8依然空缺枝缔,片段8的位置就是一個(gè)空洞布疙。
TCP協(xié)議規(guī)定,當(dāng)接收方收到亂序片段的時(shí)候愿卸,需要重復(fù)發(fā)送ACK灵临。比如接收到亂序片段9的時(shí)候,接收方需要回復(fù)ACK趴荸∪甯龋回復(fù)號(hào)為8 (7+1)。此后接收方如果繼續(xù)收到亂序片段(序號(hào)不是8的片段)发钝,將再次重復(fù)發(fā)送ACK=8顿涣。當(dāng)發(fā)送方收到3個(gè)ACK=8的回復(fù)時(shí)波闹,發(fā)送方推斷片段8丟失精堕。即使此時(shí)片段8的計(jì)時(shí)器還沒(méi)有超時(shí)歹篓,發(fā)送方會(huì)打斷計(jì)時(shí),直接重新發(fā)送片段8洞斯,這就是快速重新發(fā)送機(jī)制(fast-retransmission)。
快速重新發(fā)送機(jī)制利用重復(fù)的ACK來(lái)提示空洞的存在厅翔。當(dāng)重復(fù)次數(shù)達(dá)到閾值時(shí),認(rèn)為空洞對(duì)應(yīng)的片段在網(wǎng)絡(luò)中丟失甸昏∈┟郏快速重新發(fā)送機(jī)制提高了檢測(cè)丟失片段的效率翻默,往往可以在超時(shí)之前探測(cè)到丟失片段修械,并重復(fù)發(fā)送丟失的片段肯污。
總結(jié)
TCP協(xié)議利用重新發(fā)送(retransmission)來(lái)實(shí)現(xiàn)TCP傳輸?shù)目煽啃院逦摺V匦掳l(fā)送的基本形式是超時(shí)重新發(fā)送,根據(jù)統(tǒng)計(jì)的往返時(shí)間來(lái)設(shè)置超時(shí)標(biāo)準(zhǔn)美尸;如果超時(shí),則重新發(fā)送TCP片段胯陋。另一方面遏乔,快速重新發(fā)送則通過(guò)亂序片段的ACK來(lái)更早的推斷出片段的丟失。
TCP協(xié)議和UDP協(xié)議走了兩個(gè)極端。TCP協(xié)議復(fù)雜但可靠前计,UDP協(xié)議輕便但不可靠。在處理異常的時(shí)候男杈,TCP極端負(fù)責(zé),而UDP一副無(wú)所謂的樣子伶棒。在TCP中,分段和編號(hào)實(shí)現(xiàn)了次序苞冯;ACK和重新發(fā)送實(shí)現(xiàn)了可靠性;sliding window則讓上面的機(jī)制更加有效率的運(yùn)行舅锄。
面向報(bào)文(UDP)和面向字節(jié)流(TCP)的區(qū)別
面向報(bào)文的傳輸方式是應(yīng)用層交給UDP多長(zhǎng)的報(bào)文,UDP就照樣發(fā)送畴蹭,即一次發(fā)送一個(gè)報(bào)文。因此幔荒,應(yīng)用程序必須選擇合適大小的報(bào)文右犹。若報(bào)文太長(zhǎng),則IP層需要分片姚垃,降低效率念链。若太短,會(huì)是IP太小积糯。UDP對(duì)應(yīng)用層交下來(lái)的報(bào)文掂墓,既不合并,也不拆分看成,而是保留這些報(bào)文的邊界君编。這也就是說(shuō),應(yīng)用層交給UDP多長(zhǎng)的報(bào)文绍昂,UDP就照樣發(fā)送啦粹,即一次發(fā)送一個(gè)報(bào)文。
面向字節(jié)流的話窘游,雖然應(yīng)用程序和TCP的交互是一次一個(gè)數(shù)據(jù)塊(大小不等)唠椭,但TCP把應(yīng)用程序看成是一連串的無(wú)結(jié)構(gòu)的字節(jié)流。TCP有一個(gè)緩沖忍饰,當(dāng)應(yīng)用程序傳送的數(shù)據(jù)塊太長(zhǎng)贪嫂,TCP就可以把它劃分短一些再傳送。如果應(yīng)用程序一次只發(fā)送一個(gè)字節(jié)艾蓝,TCP也可以等待積累有足夠多的字節(jié)后再構(gòu)成報(bào)文段發(fā)送出去力崇。
下圖是TCP和UDP協(xié)議的一些應(yīng)用。
下圖是TCP和UDP協(xié)議的比較赢织。
這里再詳細(xì)說(shuō)一下面向連接和面向無(wú)連接的區(qū)別:
面向連接舉例:兩個(gè)人之間通過(guò)電話進(jìn)行通信;
面向無(wú)連接舉例:郵政服務(wù)亮靴,用戶把信函放在郵件中期待郵政處理流程來(lái)傳遞郵政包裹。顯然于置,不可達(dá)代表不可靠茧吊。
從程序?qū)崿F(xiàn)的角度來(lái)看,可以用下圖來(lái)進(jìn)行描述。
從上圖也能清晰的看出搓侄,TCP通信需要服務(wù)器端偵聽(tīng)listen瞄桨、接收客戶端連接請(qǐng)求accept,等待客戶端connect建立連接后才能進(jìn)行數(shù)據(jù)包的收發(fā)(recv/send)工作讶踪。而UDP則服務(wù)器和客戶端的概念不明顯芯侥,服務(wù)器端即接收端需要綁定端口,等待客戶端的數(shù)據(jù)的到來(lái)乳讥。后續(xù)便可以進(jìn)行數(shù)據(jù)的收發(fā)(recvfrom/sendto)工作柱查。
在前面講解UDP時(shí),提到了UDP保留了報(bào)文的邊界雏婶,下面我們來(lái)談?wù)凾CP和UDP中報(bào)文的邊界問(wèn)題物赶。在默認(rèn)的阻塞模式下,TCP無(wú)邊界留晚,UDP有邊界酵紫。
對(duì)于TCP協(xié)議,客戶端連續(xù)發(fā)送數(shù)據(jù)错维,只要服務(wù)端的這個(gè)函數(shù)的緩沖區(qū)足夠大奖地,會(huì)一次性接收過(guò)來(lái),即客戶端是分好幾次發(fā)過(guò)來(lái)赋焕,是有邊界的参歹,而服務(wù)端卻一次性接收過(guò)來(lái),所以證明是無(wú)邊界的隆判;
?而對(duì)于UDP協(xié)議犬庇,客戶端連續(xù)發(fā)送數(shù)據(jù),即使服務(wù)端的這個(gè)函數(shù)的緩沖區(qū)足夠大侨嘀,也只會(huì)一次一次的接收臭挽,發(fā)送多少次接收多少次,即客戶端分幾次發(fā)送過(guò)來(lái)咬腕,服務(wù)端就必須按幾次接收欢峰,從而證明,這種UDP的通訊模式是有邊界的涨共。
TCP無(wú)邊界纽帖,造成對(duì)采用TCP協(xié)議發(fā)送的數(shù)據(jù)進(jìn)行接收比較麻煩,在接收的時(shí)候易出現(xiàn)粘包举反,即發(fā)送方發(fā)送的若干包數(shù)據(jù)到接收方接收時(shí)粘成一包懊直。由于TCP是流協(xié)議,對(duì)于一個(gè)socket的包火鼻,如發(fā)送 10AAAAABBBBB兩次室囊,由于網(wǎng)絡(luò)原因第一次又分成兩次發(fā)送瘦陈, 10AAAAAB和BBBB,如果接包的時(shí)候先讀取10(包長(zhǎng)度)再讀入后續(xù)數(shù)據(jù)波俄,當(dāng)接收得快,發(fā)送的慢時(shí)蛾默,就會(huì)出現(xiàn)先接收了 10AAAAAB,會(huì)解釋錯(cuò)誤 ,再接到BBBB10AAAAABBBBB懦铺,也解釋錯(cuò)誤的情況。這就是TCP的粘包支鸡。
在網(wǎng)絡(luò)傳輸應(yīng)用中冬念,通常需要在網(wǎng)絡(luò)協(xié)議之上再自定義一個(gè)協(xié)議封裝一下,簡(jiǎn)單做法就是在要發(fā)送的數(shù)據(jù)前面再加一個(gè)自定義的包頭牧挣,包頭中可以包含數(shù)據(jù)長(zhǎng)度和其它一些信息急前,接收的時(shí)候先收包頭,再根據(jù)包頭中描述的數(shù)據(jù)長(zhǎng)度來(lái)接收后面的數(shù)據(jù)瀑构。詳細(xì)做法是:先接收包頭裆针,在包頭里指定包體長(zhǎng)度來(lái)接收。設(shè)置包頭包尾的檢查位( 比如以0xAA開(kāi)頭寺晌,0xCC結(jié)束來(lái)檢查一個(gè)包是否完整)世吨。對(duì)于TCP來(lái)說(shuō):
1)不存在丟包,錯(cuò)包呻征,所以不會(huì)出現(xiàn)數(shù)據(jù)出錯(cuò) 耘婚;
2)如果包頭檢測(cè)錯(cuò)誤,即為非法或者請(qǐng)求陆赋,直接重置即可沐祷。
為了避免粘包現(xiàn)象,可采取以下幾種措施攒岛。
一赖临、對(duì)于發(fā)送方引起的粘包現(xiàn)象,用戶可通過(guò)編程設(shè)置來(lái)避免阵子,TCP提供了強(qiáng)制數(shù)據(jù)立即傳送的操作指令push思杯,TCP軟件收到該操作指令后,就立即將本段數(shù)據(jù)發(fā)送出去挠进,而不必等待發(fā)送緩沖區(qū)滿色乾;
二、對(duì)于接收方引起的粘包领突,則可通過(guò)優(yōu)化程序設(shè)計(jì)暖璧、精簡(jiǎn)接收進(jìn)程工作量、提高接收進(jìn)程優(yōu)先級(jí)等措施君旦,使其及時(shí)接收數(shù)據(jù)澎办,從而盡量避免出現(xiàn)粘包現(xiàn)象嘲碱;
三、由接收方控制局蚀,將一包數(shù)據(jù)按結(jié)構(gòu)字段麦锯,人為控制分多次接收,然后合并琅绅,通過(guò)這種手段來(lái)避免粘包扶欣。
UDP是無(wú)連接的,TCP面向連接的千扶,這個(gè)連接指的就是在運(yùn)用TCP協(xié)議之前料祠,必須先建立TCP連接,在數(shù)據(jù)傳送完畢之后必須釋放連接澎羞,通俗地將通信的雙方知道彼此的存在這個(gè)連接是一個(gè)虛電路髓绽。
影響:
①因?yàn)橐⑦B接所以TCP有建立連接的“三次握手”,所以UDP在發(fā)送數(shù)據(jù)之前時(shí)延更加小妆绞。
②因?yàn)槊嫦蜻B接是端到端的通信顺呕,所以多播和廣播就只能運(yùn)用UDP協(xié)議。
影響:
①UDP不保證可靠交付摆碉,當(dāng)發(fā)生丟包的時(shí)候不重傳塘匣,并且不保證數(shù)據(jù)包按序到達(dá)。所以UDP數(shù)據(jù)報(bào)報(bào)頭小巷帝,并且不用維護(hù)很多的定時(shí)器忌卤、連接狀態(tài)表、算法楞泼。
②TCP提供可靠交付驰徊。保證數(shù)據(jù)包無(wú)差錯(cuò)、不丟失堕阔、不重復(fù)棍厂、并且按序到達(dá)。
UDP面向報(bào)文超陆,發(fā)送方的UDP應(yīng)用程序交下來(lái)的報(bào)文牺弹,在添加首部后就向下交付給IP層。UDP對(duì)應(yīng)用層交下來(lái)的報(bào)文时呀,既不合并张漂,也不拆分,而是保留這些報(bào)文的邊界谨娜。這就是說(shuō)應(yīng)用層交給UDP多大的報(bào)文航攒,UDP就照樣發(fā)送,即一次發(fā)送一個(gè)報(bào)文趴梢。
TCP面向字節(jié)流漠畜,TCP中的“流”指的是流入到進(jìn)程或從進(jìn)程流出的字節(jié)序列币他。面向字節(jié)流的含義是:雖然應(yīng)用程序和TCP的交互式一次一個(gè)數(shù)據(jù)塊(大小不等),但是TCP把應(yīng)用程序交下來(lái)的數(shù)據(jù)看成僅僅是一連串的無(wú)結(jié)構(gòu)字節(jié)流憔狞。所以TCP才有滑動(dòng)窗口機(jī)制蝴悉、Nagle算法等等。因此TCP并不保證發(fā)送的數(shù)據(jù)塊和應(yīng)用層交付的數(shù)據(jù)塊相等瘾敢,比如應(yīng)用層交付了10個(gè)數(shù)據(jù)塊辫封,但是TCP將此10個(gè)數(shù)據(jù)塊合并為4個(gè)數(shù)據(jù)塊發(fā)送,這是出于效率的考慮廉丽,但是字節(jié)流是一樣的。
?????? 擁塞控制是面向全局網(wǎng)絡(luò)的妻味,和應(yīng)用程序沒(méi)有太多的關(guān)系正压。