一、報文格式

其中幾個欄位比較重要:

source/destination port:源埠、目的埠

sequence number:包的序號,用於保證包不亂序

acknowledge number:用於確認收到,確保不丟包

window:滑動窗口,用於流控

TCP flag:包的類型,用於實現狀態機,具體動作有:SYN(synchronous建立聯機) ACK(acknowledgement 確認) PSH(push傳送) FIN(finish結束) RST(reset重置) URG(urgent緊急)

二、握手與揮手

1.三次握手

第一次握手:主機A發送位碼為syn=1,隨機產生seq number=1234567的數據包到伺服器,主機B由SYN=1知道,A要求建立聯機;

第二次握手:主機B收到請求後要確認聯機信息,向A發送ack number=(主機A的seq+1),syn=1,ack=1,隨機產生seq=7654321的包

第三次握手:主機A收到後檢查ack number是否正確,即第一次發送的seq number+1,以及位碼ack是否為1,若正確,主機A會再發送ack number=(主機B的seq+1),ack=1,主機B收到後確認seq值與ack=1則連接建立成功。

完成三次握手,主機A與主機B開始傳送數據。

實例:

IP 192.168.1.116.3337 > 192.168.1.123.7788: S 3626544836:3626544836

IP 192.168.1.123.7788 > 192.168.1.116.3337: S 1739326486:1739326486 ack 3626544837

IP 192.168.1.116.3337 > 192.168.1.123.7788: ack 1739326487,ack 1

第一次握手:192.168.1.116發送位碼syn=1,隨機產生seq number=3626544836的數據包到192.168.1.123,192.168.1.123由SYN=1知道192.168.1.116要求建立聯機;

第二次握手:192.168.1.123收到請求後要確認聯機信息,向192.168.1.116發送ack number=3626544837,syn=1,ack=1,隨機產生seq=1739326486的包;

第三次握手:192.168.1.116收到後檢查ack number是否正確,即第一次發送的seq number+1,以及位碼ack是否為1,若正確,192.168.1.116會再發送ack number=1739326487,ack=1,192.168.1.123收到後確認seq=seq+1,ack=1則連接建立成功。

2.四次揮手

由於TCP連接是全雙工的,因此每個方向都必須單獨進行關閉。這個原則是當一方完成它的數據發送任務後就能發送一個FIN來終止這個方向的連接。收到一個 FIN只意味著這一方向上沒有數據流動,一個TCP連接在收到一個FIN後仍能發送數據。首先進行關閉的一方將執行主動關閉,而另一方執行被動關閉。

(1)客戶端A發送一個FIN,用來關閉客戶A到伺服器B的數據傳送。

(2)伺服器B收到這個FIN,它發回一個ACK,確認序號為收到的序號加1。和SYN一樣,一個FIN將佔用一個序號。

(3)伺服器B關閉與客戶端A的連接,發送一個FIN給客戶端A。

(4)客戶端A發回ACK報文確認,並將確認序號設置為收到序號加1。

需要注意的問題:

(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報文。

(3)SYN Flood攻擊的問題

如果server端接收到了client端發的syn後 回了syn-ack後 client掉線了,server端沒有收到client回復的ack,那麼這個連接就處於一個中間狀態,即沒有成功,也沒有失敗。linux下,server端會重發syn-ack五次,第五次也超時的話,tcp才會斷開連接。那麼如果有惡意的client端給伺服器發送一個syn後就下線,大量的這種報文會造成伺服器的syn連接的隊列耗盡,讓正常的請求也不能處理。linux下,當syn隊列打滿後,會構造一個特別的sequence number發回去,用來判別誰是攻擊者,誰是正常的連接。

三、TCP重傳機制

因為TCP要保證所有數據包都可達,所以必須要有重傳機制。

但是server端發送給client的ack確認只會確認最後一個連續的包,比如client端發送了1,2,3,4,5五份數據,但是server端只接收到了1,2於是回ack 3,然後收到了4,此時server端怎麼辦呢?有兩種處理方式:

(1)不回ack,死等3,當client發現收不到ack 3時,會重傳3。一旦server端收到3後,會回復ack 4,表示3和4都收到了;

這種方式存在的問題是,client端可能認為4也丟了,導致4的重傳,client會只重傳3或者重傳3和4。

(2)回復ack 3。比如收到4後,回復ack 3,收到5後再回復ack 3,等client收到了三次ack 3 就知道了3還沒有送到,於是馬上重發3。

另外一種更好的方式是SACK,如下圖所示:

四、流量控制

1.TCP滑動窗口

由於TCP要解決可靠傳輸以及亂序的問題,所以TCP要知道網路實際的處理速度,這樣才不至於引起網路擁塞,導致丟包。

TCP頭中有一個欄位window,這個欄位用於server端告訴client自己還有多少緩衝區可以接收數據,於是client就可以根據這個來控制發送數據的上限,而不會導致server端處理不過來。

2.擁塞控制

網路傳輸裡面,除了發送方,接收方,還有個傳輸路徑。

早期的TCP實現是沒有擁塞窗口的,發送方會一次將接收方所建議的數據長度,也就是滑動窗口都發過去。如果傳輸路徑沒問題,例如發送方,接收方都在一個區域網,那自然沒問題。如果傳輸路徑有問題,比如帶寬有限,承受不了這麼多數據,那就會走到TCP的確認重傳機制。實際中,確認重傳會進一步降低網路的負載能力,本來一趟能走完的數據,現在要走好幾次。

所以TCP才提出了擁塞控制。擁塞窗口(congestion window)只是擁塞控制的一部分。它一個應用就是slow start,簡單來說,就是TCP的發送方雖然收到了接收方的建議數據長度,但是TCP發送方也不知道傳輸路徑負載能力如何。因此TCP的發送方開始只發一小段數據,當收到了ACK之後,再增加每次發送的數據,直到達到接收方的處理能力上限或者傳輸路徑的負載能力上限。有點類似摸著石頭過河。發送方每次發送的數據長度,就是擁塞窗口的大小。slowstart剛開始TCP的數據傳輸較慢,之後漸漸增加,最後趨於線性。

參考文章:

TCP 的那些事兒(上) | | 酷 殼 - CoolShell?

coolshell.cn
圖標

推薦閱讀:
相关文章