《TCP/IP详解》之二:流式数据交互
和UDP這種“滾珠”式的協(xié)議不同(一份數(shù)據(jù)就是一個(gè)udp packet),TCP以報(bào)文段的方式傳遞數(shù)據(jù),其大小受網(wǎng)絡(luò)鏈路的限制。在SYN報(bào)文段中互相通告最大報(bào)文段長(zhǎng)(MSS)。所以業(yè)務(wù)層交付的數(shù)據(jù),會(huì)被TCP拆分/合并為合適的報(bào)文段(這也就是為嘛TCP數(shù)據(jù)跟水流似的,沒(méi)有邊界)。
對(duì)于每個(gè)報(bào)文段而言,就很像UDP的“滾珠”了,不保證順序、不保證到達(dá)。TCP要對(duì)收到的報(bào)文重新排序,再才交給應(yīng)用層。發(fā)出一個(gè)報(bào)文段后,會(huì)啟動(dòng)一個(gè)定時(shí)器,等待對(duì)端ACK確認(rèn)收到,否則將重傳該報(bào)文。由于重傳機(jī)制,報(bào)文段可能發(fā)生重復(fù),接收端須丟棄重復(fù)報(bào)文。借此TCP實(shí)現(xiàn)了自己的可靠性。
那如何高效實(shí)現(xiàn)此可靠性的呢?
?
【ack確認(rèn)】
首先是ack的設(shè)計(jì),最直白的方式:跟我們平時(shí)寫(xiě)異步IO很相似,發(fā)送一份數(shù)據(jù),等待對(duì)方確認(rèn),再發(fā)下一份。
由于TCP是雙全工的,兩端都可有數(shù)據(jù)交互,對(duì)端的ack可以合并至其正常報(bào)文段中,減少交互量。此即:“經(jīng)受延時(shí)的確認(rèn)”——通常TCP在接收到數(shù)據(jù)時(shí)并不立即發(fā)送ACK;相反,它推遲發(fā)送,以便將ACK與需要沿該方向發(fā)送的數(shù)據(jù)一起發(fā)送。
再者,ACK是確認(rèn)收到的字節(jié)序,未必每一條報(bào)文都須ACK,可一次ACK多條數(shù)據(jù)。比如連續(xù)收到兩個(gè)報(bào)文段“PSH 1:1025; PSH 1025:2049”,只需回復(fù)一條“ack 2049”即可。“隔一個(gè)報(bào)文段確認(rèn)”策略——接收方不必確認(rèn)每一個(gè)收到的分組,ACK是累積的,它們表示收方已正確收到了一直到確認(rèn)序號(hào)減一的所有字節(jié)。
?
【快速重傳】
如果數(shù)據(jù)丟失/錯(cuò)誤,每次都等超時(shí)才重傳會(huì)慢。
收到一個(gè)失序報(bào)文段(前面一條丟失/錯(cuò)誤/暫未抵達(dá)),TCP立即產(chǎn)生一個(gè)ack(重復(fù)的,且不被延時(shí)),如果發(fā)方連續(xù)收到3(或以上)次相同的ack,就非常可能是報(bào)文段丟失/錯(cuò)誤,于是重傳報(bào)文段,不必等timeout。
(PS:為什么是3次,查資料說(shuō)是經(jīng)驗(yàn)數(shù)據(jù),連續(xù)3次,即表示嫌疑報(bào)文后的兩條報(bào)文都正在到達(dá)對(duì)端了,由于由于鏈路層的失序造成的時(shí)差幾乎不可能如此劇烈,可以認(rèn)為是丟了或校驗(yàn)出錯(cuò))
還有重傳哪些報(bào)文段的問(wèn)題。譬如連續(xù)發(fā)生5條報(bào)文,第二條丟了,其它均到達(dá),發(fā)方收到4個(gè)ack(2),僅知道2號(hào)報(bào)文必須重傳,3/4/5號(hào)是不清楚的(某些TCP實(shí)現(xiàn)采用的是重傳丟包后的所有報(bào)文)。
可在TCP包頭中加入SACK,匯報(bào)收到的數(shù)據(jù)碎片,這樣發(fā)送端就可知道哪些數(shù)據(jù)到了,哪些是丟了。還是剛才的例子,發(fā)方仍收到4個(gè)ack(2),多了sack:sack(1, 3-5)。
?
【Nagle算法和滑動(dòng)窗口】
其次,我們是有寬帶的,要是用戶(hù)讓發(fā)多少就發(fā)多少,會(huì)很浪費(fèi)。好像200人的航班帶兩個(gè)人飛過(guò)。解決方案也很簡(jiǎn)單:
一、限制小包的數(shù)目(Nagle算法,連接上最多只能有一個(gè)未確認(rèn)的小分組,該分組的ack到達(dá)之前不能發(fā)送其他小分組了,算法是自適應(yīng)的,收到的ack越快發(fā)的就越快);
二、上buffer,緩存用戶(hù)數(shù)據(jù),再分發(fā)成塊交給IP層傳輸,相應(yīng)的對(duì)端也要要個(gè)接收buffer,兩片buffer配合工作,保證收發(fā)正確性。
這就是TCP最大特征的“滑動(dòng)窗口”了。
每條報(bào)文段的TCP頭部中都會(huì)通告當(dāng)前窗口大小,發(fā)送端據(jù)此得知可預(yù)留多少字節(jié)的buffer給應(yīng)用層。具體工作流程如下圖(請(qǐng)忽略字體,多謝):
通告窗口為0,顯示接收方雖已收到所有數(shù)據(jù),但其應(yīng)用層尚未及時(shí)從TcpRecvBuff中讀取,發(fā)方會(huì)停止發(fā)送。
Zero Window下,應(yīng)用層讀取數(shù)據(jù)后,窗口重新空閑,要通知發(fā)送方(窗口更新)。一個(gè)單獨(dú)的主動(dòng)ack,不確認(rèn)數(shù)據(jù),僅報(bào)告新的窗口大小。
【堅(jiān)持定時(shí)器】
“單獨(dú)的主動(dòng)ack”,跟一般的ack不一樣,還記得一般得ack丟了怎么重傳的嗎?那這貨丟了怎么辦?
TCP四大定時(shí)器(重傳、堅(jiān)持、保活、2MSL)之堅(jiān)持定時(shí)來(lái)搞定。
Zero Window后,發(fā)方停止發(fā)送數(shù)據(jù),并設(shè)置其堅(jiān)持定時(shí)器,若超時(shí)前仍未收到窗口更新,則發(fā)送一個(gè)特殊的探查報(bào)文,獲知對(duì)端窗口大小。探查含1字節(jié),但不被ack確認(rèn),會(huì)一直重復(fù)。
?
【糊涂窗口綜合征】
好,我們有了Nagle算法,有了滑動(dòng)窗口,是不是就完全搞定了寬帶高效利用呢?
考慮這樣一種場(chǎng)景,Zero Window后,收方應(yīng)用程序每次僅拷走1個(gè)字節(jié),觸發(fā)窗口更新,ack win 1,發(fā)方窗口右邊沿更新,發(fā)送了1字節(jié)數(shù)據(jù),停止,等下一波窗口更新……loop(PS:實(shí)際不可能只是1字節(jié),tcp頭都20字節(jié))
相應(yīng)的解決方案:
1)接收方不通告小窗口,除非窗口增加至MSS(最大報(bào)文段長(zhǎng)),或增加到接收方緩沖空間的一半。
2)發(fā)送方只在下列條件之一滿(mǎn)足時(shí)才發(fā)數(shù)據(jù):
(a) 可以發(fā)送一個(gè)滿(mǎn)長(zhǎng)度的報(bào)文段
(b) 可以發(fā)送至少接收方窗口大小一半的報(bào)文段
(c) 無(wú)未被確認(rèn)的數(shù)據(jù),且能夠發(fā)送所有緩沖數(shù)據(jù)
(d) 連接關(guān)閉了Nagle算法
【擁塞避免、慢啟動(dòng)】
還有問(wèn)題嗎?之前由于寬帶利用問(wèn)題,我們要滑動(dòng)窗口,避免頻繁發(fā)小包;類(lèi)似的,由于路由問(wèn)題,我們還得避免不要太粗暴。
假設(shè)連接建立起始(窗口均空閑)便發(fā)送大量數(shù)據(jù),塞滿(mǎn)網(wǎng)絡(luò),而鏈路速率較慢——中間路由器就必須緩存分組了,一旦路由存儲(chǔ)空間耗盡,后來(lái)的分組會(huì)被直接丟棄,TCP吞吐量便隨之嚴(yán)重降低了。
“慢啟動(dòng)、擁塞避免”上場(chǎng)——擁塞窗口(congestion window,記作cwnd),慢啟動(dòng)門(mén)限(ssthresh)。
TCP連接建立時(shí),ssthresh初始化為65535字節(jié),擁塞窗口初始化為1個(gè)報(bào)文段(MSS),每收到一個(gè)ack,cwnd便增加一個(gè)報(bào)文段大小。發(fā)送方取min(擁塞窗口,滑動(dòng)窗口)作為發(fā)送上限。
發(fā)方先發(fā)送一個(gè)報(bào)文段,等ack,收到后cwnd增加到2,可發(fā)兩個(gè)報(bào)文段。收到這兩個(gè)報(bào)文段的ack時(shí),cwnd增加到4……指數(shù)增漲(慢啟動(dòng))。
攀升到一定值后抵達(dá)網(wǎng)絡(luò)容量,中間路由丟棄分組,發(fā)方重傳定時(shí)器超時(shí),知曉cwnd過(guò)大引起擁塞,cwnd被重設(shè)為1個(gè)報(bào)文段,ssthresh則被設(shè)置為當(dāng)前cwnd的一半。
還有種擁塞指示:收到重復(fù)ack,比如中間某報(bào)文損壞。此時(shí)僅設(shè)置ssthresh =?cwnd/2,并不改寫(xiě)cwnd。
若 cwnd <=?ssthresh,則進(jìn)行慢啟動(dòng),cwnd按指數(shù)更新;否則進(jìn)行擁塞避免,cwnd按線性方式增長(zhǎng)(每次ack增加1/cwnd,一個(gè)RTT內(nèi)最多增加一個(gè)報(bào)文段,無(wú)論期間受到多少個(gè)ack)。
轉(zhuǎn)載于:https://www.cnblogs.com/3workman/p/5738328.html
總結(jié)
以上是生活随笔為你收集整理的《TCP/IP详解》之二:流式数据交互的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: SQL Server 2012 新特性:
- 下一篇: keytool 错误:java.to.F