<acronym id="s8ci2"><small id="s8ci2"></small></acronym>
<rt id="s8ci2"></rt><rt id="s8ci2"><optgroup id="s8ci2"></optgroup></rt>
<acronym id="s8ci2"></acronym>
<acronym id="s8ci2"><center id="s8ci2"></center></acronym>
0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

TCP協議的連接狀態

科技綠洲 ? 來源:Linux開發架構之路 ? 作者:Linux開發架構之路 ? 2023-11-13 15:47 ? 次閱讀

TCP是一個巨復雜的協議,因為他要解決很多問題,而這些問題又帶出了很多子問題和陰暗面。所以學習TCP本身是個比較痛苦的過程,但對于學習的過程卻能讓人有很多收獲。

一、TCP協議的定義

TCP在網絡OSI的七層模型中的第四層——Transport層,IP在第三層——Network層,ARP在第二層——Data Link層,在第二層上的數據,我們叫Frame,在第三層上的數據叫Packet,第四層的數據叫Segment。

首先,我們需要知道,我們程序的數據首先會打到TCP的Segment中,然后TCP的Segment會打到IP的Packet中,然后再打到以太網Ethernet的Frame中,傳到對端后,各個層解析自己的協議,然后把數據交給更高層的協議處理。

TCP頭格式

TCP協議是面向連接的協議,把連接作為最基本的抽象。每一條TCP連接唯一的被通信兩端的兩個端點所確定。TCP協議是點對點,而不是點對多點。端點又被稱為套接字,TCP協議規定,IP地址拼接端口號就構成套接字。

接下來,我們來看一下TCP頭的格式:

圖片

1、seq:序號。TCP協議是面向字節流的,在一個TCP連接中的傳送的字節流的每一個字節都是按照順序編號,seq需要占用4個字節,所以范圍是[0 4294967296],序號可以重復使用。TCP規定,首部中序號字段值是本報文段所發送數據的第一個字節的序號。序號用于跟蹤該端發送的數據量。4294967296個字節(2的32次方),如果不重復利用就是4G的數據量。序號為當前端成功發送的數據字節數,確認號為當前端成功接收的數據字節數,SYN標志位和FIN標志位也各自要占用1個序號。TCP的標準規定,ACK報文段可以攜帶數據,但如果不攜帶數據則不消耗序號。A(客戶端)把自己的初始化序列號放在SYN數據包中發送給B(服務器),B收到后會回發一個SYN+ACK數據包,該數據包中B會把收到的來自A的序列號加一后的值發送回去(確認號),同時數據包中也包含B的初始化序列號,當A收到數據后發送一個ACK數據包其中包含了B發過來的序列號加一后所得的數值(確認號)。

2、ACK:僅當ACK=1時確認字段才有效,當ACK=0時確認字段無效,并且TCP規定,在連接建立后所有的傳送報文段都必須要把ACK置為1。

3、SYN:同步序列號,用來發起一個連接。當SYN=1而ACK=0時表明這是一個請求報文段;若對方同意連接,則響應報文中SYN=1,ACK=1。

4、FIN :用來釋放一個連接,當FIN=1表示此報文段的發送方已經發送完畢。并要求釋放鏈接。

5、SYN、ACK、FIN是標志位,在屬性flag中,flag占用一個字節。含有SYN或FIN標志位的包并不攜帶有效數據。

注:SYN位被啟動時,向對方告知自己(客戶端或者服務器)的初始序列號以便對方知道如何接收自己發送過來的數據包。如果是ACK比特位被啟動,它表明數據包用于通知接收方我收到了你上次發來的數據。

你需要注意這么幾點:

1、TCP的包是沒有IP地址的,那是IP層上的事。但是有源端口和目標端口。

2、一個TCP連接需要四個元組來表示是同一個連接(src_ip, src_port, dst_ip, dst_port)準確說是五元組,還有一個是協議。但因為這里只是說TCP協議,所以,這里我只說四元組。

3、注意上圖中的四個非常重要的東西:

  • Sequence Number是包的序號,用來解決網絡包亂序(reordering)問題。
  • Acknowledgement Number就是ACK——用于確認收到,用來解決不丟包的問題。
  • Window又叫Advertised-Window,也就是著名的滑動窗口(Sliding Window),用于解決流控的。
  • TCP Flag ,也就是包的類型,主要是用于操控TCP的狀態機的。

關于其它的東西,可以參看下面的圖示:

圖片

TCP的狀態機

其實,網絡上的傳輸是沒有連接的,包括TCP也是一樣的。而TCP所謂的“連接”,其實只不過是在通訊的雙方維護一個“連接狀態”,讓它看上去好像有連接一樣。所以,TCP的狀態變換是非常重要的。

可靠數據運輸原理:

1、TCP協議發送兩種數據包,一種數據包用來傳輸數據,一種數據包用來發送控制信息。TCP數據都會有一個包頭,包頭中有相應標志位,標志位的設定用于表明數據包是用于數據發送還是用于傳輸控制信息。

2、TCP建立一個連接需要三個報文段:

情況1:防止已失效的請求報文段突然又傳送到了服務端而產生連接的誤判。

客戶端發送了一個連接請求報文段A到服務端,但是在某些網絡節點上長時間滯留了,而后客戶端又超時重發了一個連接請求報文段B該服務端,而后 正常建立連接,數據傳輸完畢,并釋放了連接。但是請求報文段A延遲了一段時間后,又到了服務端,這本是一個早已失效的報文段,但是服務端收到后會誤以為客戶端又發出了一次連接請求,于是向客戶端發出確認報文段,并同意建立連接。那么問題來了,假如這里沒有三次握手,這時服務端只要發送了確認,新的連接就建立了,但由于客戶端沒有發出建立連接的請求,因此不會理會服務端的確認,也不會向服務端發送數據,而服務端卻認為新的連接已經建立了,并在 一直等待客戶端發送數據,這樣服務端就會一直等待下去,直到超出?;钣嫈灯鞯脑O定值,而將客戶端判定為出了問題,才會關閉這個連接。這樣就浪費了很多服務 器的資源。而如果采用三次握手,客戶端就不會向服務端發出確認,服務端由于收不到確認,就知道客戶端沒有要求建立連接,從而不建立該連接。

情況2:防止形成死鎖。

服務器的SYN和ACK報文段沒有發送到客戶端,服務器認為連接已經建立,但是客戶端不知道服務器是否已準備好,不知道服務器建立什么樣的序列號??蛻舳苏J為連接還未建立成功,將忽略服務器發來的任何數據分組,只等待連接確認應答分組。而服務器在發出的分組超時后,重復發送同樣的分組。這樣就形成了死鎖。

圖片

3、TCP釋放一個連接卻需要四個報文段

第一個報文段,客戶端向服務器發送釋放連接報文段,釋放連接報文段FIN置1,此時客戶端序列號為w

第二個報文段,服務器回復確認收到客戶端釋放連接報文段的報文段,確認號u+1,之后客戶端不會像服務器發送報文段,但是服務器可以向客戶端發送報文段,全部發送完成服務器的序列號為v,客戶端的確認號為v,但是客戶端序列號為w+1

第三個報文段,服務器向客戶端發送釋放連接報文段,服務器序列號v+1,確認號還是w+1

第四個報文段,客戶端回復確認收到服務器的釋放連接報文段的報文段,,客戶端的確認號+1,序列號+1。

服務器收到第四個報文段,就進入CLOSED狀態;客戶端要等待最長報文段壽命(2MSL),才進入到CLOSED狀態。

注:

1、MSL是任何IP數據報能夠在網絡中存活的最長時間,每個數據報含有一個稱為跳限(hop limit)的8位字段,它的最大值是255,即最大為255跳。具有最大跳限的數據報在網絡中存在的時間不可能超過MSL秒。

2、客戶端在TIME-WAIT狀態必須2MSL的時間的兩個理由:

可靠地實現TCP全雙工連接的終止:第四個報文段如果丟失,在2MSL時間內服務器會重新發送第三報文段客戶端接收到之后重新發送第四報文段。

防止“已失效的連接請求報文段”出現在本連接中,在2MSL時間之后,本連接持續的時間內所產生的所有報文段都會網絡中消失。因為一個TCP端口不能同時被打開多次,如果沒有TIME-WAIT狀態,TCP端口關閉之后在TIME-WAIT時間內重新打開,可能會接受原來還沒有消失的報文段,這是不能發生的?。?!

下面是:“TCP協議的狀態機” 和 “TCP建鏈接”、“TCP斷鏈接”、“傳數據” 的對照圖,我把兩個圖并排放在一起,這樣方便在你對照著看。另外,下面這兩個圖非常非常的重要,你一定要記牢。(吐個槽:看到這樣復雜的狀態機,就知道這個協議有多復雜,復雜的東西總是有很多坑爹的事情,所以TCP協議其實也挺坑爹的)

圖片

圖片

很多人會問,為什么建鏈接要3次握手,斷鏈接需要4次揮手?

對于建鏈接的3次握手,主要是要初始化Sequence Number 的初始值。通信的雙方要互相通知對方自己的初始化的Sequence Number(縮寫為ISN:Inital Sequence Number)——所以叫SYN,全稱Synchronize Sequence Numbers。也就上圖中的 x 和 y。這個號要作為以后的數據通信的序號,以保證應用層接收到的數據不會因為網絡上的傳輸的問題而亂序(TCP會用這個序號來拼接數據)。

對于4次揮手,其實你仔細看是2次,因為TCP是全雙工的,所以,發送方和接收方都需要Fin和Ack。只不過,有一方是被動的,所以看上去就成了所謂的4次揮手。如果兩邊同時斷連接,那就會就進入到CLOSING狀態,然后到達TIME_WAIT狀態。下圖是雙方同時斷連接的示意圖(你同樣可以對照著TCP狀態機看):

圖片

兩端同時斷連接

另外,有幾個事情需要注意一下:

  • 關于建連接時SYN超時。試想一下,如果server端接到了clien發的SYN后回了SYN-ACK后client掉線了,server端沒有收到client回來的ACK,那么,這個連接處于一個中間狀態,即沒成功,也沒失敗。于是,server端如果在一定時間內沒有收到的TCP會重發SYN-ACK。在Linux下,默認重試次數為5次,重試的間隔時間從1s開始每次都翻售,5次的重試時間間隔為1s, 2s, 4s, 8s, 16s,總共31s,第5次發出后還要等32s都知道第5次也超時了,所以,總共需要 1s + 2s + 4s+ 8s+ 16s + 32s = 2^6 -1 = 63s,TCP才會把斷開這個連接。
  • 關于SYN Flood攻擊。一些惡意的人就為此制造了SYN Flood攻擊——給服務器發了一個SYN后,就下線了,于是服務器需要默認等63s才會斷開連接,這樣,攻擊者就可以把服務器的syn連接的隊列耗盡,讓正常的連接請求不能處理。于是,Linux下給了一個叫tcp_syncookies的參數來應對這個事——當SYN隊列滿了后,TCP會通過源地址端口、目標地址端口和時間戳打造出一個特別的Sequence Number發回去(又叫cookie),如果是攻擊者則不會有響應,如果是正常連接,則會把這個 SYN Cookie發回來,然后服務端可以通過cookie建連接(即使你不在SYN隊列中)。請注意,請先千萬別用tcp_syncookies來處理正常的大負載的連接的情況。因為,synccookies是妥協版的TCP協議,并不嚴謹。對于正常的請求,你應該調整三個TCP參數可供你選擇,第一個是:tcp_synack_retries 可以用他來減少重試次數;第二個是:tcp_max_syn_backlog,可以增大SYN連接數;第三個是:tcp_abort_on_overflow 處理不過來干脆就直接拒絕連接了。
  • 關于ISN的初始化。ISN是不能hard code的,不然會出問題的——比如:如果連接建好后始終用1來做ISN,如果client發了30個segment過去,但是網絡斷了,于是 client重連,又用了1做ISN,但是之前連接的那些包到了,于是就被當成了新連接的包,此時,client的Sequence Number 可能是3,而Server端認為client端的這個號是30了。全亂了。RFC793中說,ISN會和一個假的時鐘綁在一起,這個時鐘會在每4微秒對ISN做加一操作,直到超過2^32,又從0開始。這樣,一個ISN的周期大約是4.55個小時。因為,我們假設我們的TCP Segment在網絡上的存活時間不會超過Maximum Segment Lifetime(縮寫為MSL – Wikipedia語條),所以,只要MSL的值小于4.55小時,那么,我們就不會重用到ISN。
  • 關于 MSL 和 TIME_WAIT。通過上面的ISN的描述,相信你也知道MSL是怎么來的了。我們注意到,在TCP的狀態圖中,從TIME_WAIT狀態到CLOSED狀態,有一個超時設置,這個超時設置是 2*MSL(RFC793定義了MSL為2分鐘,Linux設置成了30s)為什么要這有TIME_WAIT?為什么不直接給轉成CLOSED狀態呢?主要有兩個原因:1)TIME_WAIT確保有足夠的時間讓對端收到了ACK,如果被動關閉的那方沒有收到Ack,就會觸發被動端重發Fin,一來一去正好2個MSL,2)有足夠的時間讓這個連接不會跟后面的連接混在一起(你要知道,有些自做主張的路由器會緩存IP數據包,如果連接被重用了,那么這些延遲收到的包就有可能會跟新連接混在一起)。
  • 關于TIME_WAIT數量太多。從上面的描述我們可以知道,TIME_WAIT是個很重要的狀態,但是如果在大并發的短鏈接下,TIME_WAIT 就會太多,這也會消耗很多系統資源。只要搜一下,你就會發現,十有八九的處理方式都是教你設置兩個參數,一個叫tcp_tw_reuse,另一個叫tcp_tw_recycle的參數,這兩個參數默認值都是被關閉的,后者recyle比前者resue更為激進,resue要溫柔一些。另外,如果使用tcp_tw_reuse,必需設置tcp_timestamps=1,否則無效。這里,你一定要注意,打開這兩個參數會有比較大的坑——可能會讓TCP連接出一些詭異的問題(因為如上述一樣,如果不等待超時重用連接的話,新的連接可能會建不上。正如官方文檔上說的一樣“It should not be changed without advice/request of technical experts”)。
  • 關于TIME_WAIT數量太多。從上面的描述我們可以知道,TIME_WAIT是個很重要的狀態,但是如果在大并發的短鏈接下,TIME_WAIT 就會太多,這也會消耗很多系統資源。只要搜一下,你就會發現,十有八九的處理方式都是教你設置兩個參數,一個叫tcp_tw_reuse,另一個叫tcp_tw_recycle的參數,這兩個參數默認值都是被關閉的,后者recyle比前者resue更為激進,resue要溫柔一些。另外,如果使用tcp_tw_reuse,必需設置tcp_timestamps=1,否則無效。這里,你一定要注意,打開這兩個參數會有比較大的坑——可能會讓TCP連接出一些詭異的問題(因為如上述一樣,如果不等待超時重用連接的話,新的連接可能會建不上。正如官方文檔上說的一樣“It should not be changed without advice/request of technical experts”)。
  • 關于tcp_tw_reuse。官方文檔上說tcp_tw_reuse 加上tcp_timestamps(又叫PAWS, for Protection Against Wrapped Sequence Numbers)可以保證協議的角度上的安全,但是你需要tcp_timestamps在兩邊都被打開(你可以讀一下tcp_twsk_unique的源碼 )。我個人估計還是有一些場景會有問題。
  • 關于tcp_tw_recycle。如果是tcp_tw_recycle被打開了話,會假設對端開啟了tcp_timestamps,然后會去比較時間戳,如果時間戳變大了,就可以重用。但是,如果對端是一個NAT網絡的話(如:一個公司只用一個IP出公網)或是對端的IP被另一臺重用了,這個事就復雜了。建鏈接的SYN可能就被直接丟掉了(你可能會看到connection time out的錯誤)
  • 關于tcp_max_tw_buckets。這個是控制并發的TIME_WAIT的數量,默認值是180000,如果超限,那么,系統會把多的給destory掉,然后在日志里打一個警告(如:time wait bucket table overflow),官網文檔說這個參數是用來對抗DDoS攻擊的。也說的默認值180000并不小。這個還是需要根據實際情況考慮。

Again,使用tcp_tw_reuse和tcp_tw_recycle來解決TIME_WAIT的問題是非常非常危險的,因為這兩個參數違反了TCP協議(RFC 1122)

其實,TIME_WAIT表示的是你主動斷連接,所以,這就是所謂的“不作死不會死”。試想,如果讓對端斷連接,那么這個破問題就是對方的了,呵呵。另外,如果你的服務器是于HTTP服務器,那么設置一個HTTP的KeepAlive有多重要(瀏覽器會重用一個TCP連接來處理多個HTTP請求),然后讓客戶端去斷鏈接(你要小心,瀏覽器可能會非常貪婪,他們不到萬不得已不會主動斷連接)。

數據傳輸中的Sequence Number

下圖是我從Wireshark中截了個我在訪問coolshell.cn時的有數據傳輸的圖給你看一下,SeqNum是怎么變的。(使用Wireshark菜單中的Statistics ->Flow Graph… )

圖片

你可以看到,SeqNum的增加是和傳輸的字節數相關的。上圖中,三次握手后,來了兩個Len:1440的包,而第二個包的SeqNum就成了1441。然后第一個ACK回的是1441,表示第一個1440收到了。

注意:如果你用Wireshark抓包程序看3次握手,你會發現SeqNum總是為0,不是這樣的,Wireshark為了顯示更友好,使用了Relative SeqNum——相對序號,你只要在右鍵菜單中的protocol preference 中取消掉就可以看到“Absolute SeqNum”了

TCP連接狀態總結:

一、LISTENING

提供某種服務,偵聽遠方TCP端口的連接請求,當提供的服務沒有被連接時,處于LISTENING狀態,端口是開放的,等待被連接。

二、SYN_SENT (客戶端狀態)

客戶端調用connect,發送一個SYN請求建立一個連接,在發送連接請求后等待匹配的連接請求,此時狀態為SYN_SENT.

三、SYN_RECEIVED (服務端狀態)

在收到和發送一個連接請求后,等待對方對連接請求的確認,當服務器收到客戶端發送的同步信號時,將標志位ACK和SYN置1發送給客戶端,此時服務器端處于SYN_RCVD狀態,如果連接成功了就變為ESTABLISHED,正常情況下SYN_RCVD狀態非常短暫。

四、ESTABLISHED

ESTABLISHED狀態是表示兩臺機器正在傳輸數據。

五、FIN-WAIT-1

等待遠程TCP連接中斷請求,或先前的連接中斷請求的確認,主動關閉端應用程序調用close,TCP發出FIN請求主動關閉連接,之后進入FIN_WAIT1狀態。

六、FIN-WAIT-2

從遠程TCP等待連接中斷請求,主動關閉端接到ACK后,就進入了FIN-WAIT-2 .這是在關閉連接時,客戶端和服務器兩次握手之后的狀態,是著名的半關閉的狀態了,在這個狀態下,應用程序還有接受數據的能力,但是已經無法發送數據,但是也有一種可能是,客戶端一直處于FIN_WAIT_2狀態,而服務器則一直處于WAIT_CLOSE狀態,而直到應用層來決定關閉這個狀態。

附半關閉例圖:

圖片

七、CLOSE-WAIT

等待從本地用戶發來的連接中斷請求 ,被動關閉端TCP接到FIN后,就發出ACK以回應FIN請求(它的接收也作為文件結束符傳遞給上層應用程序),并進入CLOSE_WAIT.

八、CLOSING

等待遠程TCP對連接中斷的確認,處于此種狀態比較少見。

九、LAST-ACK

等待原來的發向遠程TCP的連接中斷請求的確認,被動關閉端一段時間后,接收到文件結束符的應用程序將調用CLOSE關閉連接,TCP也發送一個 FIN,等待對方的ACK.進入LAST-ACK。

十、TIME-WAIT

在主動關閉端接收到FIN后,TCP就發送ACK包,并進入TIME-WAIT狀態,等待足夠的時間以確保遠程TCP接收到連接中斷請求的確認,很大程度上保證了雙方都可以正常結束,但是也存在問題,須等待2MSL時間的過去才能進行下一次連接。

**十一、CLOSED **

被動關閉端在接受到ACK包后,就進入了closed的狀態,連接結束,沒有任何連接狀態。

附TCP正常連接建立和終止所對應的狀態圖:

圖片

狀態遷移過程:

a、客戶端:

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

b、服務端

CLOSED->LISTEN->SYN_RECEIVED->ESTABLISHED->CLOSE_WAIT->LAST_ACK->CLOSE

在眾多狀態中,經常關注的有兩個:TIME_WAIT、CLOSE_WAIT。

附狀態遷移過程圖:

圖片

二、丟包重傳機制

TCP重傳機制

TCP要保證所有的數據包都可以到達,所以,必需要有重傳機制。

注意,接收端給發送端的Ack確認只會確認最后一個連續的包,比如,發送端發了1,2,3,4,5一共五份數據,接收端收到了1,2,于是回ack 3,然后收到了4(注意此時3沒收到),此時的TCP會怎么辦?我們要知道,因為正如前面所說的,SeqNum和Ack是以字節數為單位,所以ack的時候,不能跳著確認,只能確認最大的連續收到的包,不然,發送端就以為之前的都收到了。

超時重傳機制

一種是不回ack,死等3,當發送方發現收不到3的ack超時后,會重傳3。一旦接收方收到3后,會ack 回 4——意味著3和4都收到了。

但是,這種方式會有比較嚴重的問題,那就是因為要死等3,所以會導致4和5即便已經收到了,而發送方也完全不知道發生了什么事,因為沒有收到Ack,所以,發送方可能會悲觀地認為也丟了,所以有可能也會導致4和5的重傳。

對此有兩種選擇:

  • 一種是僅重傳timeout的包。也就是第3份數據。
  • 另一種是重傳timeout后所有的數據,也就是第3,4,5這三份數據。

這兩種方式有好也有不好。第一種會節省帶寬,但是慢,第二種會快一點,但是會浪費帶寬,也可能會有無用功。但總體來說都不好。因為都在等timeout,timeout可能會很長(在下篇會說TCP是怎么動態地計算出timeout的)

快速重傳機制

于是,TCP引入了一種叫Fast Retransmit 的算法,不以時間驅動,而以數據驅動重傳。也就是說,如果,包沒有連續到達,就ack最后那個可能被丟了的包,如果發送方連續收到3次相同的ack,就重傳。Fast Retransmit的好處是不用等timeout了再重傳。

比如:如果發送方發出了1,2,3,4,5份數據,第一份先到送了,于是就ack回2,結果2因為某些原因沒收到,3到達了,于是還是ack回2,后面的4和5都到了,但是還是ack回2,因為2還是沒有收到,于是發送端收到了三個ack=2的確認,知道了2還沒有到,于是就馬上重轉2。然后,接收端收到了2,此時因為3,4,5都收到了,于是ack回6。示意圖如下:

圖片

Fast Retransmit只解決了一個問題,就是timeout的問題,它依然面臨一個艱難的選擇,就是,是重傳之前的一個還是重傳所有的問題。對于上面的示例來說,是重傳#2呢還是重傳#2,#3,#4,#5呢?因為發送端并不清楚這連續的3個ack(2)是誰傳回來的?也許發送端發了20份數據,是#6,#10,#20傳來的呢。這樣,發送端很有可能要重傳從2到20的這堆數據(這就是某些TCP的實際的實現)??梢?,這是一把雙刃劍。

SACK 方法

另外一種更好的方式叫:Selective Acknowledgment (SACK)(參看RFC 2018),這種方式需要在TCP頭里加一個SACK的東西,ACK還是Fast Retransmit的ACK,SACK則是匯報收到的數據碎版,參看下圖:

圖片

這樣,在發送端就可以根據回傳的SACK來知道哪些數據到了,哪些沒有到。于是就優化了Fast Retransmit的算法。當然,這個協議需要兩邊都支持。在 Linux下,可以通過tcp_sack參數打開這個功能(Linux 2.4后默認打開)。

這里還需要注意一個問題——接收方Reneging,所謂Reneging的意思就是接收方有權把已經報給發送端SACK里的數據給丟了。這樣干是不被鼓勵的,因為這個事會把問題復雜化了,但是,接收方這么做可能會有些極端情況,比如要把內存給別的更重要的東西。所以,發送方也不能完全依賴SACK,還是要依賴ACK,并維護Time-Out,如果后續的ACK沒有增長,那么還是要把SACK的東西重傳,另外,接收端這邊永遠不能把SACK的包標記為Ack。

注意:SACK會消費發送方的資源,試想,如果一個攻擊者給數據發送方發一堆SACK的選項,這會導致發送方開始要重傳甚至遍歷已經發出的數據,這會消耗很多發送端的資源。

Duplicate SACK – 重復收到數據的問題

Duplicate SACK又稱D-SACK,其主要使用了SACK來告訴發送方有哪些數據被重復接收了。RFC-2883 里有詳細描述和示例。下面舉幾個例子(來源于RFC-2883)

D-SACK使用了SACK的第一個段來做標志,

  • 如果SACK的第一個段的范圍被ACK所覆蓋,那么就是D-SACK
  • 如果SACK的第一個段的范圍被SACK的第二個段覆蓋,那么就是D-SACK

示例一:ACK丟包

下面的示例中,丟了兩個ACK,所以,發送端重傳了第一個數據包(3000-3499),于是接收端發現重復收到,于是回了一個SACK=3000-3500,因為ACK都到了4000意味著收到了4000之前的所有數據,所以這個SACK就是D-SACK——旨在告訴發送端我收到了重復的數據,而且我們的發送端還知道,數據包沒有丟,丟的是ACK包。

Transmitted  Received    ACK Sent  Segment      Segment     (Including SACK Blocks)  3000-3499    3000-3499   3500 (ACK dropped)  3500-3999    3500-3999   4000 (ACK dropped)  3000-3499    3000-3499   4000, SACK=3000-3500                                        ---------

示例二,網絡延誤

下面的示例中,網絡包(1000-1499)被網絡給延誤了,導致發送方沒有收到ACK,而后面到達的三個包觸發了“Fast Retransmit算法”,所以重傳,但重傳時,被延誤的包又到了,所以,回了一個SACK=1000-1500,因為ACK已到了3000,所以,這個SACK是D-SACK——標識收到了重復的包。

這個案例下,發送端知道之前因為“Fast Retransmit算法”觸發的重傳不是因為發出去的包丟了,也不是因為回應的ACK包丟了,而是因為網絡延時了。

Transmitted    Received    ACK Sent    Segment        Segment     (Including SACK Blocks)    500-999        500-999     1000    1000-1499      (delayed)    1500-1999      1500-1999   1000, SACK=1500-2000    2000-2499      2000-2499   1000, SACK=1500-2500    2500-2999      2500-2999   1000, SACK=1500-3000    1000-1499      1000-1499   3000                   1000-1499   3000, SACK=1000-1500                                          ---------

可見,引入了D-SACK,有這么幾個好處:

1)可以讓發送方知道,是發出去的包丟了,還是回來的ACK包丟了。

2)是不是自己的timeout太小了,導致重傳。

3)網絡上出現了先發的包后到的情況(又稱reordering)

4)網絡上是不是把我的數據包給復制了。

知道這些東西可以很好得幫助TCP了解網絡情況,從而可以更好的做網絡上的流控。

Linux下的tcp_dsack參數用于開啟這個功能(Linux 2.4后默認打開)

三、TCP的RTT算法

TCP要解決一個很大的事,那就是要在一個網絡根據不同的情況來動態調整自己的發包的速度,小則讓自己的連接更穩定,大則讓整個網絡更穩定。

從前面的TCP重傳機制我們知道Timeout的設置對于重傳非常重要。

  • 設長了,重發就慢,丟了老半天才重發,沒有效率,性能差;
  • 設短了,會導致可能并沒有丟就重發。于是重發的就快,會增加網絡擁塞,導致更多的超時,更多的超時導致更多的重發。

而且,這個超時時間在不同的網絡的情況下,根本沒有辦法設置一個死的值。只能動態地設置。為了動態地設置,TCP引入了RTT——Round Trip Time,也就是一個數據包從發出去到回來的時間。這樣發送端就大約知道需要多少的時間,從而可以方便地設置Timeout——RTO(Retransmission TimeOut),以讓我們的重傳機制更高效。聽起來似乎很簡單,好像就是在發送端發包時記下t0,然后接收端再把這個ack回來時再記一個t1,于是RTT = t1 – t0。沒那么簡單,這只是一個采樣,不能代表普遍情況。

經典算法

RFC793 中定義的經典算法是這樣的:

1)首先,先采樣RTT,記下最近好幾次的RTT值。

2)然后做平滑計算SRTT( Smoothed RTT)。公式為:(其中的 α 取值在0.8 到 0.9之間,這個算法英文叫Exponential weighted moving average,中文叫:加權移動平均)

SRTT = ( α * SRTT ) + ((1- α) * RTT)

3)開始計算RTO。公式如下:

RTO = min [ UBOUND, max [ LBOUND, (β * SRTT) ] ]

其中:

  • UBOUND是最大的timeout時間,上限值
  • LBOUND是最小的timeout時間,下限值
  • β 值一般在1.3到2.0之間。

Karn / Partridge 算法

但是上面的這個算法在重傳的時候會出有一個終極問題——你是用第一次發數據的時間和ack回來的時間做RTT樣本值,還是用重傳的時間和ACK回來的時間做RTT樣本值?

這個問題無論你選那頭都是按下葫蘆起了瓢。如下圖所示:

  • 情況(a)是ack沒回來,所以重傳。如果你計算第一次發送和ACK的時間,那么,明顯算大了。
  • 情況(b)是ack回來慢了,但是導致了重傳,但剛重傳不一會兒,之前ACK就回來了。如果你是算重傳的時間和ACK回來的時間的差,就會算短了。

圖片

所以1987年的時候,搞了一個叫Karn / Partridge Algorithm,這個算法的最大特點是——忽略重傳,不把重傳的RTT做采樣(你看,你不需要去解決不存在的問題)。

但是,這樣一來,又會引發一個大BUG——如果在某一時間,網絡閃動,突然變慢了,產生了比較大的延時,這個延時導致要重轉所有的包(因為之前的RTO很?。?,于是,因為重轉的不算,所以,RTO就不會被更新,這是一個災難。于是Karn算法用了一個取巧的方式——只要一發生重傳,就對現有的RTO值翻倍(這就是所謂的 Exponential backoff),很明顯,這種死規矩對于一個需要估計比較準確的RTT也不靠譜。

Jacobson / Karels 算法

前面兩種算法用的都是“加權移動平均”,這種方法最大的毛病就是如果RTT有一個大的波動的話,很難被發現,因為被平滑掉了。所以,1988年,又有人推出來了一個新的算法,這個算法叫Jacobson / Karels Algorithm(參看RFC6289)。這個算法引入了最新的RTT的采樣和平滑過的SRTT的差距做因子來計算。公式如下:(其中的DevRTT是Deviation RTT的意思)

SRTT = SRTT + α (RTT – SRTT) —— 計算平滑RTT

DevRTT = (1-β) DevRTT + β (|RTT-SRTT|) ——計算平滑RTT和真實的差距(加權移動平均)

RTO= μ * SRTT + ? *DevRTT —— 神一樣的公式

(其中:在Linux下,α = 0.125,β = 0.25, μ = 1,? = 4 ——這就是算法中的“調得一手好參數”,nobody knows why, it just works…) 最后的這個算法在被用在今天的TCP協議中。

四、TCP滑動窗口

需要說明一下,如果你不了解TCP的滑動窗口這個事,你等于不了解TCP協議。我們都知道,TCP必需要解決的可靠傳輸以及包亂序(reordering)的問題,所以,TCP必需要知道網絡實際的數據處理帶寬或是數據處理速度,這樣才不會引起網絡擁塞,導致丟包。

所以,TCP引入了一些技術和設計來做網絡流控,Sliding Window是其中一個技術。前面我們說過,TCP頭里有一個字段叫Window,又叫Advertised-Window,這個字段是接收端告訴發送端自己還有多少緩沖區可以接收數據。于是發送端就可以根據這個接收端的處理能力來發送數據,而不會導致接收端處理不過來。為了說明滑動窗口,我們需要先看一下TCP緩沖區的一些數據結構:

圖片

上圖中,我們可以看到:

  • 接收端LastByteRead指向了TCP緩沖區中讀到的位置,NextByteExpected指向的地方是收到的連續包的最后一個位置,LastByteRcved指向的是收到的包的最后一個位置,我們可以看到中間有些數據還沒有到達,所以有數據空白區。
  • 發送端的LastByteAcked指向了被接收端Ack過的位置(表示成功發送確認),LastByteSent表示發出去了,但還沒有收到成功確認的Ack,LastByteWritten指向的是上層應用正在寫的地方。

于是:

  • 接收端在給發送端回ACK中會匯報自己的AdvertisedWindow = MaxRcvBuffer – LastByteRcvd – 1;
  • 而發送方會根據這個窗口來控制發送數據的大小,以保證接收方可以處理。

下面我們來看一下發送方的滑動窗口示意圖:

圖片

上圖中分成了四個部分,分別是:(其中那個黑模型就是滑動窗口)

  • #1已收到ack確認的數據。
  • #2發還沒收到ack的。
  • #3在窗口中還沒有發出的(接收方還有空間)。
  • #4窗口以外的數據(接收方沒空間)

下面是個滑動后的示意圖(收到36的ack,并發出了46-51的字節):

圖片

下面我們來看一個接受端控制發送端的圖示:

圖片

Zero Window

上圖,我們可以看到一個處理緩慢的Server(接收端)是怎么把Client(發送端)的TCP Sliding Window給降成0的。此時,你一定會問,如果Window變成0了,TCP會怎么樣?是不是發送端就不發數據了?是的,發送端就不發數據了,你可以想像成“Window Closed”,那你一定還會問,如果發送端不發數據了,接收方一會兒Window size 可用了,怎么通知發送端呢?

解決這個問題,TCP使用了Zero Window Probe技術,縮寫為ZWP,也就是說,發送端在窗口變成0后,會發ZWP的包給接收方,讓接收方來ack他的Window尺寸,一般這個值會設置成3次,第次大約30-60秒(不同的實現可能會不一樣)。如果3次過后還是0的話,有的TCP實現就會發RST把鏈接斷了。

注意:只要有等待的地方都可能出現DDoS攻擊,Zero Window也不例外,一些攻擊者會在和HTTP建好鏈發完GET請求后,就把Window設置為0,然后服務端就只能等待進行ZWP,于是攻擊者會并發大量的這樣的請求,把服務器端的資源耗盡。

另外,Wireshark中,你可以使用tcp.analysis.zero_window來過濾包,然后使用右鍵菜單里的follow TCP stream,你可以看到ZeroWindowProbe及ZeroWindowProbeAck的包。

Silly Window Syndrome

Silly Window Syndrome翻譯成中文就是“糊涂窗口綜合癥”。正如你上面看到的一樣,如果我們的接收方太忙了,來不及取走Receive Windows里的數據,那么,就會導致發送方越來越小。到最后,如果接收方騰出幾個字節并告訴發送方現在有幾個字節的window,而我們的發送方會義無反顧地發送這幾個字節。

要知道,我們的TCP+IP頭有40個字節,為了幾個字節,要達上這么大的開銷,這太不經濟了。

另外,你需要知道網絡上有個MTU,對于以太網來說,MTU是1500字節,除去TCP+IP頭的40個字節,真正的數據傳輸可以有1460,這就是所謂的MSS(Max Segment Size)注意,TCP的RFC定義這個MSS的默認值是536,這是因為 RFC 791里說了任何一個IP設備都得最少接收576尺寸的大?。▽嶋H上來說576是撥號的網絡的MTU,而576減去IP頭的20個字節就是536)。

如果你的網絡包可以塞滿MTU,那么你可以用滿整個帶寬,如果不能,那么你就會浪費帶寬。(大于MTU的包有兩種結局,一種是直接被丟了,另一種是會被重新分塊打包發送) 你可以想像成一個MTU就相當于一個飛機的最多可以裝的人,如果這飛機里滿載的話,帶寬最高,如果一個飛機只運一個人的話,無疑成本增加了,也而相當二。

所以,Silly Windows Syndrome這個現像就像是你本來可以坐200人的飛機里只做了一兩個人。要解決這個問題也不難,就是避免對小的window size做出響應,直到有足夠大的window size再響應,這個思路可以同時實現在sender和receiver兩端。

  • 如果這個問題是由Receiver端引起的,那么就會使用 David D Clark’s 方案。在receiver端,如果收到的數據導致window size小于某個值,可以直接ack(0)回sender,這樣就把window給關閉了,也阻止了sender再發數據過來,等到receiver端處理了一些數據后windows size 大于等于了MSS,或者,receiver buffer有一半為空,就可以把window打開讓send 發送數據過來。
  • 如果這個問題是由Sender端引起的,那么就會使用著名的 Nagle’s algorithm。這個算法的思路也是延時處理,他有兩個主要的條件:1)要等到 Window Size>=MSS 或是 Data Size >=MSS,2)收到之前發送數據的ack回包,他才會發數據,否則就是在攢數據。

另外,Nagle算法默認是打開的,所以,對于一些需要小包場景的程序——比如像telnet或ssh這樣的交互性比較強的程序,你需要關閉這個算法。你可以在Socket設置TCP_NODELAY選項來關閉這個算法(關閉Nagle算法沒有全局參數,需要根據每個應用自己的特點來關閉)

setsockopt(sock_fd, IPPROTO_TCP, TCP_NODELAY, (char *)&value,sizeof(int));

另外,網上有些文章說TCP_CORK的socket option是也關閉Nagle算法,這不對。TCP_CORK其實是更新激進的Nagle算漢,完全禁止小包發送,而Nagle算法沒有禁止小包發送,只是禁止了大量的小包發送。最好不要兩個選項都設置。

五、擁塞處理 Congestion Handling

上面我們知道了,TCP通過Sliding Window來做流控(Flow Control),但是TCP覺得這還不夠,因為Sliding Window需要依賴于連接的發送端和接收端,其并不知道網絡中間發生了什么。TCP的設計者覺得,一個偉大而牛逼的協議僅僅做到流控并不夠,因為流控只是網絡模型4層以上的事,TCP的還應該更聰明地知道整個網絡上的事。

具體一點,我們知道TCP通過一個timer采樣了RTT并計算RTO,但是,如果網絡上的延時突然增加,那么,TCP對這個事做出的應對只有重傳數據,但是,重傳會導致網絡的負擔更重,于是會導致更大的延遲以及更多的丟包,于是,這個情況就會進入惡性循環被不斷地放大。試想一下,如果一個網絡內有成千上萬的TCP連接都這么行事,那么馬上就會形成“網絡風暴”,TCP這個協議就會拖垮整個網絡。這是一個災難。

所以,TCP不能忽略網絡上發生的事情,而無腦地一個勁地重發數據,對網絡造成更大的傷害。對此TCP的設計理念是:TCP不是一個自私的協議,當擁塞發生的時候,要做自我犧牲。就像交通阻塞一樣,每個車都應該把路讓出來,而不要再去搶路了。

擁塞控制主要是四個算法:1)慢啟動,2)擁塞避免,3)擁塞發生,4)快速恢復。這四個算法不是一天都搞出來的,這個四算法的發展經歷了很多時間,到今天都還在優化中。備注:

  • 1988年,TCP-Tahoe 提出了1)慢啟動,2)擁塞避免,3)擁塞發生時的快速重傳
  • 1990年,TCP Reno 在Tahoe的基礎上增加了4)快速恢復

慢熱啟動算法 – Slow Start

首先,我們來看一下TCP的慢熱啟動。慢啟動的意思是,剛剛加入網絡的連接,一點一點地提速,不要一上來就像那些特權車一樣霸道地把路占滿。新同學上高速還是要慢一點,不要把已經在高速上的秩序給搞亂了。

慢啟動的算法如下(cwnd全稱Congestion Window):

1)連接建好的開始先初始化cwnd = 1,表明可以傳一個MSS大小的數據。

2)每當收到一個ACK,cwnd++; 呈線性上升

3)每當過了一個RTT,cwnd = cwnd*2; 呈指數讓升

4)還有一個ssthresh(slow start threshold),是一個上限,當cwnd >= ssthresh時,就會進入“擁塞避免算法”(后面會說這個算法)

所以,我們可以看到,如果網速很快的話,ACK也會返回得快,RTT也會短,那么,這個慢啟動就一點也不慢。下圖說明了這個過程。

圖片

這里,我需要提一下,Linux 3.0后采用了這篇論文的建議——把cwnd 初始化成了 10個MSS。而Linux 3.0以前,比如2.6,Linux采用了RFC3390,cwnd是跟MSS的值來變的,如果MSS< 1095,則cwnd = 4;如果MSS>2190,則cwnd=2;其它情況下,則是3。

擁塞避免算法 – Congestion Avoidance

前面說過,還有一個ssthresh(slow start threshold),是一個上限,當cwnd >= ssthresh時,就會進入“擁塞避免算法”。一般來說ssthresh的值是65535,單位是字節,當cwnd達到這個值時后,算法如下:

1)收到一個ACK時,cwnd = cwnd + 1/cwnd

2)當每過一個RTT時,cwnd = cwnd + 1

這樣就可以避免增長過快導致網絡擁塞,慢慢的增加調整到網絡的最佳值。很明顯,是一個線性上升的算法。

擁塞狀態時的算法

前面我們說過,當丟包的時候,會有兩種情況:

1)等到RTO超時,重傳數據包。TCP認為這種情況太糟糕,反應也很強烈。

  • sshthresh = cwnd /2
  • cwnd 重置為 1
  • 進入慢啟動過程

2)Fast Retransmit算法,也就是在收到3個duplicate ACK時就開啟重傳,而不用等到RTO超時。

TCP Tahoe的實現和RTO超時一樣。

TCP Reno的實現是:

  • 進入快速恢復算法——Fast Recovery
  • sshthresh = cwnd
  • cwnd = cwnd /2

上面我們可以看到RTO超時后,sshthresh會變成cwnd的一半,這意味著,如果cwnd<=sshthresh時出現的丟包,那么TCP的sshthresh就會減了一半,然后等cwnd又很快地以指數級增漲爬到這個地方時,就會成慢慢的線性增漲。我們可以看到,TCP是怎么通過這種強烈地震蕩快速而小心得找到網站流量的平衡點的。

快速恢復算法 – Fast Recovery

TCP Reno

這個算法定義在RFC5681??焖僦貍骱涂焖倩謴退惴ㄒ话阃瑫r使用??焖倩謴退惴ㄊ钦J為,你還有3個Duplicated Acks說明網絡也不那么糟糕,所以沒有必要像RTO超時那么強烈。注意,正如前面所說,進入Fast Recovery之前,cwnd 和 sshthresh已被更新:

  • cwnd = cwnd /2
  • sshthresh = cwnd

然后,真正的Fast Recovery算法如下:

  • cwnd = sshthresh + 3 * MSS (3的意思是確認有3個數據包被收到了)
  • 重傳Duplicated ACKs指定的數據包
  • 如果再收到 duplicated Acks,那么cwnd = cwnd +1
  • 如果收到了新的Ack,那么,cwnd = sshthresh ,然后就進入了擁塞避免的算法了。

如果你仔細思考一下上面的這個算法,你就會知道,上面這個算法也有問題,那就是——它依賴于3個重復的Acks。注意,3個重復的Acks并不代表只丟了一個數據包,很有可能是丟了好多包。但這個算法只會重傳一個,而剩下的那些包只能等到RTO超時,于是,進入了惡夢模式——超時一個窗口就減半一下,多個超時會超成TCP的傳輸速度呈級數下降,而且也不會觸發Fast Recovery算法了。

通常來說,正如我們前面所說的,SACK或D-SACK的方法可以讓Fast Recovery或Sender在做決定時更聰明一些,但是并不是所有的TCP的實現都支持SACK(SACK需要兩端都支持),所以,需要一個沒有SACK的解決方案。而通過SACK進行擁塞控制的算法是FACK(后面會講)

TCP New Reno

于是,1995年,TCP New Reno(參見 RFC 6582 )算法提出來,主要就是在沒有SACK的支持下改進Fast Recovery算法的——

  • 當sender這邊收到了3個Duplicated Acks,進入Fast Retransimit模式,開發重傳重復Acks指示的那個包。如果只有這一個包丟了,那么,重傳這個包后回來的Ack會把整個已經被sender傳輸出去的數據ack回來。如果沒有的話,說明有多個包丟了。我們叫這個ACK為Partial ACK。
  • 一旦Sender這邊發現了Partial ACK出現,那么,sender就可以推理出來有多個包被丟了,于是乎繼續重傳sliding window里未被ack的第一個包。直到再也收不到了Partial Ack,才真正結束Fast Recovery這個過程

我們可以看到,這個“Fast Recovery的變更”是一個非常激進的玩法,他同時延長了Fast Retransmit和Fast Recovery的過程。

算法示意圖

下面我們來看一個簡單的圖示以同時看一下上面的各種算法的樣子:

圖片

FACK算法

FACK全稱Forward Acknowledgment 算法,論文地址在這里(PDF)Forward Acknowledgement: Refining TCP Congestion Control 這個算法是其于SACK的,前面我們說過SACK是使用了TCP擴展字段Ack了有哪些數據收到,哪些數據沒有收到,他比Fast Retransmit的3 個duplicated acks好處在于,前者只知道有包丟了,不知道是一個還是多個,而SACK可以準確的知道有哪些包丟了。所以,SACK可以讓發送端這邊在重傳過程中,把那些丟掉的包重傳,而不是一個一個的傳,但這樣的一來,如果重傳的包數據比較多的話,又會導致本來就很忙的網絡就更忙了。所以,FACK用來做重傳過程中的擁塞流控。

  • 這個算法會把SACK中最大的Sequence Number 保存在snd.fack這個變量中,snd.fack的更新由ack帶秋,如果網絡一切安好則和snd.una一樣(snd.una就是還沒有收到ack的地方,也就是前面sliding window里的category #2的第一個地方)
  • 然后定義一個awnd = snd.nxt – snd.fack(snd.nxt指向發送端sliding window中正在要被發送的地方——前面sliding windows圖示的category#3第一個位置),這樣awnd的意思就是在網絡上的數據。(所謂awnd意為:actual quantity of data outstanding in the network)
  • 如果需要重傳數據,那么,awnd = snd.nxt – snd.fack + retran_data,也就是說,awnd是傳出去的數據 + 重傳的數據。
  • 然后觸發Fast Recovery 的條件是:( ( snd.fack – snd.una ) > (3*MSS) ) || (dupacks == 3) ) 。這樣一來,就不需要等到3個duplicated acks才重傳,而是只要sack中的最大的一個數據和ack的數據比較長了(3個MSS),那就觸發重傳。在整個重傳過程中cwnd不變。直到當第一次丟包的snd.nxt<=snd.una(也就是重傳的數據都被確認了),然后進來擁塞避免機制——cwnd線性上漲。

我們可以看到如果沒有FACK在,那么在丟包比較多的情況下,原來保守的算法會低估了需要使用的window的大小,而需要幾個RTT的時間才會完成恢復,而FACK會比較激進地來干這事。但是,FACK如果在一個網絡包會被 reordering的網絡里會有很大的問題。

其它擁塞控制算法簡介

TCP Vegas 擁塞控制算法

這個算法1994年被提出,它主要對TCP Reno 做了些修改。這個算法通過對RTT的非常重的監控來計算一個基準RTT。然后通過這個基準RTT來估計當前的網絡實際帶寬,如果實際帶寬比我們的期望的帶寬要小或是要多的活,那么就開始線性地減少或增加cwnd的大小。如果這個計算出來的RTT大于了Timeout后,那么,不等ack超時就直接重傳。(Vegas 的核心思想是用RTT的值來影響擁塞窗口,而不是通過丟包) 這個算法的論文是《TCP Vegas: End to End Congestion Avoidance on a Global Internet》這篇論文給了Vegas和 New Reno的對比:

圖片

關于這個算法實現,你可以參看Linux源碼:/net/ipv4/tcp_vegas.h「鏈接」, /net/ipv4/tcp_vegas.c「鏈接」

HSTCP(High Speed TCP) 算法

這個算法來自RFC 3649(Wikipedia詞條)。其對最基礎的算法進行了更改,他使得Congestion Window漲得快,減得慢。其中:

  • 擁塞避免時的窗口增長方式:cwnd = cwnd + α(cwnd) / cwnd
  • 丟包后窗口下降方式:cwnd = (1- β(cwnd))*cwnd

注:α(cwnd)和β(cwnd)都是函數,如果你要讓他們和標準的TCP一樣,那么讓α(cwnd)=1,β(cwnd)=0.5就可以了。對于α(cwnd)和β(cwnd)的值是個動態的變換的東西。關于這個算法的實現,你可以參看Linux源碼:/net/ipv4/tcp_highspeed.c「鏈接」

TCP BIC 算法

2004年,產內出BIC算法?,F在你還可以查得到相關的新聞《Google:美科學家研發BIC-TCP協議 速度是DSL六千倍》 BIC全稱Binary Increase Congestion control,在Linux 2.6.8中是默認擁塞控制算法。BIC的發明者發這么多的擁塞控制算法都在努力找一個合適的cwnd – Congestion Window,而且BIC-TCP的提出者們看穿了事情的本質,其實這就是一個搜索的過程,所以BIC這個算法主要用的是Binary Search——二分查找來干這個事。關于這個算法實現,你可以參看Linux源碼:/net/ipv4/tcp_bic.c「鏈接」

TCP WestWood算法

westwood采用和Reno相同的慢啟動算法、擁塞避免算法。westwood的主要改進方面:在發送端做帶寬估計,當探測到丟包時,根據帶寬值來設置擁塞窗口、慢啟動閾值。那么,這個算法是怎么測量帶寬的?每個RTT時間,會測量一次帶寬,測量帶寬的公式很簡單,就是這段RTT內成功被ack了多少字節。因為,這個帶寬和用RTT計算RTO一樣,也是需要從每個樣本來平滑到一個值的——也是用一個加權移平均的公式。另外,我們知道,如果一個網絡的帶寬是每秒可以發送X個字節,而RTT是一個數據發出去后確認需要的時候,所以,X * RTT應該是我們緩沖區大小。所以,在這個算法中,ssthresh的值就是est_BD * min-RTT(最小的RTT值),如果丟包是Duplicated ACKs引起的,那么如果cwnd > ssthresh,則 cwin = ssthresh。如果是RTO引起的,cwnd = 1,進入慢啟動。

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • 數據
    +關注

    關注

    8

    文章

    6531

    瀏覽量

    87771
  • 服務器
    +關注

    關注

    12

    文章

    8249

    瀏覽量

    82891
  • 端口
    +關注

    關注

    4

    文章

    835

    瀏覽量

    31668
  • TCP協議
    +關注

    關注

    1

    文章

    83

    瀏覽量

    12012
收藏 人收藏

    評論

    相關推薦

    TCP協議詳細解析

    TCPTCP/IP協議族中一個最核心的協議,它向下使用網絡層IP協議,向上為應用層HTTP、FTP、SMTP、POP3、SSH、Telne
    的頭像 發表于 11-03 09:14 ?1997次閱讀
    <b class='flag-5'>TCP</b><b class='flag-5'>協議</b>詳細解析

    TCP/IP協議連接指南

    前言 :之前只用了Wifi和Ethernet的連接,例程一下載就能連接的,但是沒有講到通訊。所以我還是很不懂。這次教程接觸到了TCP/IP協議了,在使用例程時,就明顯感受到,起始wif
    發表于 02-17 07:45

    Q2406B內嵌TCP協議模塊測試

    本內容提供了Q2406B內嵌 TCP協議 模塊測試 TCP指令顯示狀態指令 顯示當前狀態 #VSTATE,可能的回復有: #STATE:ID
    發表于 07-05 16:52 ?14次下載

    tcp ip協議_什么是tcp ip協議

    什么是tcp ip協議,tcp ip協議詳解,深刻講述了tcp ip協議的概念,
    發表于 05-14 16:29 ?5764次閱讀
    <b class='flag-5'>tcp</b> ip<b class='flag-5'>協議</b>_什么是<b class='flag-5'>tcp</b> ip<b class='flag-5'>協議</b>

    tcp_ip 協議講座:介紹tcp協議的特性

    介紹了tcp協議的特性,連接的建立和終止
    的頭像 發表于 07-03 09:05 ?2719次閱讀

    TCP/IP協議進階課程:TCP協議(2)

    TCP/IP協議進階課程:6、TCP協議02
    的頭像 發表于 07-05 00:10 ?3894次閱讀

    TCP與UDP協議的工作原理是怎么樣的

    TCP是一種可靠的,面向連接的全雙工傳輸層協議。 TCP連接的建立是一個三次握手的過程。
    的頭像 發表于 02-15 11:45 ?1w次閱讀
    <b class='flag-5'>TCP</b>與UDP<b class='flag-5'>協議</b>的工作原理是怎么樣的

    TCP IP協議有什么樣的狀態

    首先介紹一下TCP連接建立與關閉過程中的狀態。TCP連接過程是狀態的轉換,促使
    的頭像 發表于 02-24 14:31 ?2992次閱讀
    <b class='flag-5'>TCP</b> IP<b class='flag-5'>協議</b>有什么樣的<b class='flag-5'>狀態</b>

    ISO on TCP協議通信的連接配置

    使用ISO on TCP 協議通信,除了連接參數的定義不同,其它組態編程與 TCP 協議通信完全相同,見S7-1200 和 S7-1200
    的頭像 發表于 07-21 10:41 ?2037次閱讀

    什么是TCP狀態轉移

    TCP協議根據連接時接收到報文的不同類型,采取相應動作也不同,還要處理各個狀態的關系,如當收到握手報文時候、超時的時候、用戶主動關閉的時候等都需要不一樣的
    的頭像 發表于 02-14 10:35 ?842次閱讀
    什么是<b class='flag-5'>TCP</b><b class='flag-5'>狀態</b>轉移

    TCP協議和UDP協議的區別

    TCP/IP協議中有兩個具有代表性的傳輸層協議,分別是TCP協議和UDP協議。
    的頭像 發表于 03-21 10:39 ?928次閱讀

    什么是Socket連接?Socket與TCP連接的關系

    主機 A 的應用程序必須通過 Socket 建立連接才能與主機B的應用程序通信,而建立 Socket 連接需要底層 TCP/IP 協議來建立 TCP
    發表于 03-31 15:10 ?812次閱讀

    TCP狀態機設計與實現

    TCP狀態機是TCP連接的變化過程。TCP在三次握手和四次揮手的過程,就是一個TCP
    的頭像 發表于 04-21 11:47 ?1234次閱讀
    <b class='flag-5'>TCP</b><b class='flag-5'>狀態</b>機設計與實現

    TCP連接的建立與中止

    常重要的 。 TCP 連接的建立可以簡單地稱為三次握手,而連接的中止則可以稱為四次揮手。 建立連接 TCP/IP
    的頭像 發表于 10-08 16:52 ?519次閱讀

    如何理解HTTP協議是無狀態

    主要解決網絡路由和尋址問題,TCP 協議主要解決如何在 IP 層之上可靠的傳遞數據包,使在網絡上的另一端收到發端發出的所有包,并且順序與發出順序一致。TCP 有可靠,面向連接的特點。
    的頭像 發表于 11-11 15:46 ?934次閱讀
    如何理解HTTP<b class='flag-5'>協議</b>是無<b class='flag-5'>狀態</b>的
    亚洲欧美日韩精品久久_久久精品AⅤ无码中文_日本中文字幕有码在线播放_亚洲视频高清不卡在线观看
    <acronym id="s8ci2"><small id="s8ci2"></small></acronym>
    <rt id="s8ci2"></rt><rt id="s8ci2"><optgroup id="s8ci2"></optgroup></rt>
    <acronym id="s8ci2"></acronym>
    <acronym id="s8ci2"><center id="s8ci2"></center></acronym>