TCP协议(全面)
TCP協議
TCP的全稱是Transmission Control Protocol,即傳輸控制協議,TCP工作在傳輸層上
其職責是:實現主機間進程到進程的通信,其次還需要保證可靠性(不是安全性,換言之不能保證安全性)
什么是可靠性(重點在前3條):
TCP的機制
我們通過時序圖(時間從上往下流失)來大致描述一下:
發送方發送了數據,如果對方收到了確認,代表對方收到了數據,如果沒有收到確認,可以合理推測,對方沒有收到數據
如果發送方同時發送了很多數據,如何知道對方確認收到的是哪一份
對數據進行編號,同時確認也帶上編號,這樣對方收到數據的時候,就能知道收到的是哪一份數據
如果沒有收到對方的確認
如果沒有收到確認,就重新發送信息,一般超過一定時間沒有收到確認才進行重發,因此也稱為超時重發機制
數據編號和確認編號
發送的數據編號被稱為序列號(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就有了連接和連接管理的概念(一條連接的一生 = 一開始創建 + 正式使用 + 銷毀)
連接(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問題
交換信息雙方的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的串口字段中,發送給對方
如何做到實時?
接收窗口大致 = 接收緩沖區大小 - 已用大小(接收的數據,暫時沒被應用層讀走)
最大發送量 = 對方的接收窗口
通過滑動窗口機制控制發送量
前置知識,梳理下發送緩沖區的邏輯部分有哪些:
應用層寫入的數據有可能大于接收窗口也有可能小于接收窗口,為了便于理解,之后的介紹都將基于后者
其次,對于應用層寫入的數據,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就會一直增長到最大值
當丟包率大于某個閾值了,就相當于網絡擁塞了,會發生一下變化:
這種算法一遇到網絡擁塞,就把cwnd變為初始值,因為現代的網絡哪怕是丟包也很久就會恢復,而早期網絡不好的時候,一旦遇到丟包,可能一段時間內都會丟包,所以說該算法只適用于早期網絡,不適用于現在的網絡
至于為什么ssthresh要除以2,也好理解:如果cwnd在處于一個較高層次才發生丟包,說明當前網絡狀況還不錯,可以適當提高閾值,讓下一次開始的前期啟動更快一些;如果cwns處于一個較低層次就發生了丟包,說明當前網絡狀況可能并不樂觀,于是適當降低閾值,讓前期的啟動慢一些,盡可能避免發生擁塞
發送窗口 = 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
捎帶應答
該機制是為了減少應答次數的,如果需要應答的時候正好有數據要發送,就“搭順風車”一起發送給對方
粘包問題(面向字節流)
面向字節流給應用層提出了新的挑戰:
應用層的協議設計,必須手動設定邊界,常見的方法有:
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定時器等)
總結
- 上一篇: python是一种解释型、面向什么的计算
- 下一篇: 机器人学导论学习笔记No.1-第一章 :