TCP/IP模型——傳輸層
本篇文章,篇幅較長,全文大概18000多字,花費了近一周的時間才寫完。寫這篇文章的原因很簡單:在面試中,總是會問到計算機網絡方面的問題,而TCP是必不可少的。每一次的回答都不盡滿意,甚至有些點自己還不清楚,是以自己決定将傳輸層協定這一塊兒總結一下,相當于是一個再學習過程。如果你碰巧看到了這篇文章,又剛好對傳輸層協定感興趣,那麼不妨花費一點時間好好看一下,相信會對你有一定的幫助。
若是你在浏覽的過程中,發現文章有問題,請留言,我會盡快更正。謝謝。
注:本篇文章是以謝希仁教授的《計算機網絡》第七版為基礎學習總結的。參考文獻包括但不限于《計算機網絡》、百度百科及CSDN等博文。
文章目錄
- TCP/IP模型——傳輸層
-
- 1. 傳輸層協定概述
-
- 1.1 程序間的通信
-
- 1.1.1 傳輸層的作用
- 1.1.2 為什麼需要傳輸層?
- 1.1.3 複用和分用
- 1.2 傳輸層協定
-
- 1.2.1 協定劃分
- 1.2.2 協定端口号
- 2. 使用者資料報協定 UDP
-
- 2.1 UDP 概述
-
- 2.1.1 主要特點
- 2.2 UDP首部格式
- 3. 傳輸控制協定 TCP
-
- 3.1 TCP 概述
-
- 3.1.1 主要特點
- 3.2 TCP 首部格式
- 3.3 TCP 可靠傳輸的實作
-
- 3.3.1 确認應答機制
-
- a. 确認應答 ACK
- b. 選擇确認 SACK(了解)
- 3.3.2 滑動視窗
-
- a. 為什麼采用滑動視窗?
- b. 滑動視窗的實作
- c. 視窗和緩存的關系
- 3.3.3 逾時重傳
-
- a. 逾時重傳的含義
- b. 如何選擇逾時重傳時間?
- 3.4 流量控制
-
- 3.4.1 利用滑動視窗實作流量控制
- 3.4.2 TCP 的傳輸效率
- 3.5 擁塞控制
-
- 3.5.1 擁塞控制的原理
-
- a. 基本概念
- b. 擁塞控制的作用
- 3.5.2 擁塞控制方法
- 3.6 延遲應答和捎帶應答
-
- 3.6.1 延遲應答
- 3.6.2 捎帶應答
- 3.7 TCP 連接配接管理機制
-
- 3.7.1 建立連接配接(三次握手)
- 3.7.2 釋放連接配接(四次揮手)
- 3.7.2 狀态轉化
1. 傳輸層協定概述
1.1 程序間的通信
1.1.1 傳輸層的作用
從通信和資訊處理的角度看,傳輸層向它上面的應用層提供通信服務,它屬于面向通信部分的最高層,同時也是使用者功能中的最低層。當網絡的邊緣部分中的兩台主機使用網絡的核心部分的功能進行端到端的通信時,隻有主機的協定棧才有運輸層,而網絡核心部分中的路由器在轉發分組時都隻用到下三層的功能。
傳輸層的任務:負責向兩台主機中程序之間的通信提供通用的資料傳輸服務。
1.1.2 為什麼需要傳輸層?
從IP層來說,通信的兩端是兩台主機。IP協定能夠把源主機A發送出的分組,按照首部中的目的位址,送交到目的主機B。但是,真正通信的實體不是主機,而是主機中的程序。是以,兩台主機進行通信就是兩台主機中的應用程序互相通信。IP協定隻能将分組發送到目的主機,但是這個分組還停留在主機的網絡層而沒有傳遞給主機中的應用程序。從傳輸層的角度看,通信的真正端點是主機中的程序,端到端的通信是應用程序之間的通信。
1.1.3 複用和分用
在一台主機中經常有多個應用程序同時分别和另一台主機中的多個應用程序進行通信。如圖1,主機A的應用程序AP1和主機的AP3通信,而主機A中的AP2也和主機B中的AP4通信。這表明,傳輸層具有複用和分用的功能。
複用:發送方不同的應用程序都可以使用同一個傳輸層協定傳送資料。
分用:接收方的運輸層在去掉封包的首部後能夠把這些資料正确傳遞目的應用程序。
圖1:傳輸層為互相通信的應用程序提供了邏輯通信
1.2 傳輸層協定
1.2.1 協定劃分
傳輸層主要使用以下兩種協定:
- 使用者資料報協定 UDP(User Datagram Protocol)——提供無連接配接的、盡最大努力的資料傳輸服務(不保證資料傳輸的可靠性),其傳輸的機關是使用者資料報。
- 傳輸控制協定 TCP(Transmission Control Protocol)——提供面向連接配接的、可靠的資料傳輸服務,其傳輸的機關是封包段。
UDP與TCP的差別:
- UDP在傳輸資料之前不需要先建立連接配接。目的主機收到UDP封包後,不需要給出确認,UDP提供不可靠服務。有單點傳播,廣播,多點傳播的功能。頭部開銷小,傳輸資料封包高效。
- TCP提供面向連接配接的服務。傳輸資料之前必須先建立連接配接,資料傳輸結束後要釋放連接配接。TCP不提供廣播或多波服務。因為TCP提供可靠的、面向連接配接的運輸服務,是以需要連接配接管理、确認、流量控制等。提供全雙工通信,允許通信的雙方應用程式在任何時候同時發送資料。
圖2:使用 UDP和TCP協定的各種應用和應用層協定
1.2.2 協定端口号
協定端口号辨別了一個主機上進行通信的不同的應用程式。
為什麼需要端口号?
一台擁有IP位址的主機可以提供許多服務,比如Web服務、FTP服務、SMTP服務等,這些服務完全可以通過1個IP位址來實作。因為IP 位址與網絡服務的關系是一對多的關系,不能隻靠IP位址來區分不同的服務。實際上是通過“IP位址+端口号”來區分不同的服務的。
在TCP/IP協定,用源IP、源端口号、目的IP、目的端口号、協定号這樣一個五元組來辨別一個通信。
端口号劃分:
- 服務端使用的端口号:
- 系統端口号:數值 0~1023。IANA把這些端口号指派給了TCP/IP最重要的一些應用程式,讓所有使用者知道并使用。
應用程式 FTP SSH TELNET SMTP DNS TFTP HTTP SNMP HTTPS 端口号 21 22 23 25 53 69 80 161 443
- 登記端口号:數值為 1024~49151。這類端口号是沒有被系統端口号的應用程式使用。使用這類端口号需要在IANA按照規定的手續等級,以防止重複。
- 用戶端使用的端口号:數值為 1024~49151。這類端口号在用戶端程序運作時動态選擇,也叫短暫端口号。這類端口号留給用戶端進行選擇暫時使用。當伺服器程序收到客戶程序的封包時,就知道客戶程序使用的端口号,因而可以把資料發送給客戶程序。通信結束後,剛才已經使用過的用戶端口号就不存在,這個端口号可以供給其他客戶程序使用。
兩個問題
- 一個程序是否可以bind多個端口号?
可以,因為一個程序可以打開多個檔案描述符,而每個檔案描述符對應一個端口号,是以一個程序可以綁定多個端口号。
- 一個端口号是否可以被多個程序bind?
不可以,如果程序先綁定一個端口号,然後在fork一個子程序,這樣的話就可以實作多個程序綁定一個端口号,但是兩個不同的程序綁定同一個端口是不可以的。
2. 使用者資料報協定 UDP
2.1 UDP 概述
UDP提供無連接配接,盡最大努力傳遞的資料傳輸服務。
2.1.1 主要特點
- UDP是無連接配接的,即發送資料不需要建立連接配接(發送資料結束也無需釋放連接配接),是以減少了開銷和發送資料之前的時延。
- UDP使用盡最大努力傳遞,既不保證可靠傳遞,是以主機不需要維持複雜的連接配接狀态表。
- UDP是面向資料封包的。發送方的UDP對應用程式叫下來的封包,再添加首部後就向下傳遞IP層。UDP對應用層交下來的封包,既不拆分,也不合并,而是保留這些封包的邊界。
- UDP沒有擁塞控制,是以網絡出現擁塞不會使源主機的發送速率降低。
- UDP支援一對一、多對一、一對多和多對多的互動通信。
- UDP首部開銷小,隻有8個位元組,比TCP的20個位元組的首部要短。
- UDP沒有真正意義上的發送緩沖區,調用sendto會直接交給核心,由核心将資料傳給網絡層協定進行後續的傳輸動作;UDP具有接收緩沖區,但這個接收緩沖區不能保證收到的UDP報的順序和發送UDP報的順序一緻;如果緩沖區滿了,再到達的UDP資料就會被丢棄。
2.2 UDP首部格式
使用者資料報UDP有兩個字段:資料字段和首部字段。如圖3,各字段含義如下:
- 源端口 源端口号。在需要對方回信時選用。不需要時可以全0。
- 目的端口 目的端口号。終點傳遞封包時必須使用。
- 長度 UDP使用者資料報的長度,其最小值為8(僅有首部)
- 檢驗和 檢測UDP使用者資料報在傳輸中是否有錯。有錯就丢棄。
圖3:UDP使用者資料報格式
當傳輸層從IP層收到UDP資料報時,就根據首部中的目的端口,把UDP資料報通過相應的端口,上交最後的終點——應用程序。如果接收方UDP發現收到的封包中的目的端口号不正确,就丢棄該封包,并由網際控制封包協定ICMP發送“端口不可達”差錯封包給發送方。
3. 傳輸控制協定 TCP
3.1 TCP 概述
TCP提供一種面向連接配接的 、可靠的位元組流服務。
3.1.1 主要特點
- TCP是面向連接配接的傳輸層協定。應用程式在使用TCP協定之前,必須先建立TCP連接配接。在傳送資料完畢後,必須釋放已經建立的TCP連接配接。
- 每一條TCP連接配接隻能有兩個端點,每一條TCP連接配接隻能是點對點的。
- TCP提供可靠傳遞的服務。通過TCP連接配接傳送的資料,無差錯、不丢失、不重複,并且能按序到達。
- TCP提供全雙工通信。TCP允許通信雙方的應用程序在任何時候都能發送資料。TCP連接配接的兩端均設有發送緩存和接收緩存,用來臨時存放雙向通信的資料。
- 在發送時,應用程式把資料傳送給TCP的緩存後,就可以自己做自己的事,而TCP在合适的時候把資料發送出去。
- 在接收時,TCP把收到的資料放入緩存,上層應用程式在合适的時候讀取緩存中的資料。
- 面向位元組流。TCP中的“流”指的是流入到程序或從程序流出的位元組序列。雖然應用程式和TCP互動是一次一個資料塊,但TCP把應用程式交下來的資料僅僅看成是一連串的無結構的位元組流。
- TCP不保證接收方的應用資料塊和發送方所發出的資料塊具有對應的大小關系(例如,發送方的應用程式給發送方的 TCP 10個資料塊,接TCP的接收方可能隻用了4個資料塊就把收到的位元組流傳遞上層的應用程式)。
- TCP不關心應用程序一次把多長的封包發送到TCP緩存中,而是根據對方給出的視窗值和目前網絡的擁塞程度來決定一個封包段應包含多少個位元組。如果應用程式發送的資料塊太長,TCP 會将其劃分短一些在傳送。如果應用程式一次隻發一個位元組,TCP收端也可以累積到足夠多的位元組發送給收端的應用程式。
圖4:TCP面向位元組流
3.2 TCP 首部格式
TCP雖然是面向位元組流的,但TCP傳送的資料單元卻是封包段。一個TCP封包段分為首部和資料兩部分,而TCP的全部功能在首部的各個字段展現。
TCP封包段首部前20個位元組是固定的,後面有4n個位元組是根據需要而增加的選項。是以TCP首部的最小長度是20個位元組。
圖5:TCP 封包段的首部格式
各字段意義如下:
- 源端口和目的端口:各占2 個位元組, 分别寫入源端口号和目的端口号。
- 序号:占4 位元組。若序号達到最大,則下一個序号右回到0。在一個TCP連接配接中傳送的位元組流中的每一個位元組都按順序編号。首部中的序号字段值指的是本封包段所發送的資料的第一個位元組的序号。
例如, 一封包段的序号字段值是301, 而攜帶的資料共有100位元組。這就表明: 本封包段的資料的第一個位元組的序号是301, 最後一個位元組的序号是400。顯然, 下一個封包段(如果還有的話) 的資料序号應當從401 開始, 即下一個封包段的序号字段值應為401。這個字段的名稱也叫做“ 封包段序号”。
- 确認号:占4個位元組,是期望收到對方下一個封包段的第一個資料位元組的序号。若确認号 = N,則表明:到序号N-1為止的所有資料都已經正确收到。
例如, B 正确收到了A 發送過來的一個封包段,其序号字段值是501,而資料長度是200 位元組(序号501 - 700),這表明B 正确收到了A 發送的到序号700 為止的資料。是以,B 期望收到A 的下一個資料序号701,于是B 在發送給A 的确認封包段中把确認号置為701。請注意,現在的确認号不是501,也不是700,而是701。
- 資料偏移:占4位,占4 位,它指出TCP 封包段的資料起始處距離TCP封包段的起始處有多遠。這個字段實際上是指出TCP 封包段的首部長度。TCP 首部的最大長度為60個位元組。
- 保留:占6 位,保留為今後使用,但目前應置為0。
- 标志位,各占1位。
- 緊急URG:當URG=1時,表明緊急指針字段有效。它告訴系統此封包段中有緊急資料, 應盡快傳送(相當于高優先級的資料),而不要按原來的排隊順序來傳送。當URG 置1時,發送應用程序就告訴發送方的TCP 有緊急資料要傳送。于是發送方TCP 就把緊急資料插入到本封包段資料的最前面, 而在緊急資料後面的資料仍是普通資料。這時要與首部中緊急指針(Urgent Pointer)字段配合使用。
- 确認ACK:僅當ACK= 1時确認号字段才有效。當ACK=0時,确認号無效。TCP 規定,在連接配接建立後所有傳送的封包段都必須把ACK 置1。
- 推送PSH:當兩個應用程序進行互動式的通信時,有時在一端的應用程序希望在鍵入一個指令後立即就能夠收到對方的響應。在這種情況下,TCP就可以使用推送(push)操作。這時,發送方TCP 把PSH置1, 并立即建立一個封包段發送出去。接收方TCP收到PSH= 1的封包段,就盡快地(即“推送“ 向前)傳遞接收應用程序,而不再等到整個緩存都填滿了後再向上傳遞。
- 複位RST:當RST= 1時,表明TCP連接配接中出現嚴重差錯(如由于主機崩潰或其他原因),必須釋放連接配接,然後再重建立立運輸連接配接。RST置1還用來拒絕一個非法的封包段或拒絕打開一個連接配接。RST 也可稱為重建位或重置位。
- 同步SYN:在連接配接建立時用來同步序号。當SYN=1而ACK=0時,表明這是一個連接配接請求封包段。對方若同意建立連接配接, 則應在響應的封包段中使SYN=1 和ACK=1。是以,SYN置為1就表示這是一個連接配接請求或連接配接接受封包。
- 終止FIN:用來釋放一個連接配接。當FIN= 1時,表明此封包段的發送方的資料已發送完畢, 并要求釋放傳輸連接配接。
- 視窗:占2位元組。視窗指的是發送本封包段的一方的接收視窗(而不是自己的發送視窗)。視窗值告訴對方:從本封包段首部中的确認号算起, 接收方目前允許對方發送的資料量(以位元組為機關)。之是以要有這個限制,是因為接收方的資料緩存空間是有限的。總之,視窗值作為接收方讓發送方設定其發送視窗的依據。
- 檢驗和:占2 位元組。檢驗和字段檢驗的範圍包括首部和資料這兩部分。
- 緊急指針:占2位元組。緊急指針僅在URG= 1時才有意義,它指出本封包段中的緊急資料的位元組數(緊急資料結束後就是普通資料)。是以,緊急指針指出了緊急資料的末尾在封包段中的位置。當所有緊急資料都處理完時,TCP就告訴應用程式恢複到正常操作。值得注意的是,即使視窗為零時也可發送緊急資料。
- 選項:長度可變,最長可達40位元組。當沒有使用“ 選項“ 時,TCP的首部長度是20 位元組。最後的填充字段僅僅是為了使整個TCP首部長度是4位元組的整倍。
- 最大封包段長度 MSS:MSS是每一個TCP封包段中的資料字段的最大長度。
- 視窗擴大選項
- 時間戳選項
- 選擇确認選項
3.3 TCP 可靠傳輸的實作
為了講述可靠傳輸原理的友善,我們假定資料傳輸隻在一個方向進行,即A發送資料,B給出确認。
3.3.1 确認應答機制
a. 确認應答 ACK
TCP為了保證封包傳輸的可靠,就給每個包一個序号,同時序号也保證了傳送到接收端實體的包的按序接收。然後接收端實體對已成功收到的位元組發回一個相應的确認(ACK)。
- 每一個ACK都帶有對應的确認。
- 确認号告訴發送端已經收到了哪些資料。
- 确認号代表接收端下次需要接收的資料的序号。
- 确認号代表發送端要發送的資料的序号。
如圖6,主機B給主機A發送了确認号為1001,說明:序号為1000的資料(包括1000)主機B已經收到了,下一次主機A需要發送序号為1001的資料。
圖6:确認應答機制
b. 選擇确認 SACK(了解)
若收到的封包無差錯,隻是未按序到達,通過選擇确認 SACK機制可以隻重傳缺少的資料,而不重傳已經正确到達接收方的資料。
如果需要使用選擇确認 SACK,那麼建立TCP連接配接時,就要在TCP首部中加上SACK選項,通信雙方需要約定好。确認号用法不變,SACK選項用來報告收到的不連續的位元組塊的邊界。
因為,SACK文檔未明确指出發送方如何響應SACK,是以目前仍然是重傳所有未被确認的資料塊。
3.3.2 滑動視窗
a. 為什麼采用滑動視窗?
為了提高傳輸效率,發送方可以不使用低效率的停止等待協定,而是采用流水線傳輸。流水線傳輸就是發送方可以連續發送多個分組,不必每發完一個分組就停下來等對方确認。這種方式可以獲得很高的信道使用率。
TCP協定采用滑動視窗來提高信道使用率,進而提高傳輸效率。
TCP 的滑動視窗是以位元組為機關的。
b. 滑動視窗的實作
如圖7,現假定A收到B發來的确認封包段,其中視窗是20位元組,而确認号是31(表明B期望收到的下一個序号是31,而序号30為止的資料已經收到了)。
圖7:A的發送視窗
發送視窗的特點:
- 發送視窗表示:在沒有收到B的确認的情況下,A可以連續把視窗内的資料都發送出去。已經發送,但未收到确認的資料都必須暫時保留,以便逾時重傳時使用。
- A的發送視窗不能超過B的接收視窗數值。
- 發送方的發送視窗大小還要受到網絡擁塞程度的制約。
- 發送視窗後沿的後面部分表示已發送且已收到了确認,資料無需保留;發送視窗前沿的前面部分表示不允許發送的,因為接收方沒有為這部分資料保留臨時存放的緩存空間。
- 發送視窗的位置由視窗前沿和後沿的位置共同确定。
發送視窗的變化:
- 發送視窗後沿的變化:
- 不動 —— 沒有收到新的确認。
- 前移 —— 收到了新的确認。
- 發送視窗前沿的變化:
- 向前移動
- 不動 —— 一是沒有收到新的确認,對方通知的視窗大小也不變;二是收到了新的确認但對方通知的視窗縮小了,使得發送視窗前沿剛好不動。
- 向後收縮 —— 對方通知的視窗縮小了。TCP不贊成這樣做。
如圖8,現在假定A發送了序号為31 ~ 41的資料。這時,發送視窗位置并未改變,但發送視窗内靠後面有11個位元組(灰色方框)表示已發送但未收到确認。而發送視窗内靠前面的9(42~50)個位元組是允許發送但尚未發送的。
圖8:A發送11個位元組的資料
圖9:A收到新的确認号,發送視窗向前滑動
圖10:發送視窗内的序号都已經發送,但未被确認
描述一個發送視窗的狀态需要三個指針:P1,P2和P3。指針指向位元組的序号。
- 小于P1的是已發送并已收到确認的部分, 而大于P3 的是不允許發送的部分。
- P3 - P1 = A的發送。
- P2 - P1 = 已發送但尚未收到确認的位元組數。
- P3 - P2 = 允許發送但目前尚未發送的位元組數(又稱為可用視窗或有效視窗)。
接收視窗的特點:
- 已經接收并發送确認封包的資料傳遞主機後,不必保留。
如圖8,在接收視窗外面,到30号的資料已經發送過确認并傳遞主機了。是以B可以不保留這些資料。下一個待接收的資料為31,此時B會給A發送确認封包段,封包段中的确認号為31。
- 若接收視窗内的資料沒有按序到達,即某個資料未收到(可能丢失,也可能滞留在網絡某處),B隻能對按序收到的資料中的最高序号給出确認。
如圖8,B的接收視窗收到了序号為32和33的資料,但是沒有收到序号為31的資料,這意味着這兩個資料未按序到達。是以B發送給A的确認封包段中的确認号為31(31即期望收到的序号,31之前均為已收到的的資料)。
- 資料接收成功後,滑動視窗将繼續向前滑動。
如圖9,假定B收到了序号為31的資料,并把序号為31~33的資料傳遞給主機,然後B删除這些資料。接着把接收視窗向前移動3個序号,同時給A發送确認,其中視窗值仍然為20,但确認号為34。表明B已經收到了到序号33未知的資料。
- 若資料沒有按序到達,為按序到達的資料将暫存在接收視窗中。
如圖9,B收到了序号為37,38和40的資料,但是未收到34,35,36和39的資料。是以序号為37,38和40的資料未按序到達,隻能暫存在接收視窗,并發送确認号34,等待發送視窗發送未收到資料。
- 若A的發送視窗已滿,必須停止發送,此時原因可能是這部分資料丢掉了,也可能是B收到了,但傳回的确認号滞留在網絡中,A會在一定時間後重傳這部分資料(逾時重傳)。此時B會收到重複資料,TCP協定将根據序号去重。
如圖10,A在發送完序号42~53的資料後,指針P2向前移動和P3重合。發送視窗内的序号都已經用完,但是還是沒有收到确認。由于A的發送視窗已經滿了,可用視窗已經減小到零,是以必須停止發送。為了保證可靠傳輸,A隻能認為B沒有收到這部分資料,是以在一段時間後(逾時計時器控制)就重傳這部分資料,重新設定逾時計時器,直到收到B的确認為止。A收到确認号落在發送視窗内,那麼A就可以使發送視窗繼續向前滑動,并發送新的資料
c. 視窗和緩存的關系
TCP連接配接的兩端具有接收緩存和發送緩存,發送方的應用程式把位元組流寫入TCP的發送緩存,接收方的應用程式從TCP的接收緩存中讀取位元組流。那麼緩存和視窗之間有什麼關系呢?
圖11:TCP的緩存和視窗的關系(左為發送方,右為接收方)
這裡注意兩點:
- 第一,緩存空間和序号空間都是有限的,并且都是循環使用的。
- 第二,實際上緩存或視窗中的位元組數是非常大的,圖11僅僅是示意圖,未标出具體數字。
我們先看,圖11所示的發送方情況。
發送緩存用來暫時存放:
- 發送應用程式傳送給發送方TCP準備發送的資料;
- TCP已經發送但尚未收到确認的資料。
發送視窗通常隻是發送緩存的一部分。
- 已被确認的資料應當從發送緩存中删除,是以發送緩存和發送視窗的後沿是重合的。
- 發送應用程式最後寫入發送緩存的位元組數 - 最後被确認的位元組數 = 保留在發送緩存中被寫入的位元組數。
- 發送應用程式必須控制寫入緩存的速率,不能太快,否則發送緩存就會沒有存放資料的空間。
接下來看,圖11所示的接收方情況。
接收緩存用來暫時存放:
- 按序到達的、但尚未被接收應用程式讀取的資料。
- 未按序到達的資料。
接收視窗通常隻是接收緩存的一部分。
- 如果收到的分組被檢測出由差錯,則要丢棄。
- 接收應用程式來不及讀取收到的資料,接收緩存最終會被填滿,使接收視窗減小到0。
- 接收應用程式若及時從接收緩存中讀取收到的資料,接收視窗就可以增大。但最大不能超過接收緩存的大小。
此處需要強調:
- 第一,A的發送視窗是根據B的接收視窗設定的,但在同一時刻,A的發送視窗并不總和B的接收視窗一樣大。因為通過網絡傳送視窗值需要經曆一定的時間滞後。發送方A還可能根據網絡當時的擁塞情況适當減小自己的發送視窗數值。
- TCP通常對不按序到達的資料是先臨時存放在接收視窗中,等到位元組流中所缺少的位元組收到後,再按序傳遞給上層的應用程式。
- TCP要求接收方必須有累積确認的功能,這樣可以減少開銷。接收方可以在合适的時候發送确認,也可以在自己有資料發送時把确認資訊捎帶上。需注意兩點:
- 接收方不應過分推遲發送确認,否則會導緻發送方進行不必要的重傳,浪費網絡資源。TCP标準規定,确認推遲的時間不應超過0.5秒。若接收一連串具有最大長度的封包段,需每隔一個封包段就發送一個确認。
- 捎帶确認實際上并不經常發生,因為大多數應用程式很少同時在兩個方向上發送資料。
- TCP通信是全雙工通信。通信雙方都在發送和接收封包段,雙方均有各自的發送視窗和接收視窗。
3.3.3 逾時重傳
a. 逾時重傳的含義
逾時重傳:TCP的發送方在規定時間未收到接收方的确認就要重傳已發送的封包段。
逾時重傳的原因:
- 主機A發送資料給主機B後,可能因為網絡堵塞等原因,資料無法到達主機B;
- 如果主機A在一個特定時間間隔未收到B發來的确認應答(可能由于ACK丢失了),就會進行重發。
圖12:逾時重傳原因
逾時重傳後,主機B會收到很多重複資料,TCP如何處理?
主機B收到重複資料後,TCP協定需要識别出哪些包是重複包,并将重複的包丢掉。利用序列号,可以很容易去重。
b. 如何選擇逾時重傳時間?
TCP 為了保證無論在任何環境下都能比較高性能的通信,是以會動态計算這個最大逾時時間。
- Linux中(Windows相同),逾時以500ms為一個機關進行控制,每次判定逾時重傳時間都是500ms的整數倍。
- 如果重發一次之後,仍然得不到應答,等待2*500ms後在進行重傳。
- 如果仍然得不到應答,等待4*500ms進行重傳,依次類推,以指數形式遞增。
- 累積到一定的重傳次數,TCP認為網絡或者對端主機出現異常,強制關閉連接配接。
以下進行詳細說明,如若不感興趣,可以跳過。
由于TCP下層是網際網路環境,發送的封包段可能隻經過一個高速率的區域網路,也可能經過多個低速率的網絡,并且每個IP資料報選擇的路由可能不同。如果把逾時重傳時間設定的太短,就會引起很多封包的不必要重傳,使得網絡負荷增大。但如若逾時重傳時間設定的過長,則又使網絡的空閑時間增大,降低了傳輸效率。
那麼,應該如何選擇?
TCP采用一種自适應算法,它記錄一個封包段發出的時間,以及收到相應的确認的時間。這兩個時間之差就是封包段的往返時間RTT。
R T T = 報 文 段 發 出 時 間 − 收 到 相 應 确 認 的 時 間 RTT=封包段發出時間-收到相應确認的時間 RTT=封包段發出時間−收到相應确認的時間
TCP保留RTT的一個權重平均往返時間RTTs(稱為平滑的往返時間,S:Smoothed,表示平滑)。每當第一次測量到RTT樣本,RTTs值就取為所測量到的RTT樣本值。以後沒測量到一個新的RTT樣本,按下式重新計算一次RTTs。
新 的 R T T s = ( 1 − α ) × ( 舊 的 R T T s ) + α × ( 新 的 R T T 樣 本 ) 新的RTT_s=(1-\alpha)\times(舊的RTT_s)+\alpha\times(新的RTT樣本) 新的RTTs=(1−α)×(舊的RTTs)+α×(新的RTT樣本)
在上式中, 0 ≤ α < 1 0\leq\alpha<1 0≤α<1。
- 若 α \alpha α 接近0,表示新的RTTs值和舊的RTTs值相比變化不大,而對新的RTT樣本影響不大(RTT值更新較慢)。
- 若 α \alpha α 接近1,則表示新的RTTs值受新的RTT樣本影響較大(RTT值更新較快)。
- 标準的 RFC 6298推薦的 α \alpha α 值為 1 / 8 1/8 1/8,即0.125。
顯然,逾時計時器設定的逾時重傳時間 RTO 應略大于RTTs。RFC6298建議使用下列計算RTO:
R T O = R T T S + 4 × R T T D RTO=RTT_S+4\times RTT_D RTO=RTTS+4×RTTD
RTTD是RTT的偏差的權重平均值,它與RTTs和新的RTT樣本隻差有關。第一次測量時,RTTD值取為測量到的RTT樣本值的一半。後續計算公式如下:
新 的 R T T D = ( 1 − β ) × ( 舊 的 R T T D ) + β × ∣ R T T S − 新 的 R T T 樣 本 ∣ 新的RTT_D = (1 - \beta)\times(舊的RTT_D) + \beta \times |RTT_S-新的RTT樣本| 新的RTTD=(1−β)×(舊的RTTD)+β×∣RTTS−新的RTT樣本∣
這裡 β \beta β是一個小于1的系數,它的推薦值是 1 / 4 1/4 1/4,即 0.25。
這裡有一個新的問題:
圖13:收到的确認是對哪一個封包段的确認?
如圖13,假如A發送出封包段後,在重傳時間内未收到确認。于是重傳封包段。一段時間後收到了确認封包段。那麼問題來了:如何判斷此确認封包段是對先發送的封包段的确認,還是對後來重傳的封包段的确認?正确的判斷對确定權重平均值RTTS的值關系很大。
Karn提出一個算法:在計算權重平均RTTS時,隻要封包段重傳了,就不采用其往返時間樣本。這樣得出的權重平均RTTS和RTO就較準确。由于這樣可能會導緻逾時重傳時間無法更新,是以對此算法修正。方法是:封包段每重傳一次,就把逾時重傳時間RTO增大一些。典型的做法是新的重傳時間為舊的重傳時間的2倍。當不再發生封包段的重傳時才根據RTO公式計算逾時重傳時間。
3.4 流量控制
3.4.1 利用滑動視窗實作流量控制
接收端處理資料的速度是有限的。如果發送端發送資料太快, 導緻接收端的緩沖區被打滿, 這個時候如果發送端繼續發送,就會造成丢包, 繼而引起丢包重傳等等一系列連鎖反應。通過流量控制可以解決這一問題。
流量控制的目的:讓發送方的發送速率不要太快,要讓接受方來得及接收。
實作方法:利用滑動視窗機制。
- 接收端将自己可以接收的緩沖區大小放入 TCP 首部中的“視窗大小”字段,通過ACK端通知發送端;
- 視窗大小字段越大,說明網絡的吞吐量越高;
- 接收端一旦發現自己的緩沖區快滿了,就會将視窗大小設定成一個更小的值通知給發送端;發送端接受到這個視窗之後,就會減慢自己的發送速度;
- 如果接收端緩沖區滿了,就會将視窗置為0;這時發送方不再發送資料,但是需要定期發送一個零視窗探測封包段(僅攜帶1位元組資料),使接收端把視窗大小告訴發送端。
現在根據下圖14,詳細說明這一機制。
圖14:利用滑動視窗進行流量控制(seq:序号 rwnd:接收端視窗值)
如圖14,A向B發送資料。在TCP建立連接配接時,B會告訴A自己的接收視窗大小是多少?圖中未表明這一過程。通過該圖,我們可以看出B的rwnd(receiver window)接收視窗大小為400。
- 如圖14,接收方的主機B進行了三次流量控制。第一次為300,第二次為100,第三次為0。rwnd為0意味着接收方不能繼續發送資料,此狀态将維持到主機B重新發送一個新的視窗值為止。
- 需注意:确認位ACK = 1時,确認号ack才有效。
- 若B向A發送了零視窗的封包段之後,B的接收緩存又有了一些空間,于是B向A發送視窗值,但是這個封包段丢失了,此時A一直等待B發送的非零視窗的通知,而B也一直等A發送的資料。如果沒有其他措施,此時會造成死鎖局面。
- TCP為每一個連接配接設有一個持續計時器。隻要TCP連接配接的一方收到對方的零視窗通知,就啟動持續計時器。持續計時器設定的時間到期,就發送一個零視窗探測封包段。對方就在确認這個探測封包段給出現在的視窗值。如果視窗值仍然為0,那麼收到這個封包段的一方就重新設定持續計時器。如果視窗值不為0,死鎖的僵局就可以打破了。
3.4.2 TCP 的傳輸效率
這裡說兩個影響TCP傳輸效率的問題。
問題一:如何控制TCP發送封包段的時機?
應用程式把資料傳送到TCP的發送緩存之後,剩下的發送任務就由TCP控制。
可以用不同的機制來控制TCP封包段的發送時機。
- 第一種機制,TCP維持一個變量,它等于最大封包段長度MSS。隻要緩存中存放的資料達到MSS位元組時,就組成一個TCP封包發送出去。
- 第二種機制,由發送方的應用程序指明要求發送封包段,即TCP支援的推送(PUSH)操作。
- 第三種機制,發送方的一個計時器期限到了,這時就把目前已有的緩存資料裝入封包段(長度不能超過MSS)發送出去。
雖然有以上三種機制,但是如何控制TCP發送封包段的時機?仍然是一個較為複雜的問題。
在TCP的實作中廣泛使用Nagle算法。算法如下:
若發送應用程序把要發送的資料逐個位元組地送到TCP 的發送緩存, 則發送方就把第一個資料位元組先發送出去, 把後面到達的資料位元組都緩存起來。當發送方收到對第一個資料字元的确認後, 再把發送緩存中的所有資料組裝成一個封包段發送出去, 同時繼續對随後到達的資料進行緩存。隻有在收到對前一個封包段的确認後才繼續發送下一個封包段。當資料到達較快而網絡速率較慢時, 用這樣的方法可明顯地減少所用的網絡帶寬。
Nagle算法還規定:
當到達的資料已達到發送視窗大小的一半或已達到封包段的最大長度時, 就立即發送一個封包段。這樣做, 就可以有效地提高網絡的吞吐量。
問題二:糊塗視窗綜合征
另一個問題:做糊塗視窗綜合征(silly window syndrome),也會導緻TCP的性能變差。
設想一種情況: TCP 接收方的緩存已滿, 而互動式的應用程序一次隻從接收緩存中讀取1個位元組(這樣就使接收緩存空間僅騰出1個位元組), 然後向發送方發送确認,把視窗設定為1個位元組(但發送的資料報是40位元組長)。接着, 發送方又發來1個位元組的資料(請注意, 發送方發送的IP資料報是41位元組長)。接收方發回确認, 仍然将視窗設定為1個位元組。這樣進行下去, 使網絡的效率很低。
解決方法:
讓接收方等待一段時間, 使得或者接收緩存已有足夠空間容納一個最長的封包段, 或者等到接收緩存已有一半空閑的空間。隻要出現這兩種情況之一, 接收方就發出确認封包, 并向發送方通知目前的視窗大小。此外, 發送方也不要發送太小的封包段而是把資料積累成足夠大的封包段, 或達到接收方緩存的空間的一半大小。
3.5 擁塞控制
3.5.1 擁塞控制的原理
a. 基本概念
擁塞:在計算機網絡中的鍊路容量(即帶寬)、交換節點中的緩存和處理機等,都是網絡的資源。在某段時間。若對網絡中某一資源的需求超過了該資源所能提供的可用部分,網絡的性能就會變差。這種情況就叫做擁塞。出現網絡擁塞的條件如下:
∑ 對 資 源 的 需 求 > 可 用 資 源 \sum對資源的需求 > 可用資源 ∑對資源的需求>可用資源
網絡擁塞的原因:
網絡擁塞往往是由許多因素引起的。
- 某個結點緩存的容量太小時,到達該點的分組因無存儲空間暫存而不得不被丢棄。
- 處理機處理的速度太慢。
- 擁塞常趨于惡化。如路由器沒有足夠緩存空間,就會丢棄一部分分組。源點會重發這部分分組,甚至重傳多次,這樣會導緻更多的分組流入網絡和被路由器丢棄。此時,擁塞引起的重傳加劇了網絡的擁塞。
擁塞控制與流量控制的差別:
- 擁塞控制的目的:擁塞控制就是防止過多的資料注入到網絡中,這樣可以使網絡中的路由器或鍊路不至于過載。
- 擁塞控制所做的前提是:網絡能夠承受現有的網絡負荷。
- 擁塞控制是一個全局性的過程,涉及到所有的主機、所有的路由器,以及與降低網絡傳輸性能有關的所有因素。
- TCP連接配接的兩端隻要遲遲收不到對方的确認資訊,就會猜想目前網絡可能出現擁塞,但無法獲知擁塞位置和擁塞的原因。
- 流量控制是指點對點通信量的控制,是一個端到端的問題。
- 流量控制所要做的就是要抑制發送端發送資料的速率,以便使接收端來得及接收。
擁塞控制需要付出代價:
- 首先需要擷取網絡内部流量分布的資訊。
- 在實施控制時,還需要在結點之間交換資訊和各種指令,以便選擇控制的政策和實施控制。
- 有時需要将一些資源(如緩存、帶寬等)配置設定給個别使用者或一類别的使用者單獨使用,這将導緻網絡不能更好的實作共享。
b. 擁塞控制的作用
圖15:擁塞控制所起的作用
- 橫坐标 ——提供的負載,代表機關時間内輸入給網絡的分組數量。提供的負載也稱輸入負載或者網絡負載。
- 縱坐标 —— 吞吐量,代表機關時間内從網絡輸出的分組數目
從圖中我們可以看出:
- 理想擁塞控制的網絡,吞吐量飽和之前,網絡吞吐量等于提供的負載。當提供的負載超過一定限度,因為資源有限,吞吐量達到飽和。此時表明提供的負載中有一部分丢失。但在理想的擁塞控制作用下,網絡吞吐量依然維持在其所能達到的最大值。
- 實際網絡中,随着提供的負載增大,網絡吞吐量的增長速率逐漸減小。也就意味着,網絡吞吐量未達到飽和之前,一部分輸入分組被丢棄。
- 無擁塞控制的網絡,當網絡的吞吐量明顯小于理想的吞吐量時,網絡就進入了輕度擁塞的狀态;當提供的負載達到某一數值時,網絡的吞吐量随着提供的負載的增大而下降,這時網絡就進入了擁塞狀态。當提供的負載繼續增大到某一數值時,網絡的吞吐量就下降為0,網絡已經無法工作,這就是所謂的死鎖。
3.5.2 擁塞控制方法
TCP進行擁塞控制的算法有四種:慢開始、擁塞避免、快重傳、快恢複。通過這四種算法的合作,達到對網絡擁塞控制的目的。
發送方如何知道網絡發生了擁塞?
判斷網絡擁塞的依據就是出現了逾時。一般情況下,少量丢包判斷為重傳,大量丢包就認為網絡堵塞。
擁塞控制也稱為基于視窗的擁塞控制發送。為此,發送方維持一個叫做擁塞視窗cwnd的狀态變量。擁塞視窗的大小取決于網絡的擁塞程度,并且動态的變化。發送方讓自己的發送視窗等于擁塞視窗。
發送方控制擁塞視窗的原則:隻要網絡沒有出現擁塞,擁塞視窗就可以再增大一些,以便把更多的分組發送出去,這樣就可以提高網絡的使用率。隻要網絡出現擁塞且有可能出現擁塞,就必須把擁塞視窗減小一些,以減少網絡中的分組數,以便緩解出現的擁塞。
慢開始算法的思路:當主機開始發送資料時,由于并不清楚網絡的負荷情況呢,是以如果立即把大量資料位元組注入網絡,那麼可能會引起網絡發生擁塞。較好的方法是先探測一下,即由小到大逐漸增大發送視窗,也就是說,由小到大逐漸增大擁塞視窗數值。
初始擁塞視窗cwnd設定為不超過2至4個最大封包段SMSS的數值。具體規定如下:
- 若SMSS > 2190位元組,則設定初始擁塞視窗cwnd = 2 * SMSS位元組,且不能超多2個封包段。
- 若1095位元組 < SMSS <= 2190位元組,則設定初始擁塞視窗cwnd = 3 * SMSS位元組,且不得超過3個封包段。
- 若SMSS <= 1095位元組,則設定初始擁塞視窗cwnd = 4 * SMSS位元組,且不得超過4個封包段。
慢開始規定,在每收到一個對新的封包段的确認後,可以把擁塞視窗增加最多一個SMSS的數值。就是
擁 塞 窗 口 c w n d 每 次 的 增 加 量 = m i n ( N , S M S S ) 擁塞視窗cwnd每次的增加量 = min(N, SMSS) 擁塞視窗cwnd每次的增加量=min(N,SMSS)
其中N是原先未被确認的,但現在被剛收到的确認封包段所确認的位元組數。
圖16:慢開始示意圖
如圖16,一開始發送方設定cwnd=1,發送M1,接收方收到後确認M1。發送方收到确認後,把cwnd增大到2,于是發送方繼續發送M2和M3。接受方收到後确認M2和M3。發送方每收到一個接收方的确認,就将發送方的擁塞視窗加1,以你發送方在收到兩個确認後,cwnd從2變為了4。使用慢開始算法後,每經過一個傳輸輪次,擁塞視窗cwnd就加倍。
傳輸輪次:一個傳輸輪次所經曆的時間就是往返時間RTT,更加強調,把擁塞視窗cwnd所允許發送的封包段度連續發送出去,并收到已發送的最後一個位元組的确認。
如圖16,cwnd = 4 時,往返時間 RTT 就是發送方連續發出4個封包段并收到這4個封包段的确認,總共經曆的時間。
慢開始的"慢"并不是指cwnd的增長速率慢,而是指初始慢,但增長速率特别快。
為了防止擁塞視窗cwnd增長過大引起網絡擁塞,還需要設定一個慢開始門限 ssthresh狀态變量。慢開始門限ssthresh的用法如下:
- 當 cwnd < ssthresh時,使用慢開始算法。
- 當 cwnd > ssthresh時,停止使用慢算法而改用使用擁塞避免算法。
- 當 cwnd = ssthresh時,既可以使用慢開始算法,也可以使用擁塞避免算法。
擁塞避免算法:讓擁塞視窗 cwnd 緩慢的增大,即每經過一個往返時間就把發送方的擁塞視窗 cwnd 加1,而不是想慢開始算法那樣加倍增長。
注意:擁塞避免是指把擁塞視窗控制為線性規律增長,使網絡比較不容易出現擁塞。
有時,個别封包會在網絡中丢失,但實際上網絡并未發生擁塞。如果發送方遲遲收不到确認,就會産生逾時,就會誤認為網絡發生了擁塞這就導緻發送方錯誤的啟動慢開始,把擁塞視窗cwnd設定為1,是以降低了傳輸效率。采用快重傳算法可以讓發送方盡早知道發生了個别封包段的丢失。
快重傳算法:首先要求接收方不要等待自己發送資料時才進行捎帶确認,而是立即發送确認,即使收到了失序的封包段也要立即發出對已收到的封包段的重複确認。快重傳算法規定,發送方隻要一連收到3個重複确認,就知道接收方沒有收到封包段,因而應當立即進行重傳。
圖17:快重傳示意圖
因為快重傳算法,發送方知道現在隻是丢失個别封包段。于是不啟動慢開始,而是執行慢恢複算法。
慢恢複算法:當發送方收到三個重複的确認,就調整門限值ssthresh = cwnd / 2,然後設定擁塞視窗cwnd = ssthresh,并開始執行擁塞避免算法。
接下來通過具體例子來說明擁塞控制的過程。現假定TCP的發送視窗等于擁塞視窗。
圖18:TCP擁塞視窗cwnd在擁塞控制時的變化情況
如圖18,TCP開始連接配接時進行初始化,把擁塞視窗設定為1,慢開始門限的初始值設定為16個封包段 ,即ssthresh = 16。
- 在執行慢開始算法時,發送方沒收到一個對新封包段的确認ACK,就把擁塞視窗值加1,然後開始下一輪傳輸。是以擁塞視窗cwnd随着傳輸輪次按指數規律增長。
- 當擁塞視窗cwnd增長到慢開始門限值ssthresh時(圖中點1,此時擁塞視窗 cwnd = 16),就該為執行擁塞避免算法,擁塞視窗按現行規律增長。
- 當擁塞視窗cwnd = 24時,網絡出現了逾時,發送方判斷為網絡擁塞。于是調整門限值ssthresh = cwnd / 2 = 12,同時設定擁塞視窗cwnd = 1,進入慢開始階段。
- 接着,接着當擁塞視窗cwnd = ssthresh時,改為執行擁塞避免算法,擁塞視窗按線性規律增大。
- 當擁塞視窗cwnd= 16時,發送方連收3個對重複封包段的确認(3-ACK)。這是因為出現了個别封包段丢失,TCP采用快重傳算法,提醒發送方重傳丢失封包段,防止接收方錯誤判斷為網絡擁塞。
- 在點4,發送方知道丢失了個别的封包段。于是不啟動慢開始,而是執行快恢複算法。這是發送方調整門限值ssthresh = cwnd/2=8,同時設定擁塞視窗cwnd = ssthresh = 8,并開始執行擁塞避免算法。
圖18沒有說明慢開始階段如果出現了網絡擁塞或者出現3-ACK,發送方應采取什麼措施。下圖給出發送方應采取什麼措施。
圖19:TCP的擁塞控制流程圖
注意:發送方的發送視窗大小一定不能超過接收方的接收視窗大小。若将擁塞控制和流量控制一起考慮,發送方的視窗的上限值應當取為接收方視窗rwnd和擁塞視窗cwnd中較小的一個。
發 送 方 窗 口 的 上 限 值 = M i n ( r w n d , c w n d ) 發送方視窗的上限值 = Min(rwnd, cwnd) 發送方視窗的上限值=Min(rwnd,cwnd)
- rwnd < cwnd 時,是接收方的接受能力限制發送方視窗的最大值。
- cwnd < rwnd 時,則是網絡的擁塞程度限制發送方視窗的最大值。
- 也就是說,rwnd 和 cwnd 中數值較小一個,控制了發送方發送資料的速率。
3.6 延遲應答和捎帶應答
3.6.1 延遲應答
資料傳輸的時候,發送端給接收端發送資料,接收端給發送端發去确認應答資訊,這樣比較耗時,效率低下,延遲應答就是接收端收到資料之後,稍微等一會再應答,這樣可以提高資料的傳輸效率,因為發送端發好幾次資料,接收端隻需要一次來确認應答,這樣可以降低網絡擁塞的機率。
如果接收資料的主機立刻傳回ACK應答,這時候傳回的視窗可能比較小。
- 假設接收端緩沖區為1M,一次收到了500K的資料; 如果立刻應答, 傳回的視窗就是500K;
- 但實際上可能處理端處理的速度很快,10ms之内就把500K資料從緩沖區消費掉了;
- 在這種情況下,接收端處理還遠沒有達到自己的極限,即使視窗再放大一些, 也能處理過來;
- 如果接收端稍微等一會再應答,比如等待200ms再應答,那麼這個時候傳回的視窗大小就是1M;
一定要記得,視窗越大,網絡吞吐量就越大,傳輸效率就越高。我們的目标是在保證網絡不擁塞的情況下盡量提高傳輸效率;
那麼所有的包都可以延遲應答麼?肯定也不是;
- 數量限制: 每隔N個包就應答一次;
- 時間限制: 超過最大延遲時間就應答一次。
具體的數量和逾時時間,依作業系統不同也有差異;一般N取2,逾時時間取200ms;
圖20:延遲應答示意圖
3.6.2 捎帶應答
雖然有延遲應答,但是用戶端和伺服器在應用層還是一發一收,此時就會導緻資料傳輸效率低下,捎帶應答就是接收端在給發送端發送資料的時候,捎帶着向發送端發去确認應答,應答的内容是接收端已經收到發送端發送的資料。
- 意味着用戶端給伺服器說了 “How are you”,伺服器也會給用戶端回一個 “Fine, thank you”;
- 那麼這個時候ACK就可以搭順風車,和伺服器回應的 “Fine, thank you” 一起回給用戶端。
圖21:捎帶應答
3.7 TCP 連接配接管理機制
TCP是面向連接配接的協定。TCP連接配接的建立與釋放是每一次面向連接配接的通信必不可少的過程。傳輸連接配接有三個階段,即:連接配接建立、資料傳輸和連接配接釋放。
在TCP連接配接建立過程中需要解決以下三個問題:
- 要使每一方能夠确知對方的存在。
- 要允許雙方協商一些參數(如最大視窗值、是否使用視窗擴大選項和時間戳選項等)。
- 能夠對傳輸實體資源(如緩存大小、連接配接表中的項目等)進行配置設定。
TCP連接配接機制采用的是客戶-伺服器方式。主動發起連接配接建立的應用程式叫做用戶端,而被動等待連接配接的應用程序叫做伺服器。
3.7.1 建立連接配接(三次握手)
如下圖22,假定主機A運作的是用戶端程式,而主機B運作的是TCP伺服器程式。最初,用戶端和伺服器的TCP程序都處于 CLOSED(關閉)狀态。
圖22:TCP建立連接配接過程 - 三次握手
三次握手過程:
- 一開始,B的TCP伺服器程序先建立傳輸控制塊TCB,準備接受用戶端程序的連接配接請求。然後伺服器程序就處于LISTEN(監聽)狀态,等待用戶端的連接配接。A的TCP客戶程序也是首先建立傳輸控制塊TCB。
傳輸控制塊TCB存儲了每一個連結中的重要資訊。如TCP連接配接表,指向發送和接收緩存的指針,指向重傳隊列的指針,目前發送和接收序号等。
- A在打算建立TCP連接配接時,向B發送連接配接請求封包段。該封包段首部中的同步位
,同時選擇一個初始序号SYN = 1
。這時,TCP連接配接的用戶端進入seq = x
(同步已發送)狀态。TCP規定,SYN封包段(SYN_SENT
的封包段)不能攜帶資料,但是要消耗一個序号。SYN=1
- B收到A的連接配接請求封包段後,則向B發送确認。在确認封包段中應把SYN位和ACK位都置1,即
,SYN=1
,确認号為ACK = 1
,同時為自己選擇一個初始序号ack = x + 1
。這時TCP伺服器進入seq = y
(同步收到)狀态。注意,這個封包段也不能攜帶資料,但同樣要消耗一個序号。SYN_RCVD
此處,B發送給A的封包段,也可以拆分為兩個封包段。先發送一個确認封包段(ACK=1,ack=x+1),然後在發送一個同步封包段(SYN=1,seq=y)。此時變為四次握手,但效果一樣。
- TCP客戶程序收到B的确認後,還要向B發出确認。在确認封包段中,
,确認号ACK = 1
而自己的序号ack = y + 1,
。這時TCP連接配接已經建立,A進入seq = x + 1
(已建立連接配接)狀态。當B收到A的确認後,也進入ESTABLISHED
狀态。TCP标準規定,此處ACK封包段可以攜帶資料。但如果不攜帶資料則不消耗序号,在這種情況下,下一個資料封包段的序号還是ESTABLISHED
。seq = x + 1
為什麼A最後還要發送一次确認呢?
主要是為了防止已失效的連接配接請求封包段突然又傳輸到了B,因而産生錯誤。
所謂“已失效的連接配接請求封包段”是這樣産生的。
- 假如隻進行兩次握手,A發出的第一個連接配接請求封包段,因某些原因在某些網絡結點長時間滞留了。
- 但由于A在規定時間内未收到該确認封包段,是以誤以為封包丢失,于是第二次重新發送請求連接配接封包段。
- 重傳之後,連接配接建立成功,資料傳輸完畢,就釋放了連接配接。
- 釋放連接配接以後的某個時間,第一次發送的連接配接請求封包段到達了B,B誤以為A又發送了一次新的連接配接請求,于是向A發送确認封包段,同意建立連接配接。
- 這時候,新的連接配接又建立了,但是A并沒有請求建立連接配接,是以不會理睬B的确認,也不會向B發送資料。但B卻以為新的連接配接建立成功,并一直等待A發送資料。這樣白白浪費了資源。
采用三次握手,可以防止這類現象發生。例如在上述情況中,A不會向的确認發出确認。由于B收不到确認,就知道A 并沒有請求建立連接配接。
3.7.2 釋放連接配接(四次揮手)
資料傳輸完畢後,通信的雙方都可以釋放連接配接。現在A和B都處于
ESTABLISHED
狀态。
圖23:TCP釋放連接配接過程 - 四次揮手
四次揮手過程:
- 用戶端A發送連接配接釋放封包段,并停止發送資料,主動關閉TCP連接配接。A的釋放連接配接封包段首部的終止控制為
置1(FIN
),其序号FIN= 1
,它等于前面已經發送過的資料的最後一個位元組加1。此時,A進入seq = u
(終止等待1)狀态,等待B的确認。TCP規定,FIN封包段即使不攜帶資料也要消耗一個序号。FIN_WAIT_1
- B收到連接配接釋放封包段後,立即發出确認封包段。封包段中,确認位
,确認号為ACK = 1
,這個封包段自己的序号為ack = u + 1
,等于B前面已經傳送過的資料的最後一個位元組加1。然後B進入v
(關閉等待)狀态。TCP伺服器程序這時通知高層應用程序,因為A到B方向上的連接配接釋放了,這時候TCP處于半關閉狀态,即A沒有資料要發送了,但B若要發送A仍要接受。也就是說B到A的這個方向的連接配接并未關閉,這個狀态可能會持續一段時間。CLOSE_WAIT
- A收到B的确認後,就進入
(終止等待2)狀态,等待B發送的連接配接釋放封包段。FIN_WAIT_2
- 當B沒有像A發送的資料,B就需要釋放連接配接,發出連接配接釋放封包段。這時B發出連接配接釋放封包段必須使
。現假定B的序号為FIN = 1
(在半連接配接狀态B可能還發送了一些資料)。B必須重複上次已經發送過的确認号seq=w
,ack=u+1
。ACK=1
- 這時B進入
(最後确認)狀态,等待A的确認。LAST_ACK
- A在收到B的連接配接釋放封包段後,必須對此發出确認,發出确認封包段。在确認封包段中,确認位:
,确認号ACK = 1
,而自己的序号為ack=w+1
(根據TCP标準,前面發送過的FIN封包段要消耗一個序号)。然後進入seq=u+1
(時間等待)狀态。此時,TCP連接配接還沒有釋放掉。必須經過時間等待計時器設定的時間2MSL後,A才進入到TIME_WAIT
狀态。CLOSED
TCP協定規定,主動關閉連接配接的一方要處于TIME_ WAIT狀态,等待2個MSL(maximum segment lifetime)的時間後才能回到
CLOSED
狀态。
MSL稱為最長封包段壽命,RFC 793建議設定為2分鐘(規定為兩分鐘,但是各作業系統的實作不同, 在Centos7上預設配置的值是60s)。是以,從A進入到
狀态必須等待4分鐘後才能進入TIME_WAIT
CLOSED
狀态,才能開始建立下一個新的連接配接。當A撤銷相應的傳輸控制塊TCB後,就結束了這次的TCP連接配接。
可以通過
檢視MSL的值;cat /proc/sys/net/ipv4/tcp_fin_timeout
- B收到A發出的确認後,就進入
狀态。CLOSED
為什麼在TIME_WAIT狀态必須等待2MSL的時間呢?
- 第一,為了保證用戶端發送的最後一個ACK封包段能夠到達B。
這個ACK封包段有可能丢失,因而使處在
狀态的B收不到對已發送的确認封包段。B會逾時重傳這個LAST_ACK
封包段,而A就能在2MSL時間内收到這個重傳的FIN+ACK
封包段。接着A 重傳一次确認封包段, 重新啟動2MSL計時器。最後,A和B 都正常進入到FIN+ACK
狀态。如果A在CLOSED
态不等待一段時間, 而是在發送完ACK封包段後立即釋放連接配接, 那麼就無法收到B重傳的FIN+ ACK封包段, 因而也不會再發送一次确認封包段。這樣, B就無法按照正常步驟進入TIME_WAIT
狀态。CLOSED
- 第二,防止” 已失效的連接配接請求封包段” 出現在本連接配接中。
A在發送完最後一個ACK 封包段後,再經過時間2MSL, 就可以使本連接配接持續的時間内所産生的所有封包段都從網絡中消失。這樣就可以使下一個新的連接配接中不會出現這種舊的連接配接請求封包段。B隻要收到了A發出的确認,就進入CLOSED 狀态。同樣,B在撤銷相應的傳輸控制塊TCB後,就結束了這次的TCP連接配接。我們注意到,B結束TCP連接配接的時間要比A早一些。
解決
TIME_WAIT
狀态引起的bind失敗的方法?
在server的TCP連接配接沒有完全斷開之前不允許重新監聽,某些情況下可能是不合理的。伺服器需要處理非常大量的用戶端的連接配接(每個連接配接的生存時間可能很短, 但是每秒都有很大數量的用戶端來請求)。這個時候如果由伺服器端主動關閉連接配接(比如某些用戶端不活躍,就需要被伺服器端主動清理掉),就會産生大量TIME_WAIT連接配接。由于我們的請求量很大,就可能導緻TIME_WAIT的連接配接數很多, 每個連接配接都會占用一個通信五元組(源ip,源端口, 目的ip, 目的端口, 協定)。其中伺服器的ip和端口和協定是固定的。如果新來的用戶端連接配接的ip和端口号和TIME_WAIT占用的連結重複了,就會出現問題,bind失敗。
解決方法:使用
setsockopt()
函數設定socket描述符的 選項
SO_REUSEADDR
為1, 表示允許建立端口号相同但IP位址不同的多個socket描述符。
setsockopt()函數,用于任意類型、任意狀态套接口的設定選項值。盡管在不同協定層上存在選項,但本函數僅定義了最高的“套接口”層次上的選項。 想要更多了解可以去查閱文檔。
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
3.7.2 狀态轉化
接下來我們看一下完整的TCP連接配接和釋放時,用戶端與伺服器端各自的狀态轉化。
圖24:三次握手和四次揮手
服務端狀态轉化:
- [CLOSED -> LISTEN] 伺服器端調用listen後進入LISTEN狀态,等待用戶端連接配接;
- [LISTEN -> SYN_RCVD] 一旦監聽到連接配接請求(同步封包段),就将該連接配接放入核心等待隊列中,并向用戶端發送SYN确認封包。
- [SYN_RCVD -> ESTABLISHED] 服務端一旦收到用戶端的确認封包,就進入ESTABLISHED狀态,可以進行讀寫資料了。
- [ESTABLISHED -> CLOSE_WAIT] 當用戶端主動關閉連接配接(調用close),伺服器會收到結束封包段,伺服器傳回确認封包段并進入CLOSE_WAIT;
- [CLOSE_WAIT -> LAST_ACK] 進入CLOSE_WAIT後說明伺服器準備關閉連接配接(需要處理完之前的資料); 當伺服器真正調用close關閉連接配接時, 會向用戶端發送FIN,此時伺服器進入LAST_ACK狀态,等待最後一個ACK到來(這個ACK是用戶端确認收到了FIN)。
- [LAST_ACK -> CLOSED] 伺服器收到了對FIN的ACK,徹底關閉連接配接。
用戶端狀态轉化:
- [CLOSED -> SYN_SENT] 用戶端調用connect,發送同步封包段;
- [SYN_SENT -> ESTABLISHED] connect調用成功,則進入ESTABLISHED狀态,開始讀寫資料;
[ESTABLISHED -> FIN_WAIT_1] 用戶端主動調用close時,向伺服器發送結束封包段,同時進入
FIN_WAIT_1;
- [FIN_WAIT_1 -> FIN_WAIT_2] 用戶端收到伺服器對結束封包段的确認,則進入FIN_WAIT_2,開始等待伺服器的結束封包段;
- [FIN_WAIT_2 -> TIME_WAIT] 用戶端收到伺服器發來的結束封包段,進入TIME_WAIT,并發出LAST_ACK;
- [TIME_WAIT -> CLOSED] 用戶端要等待一個2MSL(Max Segment Life,封包最大生存時間)的時間,才會進入CLOSED狀态。
下圖是TCP狀态轉化的彙總。
圖25:TCP的有限狀态機
圖25中:
- 粗實線箭頭所指的是客戶程序的狀态變遷
- 粗虛線箭頭所指的是伺服器程序的狀态變遷。