不學不知道,TCP協議竟如此複雜

一、TCP報文字段

在這裏插入圖片描述Source Port和Destination Port(源端口和目的端口):分別佔用16位,用於區別主機中的不同進程;而IP地址是用來區分不同的主機的,源端口號和目的端口號配合上IP首部中的源IP地址和目的IP地址就能唯一 的確定一個TCP連接。

Sequence Number(序號):用來標識從TCP發送端向TCP接收端發送的數據字節流,它表示在這個報文段中的的第一個數據字節在數據流中的序號;主要用來解決網絡報亂序的問題

Acknowledgment Number(確認序號):32位確認序列號包含發送確認的一端所期望收到的下一個序號,因此,確認序號應當是上次已成功收到數據字節序號加1。不過,只有當標誌位中的ACK標誌爲1時該確認序列號的字段纔有效主要用來解決不丟包的問題

Offset(頭部字段):給出首部中32 bit(4字節)的數目,需要這個值是因爲任選字段的長度是可變的。這個字段佔4bit(最多能 表示15個32bit的字,即4*15=60個字節的首部長度),因此TCP最多有60字節的首部。然而,沒有任選字段, 正常的長度是20字節。

標誌位字段(U、A、P、R、S、F):佔6比特。各比特的含義如下:
它們中的多個可同時被設置爲1,主要是用於操控TCP的狀態機的,依次 爲URG,ACK,PSH,RST,SYN,FIN。

  • URG:此標誌表示TCP包的緊急指針域(後面馬上就要說到)有效,用來保證TCP連接不被中斷,並且督促中間層設備要儘快處理這些數據。
  • ACK:此標誌表示應答域有效,就是說前面所說的TCP應答號將會包含在TCP數據包中;有兩個取值:0和1, 爲1的時候表示應答域有效,反之爲0。
  • PSH:這個標誌位表示Push操作。所謂Push操作就是指在數據包到達接收端以後,立即傳送給應用程序, 而不是在緩衝區中排隊。
  • RST:這個標誌表示連接復位請求。用來複位那些產生錯誤的連接,也被用來拒絕錯誤和非法的數據包。
  • SYN:表示同步序號,用來建立連接。SYN標誌位和ACK標誌位搭配使用,當連接請求的時候,SYN=1, ACK=0;連接被響應的時候,SYN=1,ACK=1;這個標誌的數據包經常被用來進行端口掃描。掃描者發送 一個只SYN的數據包,如果對方主機響應了一個數據包回來 ,就表明這臺主機存在這個端口;但是由於這種掃描方式只是進行TCP三次握手的第一次握手,因此這種掃描的成功表示被掃描的機器不很安全,一臺安全的主機將會強制要求一個連接嚴格的進行TCP的三次握手。
    FIN: 表示發送端已經達到數據末尾,也就是說雙方的數據傳送完成,沒有數據可以傳送了,發送FIN標誌 位的TCP數據包後,連接將被斷開。這個標誌的數據包也經常被用於進行端口掃描。
URG和PSH的區別

URG(緊急數據標誌位):如果URG爲1,表示本數據包中包含緊急數據。此時緊急數據指針表示的值有效,它表示在緊急數據之後的第一個字節的偏移值(即緊急數據的總長度)。若URG爲0,則緊急指針沒有意義。

PSH(推位):當設置爲1時,要求把數據儘快的交給應用層,不做處理。當兩個應用進程進行交互式的通信時,有時在一端的應用進程希望再鍵入一個命令後立即就能夠收到對方的響應。在這種情況下,TCP就可以使用推送操作。這時,發送方TCP把PSH置1,並立即創建一個報文段發送出去。接收方TCP收到PSH=1的報文段,就儘快交付接收應用進程,而不再等到整個緩存都填滿了再向上交付。
雖然應用進程可以選擇推送操作,但推送操作還是很少使用。

兩者都可理解爲處理緊急數據的標誌位,只是處理方法不同。URG的緊急數據僅在報文內,而PSH的緊急數據還在接受緩衝區內。

二、TCP的三次握手機制

在這裏插入圖片描述

過程詳解

服務端的TCP進程先創建傳輸控制塊TCB,準備接受客戶端進程的連接請求,然後服務端進程處於LISTEN狀態,等待客戶端的連接請求,如有,則作出響應。
第一次握手:客戶端的TCP進程也首先創建傳輸控制模塊TCB,然後向服務端發出連接請求報文段,該報文段首部中的SYN=1,ACK=0,同時選擇一個初始序號 seq=x。TCP規定,SYN=1的報文段不能攜帶數據,但要消耗掉一個序號。這時,TCP客戶進程進入SYN—SENT(同步已發送)狀態。

第二次握手:服務端收到客戶端發來的請求報文後,如果同意建立連接,則向客戶端發送確認。確認報文中的SYN=1,ACK=1,確認號ack=x+1,同時爲自己選擇一個初始序號seq=y。同樣該報文段也是SYN=1的報文段,不能攜帶數據,但同樣要消耗掉一個序號。這時,TCP服務端進入SYN—RCVD(同步收到)狀態。

第三次握手:TCP客戶端進程收到服務端進程的確認後,還要向服務端給出確認。確認報文段的ACK=1,確認號ack=y+1,而自己的序號爲seq=x+1TCP的標準規定,ACK報文段可以攜帶數據,但如果不攜帶數據則不消耗序號,因此,如果不攜帶數據,則下一個報文段的序號仍爲seq=i+1。這時,TCP連接已經建立,客戶端進入ESTABLISHED(已建立連接)狀態,可以看出第三次握手客戶端已經可以發送攜帶數據的報文段了。

當服務端收到確認後,也進入ESTABLISHED(已建立連接)狀態。

爲什麼要進行三次而不是兩次呢?

第三次握手看似多餘其實不然,這主要是爲了防止已失效的請求報文段突然又傳送到了服務端而產生連接的誤判

比如:客戶端發送了一個連接請求報文段A到服務端,但是在某些網絡節點上長時間滯留了,而後客戶端又超時重發了一個連接請求報文段B該服務端,而後 正常建立連接,數據傳輸完畢,並釋放了連接。但是請求報文段A延遲了一段時間後,又到了服務端,這本是一個早已失效的報文段,但是服務端收到後會誤以爲客戶端又發出了一次連接請求,於是向客戶端發出確認報文段,並同意建立連接。那麼問題來了,假如這裏沒有三次握手,這時服務端只要發送了確認,新的 連接就建立了,但由於客戶端沒有發出建立連接的請求,因此不會理會服務端的確認,也不會向服務端發送數據,而服務端卻認爲新的連接已經建立了,並在 一直等待客戶端發送數據,這樣服務端就會一直等待下去,直到超出保活計數器的設定值,而將客戶端判定爲出了問題,纔會關閉這個連接。這樣就浪費了很多服務器的資源。而如果採用三次握手,客戶端就不會向服務端發出確認,服務端由於收不到確認,就知道客戶端沒有要求建立連接,從而不建立該連接。

三、TCP的四次揮手

在這裏插入圖片描述

那又爲什麼要四次揮手呢?

那四次分手又是爲何呢?TCP協議是一種面向連接的、可靠的、基於字節流的運輸層通信協議。TCP是全雙工模式,這就意味着,當主機1發出FIN報文段時,只是表示主機1已經沒有數據要發送了,主機1告訴主機2, 它的數據已經全部發送完畢了;但是,這個時候主機1還是可以接受來自主機2的數據;當主機2返回ACK報文 段時,表示它已經知道主機1沒有數據發送了,但是主機2還是可以發送數據到主機1的;當主機2也發送了FIN 報文段時,這個時候就表示主機2也沒有數據要發送了,就會告訴主機1,我也沒有數據要發送了,之後彼此 就會愉快的中斷這次TCP連接。如果要正確的理解四次分手的原理,就需要了解四次分手過程中的狀態變化。

過程詳解

第一次揮手: 客戶端發送FIN(表示要結束連接)給服務器,客戶端狀態由 ESTABLISHED 變爲 FIN_WAIT_1。

第二次揮手:
服務器收到ACK、,服務器狀態由 ESTABLISHED 變爲CLOSE_WAIT。
服務器將緩存中沒發送的數據完繼續發送給客戶端,客戶端收到ACK後狀態由FIN_WAIT_1變爲FIN_WAIT_2。

第三次揮手:服務器發送FIN給客戶端,這時服務器的狀態由CLOSE_WAIT變爲 LAST_ACK。

第四次揮手:
客戶端收到FIN後返回ACK給服務器,然後客戶端的狀態由FIN_WAIT_2變爲TIME_WAIT。
服務器收到ACK後,狀態由LAST_ACK變爲CLOSED。
而客戶端再經過TIME_WAIT時間後變爲CLOSED狀態。

PS:TIME_WAIT = 2MSL (maximum segement lifetime 分節在網絡中最長生存時間,30秒到2分鐘,根據系統實現不同而不同) 2MSL 範圍是 1分鐘到4分鐘。

爲什麼最後客戶端還要等待 2*MSL的時間呢?

MSL(Maximum Segment Lifetime),TCP允許不同的實現可以設置不同的MSL值。

  1. 保證客戶端發送的最後一個ACK報文能夠到達服務器,因爲這個ACK報文可能丟失,站在服務器的角度看來,我已經發送了FIN+ACK報文請求斷開了,客戶端還沒有給我回應,應該是我發送的請求斷開報文它沒有收到,於是服務器又會重新發送一次,而客戶端就能在這個2MSL時間段內收到這個重傳的報文,接着給出迴應報文,並且會重啓2MSL計時器。

  2. 防止類似與“三次握手”中提到了的“已經失效的連接請求報文段”出現在本連接中。客戶端發送完最後一個確認報文後,在這個2MSL時間中,就可以使本連接持續的時間內所產生的所有報文段都從網絡中消失。這樣新的連接中不會出現舊連接的請求報文。

如果已經建立了連接, 但是客戶端突發故障了怎麼辦?

TCP設有一個保活計時器,顯然,客戶端如果出現故障,服務器不能一直等下去,白白浪費資源。服務器每收到一次客戶端的請求後都會重新復位這個計時器,時間通常是設置爲2小時,若兩小時還沒有收到客戶端的任何數據,服務器就會發送一個探測報文段,以後每隔75分鐘發送一次。若一連發送10個探測報文仍然沒反應,服務器就認爲客戶端出了故障,接着就關閉連接。

四、TCP的特點

TCP相對於UDP協議的特點是:面向連接的、字節流和可靠傳輸。
  • 面向連接的:使用TCP協議通信的雙方必須先建立連接,然後才能開始數據的讀寫,TCP連接是全雙工的,即雙方的數據讀寫可以通過一個連接進行。完成數據交換之後,通信雙方都必須斷開連接以釋放資源。TCP協議的這種連接是一對一的,所以基於廣播和多播(目標是多個主機地址)的應用程序不能使用TCP服。而無連接協議UDP則非常適合於廣播和多播

  • 流式服務:TCP的字節流服務的表現形式就體現在,發送端執行的寫操作數和接收端執行的讀操作次數之間沒有任何數量關係,當發送端應用程序連續執行多次寫操作時,TCP模塊先將這些數據放入TCP發送緩衝區中。當TCP模塊真正開始發送數據的時候,發送緩衝區中這些等待發送的數據可能被封裝成一個或多個TCP報文段發出。

  • UPD的數據報服務:發送端應用程序每執行一次寫操作,UDP模塊就將其封裝成一個UDP數據報併發送之。接收端必須及時針對每一個UDP數據報執行讀操作(通過recvfrom系統調用),否則就會丟包(這經常發生在較慢的服務器上)。並且,如果沒有指定足夠的應用程序緩衝區來讀取UDP數據,則UDP數據將被截斷。

五、TCP可靠性傳輸

實現TCP可靠傳輸的具體機制見下文

TCP通過下列方式來提供可靠性:

  1. TCP沒有報文長度字段,只有序號字段,應用數據通過序號被分割成TCP認爲最適合發送的數據塊,應用程序產生的數據報長度將保持不變

  2. 超時重發——當TCP發出一個段後,它啓動一個定時器,等待目的端確認收到這個報文段。如果不能及時收到一個確認,將重發這個報文段。

  3. 對於收到的請求,給出確認響應——當TCP收到發自TCP連接另一端的數據,它將發送一個確認。這個確認不是立即發送,通常將推遲幾分之一秒 。 (之所以推遲,是因爲延時確認可以更有效利用接收端的緩衝區)

  4. TCP將保持它首部和數據的檢驗和——這是一個端到端的檢驗和,目的是檢測數據在傳輸過程中的任何變化。如果收到段的檢驗和有差錯,TCP將丟棄這個報文段和不確認收到此報文段。 (校驗出包有錯,丟棄報文段,不給出響應,TCP發送數據端,超時時會重發數據)

  5. 失序重排——既然TCP報文段作爲IP數據報來傳輸,而IP數據報的到達可能會失序,因此TCP報文段的到達也可能會失序。如果必要,TCP將對收到的數據進行重新排序,將收到的數據以正確的順序交給應用層。

  6. 丟棄重複數據——既然IP數據報會發生重複,TCP的接收端必須丟棄重複的數據。

  7. TCP還能提供流量控制——TCP連接的每一方都有固定大小的緩衝空間。TCP的接收端只允許另一端發送接收端緩衝區所能接納 的數據。這將防止較快主機致使較慢主機的緩衝區溢出。(TCP可以進行流量控制,防止較快主機致使較慢主機的緩衝區溢出)TCP使用的流量控制協議是可變大小的滑動窗口協議。

六、TCP的各類機制

ACK機制

在這裏插入圖片描述
TCP將每個字節的數據都進行了編號, 即爲序列號;每一個ACK都帶有對應的確認序列號, 意思是告訴發送者, 我已經收到了哪些數據; 下一次你要從哪裏開始發。
比如, 客戶端向服務器發送了1005字節的數據, 服務器返回給客戶端的確認序號是1003, 那麼說明服務器只收到了1~1002的數據,1003, 1004, 1005都沒收到,此時客戶端就會從1003開始重發。

超時重傳機制

在這裏插入圖片描述

在這裏插入圖片描述

主機A發送數據給B之後, 可能因爲網絡擁堵等原因, 數據無法到達主機B,如果主機A在一個特定時間間隔內沒有收到B發來的確認應答, 就會進行重發,但是主機A沒收到確認應答也可能是ACK丟失了,這種情況下, 主機B會收到很多重複數據,這時候利用前面提到的序列號, 就可以很容易做到去重

超時時間如何確定?

最理想的情況下, 找到一個最小的時間, 保證 “確認應答一定能在這個時間內返回”。但是這個時間的長短, 隨着網絡環境的不同, 是有差異的。
如果超時時間設的太長, 會影響整體的重傳效率; 如果超時時間設的太短, 有可能會頻繁發送重複的包。

TCP爲了保證任何環境下都能保持較高性能的通信, 因此會動態計算這個最大超時時間

Linux中(BSD Unix和Windows也是如此), 超時以500ms爲一個單位進行控制, 每次判定超時重發的超時時間都是500ms的整數倍。

如果重發一次之後, 仍然得不到應答, 等待 2500ms 後再進行重傳. 如果仍然得不到應答, 等待 4500ms 進行重傳。依次類推, 以指數形式遞增. 累計到一定的重傳次數, TCP認爲網絡異常或者對端主機出現異常, 強制關閉連接。

滑動窗口機制

剛纔我們討論了確認應答機制, 對每一個發送的數據段, 都要給一個ACK確認應答.,收到ACK後再發送下一個數據段。這樣做有一個比較大的缺點, 就是性能較差. 尤其是數據往返時間較長的時候。
那麼我們可不可以一次發送多個數據段呢?
在這裏插入圖片描述

  • 窗口:窗口大小指的是無需等待確認應答就可以繼續發送數據的最大值,上圖的窗口大小就是4000個字節 (四個段)。
    發送前四個段的時候, 不需要等待任何ACK, 直接發送。收到第一個ACK確認應答後, 窗口向後移動, 繼續發送第五六七八段的數據。

  • 因爲這個窗口不斷向後滑動, 所以叫做滑動窗口。
    操作系統內核爲了維護這個滑動窗口, 需要開闢發送緩衝區來記錄當前還有哪些數據沒有應答,只有ACK確認應答過的數據, 才能從緩衝區刪掉。
    在這裏插入圖片描述
    在滑動窗口下,如果出現丟包,進行重傳的方法如下:

數據包到了,可是回送的ACK丟失

在這裏插入圖片描述這種情況下, 部分ACK丟失並無大礙, 因爲還可以通過後續的ACK來確認對方已經收到了哪些數據包。
個人理解,窗口大小是協商好的,當第一個窗口段的ACK丟包,主機收到第二個窗口段的ACK時,發現確認序號是2001,那表明1001~2000的字節的數據已經收到了,可是主機並沒有收到確認序號爲1001的ACK,那麼就要對1—1000字節的數據啓用重傳機制。

數據包丟失

在這裏插入圖片描述當某一段報文丟失之後, 發送端會一直收到 1001 這樣的ACK, 就像是在提醒發送端 “我想要的是 1001”。
如果發送端主機連續三次(是固定三次還是和窗口大小有關我不太清楚)收到了同樣一個 “1001” 這樣的應答, 就會將對應的數據 1001 - 2000 重新發送。
這個時候接收端收到了 1001 之後, 再次返回的ACK就是7001了,因爲2001 - 7000接收端其實之前就已經收到了, 被放到了接收端操作系統內核的接收緩衝區中
這種機制被稱爲 “高速重發控制” ( 也叫 “快重傳” )

流量控制

接收端處理數據的速度是有限的,如果發送端發的太快,導致接收端的緩衝區被填滿, 這個時候如果發送端繼續發送, 就會造成丟包, 進而引起丟包重傳等一系列連鎖反應。
因此TCP支持根據接收端的處理能力, 來決定發送端的發送速度,這個機制就叫做流量控制(Flow Control)

在這裏插入圖片描述

那麼接收端如何把窗口大小告訴發送端呢?

我們的TCP首部中, 有一個16位窗口大小字段, 就存放了窗口大小的信息,16位數字最大表示65536,通過ACK通知發送端,窗口大小越大, 說明網絡的吞吐量越高;
接收端一旦發現自己的緩衝區快滿了, 就會將窗口大小設置成一個更小的值通知給發送端。
發送端接受到這個窗口大小的通知之後, 就會減慢自己的發送速度;
如果接收端緩衝區滿了, 就會將窗口置爲0,這時發送方不再發送數據, 但是需要定期發送一個窗口探測數據段, 讓接收端把窗口大小再告訴發送端。

那麼TCP窗口最大就是65536字節麼?

實際上, TCP首部40字節選項中還包含了一個窗口擴大因子M, 實際窗口大小是窗口字段的值左移 M 位(左移一位相當於乘以2)。

擁塞控制

雖然TCP有了滑動窗口這個大殺器, 能夠高效可靠地發送大量數據。但是如果在剛開始就發送大量的數據,仍然可能引發一些問題。因爲網絡上有很多計算機, 可能當前的網絡狀態已經比較擁堵,在不清楚當前網絡狀態的情況下, 貿然發送大量數據, 很有可能雪上加霜。

因此, TCP引入慢啓動機制, 先發少量的數據, 探探路, 摸清當前的網絡擁堵狀態以後, 再決定按照多大的速度傳輸數據。

在這裏插入圖片描述
在此引入一個概念“擁塞窗口(cwnd)”
PS:在考慮擁塞的時候我們一般不考慮rwnd的值

  • 發送開始的時候, 定義擁塞窗口大小爲1;
  • 每次收到一個ACK應答, 擁塞窗口加1;
    cwnd增加1也就是相當於字節數增加1個MSS(最大報文段)大小
  • 每次發送數據包的時候, 將擁塞窗口和接收端主機反饋的窗口大小做比較, 取較小的值作爲實際發送的窗口。TCP的真正的發送窗口=min(rwnd接收窗口, cwnd擁塞窗口)

(可以嘗試回顧一下,和上述的滑動窗口機制、流量控制機制聯繫起來,敘述一下TCP數據傳輸的過程。)

像上面這樣的擁塞窗口增長速度,是指數級別的,“慢啓動” 只是指初始時慢, 但是增長速度非常快。
爲了不增長得那麼快, 此處引入一個名詞叫做慢啓動的閾值(ssthresh), 當擁塞窗口的大小超過這個閾值的時候, 不再按照指數方式增長, 而是按照線性方式增長。

當TCP開始啓動的時候, 慢啓動閾值有一個初始值
無論是慢啓動算法還是擁塞避免算法,只要判斷網絡出現擁塞,就要把慢啓動開始門限(ssthresh)設置爲設置爲發送窗口的一半(>=2),擁塞窗口(cwnd)設置爲1。在這裏插入圖片描述

快恢復算法

快重傳算法首先要求接收方每收到一個失序的報文段後就立即發出重複確認(爲的是使發送方及早知道有報文段沒有到達對方)而不要等到自己發送數據時才進行捎帶確認。(不能使用捎帶應答,下文提到)

其過程有以下兩個要點:

  • 當發送方連續收到三個重複確認,就執行“乘法減小”算法,把慢開始門限ssthresh減半。這是爲了預防網絡發生擁塞。請注意:接下去不執行慢開始算法。

  • 由於發送方現在認爲網絡很可能沒有發生擁塞,因此與慢開始不同之處是現在不執行慢開始算法(即擁塞窗口cwnd現在不設置爲1),而是把cwnd值設置爲慢開始門限ssthresh減半後的數值,然後開始執行擁塞避免算法(“加法增大”),使擁塞窗口緩慢地線性增大。

    下圖給出了快重傳和快恢復的示意圖,並標明瞭“TCP Reno版本”。

在這裏插入圖片描述

區別:新的 TCP Reno 版本在快重傳之後採用快恢復算法而不是採用慢開始算法。

也有的快重傳實現是把開始時的擁塞窗口cwnd值再增大一點,即等於 ssthresh + 3 X MSS 。這樣做的理由是:既然發送方收到三個重複的確認,就表明有三個分組已經離開了網絡。這三個分組不再消耗網絡 的資源而是停留在接收方的緩存中。可見現在網絡中並不是堆積了分組而是減少了三個分組。因此可以適當把擁塞窗口擴大了些。

在採用快恢復算法時,慢開始算法只是在TCP連接建立時和網絡出現超時時才使用。

採用這樣的擁塞控制方法使得TCP的性能有明顯的改進。

少量的丟包, 我們僅僅是觸發超時重傳;大量的丟包, 我們就認爲是網絡擁塞。
當TCP通信開始後, 網絡吞吐量會逐漸上升;隨着網絡發生擁堵, 吞吐量會立刻下降。

延遲應答

前言:
窗口越大, 網絡吞吐量就越大, 傳輸效率就越高。
TCP的目標是在保證網絡不擁堵的情況下儘量提高傳輸效率。

如果接收數據的主機立刻返回ACK應答, 這時候返回的窗口可能比較小.
假設接收端緩衝區爲1M. 一次收到了500K的數據,如果立刻應答, 返回的窗口大小就是500K。
但實際上可能處理端處理的速度很快, 10ms之內就把500K數據從緩衝區處理掉了; 在這種情況下, 接收端其實可以再次接受1M的數據,可他返回的窗口大小隻有500K,處理還遠沒有達到自己的極限, 即使窗口再放大一些, 也能處理過來。
如果接收端稍微等一會兒再應答, 比如等待200ms再應答, 那麼這個時候返回的窗口大小就是1M。

######那麼所有的數據包都可以延遲應答麼?
有兩個限制:

  1. 數量限制: 每隔N個包就應答一次
  2. 時間限制: 超過最大延遲時間就應答一次

具體的數量N和最大延遲時間, 依操作系統不同也有差異
一般 N 取2, 最大延遲時間取200ms

捎帶應答

在延遲應答的基礎上, 我們發現, 很多情況下,客戶端和服務器在應用層也是 “一發一收” 的。
意味着客戶端給服務器說了 “How are you”,服務器也會給客戶端回一個 “Fine, thank you”,那麼這個時候ACK就可以搭順風車, 和服務器迴應的 “Fine, thank you” 一起發送給客戶端 。
在這裏插入圖片描述

面向字節流

創建一個TCP的socket, 同時在內核中創建一個發送緩衝區和一個接收緩衝區。調用write時, 數據會先寫入發送緩衝區中:

  • 如果發送的字節數太大, 會被拆分成多個TCP的數據包發出。
  • 如果發送的字節數太小, 就會先在緩衝區裏等待, 等到緩衝區大小差不多了, 或者到了其他合適的時機再發送出去。

接收數據的時候, 數據也是從網卡驅動程序到達內核的接收緩衝區,然後應用程序可以調用read從接收緩衝區拿數據。

TCP的一個連接, 既有發送緩衝區, 也有接收緩衝區,那麼對於這一個連接, 既可以讀數據, 也可以寫數據, 這個概念叫做全雙工。

由於緩衝區的存在, 所以TCP程序的讀和寫不需要一一匹配
例如:

  • 寫100個字節的數據, 可以調用一次write寫100個字節, 也可以調用100次write, 每次寫一個字節。
  • 讀100個字節數據時, 也完全不需要考慮寫的時候是怎麼寫的, 既可以一次read 100個字節, 也可以一次read一個字節, 重複100次。
粘包問題

首先要明確, 粘包問題中的 “包”, 是指應用層的數據包
在TCP的協議頭中, 沒有如同UDP一樣的 “報文長度” 字段,但是有一個序號字段。站在傳輸層的角度, TCP是一個一個報文傳過來的. 按照序號排好序放在緩衝區中,站在應用層的角度, 看到的只是一串連續的字節數據
那麼應用程序看到了這一連串的字節數據, 就不知道從哪個部分開始到哪個部分是一個完整的應用層數據包?此時數據之間就沒有了邊界, 就產生了粘包問題。

那麼如何避免粘包問題呢?
明確兩個包之間的邊界

  • 對於定長的包
    保證每次都按固定大小讀取即可,例如上面的Request結構, 是固定大小的, 那麼就從緩衝區從頭開始按sizeof(Request)依次讀取即可。

  • 對於變長的包
    可以在數據包的頭部, 約定一個數據包總長度的字段, 從而就知道了包的結束位置。
    還可以在包和包之間使用明確的分隔符來作爲邊界(應用層協議, 是程序員自己來定的, 只要保證分隔符不和正文衝突即可)。

對於UDP協議來說, 是否也存在 “粘包問題” 呢?

不會。對於UDP, 如果還沒有向上層交付數據, UDP的報文長度仍然存在。同時, UDP是一個一個把數據交付給應用層的, 就有很明確的數據邊界。
站在應用層的角度, 使用UDP的時候,要麼收到完整的UDP報文, 要麼不收,不會出現收到 “半個” 的情況。

TCP 異常情況
  • 進程終止: 進程終止會釋放文件描述符, 仍然可以發送FIN. 和正常關閉沒有什麼區別。
  • 機器重啓: 和進程終止的情況相同。
  • 機器掉電/網線斷開: 接收端認爲連接還在, 一旦接收端有寫入操作, 接收端發現連接已經不在了, 就會進行 reset. 即使沒有寫入操作, TCP自己也內置了一個保活定時器, 會定期詢問對方是否還在. 如果對方不在, 也會把連接釋放。

另外, 應用層的某些協議, 也有一些這樣的檢測機制:
例如HTTP長連接中, 也會定期檢測對方的狀態
例如QQ, 在QQ斷線之後, 也會定期嘗試重新連接

七、TCP協議總結

爲什麼TCP這麼複雜?

因爲既要保證可靠性, 同時又要儘可能提高性能。

保證可靠性的機制
  • 校驗和
  • 序列號(按序到達)
  • 確認應答
  • 超時重傳
  • 連接管理
  • 流量控制
  • 擁塞控制
提高性能的機制
  • 滑動窗口
  • 快速重傳
  • 延遲應答
  • 捎帶應答
基於 TCP 的應用層協議

HTTP、HTTPS、SSH、Telnet、FTP、SMTP等

TCP 和 UDP 的應用領域

TCP和UDP之間的優點和缺點, 不能簡單絕對地進行比較
TCP用於可靠傳輸的情況, 應用於文件傳輸, 重要狀態更新等場景
UDP用於對高速傳輸和實時性要求較高的通信領域
例如, 早期的QQ, 視頻傳輸等. 另外UDP可以用於廣播

部分內容參考博客:rugu_xxx

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章