上一節 中講過,TCP 協議是面向有連接的協議,它具有丟包重發和流量控制的功能,這是它區別于 UDP 協議最大的特點。本文就主要討論這兩個功能。
數據包重發
數據發送
丟包重發的前提是發送方能夠知道接收方是否成功的接收了消息。所以,在 TCP 協議中,接收端會給發送端返回一個通知,也叫作確認應答(ACK),這表示接收方已經收到了數據包。
根據上一節對 TCP 首部的分析得知,ACK 的值和下次發送數據包的序列號相等。因此 ACK 也可以理解為:“發送方,下次你從這個位置開始發送!”。下圖表示了數據發送與確認應答的過程:

數據包和 ACK 應答都有可能丟失,在這種情況下,發送方如果在一段時間內沒有收到 ACK,就會重發數據:

即使網絡連接正常,由于延遲的存在,接收方也有可能收到重復的數據包,因此接收方通過 TCP 首部中的 SYN 判斷這個數據包是否曾經接收過。如果已經接收過,就會丟棄這個包。
重傳超時時間(RTO)
如果發送方等待一段時間后,還是沒有收到 ACK 確認,就會啟動超時重傳。這個等待的時間被稱為重傳超時時間(RTO,Retransmission TimeOut)。RTO 的值具體是多久呢?
首先,RTO 的值不是固定的,它是一個動態變化的時間。這個時間總是略大于連接往返時間(RTT,Round Trip Time)。這個設定可以這樣理解:“數據發送給對方,再返回到我這里,假設需要 10 秒,那我就等待 12秒,如果超過 12 秒,那估計就是回不來了。”
RTT 是動態變化的,因為誰也不知道網絡下一時刻是否擁堵。而當前的 RTO 需要根據未來的 RTT 估算得出。RTO 不能估算太大,否則會多等待太多時間;也不能太小,否則會因為網絡突然變慢而將不該重傳的數據進行重傳。
RTO 有自己的估算公式,可以保證即使 RTT 波動較大,它的變化也不會太劇烈。感興趣的讀者可以自行查閱相關資料。
TCP 窗口
按照之前的理論,在數據包發出后,直至 ACK 確認返回以前,發送端都無法發送數據,而且包的往返時間越長,網絡利用效率和通信性能就越低。前兩張圖片形象的解釋了這一點。
為了解決這個問題,TCP 使用了“窗口”這個概念。窗口具有大小,它表示無需等待確認應答就可以繼續發送數據包的最大數量。比如窗口大小為 4 時,數據發送的示意圖如下:

不等確認就連續發送若干個數據包會不會有問題呢?我們首先來看數據包丟失問題。
我們知道 TCP 首部中的 ACK 字段表示接收方已經收到數據的最后位置。因此,接收方成功接收到了 1-1000 字節的數據后,它會發送一個 ACK = 1001 的確認包。假設 1001-2000 字節的數據包丟失了,由于窗口長度比較大,發送方會繼續發送 2001-3000 字節的數據包。接收端并不會返回這個數據包的確認,因為它最后收到的數據還是 1-1000 字節的數據包。
因此,接收端返回的數據包的 ACK 依然是 1001。這表示:“喂,發數據的,別往后發了,你第 1001 字節開始的數據還沒來呢”。可以想見,發送端以后每次發送數據包得到的確認中,ACK 的值都是 1001。當連續收到三次確認之后,發送方會意識到:“對方還沒有接收到數據,這個包需要重傳”。
因此,引入窗口的概念后,被發送的數據不能立刻丟棄,需要緩存起來以備將來需要重發。
利用窗口發送數據的過程可以用下圖表示:

如果是數據包沒有丟失,但是確認包丟失了呢?這就是窗口最擅長處理的問題了。假設發送發收到的確認包中的 ACK 第一次是 1001,第二次是 4001。那么我們完全可以相信中間的兩個包是成功被接收的。因為如果有沒接收到的包,接收方是不會增加 ACK 的。
在這種情況下,如果不使用窗口,發送方就需要重傳第二、三個數據包,但是有了窗口的概念后,發送方就省略了兩次重傳。因此使用窗口實際上可以理解為“空間換時間”。

流量控制
窗口大小
如果窗口過大,會導致接收方的緩存區數據溢出。這時候本該被接收的數據反而丟棄了,就會導致無意義的重傳。因此,窗口大小是一個可以改變的值,它由接收端主機控制,附加在 TCP 首部的“窗口大小”字段中。
慢啟動
在連接建立的初期,如果窗口比較大,發送方可能會突然發送大量數據,導致網絡癱瘓。因此,在通信一開始時,TCP 會通過慢啟動算法得出窗口的大小,對發送數據量進行控制。
流量控制是由發送方和接收方共同控制的。剛剛我們介紹了接收方會把自己能夠承受的最大窗口長度寫在 TCP 首部中,實際上在發送方這里,也存在流量控制,它叫擁塞窗口。TCP 協議中的窗口是指發送方窗口和接收方窗口的較小值。
慢啟動過程如下:
- 通信開始時,發送方的擁塞窗口大小為 1。每收到一個 ACK 確認后,擁塞窗口翻倍。
- 由于指數級增長非常快,很快地,就會出現確認包超時。
- 此時設置一個“慢啟動閾值”,它的值是當前擁塞窗口大小的一半。
- 同時將擁塞窗口大小設置為 1,重新進入慢啟動過程。
- 由于現在“慢啟動閾值”已經存在,當擁塞窗口大小達到閾值時,不再翻倍,而是線性增加。
- 隨著窗口大小不斷增加,可能收到三次重復確認應答,進入“快速重發”階段。
- 這時候,TCP 將“慢啟動閾值”設置為當前擁塞窗口大小的一半,再將擁塞窗口大小設置成閾值大小(也有說加 3)。
- 擁塞窗口又會線性增加,直至下一次出現三次重復確認應答或超時。
以上過程可以用下圖概括:

強烈建議讀者對照上述八個步驟理解這幅圖!
原文鏈接:http://www.jianshu.com/p/d9edbba4035b
著作權歸作者所有,轉載請聯系作者獲得授權,并標注“簡書作者”。