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


TCP/IP協議

作者:Danbo 2015-7-2
本文為參考TCP/IP詳解卷一,某些知識點加上了作者自己的理解,如有錯誤,歡迎指正,可以微博聯系我!

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狀態變遷圖

 

客戶端的狀態可以用一下流程圖來表示:

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

服務器的狀態可以流程圖:

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
 

版權聲明:本文為博主原創文章,未經博主允許不得轉載。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM