TCP/IP是互聯網的核心協議,也是大多數網絡應用的核心協議。就前面一段時間面試中問到的TCP/IP問題,這裏給出一個簡單的小結。
TCP由RFC79三、RFC112二、RFC132三、RFC200一、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 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
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(服務器)
若是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方向的通告窗口大小,來控制發送發的速率,可以在限速的基礎上,同時下降發送方的發送速率。