《TCP/IP詳解:卷一》-TCP部分講解

TCP/IP協議

做者:Danbo 2015-7-2
本文爲參考TCP/IP詳解卷一,某些知識點加上了做者本身的理解,若有錯誤,歡迎指正,能夠微博聯繫我!
java

TCP包格式和IP包格式以下:算法

TCP的正常創建與關閉

創建鏈接緩存

TCP協議提供可靠的面向鏈接服務,採用三次握手創建鏈接。
第一次握手:創建鏈接時,客戶端發送SYN包(syn=j)到服務器,並進入SYN_SEND狀態,等待服務器確認;
第二次握手:服務器收到SYN包,向客戶端返回ACK(ack=j+1),同時本身也發送一個SYN包(syn=k),即SYN+ACK包,此時服務器進入SYN_RCVD狀態;
第三次握手:客戶端收到服務器的SYN+ACK包,向服務器發送確認包ACK(ack=k+1),此包發送完畢,客戶端和服務器進入ESTABLISHED狀態,完成三次握手。
完成三次握手,客戶端與服務器開始傳送數據,也就是ESTABLISHED狀態。 服務器

 

終止鏈接網絡

採用四次揮手斷開雙向鏈接。
(1) TCP客戶端發送一個FIN,用來關閉客戶到服務器的數據傳送。
(2) 服務器收到這個FIN,它發回一個ACK,確認序號爲收到的序號加1。和SYN同樣,一個FIN將佔用一個序號。
(3) 服務器關閉客戶端的鏈接,發送一個FIN給客戶端。
(4) 客戶端發回ACK報文確認,並將確認序號設置爲收到序號加1。併發

TCP狀態變遷圖socket

 

客戶端的狀態能夠用一下流程圖來表示:ui

CLOSED->SYN_SENT->ESTABLISHED->FIN_WAIT_1->FIN_WAIT_2->TIME_WAIT->CLOSEDspa

服務器的狀態能夠流程圖:操作系統

CLOSED->LISTEN->SYN收到 ->ESTABLISHED->CLOSE_WAIT->LAST->ACK->CLOSED

 

2MSL等待狀態(兩個做用)

TIME_WAIT狀態也稱爲2MSL等待狀態。每一個具體TCP實現必須選擇一個報文段最大生存時間MSL(Maximum Segment Lifetime)。它是任何報文段被丟棄前在網絡內的最長時間。處理原則:當TCP執行一個主動關閉,併發回最後一個ACK,該連接必須在TIME_WAIT狀態停留的時間爲2MSL。這樣可以讓TCP有機會在此發送最後一個ACK以防這個ACK丟失(在另外一端發送FIN前提)

可是,在鏈接處於2MSL等待時,任何遲到返回的報文段將被丟棄。由於處於2MSL等待的、由該插口對(socket pair)定義的鏈接在這段時間內不能被再用,對於客戶程序還好 一些,可是對於服務程序,例如httpd,它老是要使用同一個端口80來進行服務,而在 2MSL時間內,啓動httpd就會出現錯誤(插口被使用)。爲了不這個錯誤,服務器給出了一個平靜時間(quit time)的概念,這是說在2MSL時間內,雖然能夠從新啓動服務器,可是這個服務器仍是要平靜等待MSL時間才能進行下一次鏈接,讓後來返回的數據包沒有機會影響到發送端,由於返回的包和從新創建的包使用同一個四元組,發送端沒法區分這兩個包屬於不一樣鏈接。(建議MSL時間爲2min,不過這個與操做系統有關。)

 

半打開狀態(Half-Open)

若是以防已經關閉或異常終止鏈接而另外一方殊不知道,咱們將這樣的TCP鏈接稱爲半打開的。這種狀態能夠經過Keepalive選項來進行發現兩一段已經消失。還有一種形式是:本端發送SYN,對端迴應ACK+SYN,此時本段不迴應ACK。

當處於半打開狀態的一方重啓並從新鏈接後,它將丟失復位前的全部信息,所以它並不知道數據報文段中提到的鏈接。此時就會返回RST(異常終止要發送RST置位的包)包應答,已關閉這次鏈接。此時只須要等待MSL時間,由於TCP默認機器重啓的時間大於MSL。PIX防火牆和IDS入侵檢測系統均可以假裝攻擊目標發送RST的包去終止異常的TCP鏈接。(好比限定鏈接的時間,減小半開鏈接限制超時時間)當咱們Telnet一個不存在的端口號時,本段立馬收到一個拒絕訪問的包,這個就是對方發送的RST包致使的。

 

半關閉狀態(Half-Close)

單方向鏈路關閉。即TCP鏈接一端在結束它的發送後還能接收來自另外一端數據的能力。程序調用的是shutdown,而不是close,不過大多數程序都是調用close終止兩個方向的鏈接。

 

最大報文段長度MSS(Option字段)

最大報文段長度表示TCP傳往另外一端的最大塊數據的長度。當創建一個鏈接時,每一方都受到對方通告的MSS值(MSS選項只能出如今SYN報文中)。若是一方收不到另外一方的MSS值,那麼就設爲默認的536字節。MSS是最長見的選項字段,還有另外一個選項叫作窗口放大因子(Window*Shift Count便可以發送超大的數據包,即乘以目前窗口的倍數爲實際一次發送的數據量。解決高速鏈路和高速主機普通TCP發包過慢問題。)。還有一些HASH值也會放在Option字段。而防火牆默認則會清掉IP和TCP的Option選項字段。

 

納格算法

網絡中某個應用程序不斷地送出小單位的資料,且某些常是1字節大小。由於TCP封包具備40字節的總頭部(加上20字節的IP頭部),這致使41字節大小的包只有一字節的數據,這形成了極大的資源浪費,更糟糕的是在慢速網絡下,這類包形成擁塞碰撞(Congestion Collapse)。TCP鏈接最多隻能有一個未被確認的小分組。只適用於低速鏈路。
 
Nagle算法過程:
1.發送端TCP將它從發送應用程序收到的第一個數據發送出去,哪怕只有一個字節;
2.在發送出第一個報文段後,發送端的TCP數據包就會在輸出緩存中積累並等待,當從接收端收到對上一個數據包的ACK或者緩存中積累到一個最大報文段後,發送端TCP就能夠發送這個報文段了。
Nagle算法的優勢就是簡單,而且它考慮到應用程序產生數據的速率,以及網絡運輸數據的速率。若應用程序比網絡更快,則報文段就更大(最大報文段)。若應用程序比網絡慢,則報文段就較小(小於最大報文段)。
不過有時候咱們必需要關閉納格算法的:比鼠標的移動,這個必須無時延的發送;還有功能鍵的發送,好比F1發送的不止一個字符,此時就不能啓用納格算法。
 

經受時延的確認

一般TCP在接收到數據時並不當即發送ACK;相反,它推遲發送,以便將ACK與須要沿該方向發送的數據一塊兒發送(有時稱這種現象爲數據捎帶ACK)。絕大多數實驗爲200ms)也就是說,TCP將以最大200ms的時延等待是否有屬於一塊兒發送。

一個例子:這裏咱們將舉另一個例子:在一個交互註冊過程當中鍵入中斷的一個特殊功能鍵。這個功能鍵一般能夠產生多個字符序列,常常從ASCII碼的轉移(escape)字符開始,若是TCP每次獲得一個字符,它極可能會發送序列中的第一個字符(ASCII碼的ESC),而後緩存其餘字符並等待對該字符的確認。但當服務器收到該字符後,它並不發送確認而是繼續的等待接受序列中的其餘字符。這就會常常觸發服務器的經受時延的確認算法,表示剩下的字符沒有在200ms內發送。對於交互用戶而言,這將產生明顯的時延。

注意只有客戶端這邊有經受時延的確認,由於客戶端這邊輸入的比較慢,服務器那邊收到數據就會當即確認。最大等待200ms尚未數據發送的話,客戶端就直接返回ACK了。

 

滑動窗口

TCP採用滑動窗口來進行傳輸控制,滑動窗口的大小意味着接收方有多大的緩存區能夠用於接收數據。發送方能夠經過滑動窗口的大小來肯定應該發送多少字節的數據。當滑動窗口爲0時,發送方通常不能再發送數據,可是緊急數據除外,例如:容許用戶終止在遠端機上的運行進程。另外一種狀況是發送方能夠發送一個1字節的數據報來通知接收方從新聲明它但願接收的下一字節及發送方的滑動窗口大小。
滑動窗口機制的基本原理就是在任意時刻,發送方都維持了一個連續的容許發送的幀的序號,稱爲發送窗口,同時,接收方也維持了一個容許接收的幀的序號,稱爲接收窗口。發送窗口和接收窗口的序號的上下界不必定要同樣,甚至大小也可不一樣。不一樣的滑動窗口協議窗口大小通常不一樣。發送窗口內的序號的上下界不必定要同樣,可是尚未被確認的幀,或者是哪些能夠被髮送的幀。

  1. 稱窗口左邊向右邊靠近爲窗口合攏。這種現象發生在數據被髮送和確認時。
  2. 當窗口右邊沿想右移動時將容許發送更多的數據,咱們稱之爲窗口張開。這種先發發送在另外一端的接收進程讀取已經確認的數據並釋放了TCP的接收緩存時。
  3. 當右邊裝口左移時,咱們稱之爲窗口收縮。

發送方打開幾號窗口表示發送方已經發送了該序列的幀,可是若是沒有獲得接收方ack確認的話,此時該序號的幀仍然在發送窗口中。接收方方打開幾號窗口表明接收端收到幾號的幀,可是並無返回ack確認。當接收方返回該序號的ack時,該序號關閉(合攏),接收方收到ack時,該序號窗口關閉(此時窗口張開,注意始終不能大於通告窗口大小)。

對於發送窗口來講窗口打開表明發送了該序列數據,但沒有收到確認,收到ACK後窗口合攏。
對於接受窗口來講窗口打開表明接受了該序列數據,但沒有發送確認,當發ACK後窗口合攏。

 

1比特滑動窗口協議
當發送窗口和接收窗口的大小固定爲1時,滑動窗口協議退化爲停等協議(stop-and-wait)。該協議規定發送方每發送一幀後就要停下來,等待接收方已正確接收的確認(Acknowledgement)返回後才能繼續發送下一個幀。因爲發送方須要判斷接收到的幀是新習發的幀仍是從新發送的幀,所以發送放要爲每一個幀加一個序號。因爲停等協議規定只要一幀徹底發送成功後才能發送新的幀,於是只用一比特來編號就夠了。

後退n協議
因爲停等協議要爲每個幀進行確認後才繼續發送下一幀,大大下降了信道利用率,所以又提出了後退n協議。後退n協議中,發送方在發完已給數據幀後 ,不停下來等待應答幀,而是連續發送若干個幀,即便在連續發送過程當中收到了接收方發來的應答幀,也能夠繼續發送。且發送方在每發完一個數據幀時都要設置超時定時器。只要在所設置的超時時間內未收到確認幀,就要重發相應的數據幀。如:當發送方發送了N個幀後,若發現該N幀的前一個幀在計時器超後仍未返回其確認信息,該幀被斷定爲出錯或者丟失,此時發送方就不得不從新發送出錯幀及其後的N幀。

從這裏不難看出,後退n協議一方面因連續發送數據幀而提升了效率,可是另外一方面,在重傳時又必須把原已正確重傳的數據幀進行重傳(僅因這些數據這以前有一個數據幀出錯),這種作法又使重傳效率下降。因而可知,若傳輸信道的傳輸質量不好於是致使誤碼率較大,連續測協議不必定優於中止等待協議。此協議中的發送窗口的大小爲k,接收窗口仍未1.

選擇重傳協議
在後退n協議中,接收方若發現錯誤幀就再也不接收後續的幀,及時是正確的幀到達,這顯然是一種浪費。另外一種效率跟高的策略是當接收方發現某幀出錯後,其後繼續送來的正確的幀雖然不能當即遞交給接收方的高層,但接收方扔可收下來,存放在一個緩衝區,同時要求發送方從新傳輸出錯的那一幀。一旦收到從新傳來的幀後,就能夠源存與緩衝區中國的其他幀一併按正確的順序遞交高層。這種方法稱爲選擇重發(Selectice Repeat),顯然,選擇重發減小了浪費,但要求接收方有足夠大的緩衝區空間。

 

糊塗窗口綜合症

當發送端應用程序產生數據很慢、或者接收端應用程序處理接收緩存區的數據很慢的時候,就會在鏈路中傳送很小的報文段,極端狀況下有小負載只有1字節而報文段卻又41字節。這種現象叫作糊塗窗口綜合症(Silly Window Syndrome)。
 
能夠在發送方或接收方任意一方採起措施來避免這種現象
在接收端避免措施
接收方不通告小窗口,一般算法是接收方不通告一個比當前窗口大的窗口除非窗口能夠增長一個報文段大小(將要接收MSS的大小)、或者能夠在增長接收方緩存空間的一半。
 
在發送端避免措施
發送方在知足一下條件之一後纔會發送數據:1.能夠發送一個滿長度的報文段;2.能夠發送至少是接收方通告窗口大小一半的報文段;3.可以發送手頭的全部數據而且不但願接收ACK或者改鏈接禁用了納格算法。
 

慢啓動

若是發送方一開始便向網絡發送多個報文段,知道達到接收方通告的窗口大小爲止。當發送方和接收方位於同一局域網還好。可是若是發送方和接收方之間存在多個路由器和速率較慢的鏈路時,可能出現問題。中間的路由器必須緩存分組,並有可能耗盡存儲器的空間。如今TCP支持一種被稱爲「慢啓動(slow start)」的算法。該算法核心是讓新分組進入網絡的速率與另外一端返回確認的速率相同而進行工做。
 
慢啓動爲發送方的TCP增長了另外一窗口:擁塞窗口(congestion window,cwnd)當與另外一個網絡的主機創建TCP鏈接時,擁塞窗口被初始化爲1個報文段。每收到一個ACK,擁塞窗口就增長一個報文段。以此成指數增加方式。發送方取擁塞窗口和通告窗口的最小值做爲發送上限。擁塞窗口是發送方使用的流量控制,而通告窗口則是接收方使用的流量控制。
 

TCP超時與重傳

TCP超時重傳採用指數退避的算法(exponential backoff)對連續重傳之間不一樣的時間差,他們取整後分別爲1\3\6\12\24\48\64(最大值爲64)
 

擁塞避免算法

擁塞算法是一種處理丟失分組的方法。網絡發生分組丟失的指示:發生超時和收到重複的ACK(3個或3個以上)
擁塞避免算法和慢啓動算法是兩個目的不一樣、獨立的算法。可是方發生擁塞時,咱們下午給你下降分組進入網絡的傳輸速率,因而能夠調用慢啓動來做到這一點。在實際中這兩個算法一般在一塊兒使用。
 
擁塞避免算法和慢啓動算法須要對每一個鏈接維持兩個變量:一個擁塞窗口(cwnd)和一個慢啓動門限(ssthresh)。這樣獲得的算法的工做過程以下:
1)對一個給定的鏈接,初始化cwnd爲1個報文段,ssthresh爲65535個字節;
2)TCP輸出數據大小不能超過cwnd和接收方通告窗口的大小。擁塞避免是發送方使用的流量控制,而通告窗口是接收方進行的流量控制。前置是發送方感覺到網絡擁塞的估計,後者則與接收方在該鏈接上的可用緩存大小有關;
3)當擁塞發生時(超時或收到重複確認),ssthresh被設置爲當前窗口的一半,但至少爲2個報文段大小。若是是超時引發的擁塞,則cwnd被設置爲1個報文段(這就是慢啓動);
4)當新的數據被對方確實時,就增長cwnd,但增長的方法依賴於咱們是否在進行慢啓動或擁塞避免。若是cwnd≤ssthresh,則正在進行慢啓動,反之進行擁塞避免。慢啓動一直持續到咱們咱們回到當擁塞發生時所處位置一半的時候才中止(即新的ssthresh)而後轉爲執行擁塞避免。
慢啓動算法初始cwnd爲1個報文段,每收到一個ack後cwnd就增長1(注意TCP是累計確認),那樣,窗口會以指數方式增加。
擁塞避免算法要求每次收到確認時將cwnd增長1/cwnd,這是個線性增加。咱們但願在一個往返時間內最多爲cwnd增長1個報文段,無論在這個RTT中收到了多少個ACK,而後慢啓動則是根據這個往返時間中所收到的確認的個數增長cwnd。
 
下圖是慢啓動和擁塞避免的可視化描述
 
解釋:上圖中,假定當cwnd爲32個報文段時就會發生擁塞。因而設置ssthresh爲16個報文段,而cwnd爲1個報文段。在時刻0發送一個報文段,並假設在時刻1接收到它的ACK,此時cwnd增長爲2.接着發送了2個報文段,並假設在時刻2接收到他們的ACK,因而cwnd增長爲4(對每一個ACK增長1次)。這種指數增長算法一直進行到在時刻3和時刻4之間收到8個ACK後cwnd等於ssthresh時才中止,從該時刻起,cwnd以線性方式增長,在每一個往返時間內最多增長1個報文段。
正如咱們在這個圖中看到的那樣,術語「慢啓動」並不徹底正確。它只是採用了比引發擁塞更慢的分組傳輸速率,但在慢啓動期間進入網絡分組速率依然是增長的。只有在達到ssthresh擁塞避免算法起做用時,這種增長的速率纔會慢下來。
 

快速重傳與快速恢復算法

算法一般按以下過程進行實現:
1)當收到第3個重複的ACK時,將ssthresh設置爲當前cwnd的一半。重傳丟失的報文段。而後設置cwnd爲當前ssthresh加上3倍的報文段大小。
代碼實現爲:[java] view plaincopy step1:if ( dupacks >= 3 ) { ssthresh = max( 2 , cwnd / 2 ) ;cwnd = ssthresh + 3 * SMSS ;}
2)每次收到另外一個重複的ACK時,cwnd++,併發送1個分組。注意!!是先按照上次cwnd發送數據包,而後再使cwnd增長一個報文段大小。
3)當下一個確認新數據的ACK達到時,設置cwnd爲ssthresh。這個ACK應該是在進行重傳後的一個往返時間內的全部中間報文段的確認。這一步採用的是擁塞避免,由於當分組丟失時咱們將當前的速率減半。
下圖是擁塞避免的一個例子:
咱們注意當cwnd爲512時進行慢啓動,由於只有當cwnd大於ssthresh才進行擁塞避免,當cwnd爲768時此時進行的仍是慢啓動,注意由於cwnd的增長是進行發送數據包以後的時,代碼實現是:cwnd++即:進行完發包後才進行自加的!
 

TCP堅持定時器

當一個通告窗口變化的ACK丟失後,則雙方就有可能由於等待對方而使鏈接終止:接收方等待接收數據(由於它已經向發送方通告了一個非0的窗口),而發送方在等待容許它繼續發送數據的窗口更新。爲防止這種死鎖狀況的發生,發送方使用一個堅持定時器(persist timer)來週期性地向接收方查詢,以便發現窗口是否增大。這些從發送發發出的報文段稱爲窗口探查(window probe)。
一樣當TCP一直收到窗口爲0的ACK時,使用指數退避的方式發送堅持定時器,TCP從不放棄發送窗口探查。這些探查每隔60s發送一次,這個過程將持續到或者窗口被打開,或者應用程序使用的鏈接被終止。
 

TCP保活定時器

keepalive
相關文章
相關標籤/搜索