TCP 可靠传输机制详解
目錄
TCP協議的特點
TCP 報文段
TCP "三次握手"
TCP "四次揮手"
客戶端和服務器端所經歷的狀態
TCP 可靠傳輸
TCP流量控制
TCP擁塞控制
面試相關問題
前言
本篇博文主要是為了復習 TCP 協議而做的總結。其中很多內容都是來自于《計算機網絡》,《Linux網絡編程》,《TCP/IP詳解》等書籍。首先可以從 TCP 協議思維導圖看到本文的大致內容。雖然有很多內容,但 "三次握手"、"四次揮手" 和 可靠傳輸機制?才是本文的重點,其中會用 wireshark 抓取相應的包用于協議分析。最后也會總結一些面試常問的問題。TCP 協議思維導圖如下:
?
?
TCP協議的特點
TCP 是在不可靠的網絡層之上實現的可靠的數據傳輸協議,它主要解決傳輸的可靠、有序、無丟失和不重復的問題。TCP 的主要特點有:
(1) TCP 是面向連接的傳輸層協議。
(2) 每一條 TCP 連接只能有兩個端點,每一條 TCP 連接只能是點對點的(一對一)。
(3) TCP 提供可靠的交付服務,保證傳送的數據無差錯、不丟失、不重復且有序。
(4) TCP 提供全雙工通信,TCP 允許通信雙方的應用進程在任何時候都能發送數據,為此 TCP 連接的兩端都設有發送緩存和接收緩存,用來臨時存放雙向通信的數據。
發送緩存用來暫存以下數據:①應用程序傳送給發送方 TCP 準備發送的數據;② TCP 已發送出但尚未收到的確認的數據。
接收緩存用來暫存以下數據:①按序到達的但尚未被接收應用程序讀取的數據;②不按序到達的數據。
(5) TCP 是面向字節流的,雖然應用程序和 TCP 的交互是一次一個數據塊,但 TCP 把應用程序交下來的數據看成僅僅是一連串的無結構的字節流。
?
TCP 報文段
TCP 傳輸的數據單元稱為報文段。一個 TCP 報文段分為 TCP 首部和 TCP 數據兩部分,整個 TCP 段作為 IP 數據報的數據部分封裝在 IP 數據報中。
各字段的含義如下:
(1) 源端口和目的端口:各占 2 個字節。端口是運輸層與應用層的服務接口。運輸層的復用和分用功能都要通過端口才能實現。
(2) 序號:占 4 個字節。TCP是面向字節流的,所以 TCP 連接中傳送的數據流中的每一個字節都編上一個序號。序號字段的值則指的是本報文段所發送的數據的第一個字節的序號。例如,一個報文段的序號字段值是 200,攜帶的數據總共有 100 字節,表明這個報文段的數據的最后一個字節的序號是 299,所以下一個報文段的數據序號應從 300 開始。
(3) 確認號:占 4 個字節,是期望收到對方的下一個報文段的數據的第一個字節的序號。若確認號為 N,表明前 N-1 的所有數據都已經正確接收。例如,B 正確的收到了 A 發送過來的一個報文段,其序號字段是指 501,而數據長度是 200 字節(序號 501~700),表明 B 正確的收到了 A 發送的序號 700 之前的數據。因此 B 希望收到 A 的下一個數據序號是 701,所以 B 在發送給 A 的確認報文段中應把確認號設置成 701。
(4) 數據偏移量:占 4 位,這里不是 IP 數據報分片的那個數據偏移,而是表示首部長度,它指出 TCP 報文段的數據起始處距離 TCP 報文段的起始處有多遠。該字段若為 15,則表明 TCP 首部達到最大長度 60 字節(以 4 字節為計算單位乘以 15)。
(5) 保留:占 6 位,目前不使用,所以置為 0。
(6) 緊急位 URG:當其值為 1 時,表明緊急指針字段有效。它會告訴系統此報文段中有緊急數據,應該盡快傳送。但是 URG 需要和緊急指針配套使用,即從第一個字節到緊急指針所指字節就是緊急數據。
(7)?確認位 ACK:只有當 ACK = 1 時,確認號字段才有效。TCP 規定,在連接建立后所有傳送的報文段都必須把 ACK 置為 1。
(8) 推送位 PSH:TCP 收到 PSH = 1 的報文段,就盡快的交付接收應用進程,而不再等到整個緩存都填滿了后再向上交付。
(9) 復位位 RST:當 RST = 1 時,表明 TCP 連接中出現了嚴重的錯誤,必須釋放連接,然后再重新建立運輸連接。
(10)?同步位 SYN:SYN = 1表示這是一個連接請求或連接接收報文。當 SYN = 1,ACK = 0 時,表明這是一個連接請求報文,對方若同意建立連接,則在響應報文中使用 SYN = 1,ACK = 1。
(11)?終止位 FIN:用來釋放一個連接。FIN = 1 表明此報文段的發送方的數據已經發送完畢,并要求釋放傳輸連接。
(12) 窗口:占 2 個字節。它指出現在允許對方發送的數據量,接收方的數據緩存空間是有限的,所以使用窗口值作為接收方讓發送方設置其發送窗口的依據,單位是字節。例如,設確認號是 101,窗口字段是 1000。這就表明,從 101 號開始,發送此報文段的一方還有接收 1000 字節數據(字節序號是 101~1100)的接收緩存空間。
(13) 校驗和:占 2 個字節。它的檢驗范圍包括首部和數據部分。計算時要在 TCP 報文段前面加上 12 字節的偽首部。
(14) 緊急指針:占 16 位,指出在本報文段中緊急數據共有多少個字節。
(15) 選項:長度可變。TCP 最初只規定了一種選項,即最大報文段長度(MSS)。
(16) 填充:這是為了使整個首部長度是 4 字節的整數倍。
?
主要需要理解以下幾個字段:
源端口號和目標端口號:誰發的和發給誰(你是誰?從哪里來?到哪里去?);
序號:為了解決亂序問題;
確認序號:發出去的包應該有確認,沒有收到就應該重新發送,直到送達;
狀態位:常見的有 SYN、ACK、FIN,分別表示發起一個連接、確認和結束連接;
窗口大小:用于 TCP 流量控制,通信雙方各聲明一個窗口,說明自己當前的處理能力。發送的太快,處理不了,發的太慢,影響發送的效率;
?
TCP "三次握手"
第一次握手:客戶端首先向服務器發送一個連接請求報文段。這個特殊的報文段中不含應用層數據,其首部中的 SYN 標志位被置為 1。另外,客戶端會隨機選擇一個起始號 seq = x (連接請求報文不攜帶數據,但要消耗掉一個序號)。客戶端進程進入 SYN_SENT(同步發送)狀態。?
第二次握手:服務器收到連接請求報文段后,如同意建立連接,就向客戶端發回確認,并為該 TCP 連接分配 TCP 緩存和變量。在確認報文段中,SYN 和 ACK 位都被置為 1,確認號字段的值為 x+1,并且服務器端隨機產生起始序號 seq = y(確認報文不攜帶數據,但也要消耗掉一個序號)。確認報文段同樣不包含應用層數據。服務端進入SYN_RCVD(同步接收)狀態。
第三次握手:當客戶端收到確認報文段后,還要向服務器給出確認,并且也要給該連接分配緩存和變量。這個報文段的 ACK 標志位被置為 1,序號字段為 x+1,確認號字段為 ack = y+1。該報文可以攜帶數據,如果不攜帶數據則不消耗序號。客戶端進入 ESTABLISHED(建立連接)狀態。
在成功的完成以上三步后,TCP 連接就建立了,接下來就可以傳送應用層數據了。TCP 提供的是全雙工通信,因此通信雙方的應用進程在任何時候都能發送數據。注意:服務器端的資源是在完成第二次握手時開始分配的,所以服務器易于受到 SYN 洪泛攻擊。
?
TCP "三次握手" 報文詳細分析
TCP報文:[29 05 01 bb f4 0a 2c 77 1f a9 79 d2 50 10 01 00 b9 84 00 00] 為了更易理解:[29 05 01 bb][f4 0a 2c 77][1f a9 79 d2][50 10 01 00][b9 84 00 00] 源端口:2905(HEX) = 10501(DEC) 目的端口:01bb(HEX) = 443(DEC) 序號:1 為什么連接過程中的"序號"顯示1,而相應字段的值不是1。詳情請看面試問題的(6) 確認號:1 十六進制[50 10]轉化成二進制[0101 0000 0001 0000] 數據偏移(占4位):單位是4個字節,此字段值為5,所以TCP首部的長度是20字節。 保留(占6位):保留為今后使用,目前置為0。 緊急位URG:0 確認位ACK:1 推送位PSH:0 復位位RST:0 同步位SYN:0 終止位FIN:0 窗口:0100(HEX) = 256(DEC) 檢驗和:0xb984 緊急指針:0?
TCP "四次揮手"
參與 TCP 連接的兩個進程中的任何一個都能終止該連接。
第一步:若客戶端打算關閉連接,就向其服務器發送一個連接釋放報文段,并停止再發送數據,主動關閉 TCP 連接,該報文段的 FIN 標志位被置為 1,seq = u,它等于前面已傳送過的數據的最后一個字節的序號加 1(FIN 報文段即使不攜帶數據,也要消耗掉一個序號)。TCP 是全雙工的,即可以想象成是一條 TCP 連接上有兩條數據通路。當發送 FIN 報文時,發送 FIN 的一端就不能再發送數據,也就是關閉了其中一條數據通路,但另一條沒有關閉的數據通路仍可以發送數據。
第二步:服務器收到連接釋放報文段后就會發出確認,確認號為 ack = u+1,而這個報文段自己的序號是 v,等于它前面已傳送過的數據的最后一個字節的序號加 1。此時,從客戶端到服務器這個方向的連接就釋放了,TCP 連接處于半關閉狀態。但服務器若發送數據,客戶端仍能接收,即從服務器到客戶端這個方向的連接并未關閉。
第三步:若服務器已經沒有要向客戶端發送的數據,就釋放連接,此時發出 FIN = 1 的連接釋放報文段。
第四步:客戶端收到連接釋放報文段后,必須發出確認。在確認報文段中,ACK 字段被置為 1,確認號 ack = w+1,序號 seq = u+1。此時 TCP 連接還沒有釋放掉,必須經過 2MSL 后,客戶端才進入連接關閉狀態。
?
客戶端和服務器端所經歷的狀態
結合連接和釋放過程圖看狀態轉換圖,以下是客戶端的狀態轉換圖:
服務器端的狀態轉換圖:
完整的狀態轉換圖(紅線是客戶端,藍線是服務器,它們表示正常的連接和斷開的過程):
?
斷開過程中的狀態解析
(1)?FIN_WAIT_1:? FIN_WAIT_1 和 FIN_WAIT_2 狀態的真正含義都是表示等待對方的 FIN 報文。而這兩種狀態的區別是:? FIN_WAIT_1 狀態實際上是當 socket?在 ESTABLISHED 狀態時,它想主動關閉連接,向對方發送了 FIN 報文,此時該 socket??即進入到 FIN_WAIT_1 狀態。而當對方回應 ACK 報文后,則進入到 FIN_WAIT_2 狀態。當然在實際的正常情況下, FIN_WAIT_1 狀態一般是比較難見到的(這個過程比較快),而 FIN_WAIT_2 狀態還有時常常可以用 netstat 看到(因為有一方還在發數據)。(主動關閉連接)
(2)?FIN_WAIT_2:上面已經詳細解釋了這種狀態,實際上 FIN_WAIT_2 狀態下的 socket 表示半連接,即有一方要求 close 連接,但另外一方告訴對方,我暫時還有點數據需要傳送給你(發送 ACK 報文),稍后再關閉連接。(主動關閉連接)
(3)?TIME_WAIT:表示收到了對方的 FIN 報文,并發送出了 ACK 報文,就等 2MSL 后即可回到 CLOSED 可用狀態了。如果 FIN_WAIT_1 狀態下,收到了對方同時帶 FIN 標志和 ACK 標志的報文時(被動方沒有數據發送),可以直接進入到 TIME_WAIT 狀態,而無須經過 FIN_WAIT_2 狀態。(主動關閉連接)
(4)?CLOSING(比較少見): 這種狀態比較特殊,實際情況中應該是很少見,屬于一種比較罕見的例外狀態。正常情況下,當你發送 FIN 報文后,按理來說是應該先收到(或同時收到)對方的 ACK 報文,再收到對方的 FIN 報文。但是 CLOSING 狀態表示你發送 FIN 報文后,并沒有收到對方的 ACK 報文,反而卻也收到了對方的 FIN 報文。什么情況下會出現此種情況呢?其實細想一下,也不難得出結論:那就是如果雙方幾乎在同時關閉一個 socket?的話,那么就出現了雙方同時發送 FIN 報文的情況,也即會出現 CLOSING 狀態,表示雙方都正在關閉 socket?連接。
(5)?CLOSE_WAIT: 被動方需要查看是否還有數據發送給對方,如果沒有的話,那么你也就可以關閉這個 socket,發送 FIN 報文給對方,即關閉連接。所以在 CLOSE_WAIT 狀態下,需要完成的事情是等待被動方去關閉連接。(被動方)
(6)?LAST_ACK: 它是被動關閉的一方在發送 FIN 報文后,最后等待對方的 ACK 報文。當收到 ACK 報文后,也即可以進入到 CLOSED 可用狀態了。(被動方)
(7)?CLOSED: 表示連接中斷。
?
小總結:
半關閉狀態的含義是:通信的一端可以發送 FIN 報文段給對方,告訴它本端已經完成了數據的發送,但允許繼續接收來自對方的數據,直到對方也發送 FIN 報文段以關閉連接。
當客戶端處于 FIN_WAIT_2 狀態時,服務器處于 CLOSE_WAIT 狀態,這時可能會處于半連接狀態,即服務器還有數據發給客戶端。如果服務器沒有數據發送,則發送 FIN 報文段關閉連接,而客戶端將確認并進入 TIME_WAIT 狀態。
上面的狀態轉換圖還給出了 FIN_WAIT_1 狀態直接進入 TIME_WAIT 狀態的一條線路,前提是處于 FIN_WAIT_1 的客戶端直接收到了帶有確認信息的 FIN 報文段(而不是正常情況下的先收到 ACK 報文段,再收到 FIN 報文段)。
?
斷開連接時的意外
當 TCP 連接發生一些物理上的意外情況時,例如網線斷開,linux 上的 TCP 實現會依然認為該連接有效,而 windows 則會在一定時間后返回錯誤信息。這似乎可以通過設置 SO_KEEPALIVE 選項來解決,不過不知道這個選項是否對于所有平臺都有效。
?
TIME_WAIT 狀態所帶來的影響
當某個連接的一端處于 TIME_WAIT 狀態時,該連接將不能再被使用。事實上,對于我們比較有現實意義的是,這個端口將不能再被使用。某個端口處于 TIME_WAIT 狀態(其實應該是這個連接)時,這意味著這個 TCP 連接并沒有斷開(完全斷開),那么,如果你 bind 這個端口,就會失敗。對于服務器而言,如果服務器突然 crash 掉了,那么它將無法再 2MSL 內重新啟動,因為 bind 會失敗。解決這個問題的一個方法就是設置 socket 的 SO_REUSEADDR 選項。這個選項意味著你可以重用一個地址。
注意:當建立一個 TCP 連接時,服務器端會繼續用原有端口監聽,同時用這個端口與客戶端通信。而客戶端默認情況下會使用一個隨機端口與服務器端的監聽端口通信。有時候,為了服務器端的安全性,我們需要對客戶端進行驗證,即限定某個 IP 某個特定端口的客戶端??蛻舳丝梢允褂?bind 來使用特定的端口。對于服務器端,當設置了 SO_REUSEADDR 選項時,它可以在 2MSL 內啟動并 listen 成功。 但是對于客戶端,當使用 bind 并設置 SO_REUSEADDR 時,如果在 2MSL 內啟動,雖然 bind 會成功,但是在 windows 平臺上 connect 會失敗。而在 linux 上則不存在這個問題。
?
TCP 可靠傳輸
TCP 的任務是在 IP 層的不可靠、盡力而為服務的基礎上建立一種可靠數據傳輸服務。TCP 提供的可靠數據傳輸服務就是要保證接收方進程從緩沖區讀出的字節流與發送方發出的字節流是完全一樣的。TCP 使用了校驗、序號、確認和重傳機制來達到這個目的。
校驗
在計算校驗和時,要在 TCP 數據報之前增加 12 個字節的偽首部,偽首部并不是 TCP 報文段真正的首部。只是在計算校驗和時,臨時添加在 TCP 數據報文段的前面,得到一個臨時的 TCP 報文段。偽首部既不向下傳送也不向上遞交,而僅僅是為了計算校驗和。注意:IP 數據報的校驗和只檢驗 IP 數據報的首部,但 TCP 的校驗和會把首部和數據部分一起檢驗。
序號
TCP 首部的序號字段用來保證數據能有序提交給應用層,TCP 把數據看成一個無結構但是有序的字節流,而序號是建立在傳送的字節流之上,而不是建立在報文段之上。TCP 連接中傳送的數據流中的每一個字節都編上一個序號。序號字段的值則指的是本報文段所發送的數據的第一個字節的序號。
確認
TCP 首部的確認號是期望收到對方的下一個報文段的數據的第一個字節的序號。TCP 默認使用累計確認,即 TCP 只確認數據流中至第一個丟失字節為止的字節。例如,接收方 B 收到了發送方 A 發送的包含字節 0~2 和 6~7 的報文段。由于某些原因,B 還沒有收到字節 3~5 的報文段,此時 B 仍在等待字節 3(和其后面的字節),因此,B 到 A 的下一個報文段將確認號字段設置為 3。
重傳
有兩種事件會導致 TCP 對報文段進行重傳:超時和冗余 ACK。
(1) 超時
TCP 每發送一個報文段,就對這個報文段設置一次計時器。只要計時器設置的重傳時間到期但還沒有收到確認,就要重傳這一報文段。當檢測到接收數據有錯誤時,會采取直接丟棄出錯的數據,發送端等待接收端的確認超時后,會自動重發該報文段。
由于 IP 數據報在傳輸的時候選擇的路由變化很大,因此傳輸層的往返時延的方差很大。為了計算超時計時器的重傳時間,TCP 采用一種自適應算法,它記錄一個報文段發出的時間,以及收到相應確認的時間,這兩個時間之差叫做報文段的往返時間(RTT)。TCP 保留了 RTT 的一個加權平均往返時間 RTTs,當第一次測量 RTT 樣本時,RTTs 值就為所測量到的 RTT 樣本的值,但之后每測量一個新的 RTT 樣本,就按下式重新計算一次 RTTs:
新的 RTTs = ( 1- a ) * (舊的 RTTs) + a(新的RTT樣本)
在上式中 0 <= a < 1。若 a 很接近于零,表示新的 RTTs 值和舊的 RTTs 值相比變化不大,而受新的 RTT 樣本影響不大(RTT值更新較慢)。若 a 接近于 1,則表示新的 RTTs 值受新的 RTT 樣本的影響較大(RTT值更新較快)。[RFC 2988] 推薦的 a 值為 0.125。
所以超時計時器設置的超時重傳時間(RTO)應略大于上面得出的加權平均往返時間 RTTs。即 RTO = RTTs + 4RTTd。其中 RTTd 是 RTT 的偏差的加權平均值,它與 RTTs 和新的 RTT 樣本之差有關。當第一次測量時,RTTd 取為測量到的 RTT 樣本值的一半,以后測量中,使用下式計算:新的 RTTd = (1-β) *(舊的 RTTd) + β*|RTTs - 新的 RTT 樣本|,其中 β 是個小于 1 的系數,它的推薦值是 0.25。
?
(2) 冗余 ACK
超時觸發重傳存在的一個問題就是超時周期往往太長。幸運的是,發送方通??稍诔瑫r事件發生之前通過注意冗余 ACK 來較好地檢測丟包情況。冗余 ACK 就是再次確認某個報文段的 ACK,而發送方先前已經收到過該報文段的確認。例如,發送方 A 發送了序號為 1、2、3、4、5 的 TCP 報文段,其中 2 號報文段丟失了,它將無法到達接收方 B。因此,3、4、5 號報文段對于 B 來說就成了失序報文段。TCP 規定每當比期望序號大的失序報文段到達時,發送冗余 ACK,指明下一個期望字節的序號 [RFC 1122, RFC 2581]。在這個例子中,3、4、5 號報文到達 B,但它們不是 B 所期待的下一個報文,于是 B 就發送 3 個對 1 號報文段的冗余 ACK,表示自己期望接收 2 號報文段。TCP 規定當發送方收到對同一個報文段的 3 個冗余 ACK 時,就可以認為跟在這個被確認報文段之后的報文段已經丟失。就前面的例子而言,當 A 收到對于 1 號報文段的 3 個冗余 ACK 時,則它可以認為 2 號報文段已經丟失。這時發送方 A 可以立即對 2 號報文執行重傳,這種技術成為快速重傳。
?
TCP流量控制
TCP 提供了流量控制服務以消除發送方使接收方緩存區溢出的可能性,因此 TCP 流量控制是為了匹配發送方的發送速率與接收方的讀取速率。TCP 提供一種基于滑動窗口協議的流量控制機制。其原理是:在通信過程中,接收方根據自己接收緩存的大小,動態地調整發送方的發送窗口大小,這就是接收窗口 rwnd,即調整 TCP 報文段首部中的 "窗口" 字段的值,來限制發送方向網絡注入報文的速率。同時,發送方根據其對當前網絡擁塞程度的估計而確定窗口值,稱為擁塞窗口 cwnd,其大小與網絡的帶寬和時延密切相關。
例如,在通信中,有效數據只從 A 發往 B,而 B 僅向 A 發送確認報文,這時,B 就可以通過設置確認報文段首部的窗口字段來將 rwnd 值來限制自己發送窗口的大小,這樣可以將未確認的數據量控制再 rwnd 大小之內,保證了 A 不會使 B 得接收緩存溢出。A 的發送窗口的實際大小取的是 rwnd 和 cwnd 中的最小值。
設主機 A 向主機 B 發送數據,在連接建立時,B 告訴 A:"我的接收窗口 rwnd = 400(字節)"。
傳輸層和數據鏈路層的流量控制的區別在于:傳輸層定義了端到端用戶之間的流量控制,數據鏈路層定義了兩個中間的相鄰結點的流量控制。另外,數據鏈路層滑動窗口協議的窗口大小不能動態變化,傳輸層則可以動態變化。
?
TCP擁塞控制
所謂的擁塞控制就是為了防止過多的數據注入網絡中,這樣可以使網絡中的路由器或鏈路不會過載。當出現擁塞時,端點并不能了解到擁塞發生的細節,對通信連接的端點來說,擁塞往往表現為通信時延的增加。擁塞控制和流量控制相似的地方是通過控制發送方發送數據的速率來達到效果。
擁塞控制與流量控制的區別:擁塞控制是讓網絡能夠承受現有的網絡負荷,它是一個全局性的過程,涉及所有的主機、所有的路由器,以及與降低網絡傳輸性能有關的所有因素。而流量控制往往使指點對點通信量的控制,即接收端控制發送端,它所做的就是抑制發送端發送數據的速率,以便使接收端來得及接收。
為了更好地對傳輸層進行擁塞控制,有以下四種算法:慢開始、擁塞避免、快重傳、快恢復。發送方在確定發送報文段的速率時,既要根據接收方的接收能力,又要從全局考慮不要使網絡發生擁塞。因此,TCP 協議要求發送方維護以下兩個窗口:
(1) 接收窗口 rwnd,接收方根據目前接收緩存大小所許諾的最新的窗口值,反映了接收方的容量。有接收方根據其放在 TCP 報文的首部的 "窗口" 字段通知發送方。
(2) 擁塞窗口 cwnd,發送方根據自己估算的網絡擁塞程度而設置的窗口值,反映了網絡當前容量。只要網絡沒有出現擁塞,擁塞窗口就再增大一些,以便把更多的分組發送出去。但只要網絡出現擁塞,擁塞窗口就減少一些,以減少注入網絡中的分組數。發送窗口的上限值應當取接收窗口 rwnd 和擁塞窗口 cwnd 中較小的一個,即:Min[rwnd, cwnd]。
?
慢開始和擁塞避免
(1) 慢開始算法
在 TCP 剛剛連接好,開始發送 TCP 報文段時,先讓擁塞窗口 cwnd = 1,即一個最大報文段長度 MSS。而在每收到一個對新的報文段的確認后,將 cwnd 加倍,即剛開始會增大一個 MSS。用這樣的方法逐步增大發送方的擁塞窗口 cwnd,可以使分組注入到網絡的速率更加合理。例如,A 向 B 發送數據,當發送時 A 的擁塞窗口為 2,那么 A 一次可以發送兩個 TCP 報文段,當經過一個 RTT 后,A 收到 B 對剛才兩個報文的確認,于是就把擁塞窗口調整為 4,即下一次發送時就可以發送 4 個報文段。
使用慢開始算法后,每經過一個傳輸輪次,擁塞窗口 cwnd 就會加倍,即 cwnd 的大小呈指數形式增長。這樣慢開始一直把擁塞窗口 cwnd 增大到一個規定的慢開始門限 ssthresh(閾值),然后改用擁塞避免算法。
?
(2) 擁塞避免算法
擁塞避免算法的做法是:發送端的擁塞窗口 cwnd 每經過一個往返時延 RTT 就增加一個 MSS 的大小,而不是加倍,使 cwnd 按線性規律緩慢增長(即加法增大),而當出現一次超時(網絡擁塞)時,會令慢開始門限 ssthresh 等于當前 cwnd 的一半(即乘法減小)。
根據 cwnd 的大小執行不同的算法,可歸納為:
當 cwnd?<?ssthresh 時,使用慢開始算法。
當 cwnd?> ssthresh 時,改用擁塞避免算法。
當 cwnd?=?ssthresh 時,既可以使用慢開始,也可以使用擁塞避免算法。
?
(3) 網絡擁塞的處理
當網絡出現擁塞時,無論是在慢開始階段還是在擁塞避免階段,只要發送方檢測到超時事件的發生(沒有按時收到確認,重傳計時器超時),就會把慢開始門限?ssthresh?設置為出現擁塞時的發送窗口?cwnd 值的一半(但不能小于2)。然后把擁塞窗口?cwnd 重新設置為 1,執行慢開始算法。這樣做的目的就是要迅速減少主機發送到網絡中的分組數,使得發生擁塞的路由器有足夠的時間把隊列中積壓的分組處理完。擁塞避免是指在擁塞避免階段把擁塞窗口控制為線性規律增長,使網絡比較不容易出現擁塞,利用以上措施想要完全避免網絡擁塞是不可能的。
1. 初始時,擁塞窗口置為 1,即 cwnd = 1,慢開始門限置為 16,即 ssthresh = 16。慢開始階段,cwnd 初值為 1,以后發送方每收到一個確認 ACK,cwnd 值加倍,即經過每個傳輸輪次(RTT),cwnd 呈指數規律增長。
2. 當擁塞窗口 cwnd 增長到慢開始門限 ssthresh 時(即當 cwnd = 16 時),就改用擁塞避免算法,cwnd 按線性規律增長。
3. 若此時 cwnd = 24 時,網絡發生擁塞,更新 ssthresh 值為 12(即變為超時的時侯 cwnd 值的一半),cwnd 重置為 1,并執行慢開始算法,當 cwnd = 12 時,改為擁塞避免算法。
注意,在慢開始階段,若 2*cwnd > ssthresh,則下一個 RTT 的 cwnd 應等于 ssthresh,而不是 2*cwnd,即 cwnd 不能越過 ssthresh 值。如上圖,在 16 個輪次時,cwnd = 8、ssthresh = 12,第 17 輪次時,cwnd = 12,而不是 16。
?
快重傳和快恢復
(1) 快重傳
TCP 可靠傳輸機制中,快速重傳技術使用了冗余 ACK 來檢測丟包的發生。同樣,冗余 ACK 也用于網絡擁塞的檢測??熘貍鞑⒎侨∠貍饔嫊r器,而是在某些情況下可更早的重傳丟失的報文段。當發送方連續收到三個重復的 ACK 報文時,直接重傳對方尚未收到的報文段,而不必等待那個報文段設置的重傳計時器超時。
如果收到 3 個相同的 ACK。TCP?在收到亂序到達包時就會立即發送 ACK,TCP 利用 3 個相同的 ACK 來判定數據包的丟失,此時進行快速重傳,快速重傳做的事情有:
1. 把 ssthresh 設置為 cwnd 的一半。
2. 把 cwnd 再設置為 ssthresh 的值(具體實現有些為 ssthresh + 3)。
3. 重新進入擁塞避免階段。
?
(2) 快恢復
后來的 "快速恢復" 算法是在上述的 "快速重傳" 算法后添加的,當收到 3 個重復 ACK 時,TCP 最后進入的不是擁塞避免階段,而是快速恢復階段??焖僦貍骱涂焖倩謴退惴ㄒ话阃瑫r使用??焖倩謴偷乃枷胧?"數據包守恒" 原則,即同一個時刻在網絡中的數據包數量是恒定的,只有當 "老" 數據包離開了網絡后,才能向網絡中發送一個 "新" 的數據包,如果發送方收到一個重復的 ACK,那么根據 TCP?的 ACK 機制就表明有一個數據包離開了網絡,于是 cwnd 加 1。如果能夠嚴格按照該原則那么網絡中很少會發生擁塞,事實上擁塞控制的目的也就在修正違反該原則的地方。
具體來說快速恢復的主要步驟是:
1. 當收到 3 個重復ACK時,把 ssthresh 設置為 cwnd 的一半,把 cwnd 設置為 ssthresh 的值加 3(有的實現版本不加 3),然后重傳丟失的報文段,加 3 的原因是因為收到 3 個重復的ACK,表明有 3 個 "老" 的數據包離開了網絡。
2. 再收到重復的 ACK 時,擁塞窗口增加 1。
3. 當收到新的數據包的 ACK 時,把 cwnd 設置為第一步中的 ssthresh 的值。原因是因為該 ACK 確認了新的數據,說明從重復 ACK 時的數據都已收到,該恢復過程已經結束,可以回到恢復之前的狀態了,也即再次進入擁塞避免狀態。
快速重傳算法首次出現在 4.3BSD 的 Tahoe 版本,快速恢復首次出現在 4.3BSD 的 Reno 版本,也稱之為 Reno 版的 TCP?擁塞控制算法。
在流量控制中,發送方發送數據的量由接收方決定,而在擁塞控制中,有發送方自己通過檢測網絡狀況而決定。實際上,慢開始、擁塞避免算法、快重傳和快恢復集中算法應該是同時應用在擁塞控制機制之中的,當發送方檢測到超時的時候就采用慢開始和擁塞避免,當發送方接收到冗余 ACK 的時候就采用快重傳和快恢復。
?
面試相關問題
(1) 為什么四次揮手發送最后一次報文后要等待 2MSL(報文最大生存時間)的時間?
答:1) 為了保證 A 發送的最后一個確認報文段能夠到達 B。如果 A 不等待 2MSL,若 A 返回的最后確認報文段丟失,則 B 不能進入正常關閉狀態,而 A 此時已經關閉,也不可能再重傳。
2)?防止出現 "已失效的連接請求報文段"。A 在發送完最后一個確認報文段后,再經過 2MSL 可保證本連接持續的時間內所產生的所有報文段從網絡中消失。
?
(2) TCP 使用的是 GBN(后退N幀協議)還是 SR(選擇重傳協議)?
答:這是為了讓人踩坑而出的問題。因為?TCP 使用累計確認,看起來像是 GBN。但是,正確收到但失序的報文并不會被丟棄,而是緩存起來,并且發送冗余 ACK 指明希望收到的下一個報文段,這是 TCP 方式和 GBN 的顯著區別。例如,A 發送了 N 個報文段,其中第 k(k < N)個報文段丟失,其余 N-1 個報文段正確地按序到達接收方 B。當使用 GBN 時,A 需要重傳分組 k,以及所有后繼分組 k+1,k+2,...,N。相反,TCP 卻最多重傳一個報文段,即報文段 k。另外,TCP 中提供一個 SACK(Selective ACK)選項,也就是選擇確認選項。當使用選擇確認選項的時候, TCP 看起來就和 SR 非常相似。因此,TCP 的差錯恢復機制可以看成是 GBN 和 SR 協議的混合體。
?
(3) 為什么超時時間發生時 cwnd 被置為 1,而收到 3 個冗余 ACK 時 cwnd 只是減半?
答:首先應分析那種情況的網絡擁塞程度更嚴重。其實不難發現,在收到 3 個冗余 ACK 的情況下,網絡雖然擁塞,但至少 ACK 報文段能夠被正確交付。而當超時發生時,說明網絡可能已經擁塞的連 ACK 報文段都傳輸不了了,發送方只能等待超時后重傳數據。因此,超時時間發生時,網絡擁塞更嚴重,所以發送方應該最大限度地抑制數據發送量,所以 cwnd 置為 1;收到 3 個冗余 ACK 時,網絡擁塞相對而言不是很嚴重,所以 cwnd 減半即可。
?
(4) 為什么不采用 "兩次握手" 建立連接?
答:這主要是為了防止兩次握手情況下已失效的連接請求報文段突然有傳送到服務端,而產生了錯誤??紤]以下情況:客戶 A 想服務器 B 發送 TCP 連接請求,第一個連接請求報文在網絡的某個結點長時間滯留,A 超時后認為報文丟失,于是再重傳一次連接請求,B 收到后建立連接。數據傳輸完畢后雙方斷開連接。此時,前一個滯留在網絡中的連接請求到達了服務端 B,而 B 認為 A 有發來連接請求,此時若是使用 "三次握手",則 B 向 A 返回確認報文段,由于是一個失效的請求,因此 A 不予理睬,建立連接失敗。若采用的是 "兩次握手",則這種情況下 B 認為傳輸連接已經建立,并一直等待 A 傳輸數據,而 A 此時并無連接請求,因此不予理睬,這樣就造成了 B 的資源白白浪費了。
?
(5) 是否 TCP 和 UDP 都需要計算往返時間 RTT?
答:往返時間 RTT 只是針對傳輸層 TCP 協議才很重要,因為 TCP 要根據 RTT 的值來設置超時計時器的超時時間。UDP 沒有確認和重傳機制,因此 RTT 對 UDP 沒有什么意義。
?
(6) 為什么 TCP 在建立連接的時候不能每次選擇相同的、固定的初始序號?
答:1) 假如 A 和 B 頻繁地建立連接,傳送一些 TCP 報文段后再釋放連接,然后又不斷的建立新的連接、傳送報文段和釋放連接。
2) 假如每一次建立連接時,主機 A 都選擇相同的、固定的初始序號,如 1。
3) 若主機 A 發送出的某些 TCP 報文段在網絡中會滯留較長的時間,以致造成主機 A 超時重傳這些 TCP 報文段。
4) 若有一些在網絡中滯留時間較長的 TCP 報文段最后終于到達了主機 B,但這時傳送該報文段的那個連接早已釋放了,而在到達主機 B 時的 TCP 連接是一條新的 TCP 連接。
以上這些情況可能會導致在新的 TCP 連接中的主機 B 有可能會接收在舊的連接傳送的、已經沒有意義的、過時的 TCP 報文段(因為這個 TCP 報文段的序號有可能正好處于新的連接所使用的序號范圍內)。因為必須使得遲到的 TCP 報文段的序號不在新的連接中使用的序號范圍內。所以,TCP 在建立新的連接時所選擇的初始序號一定要和前面的一些連接所使用過的序號不一樣。因此,不同的 TCP 連接不能使用相同的初始序號。
?
(7) 在使用 TCP 傳輸數據時,如果有一個確認報文段丟失了,也不一定會引起與該確認報文段對應的數據的重傳。試說明理由。
答:這是因為發送方可能還未重傳時,就收到了更高序號的確認。例如主機 A 連續發送兩個報文段,均正確到達主機 B。B 連續發送兩個確認 ACK1 和 ACK2(ACK2 的序號比 ACK1 的序號高)。但前一個確認幀在傳輸時丟失了。若在超時前,ACK2 被 A 接收,更高的序號代表該序號之前的所有字節都被接收了,所以 A 知道前一個報文也被正確的接收了,這種情況下 A 不會重傳第一個報文段。
?
參考:《計算機網絡》《Linux高性能服務器編程》
總結
以上是生活随笔為你收集整理的TCP 可靠传输机制详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 0-1 背包问题的 4 种解决方法算法策
- 下一篇: I/O多路复用:select、poll和