首先答捕,我們需要知道TCP在網(wǎng)絡(luò)ISO的七層模型中的第四層——Transport層靶橱,IP在第三層——Network層臊旭,ARP在第二層——Data Link層,在第二層上的數(shù)據(jù)锨能,我們叫Frame扯再,在第三層上的數(shù)據(jù)叫Packet,第四層的數(shù)據(jù)叫Segment腹侣。
首先叔收,我們需要知道齿穗,我們程序的數(shù)據(jù)首先會(huì)打到TCP的Segment中傲隶,然后TCP的Segment會(huì)打到IP的Packet中,然后再打到以太網(wǎng)Ethernet的Frame中窃页,傳到對(duì)端后跺株,各個(gè)層解析自己的協(xié)議,然后把數(shù)據(jù)交給更高層的協(xié)議處理脖卖。
TCP頭格式
接下來(lái)乒省,我們來(lái)看一下TCP頭的格式
需要注意這么幾點(diǎn):
TCP的包是沒(méi)有IP地址的,那是IP層上的事畦木。但是有源端口和目標(biāo)端口袖扛。
一個(gè)TCP連接需要四個(gè)元組來(lái)表示是同一個(gè)連接(src_ip, src_port, dst_ip, dst_port)準(zhǔn)確說(shuō)是五元組,還有一個(gè)是協(xié)議十籍。但因?yàn)檫@里只是說(shuō)TCP協(xié)議蛆封,所以,這里只說(shuō)四元組勾栗。
注意上圖中的四個(gè)非常重要的東西:
Sequence Number是包的序號(hào)惨篱,用來(lái)解決網(wǎng)絡(luò)包亂序(reordering)問(wèn)題。
Acknowledgement Number就是ACK——用于確認(rèn)收到围俘,用來(lái)解決不丟包的問(wèn)題砸讳。
Window又叫Advertised-Window琢融,也就是著名的滑動(dòng)窗口(Sliding Window),用于解決流控的簿寂。
TCP Flag漾抬,也就是包的類(lèi)型,主要是用于操控TCP的狀態(tài)機(jī)的陶耍。
關(guān)于其它的東西奋蔚,可以參看下面的圖示
TCP的狀態(tài)機(jī)
其實(shí),網(wǎng)絡(luò)上的傳輸是沒(méi)有連接的烈钞,包括TCP也是一樣的泊碑。而TCP所謂的“連接”,其實(shí)只不過(guò)是在通訊的雙方維護(hù)一個(gè)“連接狀態(tài)”毯欣,讓它看上去好像有連接一樣馒过。所以,TCP的狀態(tài)變換是非常重要的酗钞。
下面是:“TCP協(xié)議的狀態(tài)機(jī)”?和 “TCP建鏈接”腹忽、“TCP斷鏈接”、“傳數(shù)據(jù)” 的對(duì)照?qǐng)D砚作,我把兩個(gè)圖并排放在一起窘奏,這樣方便在你對(duì)照著看。
為什么建鏈接要3次握手葫录,斷鏈接需要4次揮手着裹?
對(duì)于建鏈接的3次握手,主要是要初始化Sequence Number 的初始值米同。通信的雙方要互相通知對(duì)方自己的初始化的Sequence Number(縮寫(xiě)為ISN:Inital Sequence Number)——所以叫SYN骇扇,全稱(chēng)Synchronize Sequence Numbers。也就上圖中的 x 和 y面粮。這個(gè)號(hào)要作為以后的數(shù)據(jù)通信的序號(hào)少孝,以保證應(yīng)用層接收到的數(shù)據(jù)不會(huì)因?yàn)榫W(wǎng)絡(luò)上的傳輸?shù)膯?wèn)題而亂序(TCP會(huì)用這個(gè)序號(hào)來(lái)拼接數(shù)據(jù))。
對(duì)于4次揮手熬苍,其實(shí)仔細(xì)看是2次稍走,因?yàn)門(mén)CP是全雙工的,所以柴底,發(fā)送方和接收方都需要Fin和Ack婿脸。只不過(guò),有一方是被動(dòng)的似枕,所以看上去就成了所謂的4次揮手盖淡。如果兩邊同時(shí)斷連接,那就會(huì)就進(jìn)入到CLOSING狀態(tài)凿歼,然后到達(dá)TIME_WAIT狀態(tài)褪迟。下圖是雙方同時(shí)斷連接的示意圖(可以對(duì)照著TCP狀態(tài)機(jī)看):
另外冗恨,有幾個(gè)事情需要注意一下:
關(guān)于建連接時(shí)SYN超時(shí)。試想一下味赃,如果server端接到了clien發(fā)的SYN后回了SYN-ACK后client掉線了掀抹,server端沒(méi)有收到client回來(lái)的ACK,那么心俗,這個(gè)連接處于一個(gè)中間狀態(tài)傲武,即沒(méi)成功,也沒(méi)失敗城榛。于是揪利,server端如果在一定時(shí)間內(nèi)沒(méi)有收到的TCP會(huì)重發(fā)SYN-ACK。在Linux下狠持,默認(rèn)重試次數(shù)為5次疟位,重試的間隔時(shí)間從1s開(kāi)始每次都翻售,5次的重試時(shí)間間隔為1s, 2s, 4s, 8s, 16s喘垂,總共31s甜刻,第5次發(fā)出后還要等32s就知道第5次也超時(shí)了,所以正勒,總共需要 1s + 2s + 4s+ 8s+ 16s + 32s = 2^6 -1 = 63s得院,TCP才會(huì)把斷開(kāi)這個(gè)連接。
關(guān)于SYN Flood攻擊章贞。一些惡意的人就為此制造了SYN Flood攻擊——給服務(wù)器發(fā)了一個(gè)SYN后祥绞,就下線了,于是服務(wù)器需要默認(rèn)等63s才會(huì)斷開(kāi)連接阱驾,這樣就谜,攻擊者就可以把服務(wù)器的syn連接的隊(duì)列耗盡怪蔑,讓正常的連接請(qǐng)求不能處理里覆。于是,Linux下給了一個(gè)叫tcp_syncookies的參數(shù)來(lái)應(yīng)對(duì)這個(gè)事——當(dāng)SYN隊(duì)列滿(mǎn)了后缆瓣,TCP會(huì)通過(guò)源地址端口喧枷、目標(biāo)地址端口和時(shí)間戳打造出一個(gè)特別的Sequence Number發(fā)回去(又叫cookie),如果是攻擊者則不會(huì)有響應(yīng)弓坞,如果是正常連接隧甚,則會(huì)把這個(gè) SYN Cookie發(fā)回來(lái),然后服務(wù)端可以通過(guò)cookie建連接(即使你不在SYN隊(duì)列中)渡冻。請(qǐng)注意戚扳,請(qǐng)先千萬(wàn)別用tcp_syncookies來(lái)處理正常的大負(fù)載的連接的情況。因?yàn)樽逦牵瑂ynccookies是妥協(xié)版的TCP協(xié)議帽借,并不嚴(yán)謹(jǐn)珠增。對(duì)于正常的請(qǐng)求,你應(yīng)該調(diào)整三個(gè)TCP參數(shù)可供你選擇砍艾,第一個(gè)是:tcp_synack_retries 可以用他來(lái)減少重試次數(shù)蒂教;第二個(gè)是:tcp_max_syn_backlog,可以增大SYN連接數(shù)脆荷;第三個(gè)是:tcp_abort_on_overflow 處理不過(guò)來(lái)干脆就直接拒絕連接了凝垛。
關(guān)于ISN的初始化。ISN是不能hard code的蜓谋,不然會(huì)出問(wèn)題的——比如:如果連接建好后始終用1來(lái)做ISN梦皮,如果client發(fā)了30個(gè)segment過(guò)去,但是網(wǎng)絡(luò)斷了桃焕,于是 client重連届氢,又用了1做ISN,但是之前連接的那些包到了覆旭,于是就被當(dāng)成了新連接的包退子,此時(shí),client的Sequence Number 可能是3型将,而Server端認(rèn)為client端的這個(gè)號(hào)是30了寂祥。RFC793中說(shuō),ISN會(huì)和一個(gè)假的時(shí)鐘綁在一起七兜,這個(gè)時(shí)鐘會(huì)在每4微秒對(duì)ISN做加一操作丸凭,直到超過(guò)2^32,又從0開(kāi)始腕铸。這樣惜犀,一個(gè)ISN的周期大約是4.55個(gè)小時(shí)。因?yàn)楹莨覀兗僭O(shè)我們的TCP Segment在網(wǎng)絡(luò)上的存活時(shí)間不會(huì)超過(guò)Maximum Segment Lifetime(縮寫(xiě)為MSL)虽界,所以,只要MSL的值小于4.55小時(shí)涛菠,那么莉御,我們就不會(huì)重用到ISN。
關(guān)于 MSL 和?TIME_WAIT俗冻。通過(guò)上面的ISN的描述礁叔,相信你也知道MSL是怎么來(lái)的了。我們注意到迄薄,在TCP的狀態(tài)圖中琅关,從TIME_WAIT狀態(tài)到CLOSED狀態(tài),有一個(gè)超時(shí)設(shè)置讥蔽,這個(gè)超時(shí)設(shè)置是 2*MSL(RFC793定義了MSL為2分鐘涣易,Linux設(shè)置成了30s)為什么要這有TIME_WAIT人乓?為什么不直接給轉(zhuǎn)成CLOSED狀態(tài)呢?主要有兩個(gè)原因:
1)TIME_WAIT確保有足夠的時(shí)間讓對(duì)端收到了ACK都毒,如果被動(dòng)關(guān)閉的那方?jīng)]有收到Ack色罚,就會(huì)解發(fā)被動(dòng)端重發(fā)Fin,一來(lái)一去正好2個(gè)MSL账劲。
2)有足夠的時(shí)間讓這個(gè)連接不會(huì)跟后面的連接混在一起(有些路由器會(huì)緩存IP數(shù)據(jù)包戳护,如果連接被重用了,那么這些延遲收到的包就有可能會(huì)跟新連接混在一起)瀑焦。
關(guān)于TIME_WAIT數(shù)量太多腌且。從上面的描述我們可以知道,TIME_WAIT是個(gè)很重要的狀態(tài)榛瓮,但是如果在大并發(fā)的短鏈接下铺董,TIME_WAIT 就會(huì)太多,這也會(huì)消耗很多系統(tǒng)資源禀晓。只要搜一下精续,你就會(huì)發(fā)現(xiàn),十有八九的處理方式都是教你設(shè)置兩個(gè)參數(shù)粹懒,一個(gè)叫tcp_tw_reuse重付,另一個(gè)叫tcp_tw_recycle的參數(shù),這兩個(gè)參數(shù)默認(rèn)值都是被關(guān)閉的凫乖,后者recyle比前者resue更為激進(jìn)确垫,resue要溫柔一些。另外帽芽,如果使用tcp_tw_reuse删掀,必需設(shè)置tcp_timestamps=1,否則無(wú)效导街。這里披泪,要注意,打開(kāi)這兩個(gè)參數(shù)會(huì)有比較大的坑——可能會(huì)讓TCP連接出一些詭異的問(wèn)題(因?yàn)槿缟鲜鲆粯泳漳洌绻坏却瑫r(shí)重用連接的話付呕,新的連接可能會(huì)建不上计福。正如官方文檔上說(shuō)的一樣“It should not be changed without advice/request of technical experts”)跌捆。
關(guān)于tcp_tw_reuse。官方文檔上說(shuō)tcp_tw_reuse 加上tcp_timestamps(又叫PAWS, for Protection Against Wrapped Sequence Numbers)可以保證協(xié)議的角度上的安全象颖,但是需要tcp_timestamps在兩邊都被打開(kāi)佩厚。
關(guān)于tcp_tw_recycle。如果是tcp_tw_recycle被打開(kāi)了話说订,會(huì)假設(shè)對(duì)端開(kāi)啟了tcp_timestamps抄瓦,然后會(huì)去比較時(shí)間戳潮瓶,如果時(shí)間戳變大了,就可以重用钙姊。但是毯辅,如果對(duì)端是一個(gè)NAT網(wǎng)絡(luò)的話(如:一個(gè)公司只用一個(gè)IP出公網(wǎng))或是對(duì)端的IP被另一臺(tái)重用了,這個(gè)事就復(fù)雜了煞额。建鏈接的SYN可能就被直接丟掉了思恐。
關(guān)于tcp_max_tw_buckets。這個(gè)是控制并發(fā)的TIME_WAIT的數(shù)量膊毁,默認(rèn)值是180000胀莹,如果超限,那么婚温,系統(tǒng)會(huì)把多的給destory掉描焰,然后在日志里打一個(gè)警告(如:time wait bucket table overflow),官網(wǎng)文檔說(shuō)這個(gè)參數(shù)是用來(lái)對(duì)抗DDoS攻擊的栅螟。也說(shuō)的默認(rèn)值180000并不小荆秦。這個(gè)還是需要根據(jù)實(shí)際情況考慮。
Again力图,使用tcp_tw_reuse和tcp_tw_recycle來(lái)解決TIME_WAIT的問(wèn)題是非常非常危險(xiǎn)的萄凤,因?yàn)檫@兩個(gè)參數(shù)違反了TCP協(xié)議(RFC?1122)
其實(shí),TIME_WAIT表示的是你主動(dòng)斷連接搪哪,所以靡努,這就是所謂的“不作死不會(huì)死”。試想晓折,如果讓對(duì)端斷連接惑朦,那么這個(gè)破問(wèn)題就是對(duì)方的了,呵呵漓概。另外漾月,如果你的服務(wù)器是于HTTP服務(wù)器,那么設(shè)置一個(gè)HTTP的KeepAlive有多重要(瀏覽器會(huì)重用一個(gè)TCP連接來(lái)處理多個(gè)HTTP請(qǐng)求)胃珍,然后讓客戶(hù)端去斷鏈接(你要小心梁肿,瀏覽器可能會(huì)非常貪婪,他們不到萬(wàn)不得已不會(huì)主動(dòng)斷連接)觅彰。
數(shù)據(jù)傳輸中的Sequence Number
SeqNum的增加是和傳輸?shù)淖止?jié)數(shù)相關(guān)的吩蔑。上圖中,三次握手后填抬,來(lái)了兩個(gè)Len:1440的包烛芬,而第二個(gè)包的SeqNum就成了1441。然后第一個(gè)ACK回的是1441,表示第一個(gè)1440收到了赘娄。
注意:如果用Wireshark抓包程序看3次握手仆潮,就會(huì)發(fā)現(xiàn)SeqNum總是為0,實(shí)際上遣臼,Wireshark為了顯示更友好性置,使用了Relative SeqNum——相對(duì)序號(hào),只要在右鍵菜單中的protocol preference 中取消掉就可以看到“Absolute SeqNum”了揍堰。
TCP重傳機(jī)制
TCP要保證所有的數(shù)據(jù)包都可以到達(dá)蚌讼,所以,必需要有重傳機(jī)制个榕。
注意篡石,接收端給發(fā)送端的Ack確認(rèn)只會(huì)確認(rèn)最后一個(gè)連續(xù)的包,比如西采,發(fā)送端發(fā)了1,2,3,4,5一共五份數(shù)據(jù)凰萨,接收端收到了1,2械馆,于是回ack 2胖眷,然后收到了4(注意此時(shí)3沒(méi)收到),此時(shí)的TCP會(huì)怎么辦霹崎?我們要知道珊搀,因?yàn)檎缜懊嫠f(shuō)的,SeqNum和Ack是以字節(jié)數(shù)為單位尾菇,所以ack的時(shí)候境析,不能跳著確認(rèn),只能確認(rèn)最大的連續(xù)收到的包派诬,不然劳淆,發(fā)送端就以為之前的都收到了。
超時(shí)重傳機(jī)制
一種是不回ack默赂,死等3沛鸵,當(dāng)發(fā)送方發(fā)現(xiàn)收不到3的ack超時(shí)后,會(huì)重傳3缆八。一旦接收方收到3后曲掰,會(huì)ack回4——意味著3和4都收到了。
但是奈辰,這種方式會(huì)有比較嚴(yán)重的問(wèn)題栏妖,那就是因?yàn)橐赖?,所以會(huì)導(dǎo)致4和5即便已經(jīng)收到了冯挎,而發(fā)送方也完全不知道發(fā)生了什么事底哥,因?yàn)闆](méi)有收到Ack咙鞍,所以房官,發(fā)送方可能會(huì)悲觀地認(rèn)為也丟了趾徽,所以有可能也會(huì)導(dǎo)致4和5的重傳。
對(duì)此有兩種選擇:
一種是僅重傳timeout的包翰守。也就是第3份數(shù)據(jù)孵奶。
另一種是重傳timeout后所有的數(shù)據(jù),也就是第3蜡峰,4了袁,5這三份數(shù)據(jù)。
這兩種方式有好也有不好湿颅。第一種會(huì)節(jié)省帶寬载绿,但是慢,第二種會(huì)快一點(diǎn)油航,但是會(huì)浪費(fèi)帶寬崭庸,也可能會(huì)有無(wú)用功。但總體來(lái)說(shuō)都不好谊囚。因?yàn)槎荚诘萾imeout怕享,timeout可能會(huì)很長(zhǎng)(在下面會(huì)說(shuō)TCP是怎么動(dòng)態(tài)地計(jì)算出timeout的)
快速重傳機(jī)制
于是,TCP引入了一種叫Fast Retransmit的算法镰踏,不以時(shí)間驅(qū)動(dòng)函筋,而以數(shù)據(jù)驅(qū)動(dòng)重傳。也就是說(shuō)奠伪,如果跌帐,包沒(méi)有連續(xù)到達(dá),就ack最后那個(gè)可能被丟了的包绊率,如果發(fā)送方連續(xù)收到3次相同的ack含末,就重傳。Fast Retransmit的好處是不用等timeout了再重傳即舌。
比如:如果發(fā)送方發(fā)出了1佣盒,2,3顽聂,4肥惭,5份數(shù)據(jù),第一份先到送了紊搪,于是就ack回2蜜葱,結(jié)果2因?yàn)槟承┰驔](méi)收到,3到達(dá)了耀石,于是還是ack回2牵囤,后面的4和5都到了,但是還是ack回2,因?yàn)?還是沒(méi)有收到揭鳞,于是發(fā)送端收到了三個(gè)ack=2的確認(rèn)炕贵,知道了2還沒(méi)有到,于是就馬上重轉(zhuǎn)2野崇。然后称开,接收端收到了2,此時(shí)因?yàn)?乓梨,4鳖轰,5都收到了,于是ack回6扶镀。示意圖如下:
Fast?Retransmit只解決了一個(gè)問(wèn)題蕴侣,就是timeout的問(wèn)題片酝,它依然面臨一個(gè)艱難的選擇呻引,就是,是重傳之前的一個(gè)還是重傳所有的問(wèn)題文判。對(duì)于上面的示例來(lái)說(shuō)胧谈,是重傳#2呢還是重傳#2忆肾,#3,#4菱肖,#5呢客冈?因?yàn)榘l(fā)送端并不清楚這連續(xù)的3個(gè)ack(2)是誰(shuí)傳回來(lái)的?也許發(fā)送端發(fā)了20份數(shù)據(jù)稳强,是#6场仲,#10,#20傳來(lái)的呢退疫。這樣渠缕,發(fā)送端很有可能要重傳從2到20的這堆數(shù)據(jù)(這就是某些TCP的實(shí)際的實(shí)現(xiàn))“保可見(jiàn)亦鳞,這是一把雙刃劍。
SACK 方法
另外一種更好的方式叫:Selective Acknowledgment (SACK)(參看RFC 2018)棒坏,這種方式需要在TCP頭里加一個(gè)SACK的東西燕差,ACK還是Fast Retransmit的ACK,SACK則是匯報(bào)收到的數(shù)據(jù)碎版坝冕。參看下圖:
這樣徒探,在發(fā)送端就可以根據(jù)回傳的SACK來(lái)知道哪些數(shù)據(jù)到了,哪些沒(méi)有到喂窟。于是就優(yōu)化了Fast?Retransmit的算法测暗。當(dāng)然央串,這個(gè)協(xié)議需要兩邊都支持。在 Linux下碗啄,可以通過(guò)tcp_sack參數(shù)打開(kāi)這個(gè)功能(Linux 2.4后默認(rèn)打開(kāi))质和。
這里還需要注意一個(gè)問(wèn)題——接收方Reneging,所謂Reneging的意思就是接收方有權(quán)把已經(jīng)報(bào)給發(fā)送端SACK里的數(shù)據(jù)給丟了挫掏。這樣干是不被鼓勵(lì)的侦另,因?yàn)檫@個(gè)事會(huì)把問(wèn)題復(fù)雜化了秩命,但是尉共,接收方這么做可能會(huì)有些極端情況,比如要把內(nèi)存給別的更重要的東西弃锐。所以袄友,發(fā)送方也不能完全依賴(lài)SACK,還是要依賴(lài)ACK霹菊,并維護(hù)Time-Out剧蚣,如果后續(xù)的ACK沒(méi)有增長(zhǎng),那么還是要把SACK的東西重傳旋廷,另外鸠按,接收端這邊永遠(yuǎn)不能把SACK的包標(biāo)記為Ack。
注意:SACK會(huì)消耗發(fā)送方的資源饶碘,試想目尖,如果一個(gè)攻擊者給數(shù)據(jù)發(fā)送方發(fā)一堆SACK的選項(xiàng),這會(huì)導(dǎo)致發(fā)送方開(kāi)始要重傳甚至遍歷已經(jīng)發(fā)出的數(shù)據(jù)扎运,這會(huì)消耗很多發(fā)送端的資源瑟曲。
Duplicate SACK – 重復(fù)收到數(shù)據(jù)的問(wèn)題
Duplicate SACK又稱(chēng)D-SACK,其主要使用了SACK來(lái)告訴發(fā)送方有哪些數(shù)據(jù)被重復(fù)接收了豪治。RFC-2883 里有詳細(xì)描述和示例洞拨。下面舉幾個(gè)例子(來(lái)源于RFC-2883)
D-SACK使用了SACK的第一個(gè)段來(lái)做標(biāo)志,
如果SACK的第一個(gè)段的范圍被ACK所覆蓋负拟,那么就是D-SACK
如果SACK的第一個(gè)段的范圍被SACK的第二個(gè)段覆蓋烦衣,那么就是D-SACK
示例一:ACK丟包
下面的示例中,丟了兩個(gè)ACK掩浙,所以花吟,發(fā)送端重傳了第一個(gè)數(shù)據(jù)包(3000-3499),于是接收端發(fā)現(xiàn)重復(fù)收到涣脚,于是回了一個(gè)SACK=3000-3500示辈,因?yàn)锳CK都到了4000意味著收到了4000之前的所有數(shù)據(jù),所以這個(gè)SACK就是D-SACK——旨在告訴發(fā)送端我收到了重復(fù)的數(shù)據(jù)遣蚀,而且我們的發(fā)送端還知道矾麻,數(shù)據(jù)包沒(méi)有丟纱耻,丟的是ACK包。
?示例二险耀,網(wǎng)絡(luò)延誤
下面的示例中弄喘,網(wǎng)絡(luò)包(1000-1499)被網(wǎng)絡(luò)給延誤了,導(dǎo)致發(fā)送方?jīng)]有收到ACK甩牺,而后面到達(dá)的三個(gè)包觸發(fā)了“Fast Retransmit算法”蘑志,所以重傳,但重傳時(shí)贬派,被延誤的包又到了急但,所以,回了一個(gè)SACK=1000-1500搞乏,因?yàn)锳CK已到了3000波桩,所以,這個(gè)SACK是D-SACK——標(biāo)識(shí)收到了重復(fù)的包请敦。
這個(gè)案例下镐躲,發(fā)送端知道之前因?yàn)椤癋ast Retransmit算法”觸發(fā)的重傳不是因?yàn)榘l(fā)出去的包丟了,也不是因?yàn)榛貞?yīng)的ACK包丟了侍筛,而是因?yàn)榫W(wǎng)絡(luò)延時(shí)了萤皂。
可見(jiàn),引入了D-SACK匣椰,有這么幾個(gè)好處:
1)可以讓發(fā)送方知道裆熙,是發(fā)出去的包丟了,還是回來(lái)的ACK包丟了窝爪。
2)是不是自己的timeout太小了弛车,導(dǎo)致重傳。
3)網(wǎng)絡(luò)上出現(xiàn)了先發(fā)的包后到的情況(又稱(chēng)reordering)
4)網(wǎng)絡(luò)上是不是把我的數(shù)據(jù)包給復(fù)制了蒲每。
知道這些東西可以很好得幫助TCP了解網(wǎng)絡(luò)情況纷跛,從而可以更好的做網(wǎng)絡(luò)上的流控。
Linux下的tcp_dsack參數(shù)用于開(kāi)啟這個(gè)功能(Linux 2.4后默認(rèn)打開(kāi))
TCP的RTT算法
從前面的TCP重傳機(jī)制我們知道Timeout的設(shè)置對(duì)于重傳非常重要邀杏。
設(shè)長(zhǎng)了贫奠,重發(fā)就慢,沒(méi)有效率望蜡,性能差唤崭;
設(shè)短了,會(huì)導(dǎo)致可能并沒(méi)有丟就重發(fā)脖律。于是重發(fā)的就快谢肾,會(huì)增加網(wǎng)絡(luò)擁塞,導(dǎo)致更多的超時(shí)小泉,更多的超時(shí)導(dǎo)致更多的重發(fā)芦疏。
而且冕杠,這個(gè)超時(shí)時(shí)間在不同的網(wǎng)絡(luò)的情況下,根本沒(méi)有辦法設(shè)置一個(gè)死的值酸茴。只能動(dòng)態(tài)地設(shè)置分预。 為了動(dòng)態(tài)地設(shè)置,TCP引入了RTT——Round Trip Time薪捍,也就是一個(gè)數(shù)據(jù)包從發(fā)出去到回來(lái)的時(shí)間笼痹。這樣發(fā)送端就大約知道需要多少的時(shí)間,從而可以方便地設(shè)置Timeout——RTO(Retransmission TimeOut)酪穿,以讓我們的重傳機(jī)制更高效凳干。 聽(tīng)起來(lái)似乎很簡(jiǎn)單,好像就是在發(fā)送端發(fā)包時(shí)記下t0昆稿,然后接收端再把這個(gè)ack回來(lái)時(shí)再記一個(gè)t1纺座,于是RTT = t1 – t0息拜。沒(méi)那么簡(jiǎn)單溉潭,這只是一個(gè)采樣,不能代表普遍情況少欺。
經(jīng)典算法
RFC793中定義的經(jīng)典算法是這樣的:
1)首先喳瓣,先采樣RTT,記下最近好幾次的RTT值赞别。
2)然后做平滑計(jì)算SRTT( Smoothed RTT)畏陕。公式為:(其中的 α 取值在0.8 到 0.9之間,這個(gè)算法英文叫Exponential weighted moving average仿滔,中文叫:加權(quán)移動(dòng)平均)
SRTT =?( α * SRTT ) + ((1- α) * RTT)
3)開(kāi)始計(jì)算RTO惠毁。公式如下:
RTO = min [ UBOUND, ?max [ LBOUND, ? (β * SRTT) ] ?]
其中:
UBOUND是最大的timeout時(shí)間,上限值
LBOUND是最小的timeout時(shí)間崎页,下限值
β 值一般在1.3到2.0之間鞠绰。
Karn / Partridge 算法
但是上面的這個(gè)算法在重傳的時(shí)候會(huì)出有一個(gè)終極問(wèn)題——你是用第一次發(fā)數(shù)據(jù)的時(shí)間和ack回來(lái)的時(shí)間做RTT樣本值,還是用重傳的時(shí)間和ACK回來(lái)的時(shí)間做RTT樣本值飒焦?
這個(gè)問(wèn)題無(wú)論你選那頭都是按下葫蘆起了瓢蜈膨。 如下圖所示:
情況(a)是ack沒(méi)回來(lái),所以重傳牺荠。如果你計(jì)算第一次發(fā)送和ACK的時(shí)間翁巍,那么,明顯算大了休雌。
情況(b)是ack回來(lái)慢了灶壶,但是導(dǎo)致了重傳,但剛重傳不一會(huì)兒杈曲,之前ACK就回來(lái)了驰凛。如果你是算重傳的時(shí)間和ACK回來(lái)的時(shí)間的差孝情,就會(huì)算短了。
所以1987年的時(shí)候洒嗤,搞了一個(gè)叫Karn / Partridge Algorithm箫荡,這個(gè)算法的最大特點(diǎn)是——忽略重傳,不把重傳的RTT做采樣(你看渔隶,你不需要去解決不存在的問(wèn)題)羔挡。
但是,這樣一來(lái)间唉,又會(huì)引發(fā)一個(gè)大BUG——如果在某一時(shí)間绞灼,網(wǎng)絡(luò)閃動(dòng),突然變慢了呈野,產(chǎn)生了比較大的延時(shí)低矮,這個(gè)延時(shí)導(dǎo)致要重轉(zhuǎn)所有的包(因?yàn)橹暗腞TO很小)被冒,于是军掂,因?yàn)橹剞D(zhuǎn)的不算,所以昨悼,RTO就不會(huì)被更新蝗锥,這是一個(gè)災(zāi)難。 于是Karn算法用了一個(gè)取巧的方式——只要一發(fā)生重傳率触,就對(duì)現(xiàn)有的RTO值翻倍(這就是所謂的?Exponential backoff)终议,很明顯,這種死規(guī)矩對(duì)于一個(gè)需要估計(jì)比較準(zhǔn)確的RTT也不靠譜葱蝗。
Jacobson / Karels 算法
前面兩種算法用的都是“加權(quán)移動(dòng)平均”穴张,這種方法最大的毛病就是如果RTT有一個(gè)大的波動(dòng)的話,很難被發(fā)現(xiàn)两曼,因?yàn)楸黄交袅嗽砀省K裕?988年,又有人推出來(lái)了一個(gè)新的算法合愈,這個(gè)算法叫Jacobson / Karels Algorithm(參看RFC6289)叮贩。這個(gè)算法引入了最新的RTT的采樣和平滑過(guò)的SRTT的差距做因子來(lái)計(jì)算。 公式如下:(其中的DevRTT是Deviation RTT的意思)
SRTT?= SRTT?+ α?(RTT?– SRTT) ?—— 計(jì)算平滑RTT
DevRTT?= (1-β)*DevRTT?+ β*(|RTT-SRTT|) ——計(jì)算平滑RTT和真實(shí)的差距(加權(quán)移動(dòng)平均)
RTO= μ *?SRTT + ? *DevRTT —— 神一樣的公式
(其中:在Linux下佛析,α = 0.125益老,β = 0.25, μ = 1寸莫,??= 4 ——這就是算法中的“調(diào)得一手好參數(shù)”捺萌,nobody knows why, it just works…) 最后的這個(gè)算法在被用在今天的TCP協(xié)議中。
TCP滑動(dòng)窗口
TCP必需要解決的可靠傳輸以及包亂序(reordering)的問(wèn)題膘茎,所以桃纯,TCP必需要知道網(wǎng)絡(luò)實(shí)際的數(shù)據(jù)處理帶寬或是數(shù)據(jù)處理速度酷誓,這樣才不會(huì)引起網(wǎng)絡(luò)擁塞,導(dǎo)致丟包态坦。
所以盐数,TCP引入了一些技術(shù)和設(shè)計(jì)來(lái)做網(wǎng)絡(luò)流控,Sliding Window是其中一個(gè)技術(shù)伞梯。 前面我們說(shuō)過(guò)玫氢,TCP頭里有一個(gè)字段叫Window,又叫Advertised-Window谜诫,這個(gè)字段是接收端告訴發(fā)送端自己還有多少緩沖區(qū)可以接收數(shù)據(jù)漾峡。于是發(fā)送端就可以根據(jù)這個(gè)接收端的處理能力來(lái)發(fā)送數(shù)據(jù),而不會(huì)導(dǎo)致接收端處理不過(guò)來(lái)喻旷。 為了說(shuō)明滑動(dòng)窗口生逸,我們需要先看一下TCP緩沖區(qū)的一些數(shù)據(jù)結(jié)構(gòu):
上圖中,我們可以看到:
接收端LastByteRead指向了TCP緩沖區(qū)中讀到的位置且预,NextByteExpected指向的地方是收到的連續(xù)包的最后一個(gè)位置槽袄,LastByteRcved指向的是收到的包的最后一個(gè)位置,我們可以看到中間有些數(shù)據(jù)還沒(méi)有到達(dá)辣之,所以有數(shù)據(jù)空白區(qū)掰伸。
發(fā)送端的LastByteAcked指向了被接收端Ack過(guò)的位置(表示成功發(fā)送確認(rèn)),LastByteSent表示發(fā)出去了怀估,但還沒(méi)有收到成功確認(rèn)的Ack,LastByteWritten指向的是上層應(yīng)用正在寫(xiě)的地方合搅。
于是:
接收端在給發(fā)送端回ACK中會(huì)匯報(bào)自己的AdvertisedWindow = MaxRcvBuffer – LastByteRcvd – 1;
而發(fā)送方會(huì)根據(jù)這個(gè)窗口來(lái)控制發(fā)送數(shù)據(jù)的大小多搀,以保證接收方可以處理。
下面我們來(lái)看一下發(fā)送方的滑動(dòng)窗口示意圖:
上圖中分成了四個(gè)部分灾部,分別是:(其中那個(gè)黑模型就是滑動(dòng)窗口)
#1已收到ack確認(rèn)的數(shù)據(jù)康铭。
#2發(fā)還沒(méi)收到ack的。
#3在窗口中還沒(méi)有發(fā)出的(接收方還有空間)赌髓。
#4窗口以外的數(shù)據(jù)(接收方?jīng)]空間)
下面是個(gè)滑動(dòng)后的示意圖(收到36的ack从藤,并發(fā)出了46-51的字節(jié)):
下面我們來(lái)看一個(gè)接受端控制發(fā)送端的圖示:
Zero Window
上圖,我們可以看到一個(gè)處理緩慢的Server(接收端)是怎么把Client(發(fā)送端)的TCP Sliding Window給降成0的锁蠕。此時(shí)夷野,你一定會(huì)問(wèn),如果Window變成0了荣倾,TCP會(huì)怎么樣悯搔?是不是發(fā)送端就不發(fā)數(shù)據(jù)了?是的舌仍,發(fā)送端就不發(fā)數(shù)據(jù)了妒貌,你可以想像成“Window Closed”通危,那你一定還會(huì)問(wèn),如果發(fā)送端不發(fā)數(shù)據(jù)了灌曙,接收方一會(huì)兒Window size 可用了菊碟,怎么通知發(fā)送端呢?
解決這個(gè)問(wèn)題在刺,TCP使用了Zero Window Probe技術(shù)框沟,縮寫(xiě)為ZWP,也就是說(shuō)增炭,發(fā)送端在窗口變成0后忍燥,會(huì)發(fā)ZWP的包給接收方,讓接收方來(lái)ack他的Window尺寸隙姿,一般這個(gè)值會(huì)設(shè)置成3次梅垄,第次大約30-60秒(不同的實(shí)現(xiàn)可能會(huì)不一樣)。如果3次過(guò)后還是0的話输玷,有的TCP實(shí)現(xiàn)就會(huì)發(fā)RST把鏈接斷了队丝。
注意:只要有等待的地方都可能出現(xiàn)DDoS攻擊,Zero Window也不例外欲鹏,一些攻擊者會(huì)在和HTTP建好鏈發(fā)完GET請(qǐng)求后机久,就把Window設(shè)置為0,然后服務(wù)端就只能等待進(jìn)行ZWP赔嚎,于是攻擊者會(huì)并發(fā)大量的這樣的請(qǐng)求膘盖,把服務(wù)器端的資源耗盡。
Silly Window Syndrome
Silly Window Syndrome翻譯成中文就是“糊涂窗口綜合癥”尤误。正如你上面看到的一樣侠畔,如果我們的接收方太忙了,來(lái)不及取走Receive Windows里的數(shù)據(jù)损晤,那么软棺,就會(huì)導(dǎo)致發(fā)送方越來(lái)越小。到最后尤勋,如果接收方騰出幾個(gè)字節(jié)并告訴發(fā)送方現(xiàn)在有幾個(gè)字節(jié)的window喘落,而我們的發(fā)送方會(huì)義無(wú)反顧地發(fā)送這幾個(gè)字節(jié)。
要知道最冰,我們的TCP+IP頭有40個(gè)字節(jié)瘦棋,為了幾個(gè)字節(jié),要達(dá)上這么大的開(kāi)銷(xiāo)锌奴,這太不經(jīng)濟(jì)了兽狭。
另外,你需要知道網(wǎng)絡(luò)上有個(gè)MTU,對(duì)于以太網(wǎng)來(lái)說(shuō)箕慧,MTU是1500字節(jié)服球,除去TCP+IP頭的40個(gè)字節(jié),真正的數(shù)據(jù)傳輸可以有1460颠焦,這就是所謂的MSS(Max Segment Size)注意斩熊,TCP的RFC定義這個(gè)MSS的默認(rèn)值是536,這是因?yàn)?RFC 791里說(shuō)了任何一個(gè)IP設(shè)備都得最少接收576尺寸的大蟹ネァ(實(shí)際上來(lái)說(shuō)576是撥號(hào)的網(wǎng)絡(luò)的MTU粉渠,而576減去IP頭的20個(gè)字節(jié)就是536)。
如果你的網(wǎng)絡(luò)包可以塞滿(mǎn)MTU圾另,那么你可以用滿(mǎn)整個(gè)帶寬霸株,如果不能,那么你就會(huì)浪費(fèi)帶寬集乔。(大于MTU的包有兩種結(jié)局去件,一種是直接被丟了,另一種是會(huì)被重新分塊打包發(fā)送) 你可以想像成一個(gè)MTU就相當(dāng)于一個(gè)飛機(jī)的最多可以裝的人扰路,如果這飛機(jī)里滿(mǎn)載的話尤溜,帶寬最高,如果一個(gè)飛機(jī)只運(yùn)一個(gè)人的話汗唱,無(wú)疑成本增加了宫莱,也而相當(dāng)二。
所以哩罪,Silly Windows Syndrome這個(gè)現(xiàn)像就像是你本來(lái)可以坐200人的飛機(jī)里只做了一兩個(gè)人授霸。 要解決這個(gè)問(wèn)題也不難,就是避免對(duì)小的window size做出響應(yīng)识椰,直到有足夠大的window size再響應(yīng)绝葡,這個(gè)思路可以同時(shí)實(shí)現(xiàn)在sender和receiver兩端。
如果這個(gè)問(wèn)題是由Receiver端引起的腹鹉,那么就會(huì)使用?David D Clark’s 方案。在receiver端敷硅,如果收到的數(shù)據(jù)導(dǎo)致window size小于某個(gè)值功咒,可以直接ack(0)回sender,這樣就把window給關(guān)閉了绞蹦,也阻止了sender再發(fā)數(shù)據(jù)過(guò)來(lái)力奋,等到receiver端處理了一些數(shù)據(jù)后windows size 大于等于了MSS,或者幽七,receiver buffer有一半為空景殷,就可以把window打開(kāi)讓send 發(fā)送數(shù)據(jù)過(guò)來(lái)。
如果這個(gè)問(wèn)題是由Sender端引起的,那么就會(huì)使用著名的?Nagle’s algorithm猿挚。這個(gè)算法的思路也是延時(shí)處理咐旧,他有兩個(gè)主要的條件:
1)要等到 Window Size>=MSS 或是 Data Size >=MSS。
2)收到之前發(fā)送數(shù)據(jù)的ack回包绩蜻,他才會(huì)發(fā)數(shù)據(jù)铣墨,否則就是在攢數(shù)據(jù)。
另外办绝,Nagle算法默認(rèn)是打開(kāi)的伊约,所以,對(duì)于一些需要小包場(chǎng)景的程序——比如像telnet或ssh這樣的交互性比較強(qiáng)的程序孕蝉,你需要關(guān)閉這個(gè)算法屡律。
TCP的擁塞處理 –?Congestion Handling
TCP通過(guò)Sliding Window來(lái)做流控(Flow Control),但是TCP覺(jué)得這還不夠降淮,因?yàn)镾liding Window需要依賴(lài)于連接的發(fā)送端和接收端超埋,其并不知道網(wǎng)絡(luò)中間發(fā)生了什么。我們知道TCP通過(guò)一個(gè)timer采樣了RTT并計(jì)算RTO骤肛,但是纳本,如果網(wǎng)絡(luò)上的延時(shí)突然增加,那么腋颠,TCP對(duì)這個(gè)事做出的應(yīng)對(duì)只有重傳數(shù)據(jù)繁成,但是,重傳會(huì)導(dǎo)致網(wǎng)絡(luò)的負(fù)擔(dān)更重淑玫,于是會(huì)導(dǎo)致更大的延遲以及更多的丟包巾腕,于是,這個(gè)情況就會(huì)進(jìn)入惡性循環(huán)被不斷地放大絮蒿。試想一下尊搬,如果一個(gè)網(wǎng)絡(luò)內(nèi)有成千上萬(wàn)的TCP連接都這么行事,那么馬上就會(huì)形成“網(wǎng)絡(luò)風(fēng)暴”土涝,TCP這個(gè)協(xié)議就會(huì)拖垮整個(gè)網(wǎng)絡(luò)佛寿。擁塞控制主要是四個(gè)算法:1)慢啟動(dòng),2)擁塞避免但壮,3)擁塞發(fā)生冀泻,4)快速恢復(fù)。
慢熱啟動(dòng)算法 – Slow Start
首先蜡饵,我們來(lái)看一下TCP的慢熱啟動(dòng)弹渔。慢啟動(dòng)的意思是,剛剛加入網(wǎng)絡(luò)的連接溯祸,一點(diǎn)一點(diǎn)地提速肢专,不要一上來(lái)就像那些特權(quán)車(chē)一樣霸道地把路占滿(mǎn)舞肆。新同學(xué)上高速還是要慢一點(diǎn),不要把已經(jīng)在高速上的秩序給搞亂了博杖。
慢啟動(dòng)的算法如下(cwnd全稱(chēng)Congestion Window):
1)連接建好的開(kāi)始先初始化cwnd = 1椿胯,表明可以傳一個(gè)MSS大小的數(shù)據(jù)。
2)每當(dāng)收到一個(gè)ACK欧募,cwnd++; 呈線性上升
3)每當(dāng)過(guò)了一個(gè)RTT压状,cwnd = cwnd*2; 呈指數(shù)讓升
4)還有一個(gè)ssthresh(slow start threshold),是一個(gè)上限跟继,當(dāng)cwnd >= ssthresh時(shí)种冬,就會(huì)進(jìn)入“擁塞避免算法”(后面會(huì)說(shuō)這個(gè)算法)
所以,我們可以看到舔糖,如果網(wǎng)速很快的話娱两,ACK也會(huì)返回得快,RTT也會(huì)短金吗,那么十兢,這個(gè)慢啟動(dòng)就一點(diǎn)也不慢。下圖說(shuō)明了這個(gè)過(guò)程摇庙。
?擁塞避免算法 –?Congestion Avoidance
前面說(shuō)過(guò)旱物,還有一個(gè)ssthresh(slow start threshold),是一個(gè)上限卫袒,當(dāng)cwnd >= ssthresh時(shí)宵呛,就會(huì)進(jìn)入“擁塞避免算法”。一般來(lái)說(shuō)ssthresh的值是65535夕凝,單位是字節(jié)宝穗,當(dāng)cwnd達(dá)到這個(gè)值時(shí)后,算法如下:
1)收到一個(gè)ACK時(shí)码秉,cwnd = cwnd + 1/cwnd
2)當(dāng)每過(guò)一個(gè)RTT時(shí)逮矛,cwnd = cwnd + 1
這樣就可以避免增長(zhǎng)過(guò)快導(dǎo)致網(wǎng)絡(luò)擁塞,慢慢的增加調(diào)整到網(wǎng)絡(luò)的最佳值转砖。很明顯须鼎,是一個(gè)線性上升的算法。
擁塞狀態(tài)時(shí)的算法
前面我們說(shuō)過(guò)府蔗,當(dāng)丟包的時(shí)候莉兰,會(huì)有兩種情況:
1)等到RTO超時(shí),重傳數(shù)據(jù)包礁竞。TCP認(rèn)為這種情況太糟糕,反應(yīng)也很強(qiáng)烈杉辙。
sshthresh = ?cwnd /2
cwnd 重置為 1
進(jìn)入慢啟動(dòng)過(guò)程
2)Fast Retransmit算法模捂,也就是在收到3個(gè)duplicate ACK時(shí)就開(kāi)啟重傳,而不用等到RTO超時(shí)。
TCP Tahoe的實(shí)現(xiàn)和RTO超時(shí)一樣狂男。
TCP Reno的實(shí)現(xiàn)是:
cwnd = cwnd /2
sshthresh = cwnd
進(jìn)入快速恢復(fù)算法——Fast Recovery
上面我們可以看到RTO超時(shí)后综看,sshthresh會(huì)變成cwnd的一半,這意味著岖食,如果cwnd<=sshthresh時(shí)出現(xiàn)的丟包红碑,那么TCP的sshthresh就會(huì)減了一半,然后等cwnd又很快地以指數(shù)級(jí)增漲爬到這個(gè)地方時(shí)泡垃,就會(huì)成慢慢的線性增漲析珊。我們可以看到,TCP是怎么通過(guò)這種強(qiáng)烈地震蕩快速而小心得找到網(wǎng)站流量的平衡點(diǎn)的蔑穴。
快速恢復(fù)算法 – Fast Recovery
TCP Reno
這個(gè)算法定義在RFC5681忠寻。快速重傳和快速恢復(fù)算法一般同時(shí)使用存和∞忍辏快速恢復(fù)算法是認(rèn)為,你還有3個(gè)Duplicated Acks說(shuō)明網(wǎng)絡(luò)也不那么糟糕捐腿,所以沒(méi)有必要像RTO超時(shí)那么強(qiáng)烈纵朋。注意,正如前面所說(shuō)茄袖,進(jìn)入Fast Recovery之前操软,cwnd 和 sshthresh已被更新:
cwnd = cwnd /2
sshthresh = cwnd
然后,真正的Fast Recovery算法如下:
cwnd = sshthresh ?+ 3 * MSS (3的意思是確認(rèn)有3個(gè)數(shù)據(jù)包被收到了)
重傳Duplicated ACKs指定的數(shù)據(jù)包
如果再收到 duplicated Acks绞佩,那么cwnd = cwnd +1
如果收到了新的Ack寺鸥,那么,cwnd = sshthresh 品山,然后就進(jìn)入了擁塞避免的算法了胆建。
如果你仔細(xì)思考一下上面的這個(gè)算法,你就會(huì)知道肘交,上面這個(gè)算法也有問(wèn)題笆载,那就是——它依賴(lài)于3個(gè)重復(fù)的Acks。注意涯呻,3個(gè)重復(fù)的Acks并不代表只丟了一個(gè)數(shù)據(jù)包凉驻,很有可能是丟了好多包。但這個(gè)算法只會(huì)重傳一個(gè)复罐,而剩下的那些包只能等到RTO超時(shí)涝登,于是,進(jìn)入了惡夢(mèng)模式——超時(shí)一個(gè)窗口就減半一下效诅,多個(gè)超時(shí)會(huì)超成TCP的傳輸速度呈級(jí)數(shù)下降胀滚,而且也不會(huì)觸發(fā)Fast Recovery算法了趟济。
通常來(lái)說(shuō),正如我們前面所說(shuō)的咽笼,SACK或D-SACK的方法可以讓Fast Recovery或Sender在做決定時(shí)更聰明一些顷编,但是并不是所有的TCP的實(shí)現(xiàn)都支持SACK(SACK需要兩端都支持),所以剑刑,需要一個(gè)沒(méi)有SACK的解決方案媳纬。而通過(guò)SACK進(jìn)行擁塞控制的算法是FACK(后面會(huì)講)
TCP New Reno
于是,1995年施掏,TCP New Reno(參見(jiàn)?RFC 6582)算法提出來(lái)钮惠,主要就是在沒(méi)有SACK的支持下改進(jìn)Fast Recovery算法的——
當(dāng)sender這邊收到了3個(gè)Duplicated Acks,進(jìn)入Fast Retransimit模式其监,開(kāi)發(fā)重傳重復(fù)Acks指示的那個(gè)包萌腿。如果只有這一個(gè)包丟了,那么抖苦,重傳這個(gè)包后回來(lái)的Ack會(huì)把整個(gè)已經(jīng)被sender傳輸出去的數(shù)據(jù)ack回來(lái)毁菱。如果沒(méi)有的話,說(shuō)明有多個(gè)包丟了锌历。我們叫這個(gè)ACK為Partial ACK贮庞。
一旦Sender這邊發(fā)現(xiàn)了Partial ACK出現(xiàn),那么究西,sender就可以推理出來(lái)有多個(gè)包被丟了窗慎,于是乎繼續(xù)重傳sliding window里未被ack的第一個(gè)包。直到再也收不到了Partial Ack卤材,才真正結(jié)束Fast Recovery這個(gè)過(guò)程
我們可以看到遮斥,這個(gè)“Fast Recovery的變更”是一個(gè)非常激進(jìn)的玩法,他同時(shí)延長(zhǎng)了Fast Retransmit和Fast Recovery的過(guò)程扇丛。
算法示意圖
下面我們來(lái)看一個(gè)簡(jiǎn)單的圖示以同時(shí)看一下上面的各種算法的樣子:
幾類(lèi)定時(shí)器的作用
TCP共使用以下四種計(jì)時(shí)器术吗,即重傳計(jì)時(shí)器、堅(jiān)持計(jì)時(shí)器帆精、苯嫌欤活計(jì)時(shí)器和時(shí)間等待計(jì)時(shí)器。這幾個(gè)計(jì)時(shí)器的主要特點(diǎn)如下:
????1卓练、重傳計(jì)時(shí)器
????當(dāng)TCP發(fā)送報(bào)文段時(shí)隘蝎,就創(chuàng)建該特定報(bào)文段的重傳計(jì)時(shí)器〗笃螅可能發(fā)生兩種情況:
????(1)嘱么、若在計(jì)時(shí)器截止時(shí)間到(通常是60秒)之前收到了對(duì)此特定報(bào)文段的確認(rèn),則撤銷(xiāo)此計(jì)時(shí)器顽悼。
????(2)拱撵、若在收到了對(duì)此特定報(bào)文段的確認(rèn)之前計(jì)時(shí)器截止期到辉川,則重傳此報(bào)文段,并將計(jì)時(shí)器復(fù)位拴测。
????2、堅(jiān)持計(jì)時(shí)器
????為了對(duì)付零窗口大小通知府蛇,TCP需要另一個(gè)計(jì)時(shí)器集索。假定接收TCP宣布了窗口大小為零。發(fā)送TCP就停止傳送報(bào)文段汇跨,直到接收TCP發(fā)送確認(rèn)并宣布一個(gè)非零的窗口大小务荆。但這個(gè)確認(rèn)可能會(huì)丟失。我們知道在TCP中穷遂,對(duì)確認(rèn)是不需要發(fā)送確認(rèn)的函匕。若確認(rèn)丟失了,接收TCP并不知道蚪黑,而是會(huì)認(rèn)為它已經(jīng)完成任務(wù)了盅惜,并等待著發(fā)送TCP接著會(huì)發(fā)送更多的報(bào)文段。但發(fā)送TCP由于沒(méi)有收到確認(rèn)忌穿,就等待對(duì)方發(fā)送確認(rèn)來(lái)通知窗口的大小抒寂。雙方的TCP都在永遠(yuǎn)地等待著對(duì)方。
????要打開(kāi)這種死鎖掠剑,TCP為每一個(gè)連接使用一個(gè)堅(jiān)持計(jì)時(shí)器屈芜。當(dāng)發(fā)送TCP收到一個(gè)窗口大小為零的確認(rèn)時(shí),就啟動(dòng)堅(jiān)持計(jì)時(shí)器朴译。當(dāng)堅(jiān)持計(jì)時(shí)器期限到時(shí)井佑,發(fā)送TCP就發(fā)送一個(gè)特殊的報(bào)文段,叫做探測(cè)報(bào)文段眠寿。這個(gè)報(bào)文段只有一個(gè)字節(jié)的數(shù)據(jù)躬翁。它有一個(gè)序號(hào),但它的序號(hào)永遠(yuǎn)不需要確認(rèn)澜公;甚至在計(jì)算對(duì)其他部分的數(shù)據(jù)的確認(rèn)時(shí)該序號(hào)也被忽略姆另。探測(cè)報(bào)文段提醒對(duì)端:確認(rèn)已丟失,必須重傳坟乾。
????堅(jiān)持計(jì)時(shí)器的值設(shè)置為重傳時(shí)間的數(shù)值迹辐。但是,若沒(méi)有收到從接收端來(lái)的響應(yīng)甚侣,則需發(fā)送另一個(gè)探測(cè)報(bào)文段明吩,并將堅(jiān)持計(jì)時(shí)器的值加倍和復(fù)位。發(fā)送端繼續(xù)發(fā)送探測(cè)報(bào)文段殷费,將堅(jiān)持計(jì)時(shí)器設(shè)定的值加倍和復(fù)位印荔,直到這個(gè)值增大到門(mén)限值(通常是60秒)為止低葫。在這以后,發(fā)送端每隔60秒就發(fā)送一個(gè)探測(cè)報(bào)文段仍律,直到窗口重新打開(kāi)嘿悬。
????3、彼活計(jì)時(shí)器
????鄙普牵活計(jì)時(shí)器使用在某些實(shí)現(xiàn)中,用來(lái)防止在兩個(gè)TCP之間的連接出現(xiàn)長(zhǎng)時(shí)期的空閑草则。假定客戶(hù)打開(kāi)了到服務(wù)器的連接钢拧,傳送了一些數(shù)據(jù),然后就保持靜默了炕横。也許這個(gè)客戶(hù)出故障了源内。在這種情況下豺撑,這個(gè)連接將永遠(yuǎn)地處理打開(kāi)狀態(tài)腮敌。
要解決這種問(wèn)題,在大多數(shù)的實(shí)現(xiàn)中都是使服務(wù)器設(shè)置币妓冢活計(jì)時(shí)器伯铣。每當(dāng)服務(wù)器收到客戶(hù)的信息呻此,就將計(jì)時(shí)器復(fù)位。鼻还眩活計(jì)時(shí)器通常設(shè)置為2小時(shí)焚鲜。若服務(wù)器過(guò)了2小時(shí)還沒(méi)有收到客戶(hù)的信息,它就發(fā)送探測(cè)報(bào)文段放前。若發(fā)送了10個(gè)探測(cè)報(bào)文段(每一個(gè)相隔75秒)還沒(méi)有響應(yīng)忿磅,就假定客戶(hù)出了故障,因而就終止該連接凭语。
????4葱她、時(shí)間等待計(jì)時(shí)器
??? 時(shí)間等待計(jì)時(shí)器是在連接終止期間使用的。當(dāng)TCP關(guān)閉一個(gè)連接時(shí)似扔,它并不認(rèn)為這個(gè)連接馬上就真正地關(guān)閉了吨些。在時(shí)間等待期間中,連接還處于一種中間過(guò)渡狀態(tài)炒辉。這就可以使重復(fù)的FIN報(bào)文段(如果有的話)可以到達(dá)目的站因而可將其丟棄豪墅。這個(gè)計(jì)時(shí)器的值通常設(shè)置為一個(gè)報(bào)文段的壽命期待值的兩倍。