TCP學習指北

AD_milk發表於2020-08-10
限於博主水平有限不敢說指南,但應該能夠避免剛學TCP的同學出現找不著北的情況。

TCP與UDP的區別

區別:

  • UDP是無連線的,而TCP是面向連線的,傳資料前要先建立連線。
  • UDP可以一對多,多對多通訊,而TCP只能一對一。
  • UDP使用最大努力交付,即不保證可靠交付。而TCP是可靠交付資料,並且有擁塞控制和流量控制機制。
  • UDP是面向報文的,適合一次性傳輸少量資料。而TCP是面向位元組流的

總的來說UDP不太靠譜,自管把資料發出去,丟了也不管。就好像兩個人說話,其實一個人只管自己說自己的。

UDP擬人

相比之下,TCP就靠譜多了,它通過序列號和確認應答機制保證連線的可靠性,任何一方向對方傳送訊息(SYN),都要收到回應(ACK)。相當於兩個人正常聊天,接下來的談話內容會根據對方的反應做出調整,比如對方開小差沒有聽到你剛才講什麼,就重複一遍剛剛說的話。

TCP擬人

確認應答

上圖寫的只是相對序列號(相對於初始序列號,序列號如果一直是一個固定值,會增加被攻擊的風險),初始序列號(ISN)的確定是在連線建立也就是三次握手階段。

三次握手

同步序列號

握手的首要目的是告知對方自己的初始序列號,只有同步了序列號才能實現可靠傳輸,比如擁塞控制,訊息重發等等。

雖然兩次握手就能同步雙方的序列號,但無法保證客戶端是否接收到了服務端發過去的初始序列號,所以我們需要進行第三次握手來確認。

雙方都需要確認一次自己發過去的序列號對方有沒有收到,三次握手之後雙方就都能明確自己和對方的收發能力是正常的。

實際上任意次的握手都不可靠,三次是雙方互相明確對方收發能力的最低值。這 一點可以看看兩軍問題

三次握手首部資料

第一次握手:客戶端在給服務端資料通訊之前,會傳送連線請求並等待確認應答,此時TCP首部SYN為1,seq為隨機生成的序列號(X)。

第二次握手:伺服器接受到連線請求之後,會進行答覆這時SYN仍為1,確認應答號ack為X+1,表示你發過來的請求我接收到了。同時伺服器也會生成一個隨機序列號y寫入seq,等待客戶端的確認應答。

第三次握手:前兩次都是通過TCP首部傳送SYN包作為建立連線的請求,並未攜帶資料。三次握手的最後一次握手可以在報文段負載中攜帶資料,並且因為連線已經建立,所以在之後的報文中SYN都為0(包括最後一次握手),雙方正式開始通訊。

前兩次握手都不能攜帶資料,並且需要消耗掉一個序列號,也就是seq要加一。

第三次握手可以攜帶資料,如果不攜帶資料則不消耗序列號。也就是說三次握手結束後,客戶端傳送的第一個報文中的seq仍然為x+1

實際上,本來是四次握手的

Alice ---> Bob SYNchronize with my Initial Sequence Number of X
Alice <--- Bob I received your syn, I ACKnowledge that I am ready for [X+1]
Alice <--- Bob SYNchronize with my Initial Sequence Number of Y
Alice ---> Bob I received your syn, I ACKnowledge that I am ready for [Y+1]

因為二三步的接收物件相同,所以可以合為一步

認識TCP首部

突然出現SYN,seq這些字母你可能感覺暈乎乎的。特別是ACK和ack,更是讓人傻傻分不清楚。現在就來認識一下他們吧。

TCP首部1

圖中的序列號就是seq(傳送資料的順序編號),它的值是本報文段所傳送資料的第一個位元組的序號。比如這次傳送的資料包是由檔案的第1-100個位元組,那麼seq的值為1.

小寫ack對應確認應答號,表示期望收到對方下一個報文段資料的第一個位元組的序號。比如已經收到1-100個位元組資料後,那麼確認應答號就應為101,這樣傳送端下次就會傳送101位元組及其之後的資料過來。

需要注意的是隻有控制位ACK為1時,確認應答號ack才有效。

防止歷史連線擾亂通訊

如果你有過打遊戲突然掉線或者異常卡頓的經歷,我相信你對網路通訊並不像“如意如意,隨我心意”那麼順利有著深刻的認識?

假設我們對伺服器傳送了連線請求,但因為物理鏈路選擇的問題,遲遲沒有到達。超過了等待時間之後,重發了一次請求,這一次很快就順利建立起連線了。但沒過多久之前那個請求報文風塵僕僕的來到伺服器這,也建立起了連線。

不過還好初始序列號生成的演算法比較巧妙:

ISN=M+F(本地IP,本地埠,目的IP,目的埠)

M是過四毫秒就加一的計時器,F是根據上面四個引數進行對映的雜湊函式

這樣就能保證客戶端能過憑藉序列號判斷這是否是一個過期連線(通過跟記住的最後接收到的seq進行比對)。

如果是,則傳送RST報文給服務端,終止這個過期連線。不會讓服務端還傻傻的等著客戶端發資料,避免了資源的浪費。

從這裡也可以看出,序列號seq是多麼重要。因為TCP沒有時間戳,所以通過同步雙方的序列號來“校準雙方的時間”就顯得尤為重要。

TCP的可靠連線是通過序列號和確認應答來實現的

SYN洪水攻擊

SYN洪水攻擊(SYN flood attack)利用了TCP協議 3次握手的原理,攻擊者傳送大量建立連線的網路包,但卻不實際建立連線(不進行第三次握手),導致伺服器的連線資源耗盡。

這種攻擊因為SYN cookie技術的產生,在現代網路已經不奏效了。

SYN cookie的具體做法是:

伺服器只在握手完成之後分配資源,在這之前,它會根據特殊的演算法生成一個cookie(seq序列號)發給對方。伺服器本身並不記憶該cookie。

因為該cookie的生成與對方的IP和埠號有關,所以伺服器可以憑此重新算出cookie,並與發過來的報文段中的ack進行比對。

如果對方是合法的連線,伺服器就生成一個具有套接字的全開連線。如果不合法,那我們也不虧,伺服器沒有為它分配過任何資源。

一般SYN cookie只在半連線佇列爆滿的情況下才會啟用。除此之外,常見的防範方式還有SYN Proxy防火牆,Syn Cache技術等。

多虧了序列號和確認應答

滑動視窗

如果傳輸比較大的資料,一般會將其分為小包,進行分段傳輸。如果每傳送一個資料包都要等待對方確認之後再發下一個,這段時間內什麼都不做,看起來太浪費了。

TCP採用滑動視窗來避免資源的浪費:在等待ACK號的這段時間,繼續傳送後面的資料包。如果收到了ACK號,那麼就將該ACK之前的資料包從快取中清除。

滑動視窗

不過這樣做有些問題,因為對方可能無法在短時間內處理大量的資料包,導致後來的都被丟棄(緩衝區溢位)。所以在連線的時候,雙方應該要明確傳送和接收能力。

在TCP首部的可選欄位中,雙方會協商好能夠接收的最大資料量(一般稱為視窗大小),它的值一般與接收方緩衝區大小相等。

連線的兩端都有各自的接收快取和傳送快取,無論哪方都能同時收發資訊,因此TCP提供的是全雙工服務。

雙工通訊

擁塞控制

擁塞控制的目的是防止過多資料傳輸,使得傳輸鏈路過載,甚至造成癱瘓。在網路暢通的時候,我們就多發點,猛踩油門;網路差的時候,就別一個勁的傳送大量報文添堵了。

TCP採取慢啟動、擁塞避免、快速重傳、快速恢復來進行擁塞控制。

擁塞控制

  • 慢啟動:
    剛建立起的連線,擁塞視窗大小從預設初始值開始,大膽進行試探性指數增長。
  • 擁塞避免:
    當達到慢啟動閾值(ssthresh)時,再進行指數增長顯得有點魯莽了。這時候我們改變策略,採取加法的形式謹慎增加cwnd
  • 快速重傳:
    當發現收到四個相同的ACK時(檢測到三個冗餘ACK),進行快速重傳。即立馬傳送這個丟失的報文段,無需等待超時重傳定時器。
  • 快速恢復:
    發生快速重傳之後就會進入快速恢復階段,此時ssthresh=cwnd/2,cwnd=cwnd/2,之後遷移到慢啟動階段。

四次揮手

斷開連線的步驟居然要比建立連線還多,這是怎麼回事?難道不能像握手那樣:

  • 客戶端告訴伺服器我要斷開連線了
  • 服務端回覆好的,並且告訴客戶端我也準備斷開了
  • 客戶端收到服務端的確認之後,再給服務端傳送一個確認訊息

這樣三步不就解決了嗎?

斷開連線與建立連線還是有些不同的,因為建立連線之前,雙方是沒有任何通訊的。但在結束連線的時候,雙方可能還存在著沒有傳輸完成的資料包。

所謂上山容易下山難,多加一次揮手是為了防止出現誤操作。

如果是三次揮手,這邊客戶端要求伺服器重發的請求剛出去,就收到對面說斷開連線的訊息,實在是讓人錯手不及?

所以我們得分四步進行。

四次揮手

並且連線關閉之後,並不會馬上刪除相關資料。這是為了防止烏龍事件的產生。

如果因為網路原因,最後一步ACK丟了。伺服器遲遲未收到客戶端對其斷開連線的確認,重發了FIN。

但好巧不巧,客戶端的新連線就建立在這個之前斷開的埠號上。得,執行斷開操作,全亂套了。

相關文章