TCP和UDP都使用相同的網絡層(IP),TCP卻嚮應用層提供與UDP完全不同的服務。TCP提供一種面向連接的、可靠的字節流服務。在一個TCP連接中,僅有兩 方進行彼此通信。主要通過一下方式提供可靠性:
1. 應用數據被分割成TCP認爲最爲適合發送的數據塊。這和UDP完全不同,應用程序產生的數據報長度將保持不變。由TCP傳遞給IP的信息單位稱爲報文段/段(segment)。
2. 當TCP發出一個段後,它啓動一個定時器,等待目的端確認收到這個報文段。如果不能及時收到一個確認,將重發這個報文段。
3. 當TCP收到發自TCP連接另一端的數據,它將發送一個確認。這個確認不是立即發送,通常將推遲幾分之一秒。
4. TCP將保持它首部和數據的檢驗和。這是一個端到端的檢驗和,目的是檢測數據在傳輸過程中的任何變化。如果收到段的檢驗和有差錯,TCP將丟棄這個報文段和不確認收到此報文段(希望發端超時並重發)
5. 既然TCP報文段作爲IP數據包來傳輸,而IP數據報的到達可能會失序,因此TCP報文段的到達也可能會失序。如果必要,TCP將對收到的數據進行重新排序,將收到的數據以正確的順序交給應用層。
6. 既然IP數據報會發生重複,TCP的接收端必須丟棄重複的數據。
7. TCP提供流量控制。TCP連接的每一方都有固定大小的緩衝空間。TCP的接收端只允許另一端發送接收端緩衝區所能接納的數據。這將防止較快主機致使較慢主機的緩衝區溢出。
兩個application通過TCP連接交換8bit字節構成的字節流。TCP不在字節流中插入記錄標識符——字節流服務(byte stream service)。如果一方的應用程序先傳10字節,又傳20字節,再傳50字節,連接的另一方將無法瞭解發方每次發送了多少字節。收方可以分4次接收這次80個字節,每次接收10字節。一端將字節流放到TCP連接上,同樣的字節流將出現在TCP連接的另一端。
TCP對字節流的內容不作任何解釋。TCP不知道傳輸的數據字節流是二進制數據,還是ASCII字符、EBCDIC字符或其他類型數據。對字節流的解釋由TCP連接雙方的應用層解釋。
【TCP首部】
每個TCP段都包含源端和目的端的端口號,用於尋找發端和收端應用進程。這兩個值加上IP首部中的源端IP地址和目的端IP地址唯一確定一個TCP連接。一個IP地址和一個端口號也稱爲一個插口(socket)。插口對(socket pair = 客戶IP地址 + 客戶端口號 + 服務器IP地址 + 服務器端口號的四元組)可唯一確定互聯網中每個TCP連接的雙方。
序號用來標識從TCP發端向TCP收端發送的數據字節流,它表示在這個報文段中的第一個數據字節。如果將字節流看作在兩個應用程序間的單向流動,則TCP用序號對每個字節進行計數。序號是32bit的無符號數,範圍爲 [ 0, 232 -1 ]。
當建立一個新的連接時,SYN標誌變1,ACK標誌變1。序號字段包含由這個主機選擇的該連接的初始序號ISN(initial sequence Number)。該主機要發送數據的第一個字節序號爲這個ISN加1,因爲SYN標誌消耗了一個序號。
既然每個傳輸的字節都被計數,確認序號包含發送確認的一端所期望收到的下一個序號。因此,確認序號應當是上次已成功收到數據字節序號➕1。只有ACK標誌爲1時,確認序號字段纔有效。發送ACK無需任何代價,因爲32bit的確認序號字段和ACK標誌一樣,總時TCP首部的一部分。
TCP爲應用層提供全雙工服務。數據能在兩個方向上獨立地進行傳輸。因此連接的每一端必須保持每個方向上的傳輸數據序號。TCP可以表述爲一個沒有選擇確認 or 否認的滑動窗口協議。是因爲TCP首部中的確認序號表示發方已成功收到字節,但還不包含確認序號所指的字節。當前還無法對數據流中選定的部分進行確認。TCP首部6個標誌比特如下:
URG |
緊急指針(urgent pointer)有效 |
ACK |
確認序號有效 |
PSH |
接收方應該儘快將這個報文段交給應用層 |
RST |
重建連接 |
SYN |
同步序號用來發起一個連接 |
FIN |
發端完成發送任務 |
【滑動窗口】
TCP的流量控制由連接的每一端通過聲明的窗口大小來提供。窗口大小爲字節數,起始於確認序號字段指明的值,這個值是接收端正期望接收的字節。窗口大小是一個16bit字段,最大值爲65535字節。
【檢驗和】
檢驗和覆蓋了整個TCP報文段:TCP首部和TCP數據。這是一個強制性的字段,一定是由發端計算和存儲,並由收端進行驗證。TCP檢驗和的計算和UDP檢驗和的計算相似。
~~~~~【TCP連接的建立與終止】~~~~~
【三次握手】
建立一條TCP連接的步驟:
1. 請求端(通常稱爲客戶)發送一個SYN段指明客戶打算連接的服務器的端口,以及初始序號(ISN)。這個SYN段爲報文段1.
2. 服務器發回包含服務器的初始序號的SYN報文段(報文段2)作爲應答。同時,將確認序號設置爲客戶的ISN➕1以對客戶的SYN報文段進行確認。一個SYN將佔用一個序號。
3. 客戶必須將確認號設置爲服務器的ISN➕1以對服務器的SYN報文段進行確認(報文段3)。
發送第一個SYN的一端將執行主動打開(active open)。接收這個SYN併發回下一個SYN的另一端執行被動打開(passive open)。當一個端爲建立連接而發送它的SYN時,它爲連接選擇一個初始序號。ISN隨時間而變化,因此每個連接都將具有不同的ISN。ISN可看作是一個32bit的計數器,每4ms➕1。這樣選擇序號的目的在於防止在網絡中被延遲的分組在以後又被傳送,而導致某個連接的一方對它作錯誤的解釋。
~~~~~~~~~~~~【科普小課堂】~~~~~~~~~~~~~
***如何選擇序號?***
在4.4BSD中,系統初始化時初始的發送序號被初始化爲1。這個變量每0.5 秒增加64000,並每隔9.5h 又回到0(對應這個計數器每8ms➕1,而不是每4ms➕1).另外,每次建立一個連接後,這個變量將增加64000.
【四次握手】
建立連接需要3次握手,而終止一個連接需要4次握手。這是由於TCP的半關閉(half-close)造成的。既然一個TCP連接是全雙工(即數據在兩個方向上能同時傳遞),因此每個方向必須單獨地進行關閉。這原則就是當一方完成它的數據發送任務後就能發送一個FIN來終止這個方向連接。當一端收到一個FIN,它必須知道應用層另一端幾經終止了那個方向的數據傳送。發送FIN通常是應用層進行關閉的結果。
收到一個FIN只意味着在這一方向上沒有數據流動。一個TCP連接在收到一個FIN後仍能發送數據。而這對利用半關閉的應用來說是可能的,儘管在實際應用中只有很少的TCP應用程序這樣做。
首先進行關閉的一方(即發送第一個FIN)將執行主動關閉,而另一方(收到這個FIN)執行被動關閉。通常一方完成主動關閉而另一方完成被動關閉。如上圖中,當服務器收到這個FIN,它發回一個ACK,確認序號爲收到的序號➕1(報文段5)。和SYN一樣,一個FIN將佔用一個序號。同時TCP服務器還向應用程序(即丟棄服務器)傳送一個文件結束符。接着這個服務器程序就關閉它的連接,導致它的TCP端發送一個FIN(報文段6),客戶必須發回一個確認,並確認序號設置爲收到序號➕1(報文段7)。
上圖中,這些FIN的ACK是由TCP軟件自動產生的。連接通常是由客戶端發起的。這樣第一個FIN從客戶傳到服務器,每一端都能主動關閉這個連接(即首先發送FIN)。然而一般由客戶端決定何時終止連接,因爲客戶進程通常由用戶交互控制,用戶會輸入如“quit”等來終止進程。
【連接建立超時】
第一次超時時間在 [ 5.59 s, 5.93s ] 之間變化,第二次超時時間總是24.00秒。BSD版的TCP採用一種500ms的定時器,如下:
當滴答計數器爲0時,6秒的定時器便會超時,這個定時器會在以後的24秒(48個滴答)重新復位。之後的下一個定時器將更接近24秒,因爲當TCP的500ms定時器被內核調用時,它就會被修改一次。
【最大報文段長度】
最大報文段長度(MSS)表示TCP傳往另一端的最大塊數據的長度。當一個連接建立時,連接的雙方都要通告自己的MSS。MSS的默認值時536字節(這個默認值允許20字節的IP首部和20字節的TCP首部以適合576字節IP數據報)。如果沒有分段發生,MSS還是越大越好。報文段越大允許每個報文段傳送的數據就越多,相對IP和TCP首部有更高的網絡利用率。對於一個以太網,MSS <= 1460字節,使用IEEE802.3的封裝,MSS <= 1452字節。
【TCP半關閉】
TCP提供了連接的一端在結束它的發送後還能接收來自另一端數據的能力——半關閉。爲了使用這個特性,編程接口必須爲應用程序提供一種方式來說明“我已經完成了數據傳送,因此發送一個文件結束(FIN)給另一端,但我還想接收另一端發來的數據,直到它給我發來文件結束”。
**爲什麼要半關閉?**
沒有半關閉,需要其他的一些技術讓客戶通知服務器,客戶端已經完成了它的數據傳送,但仍要接收來自服務器的數據。使用兩個TCP連接也可作爲一個選擇,但使用半關閉的單連接更好。
【TCP狀態變遷圖】
實線箭頭——> 正常的客戶端狀態變遷;虛線箭頭——> 正常的服務器狀態變遷
第二點是兩個導致進入ESTABLISHED狀態的變遷對應打開一個連接,而兩個導致從ESTABLISHED狀態離開的變遷對應關閉一個連接。ESTABLISHED狀態是連接雙方能夠進行雙向數據傳遞的狀態。
只有當SYN_RCVD狀態是從LISTEN狀態(正常情況)進入,而不是從SYN_SENT狀態(同時打開)進入時,從SYN_RCVD回到LISTEN的狀態變遷纔是有效的。這意味着如果執行被動關閉(進入LISTEN),收到一個SYN,發送一個帶ACK的SYN(進入SYN_RCVD),然後收到一個RST,而不是一個ACK,便又回到LISTEN狀態並等待另一個連接請求的到來。
【2MSL等待狀態】
TIME_WAIT狀態/2MSL等待狀態——每個具體TCP實現必須選擇一個報文段最大生存時間MSL(Maximum Segment Lifetime)。它是任何報文段被丟棄前在網絡內的最長時間。這個時間是有限的,因爲TCP報文段以IP數據報在網絡內傳輸,而IP數據報則有限制其生存時間的TTL字段。對IP數據報TTL的限制是基於跳數,而不是定時器。
對一個具體實現所給定的MSL值,處理的原則是:當TCP執行一個主動關閉,併發回最後一個ACK,該連接必須在TIME_WAIT狀態停留的時間爲2倍的MSL。這樣可讓TCP再次發送最後的ACK以防這個ACK丟失(另一端超時並重發最後的FIN)。
這種2MSL等待的另一個結果是這個TCP連接在2MSL等待期間,定義這個連接的插口(客戶的IP地址和端口號,服務器的IP地址和端口號)不能再被使用。這個連接只能在2MSL結束後才能再被使用。在2MSL等待期間,插口中使用的本地端口在默認情況下不能再被使用。
在連接處於2MSL等待時,任何遲到的報文段將被丟棄。因爲處於2MSL等待的、由該插口對(socket pair)定義的連接在這段時間內不能被使用,因此當要建立一個有效的連接時,來自該連接的一個較早替身(你 carnation)的遲到報文段作爲新連接的一部分不可能不被曲解(一個連接由一個插口對來定義。一個連接的新的實例(instance)稱爲該連接的替身)。
【平靜時間的概念】
對於來自某個連接的較早替身的遲到報文段,2MSL等待可防止將它解釋成使用相同插口對的新連接的一部分。但這只有在處於2MSL等待連接中的主機處於正常工作狀態時纔有效。
如果使用處於2MSL等待端口的主機出現故障,它會在MSL秒內重新啓動,並立即使用故障前仍處於2MSL的插口對來建立一個新的連接嗎?如果是這樣,在故障前從這個連接發出而遲到的報文段會被錯誤地當作屬於重啓後新連接的報文段。無論如何選擇重啓後新連接的初始序號,都會發生這種情況。
爲了防止這種情況,RFC 793指出TCP在重啓動後的MSL秒內不能建立任何連接。這就稱爲平靜時間(quiet time)。大多數主機重啓動的時間都比MSL秒要長。
【FIN_WAIT_2 狀態】
在FIN_WAIT_2狀態已經發出了FIN,並且另一端也已對它進行確認。除非在實行半關閉,否則將等待另一端的應用層意識到它已收到一個文件結束符說明,併發一個FIN來關閉另一方向的連接。只有當另一端的進程完成這個關閉,纔會從FIN_WAIT_2 狀態進入TIME_WAIT狀態。一端可能永遠保持這個狀態,另一端也將處於CLOSE_WAIT狀態,並一直保持這個狀態直到應用層決定進行關閉。
【復位報文段】
TCP首部中的RST比特是用於“復位”的。一般說來,無論何時一個報文段發往基準的連接(referenced connection)出現錯誤,TCP都會發出一個復位報文段。基準的連接是指目的IP地址和目的端口號以及源IP地址和源端口號指明的連接。
【到不存在的端口的連接請求】
產生復位的一種常見情況是當連接請求到時,目的端口沒有進程正在聽。對於UDP,當一個數據報到達目的端口時,該端口沒在使用,它將產生一個ICMP端口不可達的信息。而TCP則使用復位。
【異常終止一個連接】
終止一個連接的正常方式是一方你發送FIN,也稱爲有序釋放(orderly release),因爲在所有排隊數據都已發送之後才發送FIN,正常情況下沒有任何數據丟失。但也有可能發送一個復位報文段而不是FIN來中釋放一個連接,稱爲異常釋放(abortive release)。異常終止一個連接對應用程序來說有2個優點:
1. 丟棄任何待發數據並立即發送復位報文段
2. RST的接收方會區分另一端執行的是異常關閉還是正常關閉
應用程序使用的API必須提供產生異常關閉而不是正常關閉的手段。使用sock程序能夠觀察這種異常關閉的過程。Sock API通過“linger on close”選項(SO_LINGER)提供了這種異常關閉的能力。加上 -L 選項並將停留時間設爲0。這將導致連接關閉時進行復位而不是正常的FIN。
【檢測半打開連接】
如果一方已經關閉或異常終止連接而另一方卻還不知道,這樣的TCP連接稱爲半打開(Half-Open)。任何一端的主機異常都可能導致發生這種情況。只要不打算在半打開連接上傳輸數據,仍處於連接狀態的一方就不會檢測另一方已經出現異常。
半打開連接的另一個常見原因是當客戶主機突然掉電而不是正常的結束客戶應用程序後再關機,這可能發生在使用PC機作爲Telnet的客戶主機上。
【同時打開】
兩個應用程序同時彼此執行主動打開的情況是可能的,儘管發生的可能性極小。每一方必須發送一個SYN,且這些SYN必須傳遞給對方。這需要每一方使用一個對方熟知的端口作爲本地端口。
TCP是特意設計爲了可以處理同時打開,對於同時打開它僅建立一條連接而不是兩條連接(其他的協議族,最突出的是OSI運輸層,在這種情況下將建立兩條連接而不是一條連接)。如下圖:
過程:兩端幾乎在同時發送SYN並進入SYN_SENT狀態。當每一端收到SYN時,狀態變爲SYN_RCVD同時它們都再發SYN並對收到的SYN進行確認。當雙方都收到SYN及相應的ACK時,狀態都變遷爲ESTABLISHED。一個同時打開的連接需要交換4個報文段,比正常的3次握手多一個。此外,要注意的是沒有將任何一端稱爲客戶或服務器,因爲每一端既是客戶又是服務器。
【同時關閉】
同時關閉(simultaneous close):當應用層發出關閉命令時,兩端均從ESTABLISHED變爲FIN_WAIT _1。這將導致雙方各發送一個FIN,兩個FIN經過網絡傳送後分別到達另一端。收到FIN後,狀態由FIN_WAIT_1變遷到CLOSING,併發送最後的ACK。當收到最後的ACK時,狀態變化爲TIME_WAIT。同時關閉與正常關閉使用的段交換數目相同。4個報文段。
【TCP選項】
大多數的TCP服務器進程是併發的。當一個新的連接請求到達服務器時,服務器接受這個請求,並調用一個新進程來處理這個新的客戶請求。不同的OS使用不同的技術來調用新的服務器進程。在Unix系統下,常用的技術是使用fork函數來創建新的進程。如果系統支持,也可使用輕型進程,即線程(thread)。
**當一個服務器進程接受一來自客戶進程的服務請求時是如何處理端口?
**如果多個連接請求幾乎同時到達會發生什麼情況?
【TCP服務器端口號】
netstat -a -n -f inet
-a |
將顯示網絡中的所有主機端,不僅僅是處於ESTABLISHED的主機端 |
-n |
將以點分十進制的形式顯示IP地址,而不是通過DNS將地址轉化爲主機名,同時還要求顯示端口號而不是服務名稱。 |
-f inet |
僅要求顯示使用TCP或UDP的主機 |
TCP使用由本地地址和遠端地址組成的4元組= 目的IP地址 ➕目的端口號 ➕ 源IP地址 ➕源端口號來處理傳入的多個連接請求。TCP僅通過目的端口號無法確定那個進程接收了一個連接請求。
【限定的本地IP地址】
服務器不能任選其本地IP地址而必須使用特定的IP地址。
1.如果爲sock程序指明一個IP地址(或主機名),並將它作爲服務器,那麼該IP地址就成爲處於LISTEN服務器的本地IP地址。如
Sock -s IP地址 端口號 ——連接僅侷限於來自SLIP(IP地址)。
【限定的遠端IP地址】
UDP服務器通常在指定IP本地地址和本地端口外,還能指定遠端IP地址和遠端端口。RFC 793中顯示的接口函數允許一個服務器在執行被動打開時,可指明遠端插口(等待一個特定的客戶執行主動打開),也可不指明遠端插口(等待任何客戶)。大多數API都不支持這麼做,服務器必須不指明遠端插口,而等待連接請求的到來,然後檢查客戶端的IP地址和端口號。TCP服務器進行連接時3種類型的地址綁定由先到後如下:
Iport是服務器的熟知端口,而localIP必須是一個本地接口的IP地址。表中由上到下就是綁定的優先順序。
【呼入連接請求隊列】
當服務器正處於忙時,TCP是如何處理這些呼入的連接請求???
1. 正等待連接請求的一端有一個固定長度的連接隊列,該隊列中的連接已被TCP接受(即三次握手已經完成),但還沒有被應用程序所接受。
注意區分TCP接受一個連接是將其放入這個隊列,而應用層接受連接是將其從該隊列中移除。
2. 應用層將指明該隊列的最長長度,成爲積壓值(backlog)。範圍爲0~5之間的整數,包括0和5。
3. 當一個連接請求(SYN)到達時,TCP使用一個算法根據當前連接隊列中的連接數來確定是否接收這個連接。積壓值說明的是TCP監聽的端點已被TCP接受而等待應用層接受的最大連接數。這個積壓值對OS所允許的最大連接數,或者併發服務器所能併發處理的客戶數並無影響。
4. 如果對於新的連接請求,該TCP監聽的端點的連接隊列中還有空間,TCP模塊將對SYN進行確認並完成連接的建立。但應用層只有在3次握手中的第三個報文段收到後纔會知道這個新連接時。另外,當客戶進程的主動打開成功但服務器的應用層還不知道這個新的連接時,它可能會認爲服務器進程已經準備好接收數據了(如果發生這種情況,服務器的TCP僅將接收的數據放入緩衝隊列)。
5. 如果對於新的連接請求,連接隊列已沒有空間,TCP將不理會收到的SYN,也不發回任何報文段(即不發回RST)。如果應用層不能及時接受已被TCP接受的連接,這些連接可能佔滿整個連接隊列,客戶的主動打開最終超時。
當隊列已滿時,TCP將不理會傳入的SYN,也不發回RST作爲應答,因爲這是一個軟錯誤而不是一個硬錯誤。通常隊列已滿是由於應用程序或OS忙造成的,這樣可防止應用程序對傳入的連接進行服務。這個條件在一個很短的時間內可以改變。但如果服務器的TCP以系統復位作爲響應,客戶進程的主動打開將被廢棄(如果服務器程序沒有啓動)。由於不應答SYN,服務器程序迫使客戶TCP隨後重傳SYN,以等待連接隊列又空間接受新的連接 。
~~~~~~~~【TCP的交互數據流】~~~~~~~~
據研究發現,如果按照分組數量計算,約有一半的TCP報文段包含成塊數據(如FTP、電子郵件和Usenet新聞),另一半則包含交互數據(如Telnet和Rlogin)。如果按照字節計算,則成塊數據與交互數據的比例約爲90%和10%。這是因爲成塊數據的報文段基本上都是滿長度(full-sized,通常爲512字節的用戶數據),而交互數據則小得多。
【交互式輸入】
Rlogin每次總是從客戶發送一個字節,而不是每次一行到服務器,並且需要遠程系統(服務器)回顯客戶鍵入的字符。會產生4個報文段:
1. 來自客戶的交互按鍵;2. 來自服務器的按鍵確認;3. 來自服務器的按鍵回顯;4. 來自客戶的按鍵回顯確認。
TCP是怎樣確認的?!
第一行以序號0發送數據字節,第2行通過將確認序號設爲1,也就是最後成功收到的字節的序號加1,來對其進行確認。
【經受時延的確認】
絕大多數實現採用的時延爲200ms,即TCP將以最大200ms的時延等待是否數據一起發送。
【Nagle算法】
該算法要求一個TCP連接上最多只能有一個未被確認的未完成的小分組,在該分組的確認到達之前不能發送其他的小分組。相反,TCP收集這些少量的分組, 並在確認到來時以一個分組的方式發出去。該算法的優越之處在於它是自適應的:確認到達得越快,數據也就發送得越快。而在希望減少微小分組數目的低速廣域網上,則會發送更少的分組。
在以太網上一個字節被髮送、確認和回顯的平均往返時間約爲16ms。爲了產生比這個速度更快的數據,每秒鍵入的字符必須 >= 60個。這表明在局域網環境下兩個主機之間發送數據時很少使用這個算法。
插口API用戶可以使用TCP_NODELAY選項來關閉Nagle算法,Host Requirements RFC聲明TCP必須實現Nagle算法,但必須爲應用提供一種方法來關閉該算法在某個連接上執行。
在較慢的廣域網環境中,通常使用Nagle算法來減少這些小報文段的數目。這個算法限制發送者任何時候只能有一個發送的小報文段未被確認。
對於這小的報文段,接收方使用經受時延的確認方法來判斷確認是否可被推遲發送,以便與回送數據一起發送。這樣通常會減少報文段的數目,尤其是對於需要回顯用戶輸入字符的Rlogin會話。
交互數據總是以小於最大報文段長度的分組發送。在Rlogin中通常只有一個字節從客戶發送到服務器。Telnet允許一次發送一行輸入數據,但是目前大多數實現仍然發送一個字節。
~~~~~~~~【TCP的成塊數據流】~~~~~~~~
【正常數據流】
Sock -i -s 7777
-i和-s指示程序作爲以一個“吸收(sink)”服務器運行(從網絡上讀取並丟棄數據),服務器端口指明爲7777。相應的客戶程序運行爲:
Sock -i -n8 服務器 7777
該命令指示客戶向網絡發送8個1024字節的數據。
使用TCP的滑動窗口協議時,接收方不必確認每一個收到的分組。在TCP中,ACK是累積的——它們表示接收方已經正確收到了一直到確認序號減1的所有字節。
在線路上看到的分組順序依賴於許多無法控制的因素:發送方TCP的實現、接收方TCP的實現、接收進程讀取數據(依賴於OS的調度)和網絡的動態性(如以太網的衝突和退避等)。對這兩個TCP而言,沒有一種單一的、正確的方法來交換給定數量的數據。
**【滑動窗口】**
當接收方確認數據後,這個滑動窗口不時地向右移動。窗口兩個邊沿的相對運動增加或減少了窗口的大小。術語描述如下:
1. 稱窗口左邊沿向右邊沿靠近爲窗口合攏。這種現象發生在數據被髮送和確認時
2. 當窗口右邊沿向右邊移動時將允許發送更多的數據,稱之爲窗口張開。這種現象發生在另一端的接收進程讀取已經確認的數據並釋放了TCP的接收緩存時
3. 當右邊沿向左移動時,稱之爲窗口收縮。Host Requirement RFC強烈建議不要使用這種方式。但TCP必須能夠在某一端產生這種情況時處理。
因爲窗口的左邊沿受另一端發送的確認序號的控制,因此不可能向左邊移動。如果接收到一個指示窗口左邊沿向左移動的ACK,則它被認爲是一個重複ACK,並被丟棄。如果左邊沿到達右邊沿,則稱其爲一個零窗口,此時發送方不能夠發送任何數據。
1. 發送方不必發送一個全窗口大小的數據
2. 來自接收方的一個報文段確認數據並把窗口向右邊滑動。這是因爲窗口的大小是相對於確認序號的。
3. 正如從報文段7到報文段8中變化的那樣,窗口的大小可以減少,但是窗口的右邊沿卻不能夠向左移動。
4. 接收方在發送一個ACK前不必等待窗口被填滿。
【窗口大小】
插口API允許進程設置發送和接收緩存的大小,接收緩存的大小是該連接上所能夠通告的最大窗口大小。由接收方提供的窗口的大小通常可以由接收進程控制,這將影響TCP的性能。
【PUSH標誌】
發送方使用該標誌通知接收方將所收到的數據全部提交給接收進程,數據包括與PUSH一起傳送的數據以及接收方TCP已經爲接收進程收到的其他數據。一個好的TCP實現能夠自行決定何時設置這個標誌。
代碼中的註釋表明該算法對那些只有在緩存被填滿或收到一個PUSH標誌時才嚮應用程序提交數據的TCP實現有效。使用插口API通知TCP設置正在接收數據的PUSH標誌或得到該數據是否被設置PUSH標誌的信息是不可能的。
【慢啓動】
如果在發送方和接收方之間存在多個路由器和速率較慢的鏈路時,就有可能出現一些問題。一些中間路由器必須緩存分組,並可能耗盡存儲器的空間。“慢啓動(slow start)”算法通過觀察到新分組進入網絡的速率應該與另一端返回確認的速率相同而進行工作。
慢啓動爲發送發的TCP增加了另一個窗口:擁塞窗口(congestion window),記爲cwnd。當與另一個網絡的主機建立TCP連接時,擁塞窗口被初始化爲1個報文段(即另一端通告的報文段大小)。每收到一個ACK,擁塞窗口就增加一個報文段(cwnd以字節爲單位,但是慢啓動以報文段大小爲單位進行增加)。發送發取擁塞窗口與通告窗口中的最小值作爲發送上限。擁塞窗口是發送方使用的流量控制,而通告窗口則是接收方使用的流量控制。
發送方開始時發送一個報文段,然後等待ACK。當收到該ACK時,擁塞窗口從1增加爲2,即可以發送兩個報文段。當收到這兩個報文段的ACK時,擁塞窗口就增加4。這是一種指數增加的關係。
【成塊數據吞吐量】
ACK報文段通常比數據報文段小,因爲通常只有一個IP首部➕一個TCP首部。
***通常發送一個分組的時間取決於兩個因素:傳播時延(由光的有限速率、傳輸設備的等待時間等引起)和一個取決於媒體速率(即媒體每秒可傳輸的比特數)的發送時延。對於一個給定的兩個接點之間的通路,傳播時延一般是固定的,而發送時延則取決於分組的大小。在速率較慢的情況下,發送時延起主要作用。
理想連接穩定狀態:發送方和接收方之間的管道(pipe)被填滿,不論擁塞窗口和通告窗口是多少,都不能再容納更多的數據。每當接收方再某一時間單位從網絡上移去一個報文段,發送方就再發送一個報文段到網絡上。但是不管有多少報文段填充了這個管道,返回路徑上總是具有相同數目的ACK。
【帶寬時延乘積】
接收方的通告窗口必須 > = 這個數目,因爲通告窗口限制了發送方能夠發送的段的數目。可以計算通道的容量爲:
不論是帶寬or時延均會影響發送方和接收方之間通路的容量。增加一倍的RTT會使通路容量也增加一倍。
【擁塞】
1.當數據到達一個大的通道(如一個快速局域網)並向一個較小的管道(如一個較慢的廣域網)發送時便會發生擁塞。
2. 當多個輸入流到達一個路由器,而路由器的輸出流小於這些輸入流的總和時也會發生擁塞。
大多數的主機都連接在局域網上,並通過一個路由器與速率相對較低的廣域網相連。其典型情況如下:
【緊急方式】
緊急方式(urgent mode):使一端可以告訴另一端有些具有某種方式的“緊急數據”已經放置在普通的數據流中。另一端被通知這個緊急數據已被放置在普通數據流中,由接收方決定如何處理。
可以通過設置TCP首部中的兩個字段來發出這種從一端到另一端的緊急數據已經被放置在數據流中的通知。URG比特被置1,並且一個16bit的緊急指針被置爲一個正的偏移量,該偏移量必須與TCP首部中的序號字段相加,以便得出緊急數據的最後一個字節的序號。
TCP必須通知接收進程,何時已接收到一個緊急數據指針以及何時某個緊急數據指針還不在此連接上,或者緊急指針是否在數據流中向前移動。接着接收進程可以讀取數據流,並必須能夠告訴被告知何時碰到了緊急數據指針。只要從接收方當前讀取位置到緊急數據指針之間有數據存在,就認爲應用程序處於“緊急方式”。在緊急指針通過之後,應用程序便轉回到正常方式。TCP本身對緊急數據知之甚少,沒有辦法指明緊急數據從數據流的何處開始。TCP通過連接傳送的唯一信息就是緊急方式已經開始(TCP首部中的URG比特)和指向緊急數據最後一個字節的指針。其他的事情留給application去處理。
***緊急方式有什麼用???***
1.Telnet和Rlogin從服務器到客戶使用緊急方式是因爲在這個方向上的數據流很可能要被客戶的TCP停止(即通告了一個大小爲0的窗口)。但是如果服務器進程進入了緊急方式,儘管它不能夠發送任何數據,服務器TCP也會立即發送緊急指針和URG標誌。當客戶TCP接收到這個通知時就會通知客戶進程,於是客戶可以從服務器讀取其輸入、打開窗口並使數據流動。
如果接收方處理第一個緊急指針之前,發送方多次進入緊急方式會發什麼情況?
在數據流中的緊急指針會向前移動,而其在接收方的前一個位置將丟失。接收方只有一個緊急指針,每當對方有新的值到達時它將被覆蓋。這意味着如果發送方進入緊急方式時所寫的內容對接收方非常重要,那麼這些字節數據必須被髮送方用某種方式特別標記。
~~~~~~~~【TCP的超時與重傳】~~~~~~~~
TCP提供可靠的運輸層,使用的方法之一就是確認從另一端收到的數據。但數據和確認都有可能會丟失。TCP通過在發送時設置一個定時器來解決問題。如果當定時器溢出時還沒有收到確認,它就重傳該數據。對於每個連接,TCP管理4個不同的定時器。
1. 重傳定時器使用於當希望收到另一端的確認。
2. 堅持(persist)定時器使窗口大小信息保持不斷流動,即使另一端關閉了其接收窗口。
3. 保活(keepalive)定時器可檢測一個空閒連接的另一端何時崩潰或重啓。
4. 2MSL定時器測量一個連接處於TIME_WAIT狀態的時間。
【往返時間/ RTT測量】
Karn算法:當一個超時和重傳發生時,在重傳數據的確認最後到達之前,不能更新RTT估計器,因爲不知道ACK對應哪次傳輸(也許第一次傳輸被延遲而並沒有被丟棄,也有可能第一次傳輸的ACK被延遲)。
【擁塞避免算法】
擁塞避免算法是一種處理丟失分組的方法。該算法假定由於分組受到損壞引起的丟失是非常少的(遠小於1%),因此分組丟失就意味着在源主機和目的主機之間的某處網絡發生了擁塞。有兩種分組丟失的指示:發生超時和接收到重複的確認。
擁塞避免算法和慢啓動算法是兩個目的不同、獨立的算法。但是當擁塞發生時,希望能降低分組進入網絡的傳輸速率,於是可以調用慢啓動來作到這一點。在實際中這兩個算法通常在一起實現。擁塞避免算法和慢啓動算法需要對每個連接維持兩個變量:一個擁塞窗口cwnd 和一個慢啓動門限 ssthresh。具體過程如下:
1. 對於一個給定的連接,初始化cwnd = 1個報文段,ssthresh = 65535個字節。
2. TCP輸出例程的輸出不能超過cwnd和接收方通告窗口的大小。擁塞避免是發送方使用的流量控制,而通告窗口則是接收方進行的流量控制。前者是發送方感受到的網絡擁塞的估計,而後者則與接收方在該連接上的可用緩存大小有關。
3. 當擁塞發生時(超時或收到重複確認),ssthresh被設置爲當前窗口大小的一半(cwnd和接受方通告窗口大小的最小值,但最少爲2個報文段)。此外,如果是超時引起了擁塞,則cwnd被設置爲1個報文段(這就是慢啓動)。
4. 當新的數據被對方確認是,就增加cwnd,但增加的方法依賴於是否在進行慢啓動或擁塞避免。如果cwnd <= ssthresh,則正在進行慢啓動,否則正在進行擁塞避免。慢啓動一直持續到回到當擁塞發生時所處位置的半時候才停止,然後轉爲執行擁塞避免。
~~~~【慢啓動算法 VS 擁塞避免算法】~~~~
慢啓動算法:初始設置cwnd爲1個報文段,此後每收到一個確認就➕1。這會使窗口按指數方式增長:發送1個報文段,然後是2個,接着是4個……. 慢啓動只是採用了比引起擁塞更慢些的分組傳輸速率,但在慢啓動期間進入網絡的分組數增加的速率仍然是在增加的。只有在達到ssthresh擁塞避免算法起作用時,這種增加的速率纔會慢下來。
擁塞避免算法:要求每次收到一個確認時將cwnd增加 1/cwnd。這是一種加性增長(additive increase)。若希望在一個RTT內最多爲cwnd增加1個報文段(不管在這個RTT中收到了多少個ACK),然而慢啓動將根據這個RTT中收到的確認的個數增加cwnd。對比圖如下:
【快速重傳與快速恢復算法】
一個重複的ACK是由一個丟失的報文段引起的,還是由於僅僅出現了幾個報文段的重新排序。若是一些報文段的重新排序,則在重新排序的報文段被處理併產生一個新的ACK之前,只可能產生1~2個重複的ACK。如果一連串收到3個或3個以上的重複ACK,就非常可能是丟失了一個報文段。需要重傳丟失的數據報文段,而無需等待超時定時器溢出——快速重傳算法。接下來執行的不是慢啓動而是擁塞避免算法—快速恢復算法。
這個算法執行過程如下:
1. 當收到第3個重複的ACK時,將ssthresh設置爲當前擁塞窗口cwnd /2。重傳丟失的報文段並設置cwnd = ssthresh➕3倍的報文段。
2. 每次收到另一個重複的ACK時,cwnd增加1個報文段大小併發送1個分組(如果新的cwnd允許發送)。
3. 當下一個確認新數據的ACK到達時,設置cwnd爲ssthresh(擁塞窗口/2)。這個ACK應該是在進行重傳後的一個RTT內對步驟1中重傳的確認。另外,這個ACK也應該是對丟失的分組和收到的第1個重複的ACK之間的所有中間報文段的確認。這一步採用的是擁塞避免,因爲當分組丟失時將當前的速率減半。
【按每條路由進行度量】
較新的TCP實現在路由表項中維持許多介紹過的指標。當一個TCP連接關閉時,如果已經發送了足夠多的數據來獲得有意義統計資料,且目的結點的路由表項不是一個默認的表項,那麼下列信息就保存在路由表項中以備下次使用:被平滑的RTT、被平滑的均值偏差、慢啓動門限。所謂“足夠多的數據”是指16個窗口的數據,這樣就可得到16個RTT採樣,從而使被平滑的RTT過濾器能夠集中在正確結果的5%以內。
而且,管理員可以使用route(8)命令來設置給定路由的度量:前一段中給出的三個指標以及MT、輸出的帶寬時延乘積、輸入的帶寬時延乘積。當建立一個新的連接時,不論是主動還是被動,如果該連接將要使用的路由表項已經有這些度量的值,則用這些度量對相應的變量進行初始化。
【ICMP差錯】
TCP是怎樣處理一個給定的連接返回的ICMP的差錯?TCP能夠遇到的最常見的ICMP差錯就是源站抑制、主機不可達、網絡不可達。處理:
1. 一個接收到的源站抑制引起擁塞窗口cwnd被置爲1個報文段大小來發起慢啓動,但是慢啓動門限ssthresh沒有變化,所以窗口將打開直至它或開放了所有的通路(受窗口大小和RTT的限制)或發生了擁塞。
2. 一個接收到的主機不可達或網絡不可達實際上都被忽略,因爲這兩個差錯都被認爲是短暫現象。這有可能是由於中間路由器被關閉而導致選路協議要花數分鐘才能穩定到另一個替換路由。
在這個過程中就可能發生這兩個ICMP差錯中的一個,但是連接並不必被關閉。相反,TCP試圖發送引起該差錯的數據,儘管最終有可能會超時。
【重新分組】
當TCP超時並重傳時,它不一定要重傳同樣的報文段。相反,TCP允許進行重新分組而發送一個較大的報文段,這將有助於提高性能( <= 接受方聲明的MSS)。在協議中這是允許的,因爲TCP是使用字節序號而不是報文段序號來進行識別它所要發送的數據和確認。
【TCP的堅持定時器】
TCP通過讓接受方指明希望從發送方接收的數據字節數(即通告窗口大小)來進行Traffic Control。如果窗口大小爲0會發生什麼情況?這將有效地阻止發送方傳送數據,直到窗口變爲非0爲止。TCP不對ACK報文段進行確認,只確認那些包含有數據的ACK報文段。
如果一個確認丟失了,則雙方就有可能因爲等待對方而使連接終止:接受方等待接收數據(因爲它已經向發送方通告了一個非0的窗口),而發送方在等待允許它繼續發送數據的窗口更新。爲防止這種死鎖情況發生,發送方使用了一個堅持定時器(persist timer)來週期性地向接收方查詢,以便發現窗口是否已增大,範圍在5~60 s之間。這些從發送方發出的報文段稱爲窗口探查(window probe)。
糊塗窗口綜合症SWS(Silly Window Syndrome):發生在兩端中的任何一端。接收方可以通告一個小的窗口(而不是一直等到有大的窗口是才通告),而發送方也可以發送少量的數據(而不是等待其他的數據以便發送一個大的報文段)。可以在任何一端採取措施避免出現糊塗窗口綜合症的現象。
1. 接受方不通告小窗口。通常的算法是接收方不通告一個比當前窗口大的窗口(可以爲0),除非窗口可以增加一個報文段大小(也就是將要接收的MSS)或可以增加接收方緩存空間的一半,不論實際有多少。
2. 發送方避免出現糊塗窗口綜合症的措施是隻有以下條件之一滿足時才發送數據:A. 可以發送一個滿長度的報文段;B. 可以發送至少是接收方通告窗口大小一半的報文段;C. 可以發送任何數據並且不希望接收ACK(也就是沒有還未被確認的數據)或該連接上不能使用Nagle算法。
條件(B)主要對付那些總是通告小窗口(也許比1個報文段還小)的主機,條件(C)使在有尚未被確認的數據(正在等待被確認)以及在不能使用Nagle算法的情況下,避免發送小的報文段。如果應用程序在進程小數據的寫操作(例如比該報文段還小),條件(C)可以避免出現糊塗窗口綜合症。
在尚未被確認數據情況,若Nagle算法阻止發送小的Data,多小?
從條件(A)中可以看出所謂“小”是指字節數 < 報文段大小。條件(B)僅用來對付較老的、原始的主機。步驟2中的條件B要求發送方始終監視另一方通告的最大窗口大小,這是一種發送方猜測對方接收緩存大小的企圖。雖然在連接建立時接收緩存的大小可能會減少,但在實際中這種情況很少見。
【TCP的保活定時器】
場景:啓動一個客戶與服務器建立一個連接,然後離去數小時、數天、數個星期或數月,而連接依然保持。中間路由器可以崩潰和重啓,電話線可以被掛斷再連通,但是隻有兩端的主機沒有被重啓,則連接依然保持建立。這種非活動狀態可以導致應用進程中的任何一個終止其活動。
許多時候一個服務器希望知道客戶主機是否崩潰並關機或者崩潰又重新啓動。許多實現提供的保活定時器可以提供這種能力。然而RFC提供了3個不使用保活定時器的理由:A. 在出現短暫差錯的情況下,這可能會使一個非常好的連接釋放掉;B. 它們耗費不必要的帶寬;C. 在按分組計費的情況下會在互聯網上花掉更多的💰。
在連接兩個端系統的網絡出現臨時故障的時候,保活選項會引起一個實際上很好的連接終止。保活功能主要是爲服務器應用程序提供的。服務器應用程序希望知道客戶主機是否崩潰,從而可以代表客戶使用資源。保活功能就是試圖在服務器端檢測到這種半開放的連接。
如果一個給定的連接在2個小時之內沒有任何動作,則服務器向客戶發送一個探查報文段。客戶主機必須處於以下4個狀態之一:
1. 客戶主機仍然正常運行,並從服務器可達。客戶的TCP響應正常,而服務器也知道對方是正常工作的。服務器在2個小時以後將保活定時器復位。如果在2個小時定時器到時間之前應用程序的通信量通過此連接,則定時器在交換數據後的未來2個小時再復位。
2. 客戶主機已經崩潰,並且關閉或者正在重新啓動。在任何一種情況下,客戶的TCP都沒有響應。服務器將不能夠收到探查的響應,並在75s後超時。服務器總共發送10個這樣的探查,每個間隔75秒。如果服務器沒有收到一個響應,它就認爲客戶主機已經關閉並終止連接。
3. 客戶主機崩潰並已經重新啓動。這時服務器將收到一個對其保活探查的響應,但是這個響應是一個復位,使得服務器終止這個連接。
4. 客戶主機正常運行,但是從服務器不可達。這與狀態2相同,因爲TCP不能夠區分狀態4與狀態2之間的區別,它所能發現的就是沒有接收到探查的響應。
服務器不用關注客戶主機被關閉和重新啓動的情況(這指的是一個操作員的關閉,而不是主機崩潰)。當系統被操作員關閉時,所有的應用程序也被終止(也就是客戶進程),這會使客戶的TCP在連接上發出一個FIN。接收到FIN將使服務器的TCP向服務器進程報告文件結束,使服務器可以檢測到這個情況。
在第1種情況下,服務器的應用程序沒有感覺到保活探查的發生。TCP層負責一切。這個過程對Application都是透明的/transparent,直至第2、3 or 4種情況發生。在這3種情況下,服務器application將收到來自它的TCP的差錯報告(通常服務器已經向網絡發出了讀操作請求,然後等待來自客戶的數據。如果保活功能返回一個差錯,則該差錯將作爲讀操作的返回值給服務器)。其差錯對應如下:
客戶主機已經崩潰,並且關閉或者正在重新啓動 |
類似“連接超時” |
客戶主機崩潰並已經重新啓動 |
類似“連接被對方復位” |
客戶主機正常運行,但是從服務器不可達 |
類似“連接超時” |