TCP/IP是網際網路的核心協議,也是大多數網路應用的核心協議。就前面一段時間面試中問到的TCP/IP問題,這裡給出一個簡單的小結。
TCP由RFC793、RFC1122、RFC1323、RFC2001、RFC2018以及RFC2581定義。
(1) TCP概述
a. TCP提供的是面向連線的全雙工服務。
TCP所有的資料會匹配到由源地址,目的地址,源埠,目的埠構成的一個TCP連線之上。TCP連線是一種需要建立的資源,可以通過之後會講到的握手機制來完成。UDP是一種基於盡力而為機制的協議,不存在UDP連線資源的建立,資源的處理往往由應用層協議代勞了。
b. TCP是提供的可靠服務。
TCP有確認機制來保證資料包的可靠到達,
TCP有CRC校驗機制來保證資料包的無差錯性,UDP的CRC是可選的,
TCP會重新排序亂序的資料包和丟棄重複的資料,
TCP能夠提供流量控制機制,使用滑動視窗演算法,
TCP能提供擁塞控制與恢復機制,存在多種TCP擁塞控制模型,
TCP能協商傳送的資料報文長度。
TCP報頭。
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Source Port | Destination Port |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Sequence Number |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Acknowledgment Number |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Data | |U|A|P|R|S|F| |
| Offset| Reserved |R|C|S|S|Y|I| Window |
| | |G|K|H|T|N|N| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Checksum | Urgent Pointer |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Options | Padding |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| data |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Source Port | Destination Port |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Sequence Number |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Acknowledgment Number |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Data | |U|A|P|R|S|F| |
| Offset| Reserved |R|C|S|S|Y|I| Window |
| | |G|K|H|T|N|N| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Checksum | Urgent Pointer |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Options | Padding |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| data |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
TCP Header Format
對於TCP頭的標記位,SYN標記只在三次握手(或四次握手)的時候的被置位,ACK標記會在握手之後所有的TCP報文中被置位。當然也有一些特殊情況,比如有些情況下RST報文不會置位ACK。
這些規則也許在配置複雜的ACL中有用。
(2) TCP協議棧的狀態機 (摘自RFC793)

a. TCP連線的建立。TCP連線的建立有主動開啟,被動開啟以及同時開啟三種情況。
三次握手比較清楚,要強調的是ISN,就是初始序列號的選擇問題,序列號是32位的,針對不同的OS,初始序列號的選擇往往也是有規律的。
TCP傳輸的最大報文長度也是在三次握手中協商的。具體說是在也僅在SYN報文中協商的。MSS = MTU - ip_header_len - tcp_header_len。MSS這裡也是為了防止分片,提高網路頻寬利用率。
TCP三次握手中,最後一個報文ACK,不需要再有額外的確認機制,如果這個ACK在網路中丟棄了,TCP協議棧也有其他的機制來處理。
除了三次握手,還有一種很特殊的應用情況,就是TCP兩端同時開啟的情況(傳送syn),這種情況沒有描述在上面的狀態機中。

舉例子來說,A通過源埠7777發起到B的目的埠8888的連線的同時,B也通過源埠8888發起對A的目的埠7777的TCP連線。
b. TCP連線的關閉
TCP連線的關閉也有主動關閉,被動關閉和同時關閉三種情況,這三種情況在上面的TCP狀態機中都有描述。
TCP連線的關閉需要報文四次互動,因為TCP是一個全雙工的服務,所以每個方向的連線都關閉後,TCP的連線才是完整的拆除。
狀態機中,主動關閉和同時關閉最後都會進入到一個TIME_WAITE狀態。針對TCP主動關閉的最後一個報文應該是ACK,確認對端的FIN報文。這個狀態的概念是該TCP連線的資源並沒有完全釋放,因為還要確保最後一個ACK報文能夠無誤的到達對端,確認對端的FIN,否則就仍然要重傳ACK。
這個等待的過程(或者資源沒有完全釋放的過程)需要等待2MSL時間(考慮報文一次往返)。MSL是最大報文生存時間,RFC793中為2分鐘,根據不同的TCP實現,一般是30s或者1分鐘。
所以在TIME_WAITE狀態內,該TCP連線所使用的埠和連線資源,不能被繼續使用。但是很多TCP實現並沒有這個限制,只要新的TCP連線所使用的ISN大於TIME_WAITE狀態TCP連線所使用的最後序號即可。實現中往往使用
new ISN = latest ISN in time_waite + 128000
IP報文的最大生存時間是TTL值,TCP報文的最大生存時間是MSL,二層上沒有報文最大生存時間的概念,存在風暴的可能。
(3) TCP的滑動窗和定時器
a. TCP的報文確認機制。
TCP使用的是滑動視窗機制來發送資料流,所以TCP協議允許連續傳送多個TCP分組而不等待對端的確認。所以傳送的分組資料和確認不是一對一的關係。
TCP中,對資料的確認往往是延遲的,一般情況是兩個TCP資料對應一個確認,在時延定時器沒有溢位的情況下。如果時延定時器溢位了,那麼自然也會發送確認報文。
但是,針對存在互動大量微小報文的TCP應用,過於頻繁的確認會導致網路利用率的低效,所以TCP支援一種Nagle演算法。
b. 延時定時器
當TCP收到報文時候,啟動延時定時器,比如200ms。
c. Nagle演算法
TCP連線上只能存在一個未被確認的微小報文(41位元組的TCP報文),在該確認到達前,TCP僅僅收集微小報文,當確認到達後,以一個分組的形式發出去。
當然,某些應用需要關閉Nagle演算法。
d. 滑動視窗機制
視窗合攏(左移):在收到對端資料後,自己確認了資料的正確性,這些資料會被儲存到緩衝區,等待應用程式獲取。但這時候因為已經確認了資料的正確性,需要向對方傳送確認響應ACK,又因為這些資料還沒有被應用程序取走,這時候便需要進行視窗合攏,緩衝區的視窗左邊緣向右滑動。注意響應的ACK序號是對方傳送資料包的序號,一個對方傳送的序號,可能因為視窗張開會被響應(ACK)多次。
視窗張開(右移):視窗收縮後,應用程序一旦從緩衝區中取出資料,TCP的滑動視窗需要進行擴張,這時候視窗的右邊緣向右擴張,實際上視窗這是一個環形緩衝區,視窗的右邊緣擴張會使用原來被應用程序取走內容的緩衝區。在視窗進行擴張後,需要使用ACK通知對端,這時候ACK的序號依然是上次確認收到包的序號。
視窗收縮,視窗的右邊緣向左滑動,稱為視窗收縮,Host Requirement RFC強烈建議不要這樣做,但TCP必須能夠在某一端產生這種情況時進行處理。
e. 重傳定時器
目的是為了獲得對端的確認報文。如果多次重傳仍然沒有獲得確認,則會發送復位報文RST。
這裡我們再來看一下TCP的三次握手。
A(發起端) ---> syn ---> B(伺服器)
A(發起端) <--- syn/ack <--- B(伺服器)
A(發起端) ---> ack ? B(伺服器)
如果TCP客戶端A的最後一個ACK丟失了,TCP伺服器B沒有收到,會是一種什麼情況?
這個時候A已經進入到了Establish狀態,然而B還只是Syn_Recev狀態,所以伺服器會重傳syn/ack報文,只到連線的最終建立。但是客戶端A已經到建立狀態了,所以A是有可能傳送TCP資料給伺服器B的。
所以TCP的兩端,最終狀態機是有可能不一致的。
後面會詳細講述重傳和擁塞控制機制。
f. 堅持定時器
由於TCP沒有對ACK的確認機制,所以當接收端視窗從0恢復到一定值的時候,如果接收端發給傳送端的ACK報文(標識視窗大小)丟失了,傳送端就永遠不知道接收端的視窗恢復情況了。
所以傳送端會定時傳送帶一個位元組的ACK給接收端,檢視接收端的確認報文中的視窗資訊。
g. 保活定時器
由於物理原因,處於IDLE狀態的TCP連線一端崩潰的時候,TCP有保活機制來判斷對端是否仍然工作。這個設計存在爭議,也許應用層應該實現該功能。RFC1122中有描述,保活定時器預設是關閉的。下面截取了一些RFC描述。
Implementors MAY include "keep-alives" in their TCP implementations, although this practice is not universally accepted. If keep-alives are included, the application MUST be able to turn them on or off for each TCP connection, and they MUST default to off.
(4) TCP擁塞控制演算法:慢啟動、擁塞避免、快速重傳和快速恢復
針對擁塞控制,主要有四種模型,即TCP TAHOE,TCP RENO,TCP NEWRENO和TCP SACK。TCP TAHOE模型是最早的TCP協議之一,它由Jacobson提出。
Jacobson觀察到,TCP報文段(TCP Segment)丟失有兩種原因,其一是報文段損壞,其二是網路阻塞,而當時的網路主要是有線網路,不易出現報文段損壞的情況,網路阻塞為報文段丟失的主要原因。針對這種情況,TCP TAHOE對原有協議進行了效能優化,其特點是,在正常情況下,通過重傳計時器是否超時和是否收到重複確認資訊(dupack)這兩種丟包監測機制來判斷是否發生丟包,以啟動擁塞控制策略;在擁塞控制的情況下,採用慢速啟動(Slow Start)演算法和“擁塞避免”(Congestion Avoidance)演算法來控制傳輸速率。 1990年出現的TCP Reno版本增加了“快速重傳 ”(Fast Retransmit)、“快速恢復”(Fast Recovery)演算法,避免了網路擁塞不嚴重時採用“慢啟動”演算法而造成過度減小發送視窗尺寸的現象,這樣TCP的擁塞控制就主要由這4個核心演算法組成。
a. 超時與重傳
RTT的計算與RTO的計算
b. 慢啟動和擁塞避免演算法
慢啟動演算法的目的是為了保證TCP傳送方傳送分組的速率應該匹配收到該分組確認報文的速率,這樣的設計能夠應用於低速鏈路的廣域網應用。為了實現慢啟動機制,為TCP連線增加了一個新的視窗,擁塞視窗cwnd,該視窗初始化為一個報文段(非一個位元組,而是一個TCP最大傳輸報文段大小,MSS)。這樣一個方向上的TCP連線有兩個視窗,一個是接收視窗用於接收方的流量控制,一個是擁塞視窗用於傳送方的流量控制。傳送方以這兩個視窗中的小值作為方式上限。
慢啟動演算法:指數演算法,cwnd預設為1,當收到一個ack確認時候,cwnd增加為2,當收到兩個ack確認時候,cwnd增加為4,接著8,...
擁塞避免演算法的目的,是為了防止中間路由器由於網路擁塞引起的資料包超時或者丟包。擁塞避免演算法需要用到兩個變數,一個是cwnd視窗大小,一個是ssthresh慢啟動閾值,對於一個給定的初始連線,cwnd為1,ssthresh為65535。
當擁塞發生(超時或者重複確認),當擁塞發生時候,ssthresh被設定為cwnd和接收視窗中小值的一半,如果是超時引起的擁塞,則cwnd設定為1。
擁塞避免演算法:如果cwnd大於ssthresh,每收到一個數據報文的確認,cwnd=cwnd+1/cwnd,cwnd視窗大小單位仍然是mss。
擁塞避免演算法其實是和慢啟動配合使用的。cwnd和ssthresh都是動態的值,雖然初始值為1和65535。
當真正擁塞發生的時候,如果是超時或重複ack引起的擁塞,ssthreash會置為cwnd和接收視窗大小的一半,cwnd會降為1,然後執行慢啟動演算法,直到cwnd大於ssthresh的時候,執行擁塞避免演算法;
在慢啟動演算法期間和擁塞避免演算法期間,TCP的傳送速率都是在增長的,只是一個是指數增長方式,一個是線性增長方式。
c . 快速重傳和快速恢復演算法
TCP連線中有兩種情況會引起重複的ack,一種是亂序報文,一種是丟包。
快速重傳:當傳送方收到三個重複的ack後,不會進入慢啟動狀態,而是立刻重傳丟失的報文。因為只有接收方收到新的報文段的時候,才會傳送重複的ack,這表明TCP連線上仍然有資料流動,所以應該避免使用慢啟動降速。
快速恢復:
第一步,當收到第三個重複的ack的時候,ssthresh設定為當前cwnd的一半,重傳丟失的報文。設定cwnd為ssthresh加上3倍的報文段大小(cwnd=cwnd/2 + 3)。
第二步,每收到一個重複的ack,cwnd增加1併發送一個分組。
第三步,當下一個確認新資料的ack到達的時候,設定cwnd為上面第一步中ssthresh值,這個ack應該是對重傳報文的確認,同時也是對丟包後面的中間報文的確認。
最後,在收到三個重複ack的情況下,速度減半。
快速重傳演算法首次出現在4.3BSD的Tahoe版本,快速恢復首次出現在4.3BSD的Reno版本,也稱之為Reno版的TCP擁塞控制演算法。
可以看出Reno的快速重傳演算法是針對一個包的重傳情況的,然而在實際中,一個重傳超時可能導致許多的資料包的重傳,因此當多個數據包從一個數據視窗中丟失時並且觸發快速重傳和快速恢復演算法時,問題就產生了。因此NewReno出現了,它在Reno快速恢復的基礎上稍加了修改,可以恢復一個視窗內多個包丟失的情況。具體來講就是:Reno在收到一個新的資料的ACK時就退出了快速恢復狀態了,而NewReno需要收到該視窗內所有資料包的確認後才會退出快速恢復狀態,從而更一步提高吞吐量。
SACK就是改變TCP的確認機制,最初的TCP只確認當前已連續收到的資料,SACK則把亂序等資訊會全部告訴對方,從而減少資料傳送方重傳的盲目性。比如說序號1,2,3,5,7的資料收到了,那麼普通的ACK只會確認序列號4,而SACK會把當前的5,7已經收到的資訊在SACK選項裡面告知對端,從而提高效能,當使用SACK的時候,NewReno演算法可以不使用,因為SACK本身攜帶的資訊就可以使得傳送方有足夠的資訊來知道需要重傳哪些包,而不需要重傳哪些包。
(5) TCP的應用
前幾天和公司做防火牆限速的同事聊天, 我們公司新的防火牆限速實現方案就用到了TCP視窗機制. 作所周知, QoS除了分類,測速,佇列還有排程一類的藉助硬體的演算法以外,在基於快取或者丟包的限速基礎上,最好還要降低TCP端到端的真正傳送的速率,否則容易引起TCP的一系列擁塞控制動作。我們軟體新的設計,就是通過修改ACK方向的通告視窗大小,來控制傳送發的速率,能夠在限速的基礎上,同時降低傳送方的傳送速率。
本文出自 “jasonccie” 部落格,請務必保留此出處http://jasonccie.blog.51cto.com/2143955/422966