日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

TCP协议(全面)

發布時間:2023/12/14 编程问答 50 豆豆
生活随笔 收集整理的這篇文章主要介紹了 TCP协议(全面) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

TCP協議

TCP的全稱是Transmission Control Protocol,即傳輸控制協議,TCP工作在傳輸層上

其職責是:實現主機間進程到進程的通信,其次還需要保證可靠性(不是安全性,換言之不能保證安全性)

什么是可靠性(重點在前3條):

  • TCP會盡自己所能,盡量將數據發送給對方;但并不能保證100%可以發送給對方
  • TCP會在數據發送不到對方的情況下,會給應用層一個錯誤提示,告知用戶發送失敗
  • TCP可以保證接收方(應用層)嚴格按照發送時的數據順序接收
  • TCP保證數據不會出現無意間的損壞(UDP 也做到這點)
  • TCP盡可能地維護網絡質量
  • TCP的機制

  • TCP通過確認機制 ( acknowledge ) 保證了信息的成功發送
  • 我們通過時序圖(時間從上往下流失)來大致描述一下:

    發送方發送了數據,如果對方收到了確認,代表對方收到了數據,如果沒有收到確認,可以合理推測,對方沒有收到數據

    如果發送方同時發送了很多數據,如何知道對方確認收到的是哪一份

    對數據進行編號,同時確認也帶上編號,這樣對方收到數據的時候,就能知道收到的是哪一份數據

    如果沒有收到對方的確認

    如果沒有收到確認,就重新發送信息,一般超過一定時間沒有收到確認才進行重發,因此也稱為超時重發機制

    數據編號和確認編號

    發送的數據編號被稱為序列號(Sequence Number - SN)

    確認的數據編號被稱為確認序列號(Ackonwledge Sequence Number - ASN)

    編號的規則:每個字節都要占用一個號,發送時的起始編號稱為初始序列號(Initial SN - ISN),ISN不一定為0,而是一個隨機值,不過編號是連續的,因此可以記為0(相對值)

    SN在發送TCP Segment 的 header中如何體現:TCP 發送/接收的完整數據,一般稱為segment(段),TCP segment = header + payload

    當一次性發送一些數據的時候,SN只需要填寫本次發送的數據中的第一個字節的數據即可,因為編號是連續的,且segment會攜帶payload長度

    TCP協議正是通過序列號保證了傳輸順序

    參考博客:https://blog.csdn.net/wyq_tc25/article/details/51504642

    TCP由于要進行發送,也要進行確認,所以實際上TCP Segment有兩種不同的角色:

    • send segment
    • acknowledge segment

    TCP 設計的時候,一個segment可以身兼兩種不同的角色

    無論什么時候,一個segment都視為send segment的角色,而當某個標志位被置位時,segment具備了acknowledge segment的角色

    在TCP Header 中,通過ACK的標志位處理ack角色,一個ack只占據一個bit位的數據,也就是說,ack的值為0或1,為1的時候就是被置位了,表示ASN字段有意義,如果為0,則無論ASN為多少,其字段都無意義

    TCP規定,在連接建立后所有傳送的報文段都必須把ACK置1

    ASN 的填寫規則:

    填寫要接收的下一個字節的數據(本次收到的數據的最后一個字節的下一個)

  • 超時重傳機制
  • 如果沒有收到對方發送的應答,可能的情況有:

  • 對方沒有收到發送的數據,所以沒有應答,沒收到的原因可能很多,比如數據還在路上,但是還未到達,或者數據已經在路上丟失了,永遠不可能到達對方
  • 對方收到發送的數據,也應答了,但我們沒有收到,沒收到的應答的原因,比如應答還在路上,但是還未到達,或應答已經丟失了
  • 數據或者應答還在路上的情況,可以通過一定的超時機制解決該問題

    如果數據在發送的過程中就丟包了,重發肯定沒有問題

    如果數據發送到對方了,但是應答在路上丟失了,這個時候的重發,可能會導致對方收到重復的數據

    不過也沒關系,通過序列號就能判斷這個數據是不是重復的,TCP會保存SN,如果收到的信息的SN已經存在了,那就直接丟棄,如果不存在,說明是新的數據,那就保存并發送應答

    超時時間,怎么計算比較合理

    一般來說,設置一個稍大于Round Trip Time(RTT)的時間即可,RTT就是數據發送加上收到應答這個過程的時間

    但是TCP沒有辦法知道RTT,所以現實中,無法做到特別理想

    早期的時候,就直接給一個相對比較大的時間,保證比地球最遠的兩個主機之間的RTT都稍大一點,這就一定能保證超時時間大于RTT

    另一種就是實時計算RTT,這個有點智能,目前還在實驗階段,暫時不需要了解

    超時時間是會改變的,一開始會設置的比較短,出現ack丟失的情況后,會適當把超時時間延長

    比如在Linux中,超時以500ms為一個單位進行控制,每次判定 超時重發的超時時間都是500ms的整數倍,如果重發一次之后,仍然得不到應答,等待 2 * 500ms 后再進行重傳,如果仍然得不到應答,等待 4 * 500ms 進行重傳

    重發不會一直進行,否則如果物理層出現了問題,無論重發多少次,都是沒有用的

    所以一般就是嘗試幾次(不同的OS實現不同,但一般也能配置)重發,發現仍然收不到ack,則停止發送

    然后就會通知應用層,告知發送者發送失敗,在Java中write()就會以異常的形式提示

    最后放棄之前,會最后嘗試聯系下對方,會發送一種叫做reset segment

    緩沖區

    TCP是有發送緩沖區的,用于暫時保存已發送,未應答的數據,為什么要進行保存,因為TCP為了保證數據確切地被對方接收到,需要對方發送的ASN,如果對方沒有應答,就需要重發,如果不對數據進行保存,就沒有辦法重發了,所以發送緩沖區主要也是為了保證可靠性而存在的

    TCP有接收緩沖區,這與UDP協議一直,因為接收到的信息,不一定馬上就能被應用層取走使用

    應用層使用TCP進行數據發送,如果本次發送成功,只能意味著:數據放在本機TCP的發送緩沖區中

    ISN值的設置

    為什么ISN不設置成0,而是采用隨機值,這主要是站在安全角度考慮

    如果ISN設計成0,很容易有惡意的用戶推算出來合法的SN的值,這樣偽造TCP SN的成本很低,使用隨機值,能一定程度避免安全隱患

    連接

    作為一臺主機上的TCP,需要:

  • 內部針對每一條TCP的通信鏈路(信道),維護一組數據,至少包括:ISN、當前SN、ASN、發送緩沖區、接收緩沖區、五元組信息等等
  • TCP為了可靠性以及保證數據的交換,在正式的數據通信之前,需要和對方(TCP)進行一定的同步(synchronize)操作,本機把一些初始的重要信息發送給對方,若收到了對方的應答,就能知道對方一定存在
  • 根據這兩點,TCP就有了連接和連接管理的概念(一條連接的一生 = 一開始創建 + 正式使用 + 銷毀)

    連接(Connection)是一個人為抽象的概念,是看不見摸不著的

    主動連接方和被動連接方的連接就代表一條TCP信道,但實際在TCP兩層的內部,僅僅是一些數據而已

    用Java的視角,就是用一個Conection對象維護

    連接建立稱為握手(handshacke),銷毀連接稱為揮手

    三次握手

    雙方相互同步自己的基本信息

    TCP的主動連接方,一般是客戶端這個角色承擔;TCP的被動連接方,一般是服務器這個角色承擔;但實際上,離開了應用層,很少提客戶端和服務器的概念,因此建議還是使用主動連接方和被動連接方

    TCP的所有segment都要確認應答,同步信息segment也不例外,因此主動連接方發送SYN,然后被動連接方回應ACK,然后被動接收方也發送SYN,主動接收方也回應ACK,邏輯上至少要有4層,少任意一個就會存在漏洞

    被動連接方的ACK和SYN幾乎是同時的,且TCP也支持一個segment同時起到SYN和ACK的作用,所以這兩個可以合并

    最終就變成了主動連接方發送SYN,被動接收方發送并回應SYN + ACK,然后主動接收方收到后再次回應ACK,這就是三次握手

    SYN標志位在header中也只占據一個bit位,當其值為1的時候,具有SYN功能,如果為0則不具備SYN功能

    關于三次握手是否能夠攜帶payload問題

  • 第一次SYN,不能攜帶數據
  • 第二次SYN + ACK攜帶數據(因為前兩次不能確認連接一定是成功的,如果在這個階段攜帶數據,會提升發送成本,但有可能失敗,所以協議設計時,禁止了攜帶數據)
  • 第三次ACK,可以攜帶數據,但不強制
  • 交換信息雙方的SN是獨立,不同的:

    • 第一次發送SYN:[SYN] (這個方括號表示置位) len = 0 SN = a ASN = 0(這里還沒置位,什么值都無意義)
    • 第二次發送SYN + ACK:[SYN, ACK] len = 0 SN = b ASN = a + 1
    • 第三次發送ACK:[ACK] len > 0 SN = a + 1 ASN = b + 1

    請求連接則置同步位SYN=1確認連接就置確認位ACK=1(TCP規定,SYN報文段不能攜帶數據,但要消耗一個序號;ACK報文段可以攜帶數據,但如果不攜帶數據則不消耗序號)

    這里的首部header長度表示TCP頭部有多少個32bit位,也就是多少個4字節,上圖中是8,所以總共32字節,然后整個segment也是32個字節,說明payload的長度為0

    三次握手的狀態轉移過程

    狀態是指連接當前處于何種情況,引入狀態就是因為通過狀態,管理者(TCP)能了解每個連接當前處于何種情況

    觀察下面的狀態轉移圖,三次握手階段主要關注紅色部分

    虛線表示被動連接方,實線表示主動連接方;線的起點是起始狀態,線的終點是轉移后的狀態

    線上的文字有兩層含義:第一個是因為什么原因導致的狀態轉移;第二個是狀態轉移期間需要做的動作

    • CLOSE:表示初始狀態
    • LISTEN: 表示服務器端的某個SOCKET處于監聽狀態,可以接受連接了

    ServerSocket serverSocket = new ServerSocket(8888); 代碼中,創建服務器套接字就開始監聽了即close到listen

    • SYN_SENT:當客戶端主動發送SYN之后,就會進入該狀態,然后等待對方回復SYN + ACK,收到對方的SYN和ACK之后,回復ACK,該狀態結束,進入ESTABLISHED狀態
    • SYN_RCVD: 當接收到了客戶端發送的SYN報文時進入該狀態,然后給客戶端發送SYN + ACK,當再次收到對方的ACK的時候,它會進入到ESTABLISHED狀態
    • ESTABLISHED:表示連接已經建立了

    需要注意的是,這些狀態并不代表某一時刻的狀態,而是一個時期或者說一個過程,這一過程的任意時刻都是該狀態

    再次結合下圖理解:

    這個過程無法被應用層看到,換言之,應用層無法看到三次握手的中間過程

    Socket socket = serverSocket.accept();執行該語句前是listen狀態,執行完之后就是establish狀態了

    為什么要有握手階段(同步階段)

    為了可靠性,確保對方在線,并且需要同步給對方一些基本信息

    三次握手過程中的狀態轉移

  • 為什么要有狀態
  • 學會閱讀狀態轉移圖
  • 狀態變化和外部事件的關系
  • 四次揮手

    揮手的標志位:FIN

    揮手過程中主機的角色:

    • 主動揮手方:主動斷開連接的一方
    • 被動揮手方:被動斷開連接的一方
    • 同時關閉:雙方均屬于主動揮手方

    要注意,主動揮手方并不一定就是主動連接方,兩者沒有直接關系,是相互獨立的

    四次揮手的變化:

    梳理一下四次揮手的整體流程:

    客戶作為主動揮手方,發送斷開連接的請求,狀態從ESTABLISHED轉換到FIN_WAIT1狀態

    服務器作為被動揮手方,接收到FIN后,狀態從ESTABLISHED轉換到CLOSE_WAIT(該狀態將在下面做詳細解釋,這里先略過),然后發送應答,ACK標志位置位

    然后主動揮手方在接收到對方發送的ACK之后,進入FIN_WAIT2狀態,等待被動揮手方發送FIN

    被動揮手方過了一段時間之后(這一段時間都在做什么?詳見下文CLOSE_WAIT狀態分析),發送FIN給主動揮手方,然后進入LAST_ACK狀態,等待對方的ACK應答;

    主動揮手方接收到了FIN之后,進入TIME_WAIT狀態(關于為什么這里要等待一段時間,下文會與CLOSE_WAIT一起分析),然后發送ACK應答

    被動揮手方收到應答之后就進入CLOSED狀態,主動揮手方在經過一段時間等待之后也進入CLOSED狀態,至此,連接斷開

    這里我們看到和三次握手不同的是FIN和ACK并沒有合并,那是因為TCP協議允許一方揮手,而另一方不揮手的情況

    當然,想合并也不是不行,來看一下狀態轉移圖:

    紅色表示三次揮手,藍色表示同時關閉 ,沒有任何標注的是標準的四次揮手

    三次揮手時序圖:

    同時揮手時序圖:

    關于CLOSE_WAIT和TIME_WAIT狀態:

    • CLOSE_WAIT:發生在被動方,出現在單方面揮手的情況下,主動方發送了FIN之后,被動揮手方做出ACK應答,作出應答之后,進入該狀態,這種狀態的含義其實是表示在等待關閉,為什么要特意等待?主要是為了看你現在是否還有數據需要發送給對方的,如果沒有了才發送FIN報文
    • TIME_WAIT:在該狀態下,我們去思考一個問題:為什么主動揮手方在最后一次揮手之后,即最后一次發送ACK給被動揮手方之后還要等待一段時間?事實上,主動揮手方在該狀態下的主要目的是為了保證對方收到了ACK,如果主動揮手方發送的ACK沒有準時到達對方,對方會因為超時等待機制再次發送FIN報文,處于TIME_WAIT狀態下的主動揮手方收到后會再次發送ACK;假設沒有TIME_WAIT狀態,主動揮手方發送了ACK就直接關閉之后,完全有一種可能就是對方沒有收到該ACK,最終導致連接無法斷開,因此這個階段的存在是非常有必要的

    思考:如果服務器上出現了大量的CLOSE_WAIT狀態的TCP連接,請問這種現象是否合理?并說明理由

    答案是不確定;單純從現象上看,無法斷定是否合理

    因為如果程序設計的時候,會出現較長時間的單方面關閉的情況時,出現大量的CLOSE_WAIT是合理現象

    但如果程序沒有這么設計,那么就是不合理,可能的原因是被動揮手方忘記調用socket.close()所致

    為什么會有TIME_WAIT?是否有存在的必要(參考上面對TIME_WAIT的介紹)

    先說結論,TIME_WAIT的存在是為了確保被動揮手方已經關閉,并且有存在的必要

    在進入TIME_WAIT狀態前,主動揮手方發送了ACK應答,如果該報文沒有被對方接收,對方會再次發送FIN報文,為了確保連接能夠正常斷開,就不能直接釋放連接,需要在該狀態等待確認對方是否因為沒收到ACK而再次發送FIN的情況,如果沒有才可以正常關閉

    針對TIME_WAIT狀態,我們可以設想一個場景來說明其必要性:

    路人甲擁有一個手機號碼123123,有一天,甲注銷了這個手機號,運營商回收該手機號,如果運營商回收之后,直接放開申請,然后路人乙就把這個號碼申請了

    結果路人甲的朋友丙想給甲打電話,于是撥通了手機號碼123123,路人乙接到電話之后,發現丙不是來找自己的,兩個都很奇怪:你是誰啊?

    同樣的,數據的傳輸中,TCP靠五元組來區分連接,五元組作為一條連接的主鍵(PK),如果主動揮手方不經過TIME_WAIT直接關閉連接之后,五元組又立刻被分配出去了,如果這個時候收到了發給五元組的segment(可能是網絡傳輸較慢的數據),那這個數據到底是給誰傳的?很顯然,我們已經不能區分了

    為什么TIME_WAIT的時間是2MSL?

    首先說一下什么是MSL(Maximum Segment Live):一個Segment能在網絡上活著的最大時間;MSL是個理論值,實際中很多OS取的是經驗值,一般是一分鐘,所以默認情況下,TIME_WAIT持續的時間是2分鐘,但這個值可以被修改

    2 * MSL時間過去之后,Segment的一個來回肯定是夠了

    如果在2MSL中沒有收到segment,則說明:

    • 認為對方收到了ack(即使對方沒有收到,由于我們也沒收到fin,說明網絡出現了問題 )
    • 網絡上肯定沒有發送給甲的segment了,之后五元組收到的segment一定是給新的連接的

    思考:服務器上發現了大量的TIME_WAIT狀態的TCP連接,是否合理?并說明理由

    理論上來說,確實是合理的,從標準上來說,沒有任何問題,代碼正常地關閉了連接

    但從實踐的角度來看,是不合理的,因為維護連接是有成本的(最主要的硬件成本是內存)

    客戶端和服務器之間的壓力是不同的,客戶端身上背負的連接比較少(幾百條),服務器身上背負的連接很多(幾十萬 - 幾百萬)

    所以,如果讓服務器背負這個TIME_WAIT連接的成本,相對壓力較大,所以一般建議讓客戶端來背負這個成本

    因此,一般做網絡編程設計的時候,不建議服務器去主動關閉連接(某些特殊情況下該主動還是要主動)

    總結:

    • 為什么說四次揮手而不是三次揮手:因為被動揮手方在收到主動方發送的FIN報文之后,還有一些數據需要發送處理,不能直接關閉連接,所以先發送一個ACK告知主動方“報文已收到,等我把數據都發送完了再給你發FIN + ACK報文”,所以在被動方確認FIN報文時要分兩次完成,所以就說是四次揮手
    • 四次揮手的三種情況:正常情況下的四次揮手,三次揮手、同時揮手
    • 四次揮手的tcp header的標志位變化:FIN -> ACK -> FIN + ACK -> ACK
    • 四次揮手的狀態變化:主動方:ESTABLISHED -> FIN_WAIT1 -> FIN_WAIT2 -> TIME_WAIT -> COLSED;被動方:ESTABLISHED -> CLOSE_WAIT -> LAST_ACK -> CLOSED
    • 重點掌握CLOSE_WAIT和TIME_WAIT

    三次握手、四次揮手中的細節都是TCP協議內部所做的事情,作為應用層,是看不到這些細節的,對Java來說,三次握手只有簡單的connect() 和 accept()

    異常情況

    情況1:在甲的任務管理器中,直接把A進程kill(停止)掉,請問,這條連接的命運如何?

    雖然進程被kill掉了(即進程沒有走完main方法,也沒有執行close()方法),但是一個進程的資源都是由OS分配的,一個進程有哪些資源OS都直到,所以即使進程內部沒有關閉TCP連接,OS也會走進程資源釋放流程,將TCP連接正常關閉,因此這條連接看起來還是甲主動關閉,正常執行了四次揮手

    如果資源通過close方法釋放,那么OS還會不會執行資源釋放流程了?

    • TCP就是OS提供的機制,應用層可以通過Socket使用這些有OS提供的機制,比如socket.close()方法
    • 由OS提供的系統調用稱為System Call Interface - SCI

    情況2:直接重啟電腦,連接的命運是怎么樣的?

    點擊重啟之后,執行OS的邏輯,在電腦關閉之前,關閉所有的進程并釋放所有進程的資源

    因此也是正常的四次揮手

    情況3:直接關機

    同理,只要OS的代碼還能執行,連接就會正常關閉

    情況4:直接拔掉甲的電源或者強制關機

    首先要知道OS是軟件(軟件就是程序,就是數據 + 指令,就是運行在CPU上的指令,以及指令要處理的數據),拔掉電源之后硬件都無法工作了,作為軟件的OS更不能再執行任何操作

    至于該鏈接的命運需要分情況討論:

    • 甲主機的命運:由于連接只是邏輯上的概念,表現在現實中,只不過是內存中的一段數據罷了,如果斷開電源,作為硬件的內存無法工作,這些數據就不存在了,因此連接也就不存在了,但這個連接既不屬于正常關閉,也不屬于異常關閉,就是突然消失了

    • 乙主機上的連接的命運,需要分情況討論:

    • 如果乙發生了寫事件(即乙向甲主機發送數據了),由于甲主機都關機了,因此乙主機無法收到應答,即使超時重傳之后還是收不到,經過多次嘗試的乙主機開始走異常關閉流程:關閉該TCP連接;以異常的形式通知應用層;最后發送一條reset segment。從甲消失到乙最終斷開連接需要一段時間,而不是瞬時的
    • 如果乙只是在單純的讀取數據,那么乙根本無法得知甲到底還在不在,只能說甲一直沒發送數據,乙也一直收不到數據,那么這條連接就永遠無法斷開,一直保持ESTABLISHED狀態

    針對這種情況4的解決方案

  • TCP層面有種Keepalive機制:定期地發送一些數據給對方(payload長度為0),segment長度不是0,就可以根據對方有沒有應答來判斷;這個機制應用不多。。。。

  • 更常見的辦法是應用層自己來做這個工作:

    • 一種方法是應用在進行read的時候,不要無限制地read,而是帶上一個超時時間(read timeout);
    • 另一種方法是定期主動給對方發送數據(相互報平安),這種數據包稱為heartbeat - 心跳包
  • 標志位中的RST表示異常

    Reset Segment - RST:收到這種rst segment,就代表異常了,立即關閉連接,不用在四次揮手了,然后以異常的方式通知應用層

    通過命令行命令,可以查看主機上的TCP:由于macOS的操作有些不同,因此不在這里解釋

    流量控制

    Flow Control - 流量控制

    流量控制(廣義):發送端會根據對方接收能力和網絡承載能力,動態地調節自己的發送流量

    如果在對方只能接收少量的文件或者網絡很堵的情況下,發送大量的數據,對方只能收到一部分,收不到的另一部分,就可能來不及接收或者根本就沒收到而導致數據的丟失,因此通過流量控制來提高數據的到達率來提高可靠性

    廣義可以分為狹義的流量控制(其實指TCP協議下專門的流量控制)和擁塞控制 - Congestion Control

    • 流量控制(狹義):根據對方的接收能力來調節發送流量
    • 擁塞控制:根據網絡的承載能力來調節發送流量

    接下來就專門針對TCP協議下的流量控制進行介紹

    流量控制:

  • 需要知道對象的接收能力,最好是實時的感知到,怎么做到?
  • 讓對方主動告知,也就是放在Segment Header中把接受能力攜帶發送過來

    發送segment的時候,把自己的接收能力(接收窗口)填寫到segment header的串口字段中,發送給對方

    如何做到實時?

  • 讓所有發送的segment都攜帶接收窗口
  • 即使一段時間內沒有數據要發送,也時不時發送一個ACK + WINDOW過去
  • 拿到對方的接收能力后,怎么換算成發送流量
  • 接收窗口大致 = 接收緩沖區大小 - 已用大小(接收的數據,暫時沒被應用層讀走)

    最大發送量 = 對方的接收窗口

  • 通過什么機制來控制發送量
  • 通過滑動窗口機制控制發送量

    前置知識,梳理下發送緩沖區的邏輯部分有哪些:

    應用層寫入的數據有可能大于接收窗口也有可能小于接收窗口,為了便于理解,之后的介紹都將基于后者

    其次,對于應用層寫入的數據,TCP既可以全部發送,也可以部分發送

    在TCP協議下發送的數據,也可能只有部分數據收到應答,也有可能全部都會應答

    那么在上圖中,發送并應答的數據就沒必要再保留了,因此我們拿來做可用空間

    整個發送緩沖區被看做邏輯上的幾個部分:

  • 未使用空間
  • 應用層已寫入數據(包括目前可發送的(處于滑動窗口內)、暫不可發送的)
  • 目前已發送(包括已發送未應答、已發送已應答(這部分空間已經可以利用了))
  • 滑動窗口機制

    如果發送方每次都只發送一個數據,接收方每收到一個數據就做一次應答,然后發送方收到應答之后再發送下一個數據,這樣的效率是比較低的,那么我們可以一次發送多條數據,這樣就可以大大提高性能;

    比如一次性發送四個數據,然后發送方收到第一個數據的ACK后,滑動窗口向后移動,繼續發送第五個段的數據;依次類推;操作系統內核為了維護這個滑動窗口,需要開辟發送緩沖區來記錄當前還有哪些數據沒有應答;只有確認應答過的數據,才能從緩沖區刪掉

    我們下圖中發送方發送的數據就是建立在連續發送的基礎上的

    TCP主動發送數據情況下,導致窗口滑動的原因:

    左側是根據ASN進行變化,右側是根據ASN + window進行變化的

    左側數據收到應答之后,就可以丟棄了,滑動窗口的左側就要右移

    而右側則根據ASN應答,和window來決定

    通過滑動窗口機制,保證了TCP不會發送過量,即不會發送對方沒有能力接收的數據

    如果滑動窗口發送的數據在中途丟包了會如何?

    • 情況一:數據包已經到達,但是ACK在中途丟失了

      這種情況無需擔心,如果發送方接收到某個ACK,就能推測該ACK之前所有的數據都已經到達,因此前面的數據也可以通過這個ACK進行確認

    • 情況二:數據報還未到達就丟失了

      這種情況下,如果中間某個數據包丟失,那么后續的數據包的ACK,全是上圖中1001這樣的ASN,因為ASN的填寫規則就是要接收的下一個字節的數據,0-1000之后的下一個數據就是1001,由于一直沒有收到1001數據包,接收方收到數據就發送1001的應答,就好像在告訴發送方,我的下一個數據是1001

      當發送方收到3次重復的應答時,就認為該數據包已經丟失了,因此不會等到超時重傳,而是立即重發

      當接收方正確收到這個1000-2000的數據以后,后續ASN恢復到要接收的下一個數據

      這種機制被稱為“高速重發控制”,也叫“快重傳”

    滑動窗口的變化和擁塞控制也有關,下面就對擁塞控制進行介紹

    擁塞控制

    Congestion Control - 擁塞控制:根據網絡目前的承載能力控制發送量

    我們從3個角度來理解擁塞控制:

  • 作為發送方是如何得到當前的網絡承載能力的?
  • 網絡的承載能力就好像現實中路面擁堵的情況,這個值無法通過某角色,發送一個精確值告訴你

    所以擁塞窗口實際上是一個通過動態算法來實時計算出來的結果 - 本質上是一個估算值

    擁塞控制的算法有很多:慢啟動、擁塞避免算法、擁塞狀態時的算法、快速恢復算法;接下去我們要介紹的算法是「慢開啟,快啟動」,即上述的擁塞避免算法,這是一種比較古老的算法,該算法的精確度不夠高,不適用于現在的網絡環境,但是面試要考

    該算法根據丟包率作為重要的因素來推算,丟包率 = 單位時間內,沒有收到應答的占比,或者說TCP重發的次數占比

    擁塞避免算法,又稱為慢開始,快啟動算法

    • 橫坐標:時間
    • 縱坐標:當前計算出來的擁塞窗口
    • 慢開始:cwnd的初始值非常小,為1
    • 指數增長轉變為線性增長的中間閾值:ssthresh
    • 指數增長:cwnd = cwnd * C(常數,下圖中取2)
    • 線性增長:cwnd = cwnd + C(常數,下圖中取1)
    • 快啟動:指數增長速率快于線性增長

    當處于指數增長的過程中時,如果cwnd大于ssthresh,指數增長就變為線性增長,如果一直沒有丟包,cwnd就會一直增長到最大值

    當丟包率大于某個閾值了,就相當于網絡擁塞了,會發生一下變化:

  • ssthresh根據當前cwnd重新計算,得到一個新的ssthresh = cwnd / 2;
  • cwnd會重新變為初始值,即cwnd = 1
  • 這種算法一遇到網絡擁塞,就把cwnd變為初始值,因為現代的網絡哪怕是丟包也很久就會恢復,而早期網絡不好的時候,一旦遇到丟包,可能一段時間內都會丟包,所以說該算法只適用于早期網絡,不適用于現在的網絡

    至于為什么ssthresh要除以2,也好理解:如果cwnd在處于一個較高層次才發生丟包,說明當前網絡狀況還不錯,可以適當提高閾值,讓下一次開始的前期啟動更快一些;如果cwns處于一個較低層次就發生了丟包,說明當前網絡狀況可能并不樂觀,于是適當降低閾值,讓前期的啟動慢一些,盡可能避免發生擁塞

  • 發送最大流量(發送窗口) = f(擁塞窗口,接收窗口)f(擁塞窗口, 接收窗口)f(擁塞窗口,接收窗口)
  • 發送窗口 = min(擁塞窗口,接收窗口)min(擁塞窗口, 接收窗口)min(擁塞窗口,接收窗口)

    如果擁塞窗口為100,接收窗口為10,那么發送窗口就是10

    反過來如果擁塞窗口為10,接收窗口為100,那么發送窗口也是10

    ? 因此發送窗口并不需要擔心擁塞窗口一直增長,因為接收窗口可能達不到那個大小

  • 如何進行發送流量控制 - 流量控制
  • 依然是通過滑動窗口,當擁塞窗口發生了變化(可能增長也可能發生擁塞而減少),滑動窗口就會根據擁塞窗口進行調整,由于只是發送窗口的變化導致的滑動窗口的變化,因此在滑動窗口的右側改變,左側不動

    至此,我們能夠直到滑動窗口的左邊根據發送并應答移動,右邊則是根據發送并應答 + 發送窗口移動,而發送窗口由擁塞窗口以及對方接收窗口決定

    思考:

    由于有流量控制、擁塞控制的存在,請問發送方的應用層本次寫入[a, b, c, d]4個字節的數據,請問發送方的TCP層能包裝數據是按照[a, b, c, d]作為完整的segment的方式去發送的嗎?

    并不一定,由于滑動窗口的存在,我們并不能保證 [a, b, c, d] 這四個字節的數據正好處在滑動窗口內,完全哪有可能就被一刀兩斷了

    那么假設接收方收到數據:[a, b, c, d, e, f, g, h, i, j, k],請問接收方能夠分辨發送方寫了幾次,每次是哪幾個嗎?

    很顯然是不能的,因此作為接收方的應用層也無法直到這些數據到底是分了幾次來的,屬于那一部分的

    這就說明面向報文的特點已經無法做到了,TCP協議為了可靠性,放棄了面向報文的特性,稱之為面向字節流

    延遲應答

    如果接收數據的主機立刻返回ACK應答,這時候返回的窗口可能比較小

    假設接收方,每收到一次數據馬上就應答,那么對于發送方來說,每次應答返回的窗口都很小,遠小于接收端的接收緩沖區,這么小的數據又會很快被處理掉,這樣的傳輸效率是不高的

    因此可以讓接收方收到數據之后先等一等,等到窗口比較大了,在發送應答,這樣一次就能處理較多的數據,傳輸效率也就比較高

    當然,并非一定要等到窗口足夠大了才發送應答,還是存在限制的:

    數量限制:每隔N個包就應答一次

    時間限制:每隔最大延遲時間就應答一次

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

    捎帶應答

    該機制是為了減少應答次數的,如果需要應答的時候正好有數據要發送,就“搭順風車”一起發送給對方

    粘包問題(面向字節流)

    面向字節流給應用層提出了新的挑戰:

    應用層的協議設計,必須手動設定邊界,常見的方法有:

  • 采用定長的信息:比如一個請求一定是100個字節
  • 先定長發送后續數據的長度,再發送數據,也就是攜帶長度的數據:比如先發送4個字節的定長數據(內容是后續數據的長度),然后在發送該長度的數據
  • 通過特殊字符來分隔,比如“/r/n”, "$$$"等,這個有程序員自己設定,只要不與正文內容沖突即可
  • TCP的三個特點

  • 可靠
  • 有連接:意味著使用TCP協議的應用在建立聯系之前,彼此需要先建立TCP聯系;在一個TCP連接中,僅有兩方進行彼此通信。廣播和多播不能用于TCP
  • 面向字節流:為了可靠性,放棄了面向報文的特性
  • 關于TCP的標志位URG和PSH:

    URG:Urgent(緊急)配合16位的緊急指針來使用(這套設計已經過時了,現在都是通過兩條信道實現)

    假設通過TCP協議發送了一段數據:[a, b, c, d, e, f, g],其中d這個字節的數據非常重要(稱為緊急指令),

    將urg置位為1,然后讓緊急指針指向d字節所在的偏移量 = 3,讓接收方優先處理這個字節,優先傳遞給對方應用層

    現在比較好的處理是使用兩條信道,讓緊急指令單獨走一條信道,其他走普通信道,接收方收到之后,會優先處理緊急指令所在信道的數據

    PSH:Push(推)要求發送方和接收方的TCP盡快發送數據出去

    TCP會針對數據發送做優化(一次盡量多發一點數據)

    這個標志位主要是給接收方用的,讓發送方趕緊發送數據,哪怕只有一點數據也先發過來的意思

    在這個標志位現在基本失去了作用,因為被“濫用”,如果所有人的PSH都置位,也就是每個人都說自己很急,那就沒辦法處理了

    TCP協議如何保證數據的有序性?

    TCP協議通過序列號保證了數據的有序,發送端主機每次發送數據的時候,會帶上SN,而對于接收端來說,它需要通過SN對發送來的數據進行確認,只有當接收端收到了連續的序號的數據時,才會將數據上交給應用層,否則它不會上交給應用層,比如發送0-1000,1000-2000,2000-3000的數據,中間的1000-2000丟包了,那么接收方最多只把0-1000上傳給應用層,而不會把后面的數據上傳給應用層,并且由于快速重傳機制,會一直發送1001告訴發送方這部分數據沒收到,當接收方收到這個數據之后,就對這些數據重新排序,所以就保證了數據的有序性

    TCP總結

    可靠性:

    • 校驗和
    • 序列號(按序到達)
    • 確認應答
    • 超時重發
    • 連接管理
    • 流量控制
    • 擁塞控制

    提高性能:

    • 滑動窗口
    • 快速重傳
    • 延遲應答
    • 捎帶應答

    其他:

    • 定時器(超時重傳定時器,保活定時器,TIME_WAIT定時器等)

    總結

    以上是生活随笔為你收集整理的TCP协议(全面)的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。