天天看點

Socket TCP技術詳解

TCP

一、TCP資料包格式

首部格式:http://en.wikipedia.org/wiki/Transmission_Control_Protocol

建立和關閉連接配接時,SYN和FIN需要占一個序列号,其它的純ACK不占序列号。

Socket TCP技術詳解

僞首部計算:http://www.tcpipguide.com/free/t_TCPChecksumCalculationandtheTCPPseudoHeader-2.htm

TCP協定:http://www.networksorcery.com/enp/protocol/tcp.htm

Socket TCP技術詳解

選項:用來傳遞附加資訊給終點。

二、流量控制與可變視窗

Socket TCP技術詳解

三、TCP建立連接配接和關閉的三次握手

Socket TCP技術詳解

1.2 關閉一個 TCP 連接配接

TCP 連接配接建立起來後,就可以在兩個方向傳送資料流。當 TCP 的應用程序再沒有資料需要發送時,就發關閉指令。 TCP 通過發送控制位 FIN=1 的資料片來關閉本方資料流,但還可以繼續接收資料,直到對方關閉那個方向的資料流,連接配接就關閉。

TCP 協定使用修改的三次握手協定來關閉連接配接, 如圖 3-11 所示,即終止一個連接配接要經過 4 次握手。這是因為 TCP 的半關閉( half-close )造成的。由于一個 TCP 連接配接是全雙工(即資料在兩個方向上能同時傳遞),是以每個方向必須單獨地進行關閉。關閉的原則就是當一方完成它的資料發送任務後就能發送一個 FIN 來終止這個方向連接配接。當一端收到一個 FIN ,它必須通知應用層另一端已經終止了那個方向的資料傳送。發送 FIN 通常是應用層進行關閉的結果。

Socket TCP技術詳解

從一方的 TCP 來說,連接配接的關閉有三種情況:

? 本方啟動關閉

收到本方應用程序的關閉指令後, TCP 在發送完尚未處理的封包段後,發 FIN = 1 的封包段給對方,且 TCP 不再受理本方應用程序的資料發送。在 FIN 以前發送的資料位元組,包括 FIN ,都需要對方确認,否則要重傳。注意 FIN 也占一個順序号。一旦收到對方對 FIN 的确認以及對方的 FIN 封包段,本方 TCP 就對該 FIN 進行确認,在等待一段時間,然後關閉連接配接。等待是為了防止本方的确認封包丢失,避免對方的重傳封包幹擾新的連接配接。

? 對方啟動關閉

當 TCP 收到對方發來的 FIN 封包時,發 ACK 确認此 FIN 封包,并通知應用程序連接配接正在關閉。應用程序将以關閉指令響 應。 TCP 在發送完尚未處理的封包段後,發一個 FIN 封包給對方 TCP ,然後等待對方對 FIN 的确認,收到确認後關閉連接配接。若對方的确認未及時到達,在等待一段時間後也關閉連接配接。

? 雙方同時啟動關閉

連接配接雙方的應用程序同時發關閉指令,則雙方 TCP 在發送完尚未處理的封包段後,發送 FIN 封包。各方 TCP 在 FIN 前所發封包都得到确認後,發 ACK 确認它收到的 FIN 。各方在收到對方對 FIN 的确認後,同樣等待一段時間再關閉連接配接。這稱之為同時關閉( simultaneous close )。

1.3 TCP 狀态機

TCP 協定的操作可以使用一個具有 11 種狀态的有限狀态機( Finite State Machine )來表示,圖 3-12 描述了 TCP 的有限狀态機,圖中的圓角矩形表示狀态,箭頭表示狀态之間的轉換,各狀态的描述如表 3-2 所示。圖中用粗線表示用戶端主動和被動的伺服器端建立連接配接的正常過程:用戶端的狀态變遷用粗實線,伺服器端的狀态變遷用粗虛線。細線用于不常見的序列,如複位、同時打開、同時關閉等。圖中的每條狀态變換線上均标有“事件/動作”:事件是指使用者執行了系統調用( CONNECT 、 LISTEN 、 SEND 或 CLOSE )、收到一個封包段( SYN 、 FIN 、 ACK 或 RST )、或者是出現了超過兩倍最大的分組生命期的情況;動作是指發送一個封包段( SYN 、 FIN 或 ACK )或什麼也沒有(用“-”表示)。

Socket TCP技術詳解

1. 正常狀态轉換

我們用圖 3-13 來顯示在正常的 TCP 連接配接的建立與終止過程中,客戶與伺服器所經曆的不同狀态。讀者可以對照圖 3-12 來閱讀,使用圖 3-12 的狀态圖來跟蹤圖 3-13 的狀态變化過程,以便明白每個狀态的變化:

  • 伺服器端首先執行 LISTEN 原語進入被動打開狀态( LISTEN ),等待用戶端連接配接;
  • 當用戶端的一個應用程式發出 CONNECT 指令後,本地的 TCP 實體為其建立一個連接配接記錄并标記為 SYN SENT 狀态,然後給伺服器發送一個 SYN 封包段;
  • 伺服器收到一個 SYN 封包段,其 TCP 實體給用戶端發送确認 ACK 封包段同時發送一個 SYN 信号,進入 SYN RCVD 狀态;
  • 用戶端收到 SYN + ACK 封包段,其 TCP 實體給伺服器端發送出三次握手的最後一個 ACK 封包段,并轉換為 ESTABLISHED 狀态;
  • 伺服器端收到确認的 ACK 封包段,完成了三次握手,于是也進入 ESTABLISHED 狀态。

在此狀态下,雙方可以自由傳輸資料。當一個應用程式完成資料傳輸任務後,它需要關閉 TCP 連接配接。假設仍由用戶端發起主動關閉連接配接。

  • 用戶端執行 CLOSE 原語,本地的 TCP 實體發送一個 FIN 封包段并等待響應的确認(進入狀态 FIN WAIT 1 );
  • 伺服器收到一個 FIN 封包段,它确認用戶端的請求發回一個 ACK 封包段,進入 CLOSE WAIT 狀态;
  • 用戶端收到确認 ACK 封包段,就轉移到 FIN WAIT 2 狀态,此時連接配接在一個方向上就斷開了;
  • 伺服器端應用得到通告後,也執行 CLOSE 原語關閉另一個方向的連接配接,其本地 TCP 實體向用戶端發送一個 FIN 封包段,并進入 LAST ACK 狀态,等待最後一個 ACK 确認封包段;
  • 用戶端收到 FIN 封包段并确認,進入 TIMED WAIT 狀态,此時雙方連接配接均已經斷開,但 TCP 要等待一個 2 倍封包段最大生存時間 MSL ( Maximum Segment Lifetime ),確定該連接配接的所有分組全部消失,以防止出現确認丢失的情況。當定時器逾時後, TCP 删除該連接配接記錄,傳回到初始狀态( CLOSED )。
  • 伺服器收到最後一個确認 ACK 封包段,其 TCP 實體便釋放該連接配接,并删除連接配接記錄,傳回到初始狀态( CLOSED )。
Socket TCP技術詳解

1、建立連接配接協定(三次握手)

(1)用戶端發送一個帶SYN标志的TCP封包到伺服器。這是三次握手過程中的封包1。

(2) 伺服器端回應用戶端的,這是三次握手中的第2個封包,這個封包同時帶ACK标志和SYN标志。是以它表示對剛才用戶端SYN封包的回應;同時又标志SYN給用戶端,詢問用戶端是否準備好進行資料通訊。

(3) 客戶必須再次回應服務段一個ACK封包,這是封包段3。

2、連接配接終止協定(四次握手)

   由于TCP連接配接是全雙工的,是以每個方向都必須單獨進行關閉。這原則是當一方完成它的資料發送任務後就能發送一個FIN來終止這個方向的連接配接。收到一個 FIN隻意味着這一方向上沒有資料流動,一個TCP連接配接在收到一個FIN後仍能發送資料。首先進行關閉的一方将執行主動關閉,而另一方執行被動關閉。

 (1) TCP用戶端發送一個FIN,用來關閉客戶到伺服器的資料傳送(封包段4)。

 (2) 伺服器收到這個FIN,它發回一個ACK,确認序号為收到的序号加1(封包段5)。和SYN一樣,一個FIN将占用一個序号。

 (3) 伺服器關閉用戶端的連接配接,發送一個FIN給用戶端(封包段6)。

 (4) 客戶段發回ACK封包确認,并将确認序号設定為收到序号加1(封包段7)。

CLOSED: 這個沒什麼好說的了,表示初始狀态。

LISTEN: 這個也是非常容易了解的一個狀态,表示伺服器端的某個SOCKET處于監聽狀态,可以接受連接配接了。

SYN_RCVD: 這個狀态表示接受到了SYN封包,在正常情況下,這個狀态是伺服器端的SOCKET在建立TCP連接配接時的三次握手會話過程中的一個中間狀态,很短暫,基本上用netstat你是很難看到這種狀态的,除非你特意寫了一個用戶端測試程式,故意将三次TCP握手過程中最後一個ACK封包不予發送。是以這種狀态時,當收到用戶端的ACK封包後,它會進入到ESTABLISHED狀态。

SYN_SENT: 這個狀态與SYN_RCVD遙想呼應,當用戶端SOCKET執行CONNECT連接配接時,它首先發送SYN封包,是以也随即它會進入到了SYN_SENT狀态,并等待服務端的發送三次握手中的第2個封包。SYN_SENT狀态表示用戶端已發送SYN封包。

ESTABLISHED:這個容易了解了,表示連接配接已經建立了。

FIN_WAIT_1: 這個狀态要好好解釋一下,其實FIN_WAIT_1和FIN_WAIT_2狀态的真正含義都是表示等待對方的FIN封包。而這兩種狀态的差別是:FIN_WAIT_1狀态實際上是當SOCKET在ESTABLISHED狀态時,它想主動關閉連接配接,向對方發送了FIN封包,此時該SOCKET即進入到FIN_WAIT_1狀态。而當對方回應ACK封包後,則進入到FIN_WAIT_2狀态,當然在實際的正常情況下,無論對方何種情況下,都應該馬上回應ACK封包,是以FIN_WAIT_1狀态一般是比較難見到的,而FIN_WAIT_2狀态還有時常常可以用netstat看到。

FIN_WAIT_2:上面已經詳細解釋了這種狀态,實際上FIN_WAIT_2狀态下的SOCKET,表示半連接配接,也即有一方要求close連接配接,但另外還告訴對方,我暫時還有點資料需要傳送給你,稍後再關閉連接配接。

TIME_WAIT: 表示收到了對方的FIN封包,并發送出了ACK封包,就等2MSL後即可回到CLOSED可用狀态了。如果FIN_WAIT_1狀态下,收到了對方同時帶 FIN标志和ACK标志的封包時,可以直接進入到TIME_WAIT狀态,而無須經過FIN_WAIT_2狀态。

CLOSING: 這種狀态比較特殊,實際情況中應該是很少見,屬于一種比較罕見的例外狀态。正常情況下,當你發送FIN封包後,按理來說是應該先收到(或同時收到)對方的 ACK封包,再收到對方的FIN封包。但是CLOSING狀态表示你發送FIN封包後,并沒有收到對方的ACK封包,反而卻也收到了對方的FIN封包。什麼情況下會出現此種情況呢?其實細想一下,也不難得出結論:那就是如果雙方幾乎在同時close一個SOCKET的話,那麼就出現了雙方同時發送FIN封包的情況,也即會出現CLOSING狀态,表示雙方都正在關閉SOCKET連接配接。

CLOSE_WAIT: 這種狀态的含義其實是表示在等待關閉。怎麼了解呢?當對方close一個SOCKET後發送FIN封包給自己,你系統毫無疑問地會回應一個ACK封包給對方,此時則進入到CLOSE_WAIT狀态。接下來呢,實際上你真正需要考慮的事情是察看你是否還有資料發送給對方,如果沒有的話,那麼你也就可以 close這個SOCKET,發送FIN封包給對方,也即關閉連接配接。是以你在CLOSE_WAIT狀态下,需要完成的事情是等待你去關閉連接配接。

LAST_ACK: 這個狀态還是比較容易好了解的,它是被動關閉一方在發送FIN封包後,最後等待對方的ACK封包。當收到ACK封包後,也即可以進入到CLOSED可用狀态了。

最後有2個問題的回答,我自己分析後的結論(不一定保證100%正确)

1、 為什麼建立連接配接協定是三次握手,而關閉連接配接卻是四次握手呢?

這是因為服務端的LISTEN狀态下的SOCKET當收到SYN封包的建連請求後,它可以把ACK和SYN(ACK起應答作用,而SYN起同步作用)放在一個封包裡來發送。但關閉連接配接時,當收到對方的FIN封包通知時,它僅僅表示對方沒有資料發送給你了;但未必你所有的資料都全部發送給對方了,是以你可以未必會馬上會關閉SOCKET,也即你可能還需要發送一些資料給對方之後,再發送FIN封包給對方來表示你同意現在可以關閉連接配接了,是以它這裡的 ACK封包和FIN封包多數情況下都是分開發送的。

2、 為什麼TIME_WAIT狀态還需要等2MSL後才能傳回到CLOSED狀态?

這是因為:雖然雙方都同意關閉連接配接了,而且握手的4個封包也都協調和發送完畢,按理可以直接回到CLOSED狀态(就好比從SYN_SEND狀态到 ESTABLISH狀态那樣);但是因為我們必須要假想網絡是不可靠的,你無法保證你最後發送的ACK封包會一定被對方收到,是以對方處于 LAST_ACK狀态下的SOCKET可能會因為逾時未收到ACK封包,而重發FIN封包,是以這個TIME_WAIT狀态的作用就是用來重發可能丢失的 ACK封包,并保證于此。

注意:在主動關閉方發送的最後一個 ack(fin) ,有可能丢失,這時被動方會重新發fin, 如果這時主動方處于 CLOSED 狀态 ,就會響應 rst 而不是 ack。是以主動方要處于 TIME_WAIT 狀态,而不能是 CLOSED 。

一般來說,tcp正常關閉需要四個包。比如a和b關閉連接配接,a先給b發一個fin,b會進行确認ack,然後b也會發出fin,當a接受到這個 fin,并發出最後一個ack後,就會處于time_wait狀态。這個時間長短跟作業系統有關,一般會在1-4分鐘,也就是兩倍的資料包(2msl)最大生存時間。TCP主動關閉方采用TIME_WAIT主要是為了實作終止 TCP全雙工連接配接的可靠性及允許老的重複分節在網絡中消逝,等過了2msl(大約1~4分鐘)後TIME_WAIT就會消失。

TIME_WAIT狀态的目的是為了防止最後a發出的ack丢失,讓b處于LAST_ACK逾時重發FIN

是以說,主動發起關閉連接配接的一方會進入time_wait狀态,這個時候,程序所占用的端口号不能被釋放。除非在你的程式中用setsockopt設定端口可重用(SOCK_REUSE)的選項,但這不是所有作業系統都支援的

解決TIME_WAIT的辦法主要有以下幾種:

1、修改LINGER值,縮短關閉時間

LINGER lingerStruct;

lingerStruct.l_onoff = 1;

lingerStruct.l_linger = 0;

setsockopt(m_socket,SOL_SOCKET,SO_LINGER,(char *)&lingerStruct,sizeof(lingerStruct));

不過這種辦法不是很安全的,不過現在網絡都很好啦,不會有問題的。

2、修改系統資料庫

[HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Services\Tcpip\Parameters]

"TcpTimedWaitDelay"=dword:00000005

這個值好像是300秒到30秒之間,改成30秒後你會發現TIME_WAIT很快就會消失了。

3、禁用LINGER

//如果你使用的是Socket API,可以這樣

BOOL bDontLinger=FALSE;

setsockopt(m_socket,SOL_SOCKET,SO_DONTLINGER,(LPCTSTR)&bDontLinger,sizeof(BOOL));

closesocket(s);

//如果你使用的是CAsyncSocket,需要響應的修改,例如禁用LINGER可以這樣

BOOL bDontLinger=FALSE;

m_socket->SetSockOpt(SO_DONTLINGER,(const char *)&bDontLinger,sizeof(bDontLinger),SOL_SOCKET);

m_socket->Close();

4、用戶端可以不BIND(),這樣,即使斷開連接配接後再次連接配接,SOCKET将使用不同的端口(1025-5000),

等幾分鐘後,原有的端口就會自動關閉。

關閉BITCOMET後系統出現的幾個TCP狀态

Time_wait狀态

TIME_WAIT狀态是TCP協定中最容易被誤解的特性之一。這很可能是因為最初的規約

RFC793中隻對該狀态做了扼要的解釋,盡管後來的RFC,如RFC1185,對TIMEWAIT狀态

做了詳細說明。設定TIMEWAIT狀态的原因主要有兩個:

1)它實作了全雙工的連接配接關閉。

2)它使過時的重複封包段廢棄。

下面我們對這兩個原因做進一步的讨論。

TCP全雙工關閉

圖 4-4給出了一般情況下連接配接關閉時的封包段交換過程。圖中還給出了連接配接狀态的變遷和在伺服器端測得的RTT值。圖中左側為用戶端,右側為伺服器端。要注意,其中的任何一端都可以主動關閉連接配接,但一般都是用戶端執行主動關閉。下面我們來看看最後一個封包段(最後一個ACK)丢失時會發生什麼現象。這個現象就給在圖4-5中。

Socket TCP技術詳解
Socket TCP技術詳解

由于沒有收到客戶的最後一個确認,伺服器會逾時,并重傳最後一個FIN封包段。我們特意把伺服器的重傳逾時(RTO)給得比圖4-4中的RTT大,這是因為RTO的取值是估計的RTT值加上若幹倍的RTT方差(卷2的第25章詳細論述了如何測量RTT值以及如何計算RTO)。處理最後一個FIN封包段丢失的方法也是一樣:伺服器在逾時後繼續重傳FIN。

這個例子說明了為什麼TIMEWAIT狀态要出現在執行主動關閉的一端:該端發出最後一個ACK封包段,而如果這個ACK丢失或是最後一個FIN丢失了,那麼另一端将逾時并重傳最後的FIN封包段。是以,在主動關閉的一端保留連接配接的狀态資訊,這樣它才能在需要的時候重傳最後的确認封包段;否則,它收到最後的FIN封包段後就無法重傳最後一個ACK,而隻能發出RST封包段,進而造成虛假的錯誤資訊。圖4-5還說明了另一個問題,即如果重傳的FIN封包段在用戶端主機仍處于TIMEWAIT狀态的時候到達,那麼不僅僅最後一個ACK會重傳,而且TIMEWAIT狀态也重新開始。這時,TIMEWAIT狀态的持續時間定時器重置為2倍的封包段最大生存時間,即2MSL。

問題是,執行了主動關閉的一端,為了處理圖4-5所示的情況,需要在TIMEWAIT狀态保持多長的時間?這取決于對端的RTO 值;而RTO又取決于該連接配接的RTT值。RFC1185中指出RTT的值超過1分鐘不太可能。但實際上RTO卻很有可能長達1分鐘:在廣域網發生擁塞期間時就會有這種情形。這是因為擁塞會導緻多次重傳的封包段仍然丢失,進而使TCP的指數退避算法生效,RTO的值越來越大。過時的重複封包段失效設定 TIMEWAIT狀态的第二個原因是為讓過時的重複封包段失效。TCP協定的運作基于一個基本的假設,即:互連網上的每一個IP資料報都有一個有限的生存期限,這個期限值是由IP首部的TTL(生存時間)字段決定的。每一台路由器在轉發IP資料報時都要将其TTL值減1;但如果該IP資料報在路由器中等待的時間超過1秒,那就要把TTL的值減去等待的時間。實際上,很少有IP資料報在路由器中的等待時間超過1秒的,因而每個路由器通常都是把TTL的值減 1(RFC1812[Baker1995])的5.3.1節)。由于TTL字段的長度是8比特,是以每個IP資料報所能經曆的轉發次數至多為255。

RFC793把該限制定義為封包段最大生存時間MSL,并規定其值為2分鐘。該RFC同時指出,将封包段最大生存時間MSL定義為2分鐘是一個工程上的選擇,其值可以根據經驗進行修改。最後,RFC793規定TIMEWAIT狀态的持續時間為MSL的2倍。

圖4-6給出的是一個連接配接關閉後在TIME_WAIT狀态保持了2倍封包段最大生存時間(2MSL),然後發起建立新的連接配接替身。

Socket TCP技術詳解

由于該連接配接的新的替身必須在前一個連接配接替身關閉2MSL之後才能再次發起,而且由于前一個連接配接替身的過時重複封包段在TIMEWAIT狀态的第1個封包段最大生存時間裡就已經消失,是以我們可以保證前一次連接配接的過時重複封包段不會在新的連接配接中出現,也就不可能被誤認為是第二次連接配接的封包段。

TIMEWAIT狀态的自結束

RFC793中規定,處于TIMEWAIT狀态的連接配接在收到RST後變遷到CLOSED狀态,這稱

為TIME_WAIT狀态的自結束。RFC1337[Braden1992a]中則建議不要用RST過早地結束TIMEWAIT狀态。

主要有兩個原因

1。防止上一次連接配接中的包,迷路後重新出現,影響新連接配接(經過2MSL,上一次連接配接中所有的重複包都會消失)

2。可靠的關閉TCP連接配接在主動關閉方發送的最後一個 ack(fin) ,有可能丢失,這時被動方會重新發fin, 如果這時主動方處于 CLOSED 狀态 ,就會響應 rst 而不是 ack。是以主動方要處于 TIME_WAIT 狀态,而不能是 CLOSED 。

四、TCP确認與重傳機制

1累計确認 确認針對資料流中的資料,不是封包段

優點:無二義性,确認丢失不一定會引起重傳。

執行個體:A向B發送資料

1)設發送端視窗為{1201,2000};分2封包段傳送;

2)第一個封包段{1201,1600},第二個封包段{1601,2000};

B首先發回确認{1601},再發回确認{2001}

3)若A在定時器未到之前隻收到确認{2001},而{1601}丢失,則A不會重傳 {1201,1600}封包段;

Socket TCP技術詳解
Socket TCP技術詳解

2逾時與重傳

(1)定時器時間是由封包段的平均往返時間确定的

TimeOut=B*RTT(Round Trip Time)

(2) RTT的确定

某個封包段的 RTT=收到确認的時間-發送的時間(但是在實際過程中很難測出)

平均RTT=a*Old_RTT+(1-a)*New_RTT

a接近于1的含義:時間不敏感型

a接近于0的含義:時間敏感型

(3) RTT的不精确性

當重傳一個資料時,此時收到的确認封包,主機無法判斷出到底是對哪個封包的确認。

Socket TCP技術詳解

若認為是對原封包确認,增加了對RTT的估值,降低了效率,該重傳也沒重傳;若認為是對重傳封包的确認,RTT值變小,導緻正确傳送的資料也重傳,這就造成了不必要的重傳。

RTT的Karn補償方法

1) 忽略重傳封包,不用估算其RTT。

2) 當然就不用重傳封包的RTT來修正定時器的Timeout值。

3) 發生重傳時,增加Timeout

New_Timeout=r*Old_Timeout

4) Timeout不會無限增加,受上限限制,無重傳時,重新估計

Timeout值。實驗證明r=2網絡就能很好地工作了,小于2會導緻時限不穩定。

Karn算法:計算往返估計值時,忽略對應于重傳封包段的樣本,但是要使用補償政策,對一個重傳分組的許多後續分組,其定時時限均不變,直到獲得一個新的有效樣本時再修改時限值。

五、擁塞控制

擁塞(Congestion)

擁塞是因為封包在一個或多個路由器發生過栽而導緻嚴重延遲。一旦發生擁塞,延遲顯著增加,路由器開始将封包緩存起來,直到它有能力将其發送出去。最壞情況下,到達擁塞路由器的封包持續增加到超過器存儲能力,此時路由器就丢棄 (discard)封包。

TCP擁塞控制政策

(1)擁塞發生時,封包傳送時延增加、使得定時器逾時。在擁塞情況下,定時器逾時重傳會加重擁塞至網絡癱瘓。

(2)TCP擁塞處理政策

1)假設封包丢棄大多數都是擁塞引起的

2)加速遞減機制:發生擁塞時的抑制機制:指數遞減

3)慢啟動:擁塞解除時的恢複機制:指數增加

4)擁塞避免:避免慢起動視窗增長過大的機制恢複:指數增加-〉線性增加

路由器封包丢棄政策與擁塞控制

(1) 早期路由器使用尾部每端丢棄(TAIL-DROP) 政策來處理擁塞:

若封包到來時隊列已滿,則丢棄該封包。是以,路由器丢棄隊列的尾部。尾部丢棄帶來的問題就是全局性同步(Global synchronization),路由器不是丢棄一個連接配接的n個封包段,而是丢棄n個連接配接的各一個封包段,造成所有 N 個連接配接的發送端同時進入慢起動。慢啟動會顯著降低流量。

(2) 解決辦法

随機丢棄政策 (Random Early Discard、RED)

1)RED 盡可能避免末端丢棄

2)RED 用兩個門檻值分别表示隊列的位置:Tmin 與 Tmax

圖17

RED 基本操作:若目前隊列的封包數小于 Tmin,則把新封包加入隊列中。若目前隊列的封包數大于 Tmax,丢棄新封包。若目前隊列的封包數在Tmin 與 Tmax之間,根據一機率P來随機決定是否丢棄此封包。

選擇Tmin和Tmax時注意:Tmin 必須大到確定輸對外連結路有高使用率,Tmax - Tmin必須大于在一個tcp往返時間内增加的隊列大小(例如 Tmax 為 Tmin 的兩倍)。

利用平均隊列長度Avg來确定p

這種政策可以在隊列滿時p較大;在突發情況下不會丢棄資料。權重平均隊列大小為avg=(1–γ)*Old_avg+γ*Current_queue_size

其中γ介于0和1之間。若γ足夠小(例如0.002),平均值會呈長期穩定的趨勢(亦即old_avg),進而不受突發通訊的影響。

六、糊塗視窗綜合症與短分組

主要特征是發送短的封包段,造成帶寬浪費。

當收發兩端的應用程式以不同的速率工作時,軟體性能會出現嚴重的問題。如接收方每次僅接收一個八位組,發送方能夠快速地生成資料,發送方TCP軟體所傳輸的封包段就能很快填滿整個緩沖區。接收方每讀取一個八位組資料就通告發送方一個八位組的視窗。如此這樣,每次就隻有一個八位組視窗正常工作,而在傳輸過程中加上各種協定資料的首部,TCP20位元組,IP20位元組,有效資料比1/41,加上實體幀頭比例更小。這就浪費了網絡帶寬。

1、接收方糊塗視窗的避免

啟發式政策:接收方為目前可用的視窗值維護一個内部記錄,并在視窗大小顯著增加之前推遲發送增加視窗的通告。視窗大小至少為緩沖區空間的一半或最長封包段資料的位元組數時TCP認為是顯著的。

通俗點說就是當接收方緩沖區達到飽和時通告一個0視窗,當接收應用程式取到至少占緩沖區一半的八位組資料時再發送更收的視窗通告。

推遲确認技術:就是在收到一個封包段後并不馬上發送确認。

優點:降低通信量提高吞吐率。為了使發送方正确估計RTT,至少每隔一個封包段要進行正常的确認。 确認時間最多500ms.

缺點:(1)當接收方确認遲延太大時,發送方會進行封包段的重傳,不必要的重傳浪費了網絡帶寬,降低了吞吐率,還加大了收發雙方的計算負載。

(2)确認遲延混亂了往返時間的估計值。

2、發送方對糊塗視窗的避免

目的是防止發送短封包段,是以發送方在發送封包段之前必須延遲,以積聚合理的資料量,這就叫組塊技術(clumping)。

政策:在一個連接配接上已經傳輸的資料還未被确認的情況下,發送方的應用程式又生成了後續資料,并照常将資料送到緩沖區中。但并不發送後續封包段,而是等到資料足以填滿一個最大長度的封包段之後再把緩沖區中的資料發送出去。(Nagle算法)

TCP的計時器.

重傳計時器---若在計時器截止時間之前收到了對此資料段的确認,則撤銷此計時器;若在收到對此特定資料段的确認之前計時器截止期到,則重傳此資料段,并将計時器複位.設一次資料傳輸的往返時間為RTT,則重傳時間=2RRT....資料傳輸的往返時間RTT是利用發送端發送資料時産生的時間戳和目前時間計算得來的.時間戳存放在TCP資料首部的可選項中.

堅持計時器---假定接收端的TCP宣布了視窗大小為0,發送端的TCP将停止傳送資料段,直到接收端的TCP 發送确認并宣布一個非0的視窗大小為止.但應該注意一點的是,在TCP中對确認是不需要确認的.若确認丢失,接收端的TCP就認為它完成任務了,并等待發送端的TCP發送更多的資料段.發送端的TCP由于沒有收到确認,就等等對方發送确認來通知視窗的大小.雙方進入了死鎖等待的情況.為了解開這個死鎖,TCP為每個連接配接使用一個堅持計時器.當發送端收到一個視窗大小為0的确認時,就啟動堅持計時器.當堅持計時器期限到時,發送端的TCP就發送一個特殊的資料段,稱為探測資料段,這個資料段隻有一個位元組的資料.它有一個序号,但它的序号永遠不需要确認,它隻是提醒接收端的TCP:确認已經丢失,必須重傳.堅持計時器的時間值設定是重傳時間的數值.但若沒有收到接收端來的響應.就需要發送另一個探測資料段,并将堅持計時器的值加倍和複位,直到這個值增大到極限值(通常是60s)為止.在此之後,如果還沒有得到響應,發送端每隔60s就發送一個探測資料段,直到視窗重新打開.

保活計時器---用來防止在兩個TCP之間的連接配接長時間空閑.假定客戶打開了到伺服器的連接配接,傳送了一些資料, 然後就保持沉默了,也許這個客戶出現了故障.在這種情況下,這個連接配接将永遠地處于打開狀态,白白浪費了伺服器寶貴的資源...是以每當伺服器收到客戶的資訊就将保活計時器複位.逾時時間通常設為2小時.若伺服器過了2小時還沒有收到客戶的資訊,它就發送探測資料段.若發送10個探測資料段(每個相隔 75s)還沒有響應,就假定客戶出了故障,因而就中止該連接配接.

時間等待計時器---TCP協定在斷開的時候,如果A發送完最後一個ACK就立即關閉連接配接,而這時,如果這個ACK資料段丢失了,B無法判斷是FIN丢失還是ACK丢失,是以B會重傳FIN資料段,而此時A已經關閉了連接配接,B永遠也無法收到A的ACK字段了.

是以TCP協定設定了一個時間等待計時器,A在發送了最後一個ACK封包後,并不立即關閉連接配接,而是經過一個時間等待計時器的時間再關閉.這個時間可以保證 A能收到重複的FIN資料段...由于此時資料連接配接已經完成了建立和斷開的生存周期,是以資料段的壽命期已經知道.時間等待計時器的值通常設定為一個資料段壽命期的兩倍..

擁塞控制算法

 Reno是目前應用最廣泛且較為成熟的算法。該算法所包含的慢啟動、擁塞避免和快速重傳、快速恢複機制,是現有的衆多算法的基礎。

1.慢啟動與擁塞避免

TCP發送端采用慢啟動和擁塞避免算法來控制向網絡輸送的資料量。為了實作這些算法,必須向TCP每個連接配接狀态加入3個參量:

丢失視窗(lw):丢失視窗是在一個TCP根據它的重傳定時器檢測到了資料丢失之後,擁塞視窗的尺寸。

重新開機視窗(rw):重新開機視窗是TCP在一段閑置期之後重新開始傳送後擁塞視窗的尺寸(如

果使用慢啟動算法;

  (1)擁塞視窗(cwnd),如前所述,它是對發送端收到确認(ACK)之前能向網絡傳送的最大資料量的一個發送端的限制。

  (2)接收端通知視窗(rwnd),它是對未完成資料量的接收端的限制,cwnd和rwnd的最小值決定了資料傳送。

  (3)慢啟動閥值(ssthresh),被用來确定是用慢啟動還是用擁塞避免算法來控制資料傳送,具體用法如下:當 cwnd<ssthresh時使用慢啟動算法;cwnd>ssthresh時使用擁塞避免算法;當cwnd=ssthresh時,發送端既可以使用慢啟動也可以使用擁塞避免。ssthresh的初始值可以任意大(比如,一些實作中使用接收端通知視窗的尺寸),但是一旦對擁塞響應之後,其大小可能會被減小。

  在不清楚網絡環境的情況下向網絡傳送資料,要求TCP緩慢地探測網絡以确定可用帶寬,以避免突然傳送大量資料而使網絡擁塞。為達此目的,在傳送開始時,采用了慢啟動機制,這個機制在修複了由重發定時器探測到的資料丢失之後也被采用。

  首先要确定的是cwnd的初始值IW(初始視窗大小),這裡規定它必須小于或等于2*SMSS位元組而且不能大于兩個資料段。

  在慢啟動期間,每收到一個新的ACK,cwnd最多增長1。直到cwnd超過ssthresh或者檢測到擁塞時,停止執行慢啟動算法,轉入擁塞避免階段。在擁塞避免期間,cwnd在每個ACK以1/cwnd(或每個RTT增加SMISS個位元組)的速度遞增。擁塞避免算法一直保持直到檢測出擁塞。等式(5.1.1)給出了一個在擁塞避免期間用來修正cwnd值的公式:

  cwnd+=1/cwnd (5.1.1)

  每收到一個非重複的ACK都采用等式(5.1.1)來調整cwnd。等式(5.1.1)用于近似擁塞避免算法的增長。

在實作中,在擁塞避免期間常用公式:

cwnd+=SMSS*SMSS/cwnd

來修正cwnd的值,當SMSS*SMSS/cwnd<1時,cwnd+=1。

  另一種改進的方案是每當新的ACK到來時記下被新确認的位元組數,然後cwnd就可增加相應位元組數,這個增加的數目最多可達到SMSS位元組。

  一旦TCP發送端使用重傳定時器檢測到包丢失時,ssthresh的值就如下設定:

  Ssthresh=max(FlightSize/2,2*SMSS) (5.1.2)

  式中,Filght Size是已發送但未收到ACK的資料的大小。

  在重發了丢失的資料段之後,cwnd必須被設定成LW(丢失視窗),它等于一個滿尺寸資料段的大小。再發丢失的資料段之後,發送端起用慢啟動算法增長視窗直到該視窗大小增長到等于新設定的ssthresh值之後,又采用擁塞避免算法了。

2.快速重傳與快速恢複

  當接收端收到一個失序的資料報時,會立即發回一個重複ACK,這個ACK的目的是告知發送端收到一個失序的資料報并說明其所期望的接受序号。從發送端的角度看,重複ACK可能是許多網絡問題引起的。首先,它們有可能是因為包丢失而引起。在此情況下,在此資料段之後的所有資料段都會觸發重複 ACK。其次,重複ACK可能是由于網絡對資料段的重新排序引起的。最後,重複ACK有可能是ACK或資料段被網絡複制所引起的。此外,當接收端部分或完整地填補了序号空缺應立即發送一個ACK,這樣可以更及時地通知發送端,使其迅速從重發狀态中恢複過來。快速重傳算法如圖5-2所示。

  TCP發送端應該使用快速重傳算法來探測或者修複資料丢失,在收到3個重複ACK(即連續的4個相同的ACK,标志着1個資料段已丢失) 時,TCP不等重傳定時器逾時就立即重傳看來已丢失的資料段。此後起用快速恢複算法來進行新的資料傳輸,直到1個非重複 ACK到達。

下面是快速傳送/快速恢複算法的實作:

(1)當第二個重複ACK收到時,ssthresh根據等式(5.1.2)設值。即

ssthresh=max(FlightSize/2,2*SMSS)

Filght Size是已發送但未收到ACK的資料的大小。

(2)重傳丢失的資料段并将cwnd的值設定為ssthresh+3*SMSS,稱之為給擁塞視窗“充氣”。

cwnd=ssthresh+3*SMSS

(3)此後對每個接收到一個重複ACK,将cwnd增大SMSS位元組,這将人為地擴充擁塞視窗用以反映已經離開網絡的附加資料段。

  (4)如果cwnd和接收端的通知視窗值允許的話,發送一個資料段。

  (5)當下一個确認新資料的ACK到達時,設定cwnd值為ssthresh(步驟1設定的值),這稱作給視窗“放氣”。這個ACK必須是步驟 1觸發的重發引起的确認,重發之後一個RTT(在接收端有次序紊亂的資料段的情況下,它可能一會兒就到達)。另外,此ACK應該确認丢失資料段和第二個重複ACK期間的資料段,如果它們一個也沒有丢失的話。

Reno算法的性能分析

  從Reno運作機制中很容易看出,為了維持一個動态平衡,必須周期性地産生一定量的丢失,再加上AIMD機制--減少快,增長慢,尤其是在大視窗環境下,由于一個資料報的丢失所帶來的視窗縮小要花費很長的時間來恢複,這樣,帶寬使用率不可能很高且随着網絡的鍊路帶寬不斷提升,這種弊端将越來越明顯。

公平性方面,根據統計資料,Reno的公平性還是得到了相當的肯定,它能夠在較大的網絡範圍内理想地維持公平性原則。

快速重傳與快速恢複算法

在收到一個失序的封包段時, T C P立即需要産生一個A C K(一個重複的A C K)。這個重複的A C K不應該被遲延。該重複的A C K的目的在于讓對方知道收到一個失序的封包段,并告訴對方自己希望收到的序号。

由于我們不知道一個重複的A C K是由一個丢失的封包段引起的,還是由于僅僅出現了幾個封包段的重新排序,是以我們等待少量重複的A C K到來。假如這隻是一些封包段的重新排序,則在重新排序的封包段被處理并産生一個新的A C K之前,隻可能産生1 ~ 2個重複的A C K。如果一連串收到3個或3個以上的重複A C K,就非常可能是一個封包段丢失了。于是我們就重傳丢失的資料封包段,而無需等待逾時定時器溢出。這就是快速重傳算法。接下來執行的不是慢啟動算法而是擁塞避免算法。這就是快速恢複算法。

沒有執行慢啟動的原因是由于收到重複的A C K不僅僅告訴我們一個分組丢失了。由于接收方隻有在收到另一個封包段時才會産生重複的A C K,而該封包段已經離開了網絡并進入了接收方的緩存。也就是說,在收發兩端之間仍然有流動的資料,而我們不想執行慢啟動來突然減少資料流。這個算法通常按如下過程進行實作:

1) 當收到第3個重複的A C K時,将s s t h re s h設定為目前擁塞視窗c w n d的一半。重傳丢失的封包段。設定c w n d為s s t h re s h加上3倍的封包段大小。

2) 每次收到另一個重複的A C K時, c w n d增加1個封包段大小并發送1個分組(如果新的c w n d允許發送)。

3) 當下一個确認新資料的A C K到達時,設定c w n d為s s t h re s h(在第1步中設定的值)。這個A C K應該是在進行重傳後的一個往返時間内對步驟1中重傳的确認。另外,這個A C K也應該是對丢失的分組和收到的第1個重複的A C K之間的所有中間封包段的确認。這一步采用的是擁塞避免,因為當分組丢失時我們将目前的速率減半。