整體結構流程可參考:深入淺出 TCP/IP 協議棧
好文推薦 TCP /IP協議詳解
好文推薦 鍵入網址后,期間發生了什么?
先了解整體結構,然后逐個擊破,了解細節
1. 網絡通信
中繼器:信號放大器
集線器(hub):是中繼器的一種形式,區別在於集線器能夠提供多端口服務,多口中繼器,每個數據包的發送都是以廣播的形式進行的,容易阻塞網絡。
網橋:局域網之間建立連接的橋梁,網橋是一種對幀進行轉發的技術,根據MAC分區塊,可隔離碰撞。網橋將網絡的多個網段在數據鏈路層連接起來。
交換機(switch):工作在數據鏈路層,交換機與網橋的細微差別在於交換機常常用來連接獨立的計算機,而網橋連接的目標是LAN,所以交換機的端口較網橋多。而且集線器是以廣播形式發送數據包,交換機有一個智能化的功能,可以根據相應的地址發送數據包。交換機mac表的獲取?
- 轉發過濾: 當一個數據幀的目的地址在MAC地址中有映射時,它被轉發到連接目的節點的端口而不是所有端口
- 學習功能:以太網交換機了解每一個端口相連設備的MAC地址,並將地址同相應的端口映射起來存放在交換機緩存中的MAC地址表中。
路由器:連接多個邏輯上分開的網絡,能夠判斷網絡地址和選擇IP路徑,內部存儲路由表(配置路由“”),路由表可靜態設置,亦可動態設置(根據RIP路由解析協議自動記錄),每經過一次路由器,TTL值就會減1。
ping命令使用的是ICMP協議
ARP協議: 根據IP地址獲取mac地址 (arp -a, 查看插卡的Mac地址)
RARP協議:根據mac地址獲取IP地址
IP:標記邏輯上的地址
MAC:標記實際轉發數據時的設備地址
netmask:和IP地址一起來確定網絡號,
默認網關:發送的IP不在同一個網段內,那么會把這個數據轉發給默認網關。Mac地址,在兩個設備之間通信時變化(路由器),IP地址在整個通信過程中不會發生任何變化。
DNS服務器:域名解析服務器,根據域名解析IP地址
通信領域的單工、半雙工、全雙工
- 單工通信:傳輸數據只支持數據在一個方向上傳輸(收音機)
- 半雙工:傳輸允許在兩個方向上傳輸,但是,在某一時刻,只允許數據在一個方向上傳輸,實際上是一種切換方向的單工通信放心,如:對講機,單行道
- 全雙工:允許數據同時在兩個方向上傳輸,同一時間,允許發送和接收數據。如:網卡,電話,手機,socket。軟件開發領域實現TCP的全雙工只能是通過多線程或者多進程來處理。
OSI模型
常用的以太網幀格式
- MAC幀主要有兩種格式 一種是以太網V2標准,一種是IEEE802.3,常用是前者。參考:Mentalflow同志
- Ethernet II
- DMAC(Destination Mac) 目的MAC地址 DMAC字段的長度是6個字節,標識幀的接收者
- SMAC(Source Mac) 是源MAC地址,字段長度6個字節,標識發送者
- TYPE 用於標識數據字段中包含的高層協議,該字段長度為2個字節,類型字段為0x0800的幀代表IP協議幀,類型字段值為0x0806的幀標識ARP協議幀。
- Data 是網絡層數據,最小長度必須是46字節以保證幀長至少為64字節,數據字段的最大長度是1500字節
- FCS 循環冗余校驗字段 提供了一種錯誤檢測機制,該字段長度為4個字節
- 802.3幀
- EEE802.3幀格式類似於Ethernet_II幀,只是Ethernet_II幀的Type域被802.3幀的Length域取代,並且占用了Data字段的8個字節作為LLC和SNAP字段。
- Length 字段定義了Data字段包含的字節數
- LLC 邏輯鏈路控制,由目的服務訪問點DSAP、源服務訪問點SSAP和Control字段組成
- SNAP (sub network Access Protocol)由機構代碼(Org Code)和類型(TYPE)字段組成,Org code三個字節都為0。Type字段的含義與Ethernet_II幀中的Type字段相同。
- 兩台電腦之間通信的前提是什么? 在同一網段
ping命令的過程:
以太網幀 TYPE: 0x806 代表ARP協議,先通過ARP協議在同網段中廣播獲取目的IP的MAC地址,
2 TCP/IP協議族詳解之IP協議
2.1 IP協議的功能:
- 路由尋址
- 傳遞服務,有兩個特點:不可靠,可靠性由上層協議提供,如TCP協議,無連接(IP並不維護任何關於后續數據報的狀態信息。每個數據報的處理是相互獨立的,這也就是說IP數據報可以不按發送順序接收)
- 數據包分段(Segment)和重組
2.2 IP協議頭部格式
可根據Wireshark抓包工具分析數據包含義 參考:TCP/IP協議族詳解(二)
- 版本: IP協議的版本,目前版本號為4, 下一代IP協議的版本號為6
- 首部長度: IP報頭的長度,占4位,固定部分的長度(20字節)和可變部分的長度之和,最大為60字節
- 服務類型:TOS, 目前暫沒有人使用
- 報文總長度:報頭的長度和數據部分之和
- 標識:是一個計數器,用來產生數據報的標識。唯一的標識主機發送的每一分數據報。通常每發送一個報文,它的值加一。當IP報文長度超過傳輸網絡的MTU(最大傳輸單元)時必須分片,這個標識字段的值被復制到所有數據分片的標識字段中,使得這些分片在達到最終目的地時可以依照標識字段的內容重新組成原先的數據
- 標志位:共3位。R、DF、MF三位。目前只有后兩位有效,DF位:為1表示不分片,為0表示分片。MF:為1表示“更多的片”,為0表示這是最后一片
- 片偏移:本分片在原先數據報文中相對首位的偏移位。(需要再乘以8)
- 報文生存時間:IP報文所允許的通過路由器的最大數量。沒經過一個路由器,TTL減1,當為0時,路由器將該數據報丟棄,一般是64, 當發送ICMP回顯應答時設置為最大值255
- 協議:指定IP報文所攜帶的數據使用的是什么協議。以便目的主機IP層知道要將數據上交到那個進程(不同的協議有專門不同的進程處理),和端口號類型,此處采用協議好。TCP的協議號為6,UDP的協議號為17, ICMP的協議號為1,IGMP的協議號為2.
- 首部校驗和:計算IP頭部的校驗和,檢查IP報頭的完整性
應用程序使用TCP/IP協議傳輸數據時,數據要被送入協議棧經過逐層封裝,最后作為比特流在媒體上傳送,其過程示意圖如下所示:
注:從上圖可以看到以太網幀的數據長度是有大小限制的,這個最大值稱為 MTU,所以當 IP 數據包長度大於 MTU 時會被拆成多個幀傳輸,稱為 “IP分片”-------Mr.su Blog
2.2 IP路由尋址協議
推薦閱讀:如何形象說明路由協議RIP和OSPF?
RIP
RIP( Routing Information Protocol )路由信息協議
是在一個AS系統中使用地內部路由選擇協議,是個非常簡單的基於距離向量路由選擇的協議。 它路由器生產商之間使用的第一個開放標准,是最廣泛的路由協議,在所有IP路由平台上都可以得到。當使用RIP時,一台Cisco路由器可以與其他廠商的路由器連接。
RIP 主要設計來利用同類技術與大小適度的網絡一起工作,因此通過速度變化不大的接線連接。RIP 比較適用於簡單的校園網和區域網,不適於復雜網絡的情況
RIP的算法簡單,距離向量路由選擇算法,, RIP使用UDP數據包更新路由信息。路由器每隔30s更新一次路由信息,如果在180s內沒有收到相鄰路由器的回應,則認為去往該路由器的路由不可用,該路由器不可到達。如果在240s后仍未收到該路由器的應答,則把有關該路由器的路由信息從路由表中刪除。
RIP具有以下特點:
不同廠商的路由器可以通過RIP互聯;
配置簡單; • 適用於小型網絡(小於15跳);
RIPv1不支持VLSM;
需消耗廣域網帶寬;
需消耗CPU、內存資源。
OSPF
OSPF( Open Shortest Path First,開放最短路徑優先)
開放式最短路徑優先(Open Shortest Path First,OSPF)協議是一種為IP網絡開發的內部網關路由選擇協議,由IETF開發並推薦使用。OSPF協議由三個子協議組成:Hello協議、交換協議和擴散協議。其中Hello協議負責檢查鏈路是否可用,並完成指定路由器及備份指定路由器;交換協議完成“主”、“從”路由器的指定並交換各自的路由數據庫信息;擴散協議完成各路由器中路由數據庫的同步維護.
OSPF 采用鏈路狀態路由選擇技術,開放最短路徑優先算法(迪傑斯特拉(Dijkstra)算法)
OSPF協議具有以下優點:
• OSPF能夠在自己的鏈路狀態數據庫內表示整個網絡,這極大地減少了收斂時間,並且支持大型異構網絡的互聯,提供了一個異構網絡間通過同一種協議交換網絡信息的途徑,並且不容易出現錯誤的路由信息。
• OSPF支持通往相同目的的多重路徑。
• OSPF使用路由標簽區分不同的外部路由。
• OSPF支持路由驗證,只有互相通過路由驗證的路由器之間才能交換路由信息;並且可以對不同的區域定義不同的驗證方式,從而提高了網絡的安全性。
• OSPF支持費用相同的多條鏈路上的負載均衡。
• OSPF是一個非族類路由協議,路由信息不受跳數的限制,減少了因分級路由帶來的子網分離問題。
• OSPF支持VLSM和非族類路由查表,有利於網絡地址的有效管理
• OSPF使用AREA對網絡進行分層,減少了協議對CPU處理時間和內存的需求。
• 適用於規模龐大、環境復雜的互聯網
BGP
BGP (邊界網關協議,Border Gateway Protocol)
BGP用於連接Internet。作為最新的外部網關協議,現有四個版本。
BGP 是唯一一個用來處理像因特網大小的網絡協議,也是唯一能夠妥善處理好不相關路由域間的多路連接協議。BGPv4是一種外部的路由協議。可認為是一種高級的距離向量路由協議。
在BGP網絡中,可以將一個網絡分成多個自治系統。自治系統間使用eBGP廣播路由,自治系統內使用iBGP在自己的網絡內廣播路由。
BGP路由選擇方法是基於距離向量路由選擇, 與傳統的距離向量(1個單獨的度量,如跳數)協議不同,BGP將AS外部路徑的度量復雜化。BGP系統的主要功能是和其他BGP系統交換網絡可達信息。網絡可達信息包括列出的AS信息。這些信息有效地構造了 AS互聯的拓朴圖並由此清除了路由環路,同時在 AS級別上可實施策略決策。
BGP特點:
BGP是一種外部路由協議,與OSPF、RIP不同,其着眼點不在於發現和計算路由,而在於控制路由的傳播和選擇最好的路由。
BGP通過攜帶AS路徑信息,可以徹底的解決路由循環問題。
為了控制路由的傳播和路由的選擇,為路由附帶屬性信息。
使用TCP作為其傳輸層協議,提高了協議的可靠性。端口號179。
BGP-4支持CIDR(無類別域間選路),CIDR的引入簡化了路由聚合,減化了路由表。
BGP更新時只發送增量路由,減少了BGP傳播路由占用的帶寬。
提供了豐富的路由策略。
————————————————
以上內容轉自RIP、OSPF、BGP三種協議
3 TCP/IP協議族詳解之ARP協議
ARP首先會發起一個請求數據包,數據包的首部包含了目標主機的IP地址,然后這個數據包會在鏈路層進行再次包裝,生成以太網數據包,最終由以太網廣播給子網內的所有主機,每一台主機都會接收到這個數據包,並取出標頭里的IP地址,然后和自己的IP地址進行比較,如果相同就返回自己的MAC地址,如果不同就丟棄該數據包。ARP接收返回消息,以此確定目標機的MAC地址;與此同時,ARP還會將返回的MAC地址與對應的IP地址存入本機ARP緩存中並保留一定時間,下次請求時直接查詢ARP緩存以節約資源。cmd輸入 arp -a 就可以查詢本機緩存的ARP數據。
ARP是為IP協議提供服務的,所以,把ARP划分到了網絡層
3. 1 為什么有了IP地址還要使用Mac地址
- IP地址容易修改和變動,不能再網絡上固定標識一個設備
- Mac地址一般在出場時被燒錄到硬件中,不易修改,能在局域網中定位唯一一台設備
- 從拓撲結構和分層上分析,IP地址屬於網絡層,主要功能實在廣域網范圍內路由尋址,選擇最佳路由,而Mac地址是網絡接口層要形成適合於網絡媒體上傳輸的數據幀。
請求包是廣播,而應答包是單播
3.2 ARP緩存表以及相關命令
ARP高速緩存表的作用:
為了減少網絡上的通信量,主機 A 在發送其 ARP 請求分組時,就將自己的 IP 地址到硬件地址的映射寫入 ARP 請求分組。當主機 B 收到 A 的 ARP 請求分組時,就將主機 A 的這一地址映射寫入主機 B 自己的 ARP 高速緩存中。這對主機 B 以后向 A 發送數據報時就更方便了。
注意:arp緩存表分為靜態和動態兩種方式,默認情況下ARP緩存的超時時限是兩分鍾。
ARP命令:
- arp -d 清除本機arp 緩存表
- arp -a 查看本機當前arp表
- arp -s 綁定arp地址(機器重啟后全部失效)
3 TCP/IP協議族詳解之ICMP協議
存在的意義:由於IP協議無差錯報告和差錯糾正機制,缺少一種為主機和管理查詢的機制。如, 當IP數據報在網絡中超時了它的TTL, 那么路由器就會將這個數據報丟失,但是沒有對這個丟棄操作返回錯誤報告。為了彌補這個缺點,所以產生了ICMP協議。注:ICMP沒有糾正錯誤的機制
ICMP是網絡層協議,報文首先封裝成IP數據報,然后再傳送給下一層,在IP數據報中的協議位,標識值為1(PRO:0x1f)
3.1 Ping命令使用詳解
ping
命令是基於ICMP的查詢報文,分為回送請求和回送應答,請求類型為8, 應答類型為0。
C:\Windows\System32>ping www.baidu.com
正在 Ping www.a.shifen.com [14.215.177.39] 具有 32 字節的數據:
來自 14.215.177.39 的回復: 字節=32 時間=7ms TTL=54
來自 14.215.177.39 的回復: 字節=32 時間=8ms TTL=54
來自 14.215.177.39 的回復: 字節=32 時間=8ms TTL=54
來自 14.215.177.39 的回復: 字節=32 時間=7ms TTL=54
14.215.177.39 的 Ping 統計信息:
數據包: 已發送 = 4,已接收 = 4,丟失 = 0 (0% 丟失),
往返行程的估計時間(以毫秒為單位):
最短 = 7ms,最長 = 8ms,平均 = 7ms
詳細流程:
- 首先經過DNS服務器,將域名解析成IP地址,即www.a.shifen.com [14.215.177.39]
- 然后Ping命令發送一個帶有32字節數數據的ICMP請求,收到回復后顯示結果,也就是上面的
來自 14.215.177.39 的回復: 字節=32 時間=7ms TTL=54
, 其中字節表示測試數據長度(可以通過-l參數指定測試數據的大小,例如ping -l 1024 www.baidu.com
); 時間表示包的往返時間(一去一來所用的時間); TTL為54(請求包的TTL為64),也就是回復的包進過了10個路由器
3.2 tracert命令詳解
Linux下的命令是traceroute, windows下的命令是tracert。
tracert是路由跟蹤程序,用於確定 IP 數據報訪問目標所經過的路徑。
tracert 命令用 IP 生存時間 (TTL) 字段和 ICMP 錯誤消息來確定從一個主機到網絡上其他主機的路由。 在工作環境中有多條鏈路出口時,可以通過該命令查詢數據是經過的哪一條鏈路出口。
tracert一般用來檢測故障的位置,我們可以使用用tracert IP命令確定數據包在網絡上的停止位置,來判斷在哪個環節上出了問題,雖然還是沒有確定是什么問題,但它已經告訴了我們問題所在的地方,方便檢測網絡中存在的問題。
常用相關命令:
tracert -d www.baidu.com // 不講地址解析成主機名,能夠更快顯示路由器路徑
tracert -h 5 www.baidu.com // 指定跟蹤的躍點數
tracert -w 10 www.baidu.com //指定等待每個應答的時間(以毫秒為單位)。默認值為 3000 毫秒(3 秒)
4. UDP 用戶數據報協議
4.1 UDP用戶數據報協議
無連接的簡單的面向數據報的運輸層協議。
-
特點: UDP數據報文中包括目的端口號和源端口號信息,由於通訊不需要連接,所以可以實現廣播發送。UDP傳輸數據時有大小限制,每個被傳輸的數據報必須限定在64KB之內。不可靠傳輸協議,發送方所發送的數據報並不一定以相同的次序到達接收方。傳輸速度快。
-
適用場景:UDP一般用於多點通信和實時數據的業務,注重速度流暢
- 語音廣播
- 視頻會議系統
- TFTP SNMP RIP(路由信息協議,如報告股票市場,航空信息)
- DNS(域名解釋)
4.2 創建UDP網絡程序流程:
- 1.創建客戶端套接字
- 2.發送/接受數據
- 3.關閉套接字
通信流程:
5 TCP/IP協議族詳解之TCP協議
面向連接的、可靠的、基於字節流的傳輸層通信協議,由IETF的RFC 793定義。
- 特性:
- 面向連接,通信雙方必須先建立連接,雙方都必須為該連接分配一定的內核資源,以管理連接的狀態和連接上的傳輸。
- 可靠傳輸:
- TCP采用發送應答機制
- 超時重傳:發送端發出一個報文段之后就會啟動定時器,在定時時間內沒收到應答就重發這個報文段,為了保證不發生丟包,就給每一個包一個序號,同時序號也保證了傳送到接收端實體的包按序接收。然后接收端對已成功收到的包回一個 ACK包。如果發送端在合理的RTT內未收到確認,對應的數據包將被假設為已丟失,將會進行重傳
- 錯誤校驗:TCP用一個校驗和函數來檢驗數據是否有錯誤;在發送和接收時都要計算校驗和。
- 流量控制和阻塞管理:流量控制用來避免主機發送得過快而使接收方來不及完全收下
5.1 創建TCP網絡程序流程
-
服務端
-
# coding: utf-8 import socket # 創建tcp套接字 tcpserver = socket.socket(socket.AF_INET, socket.SOCK_STREAM) addr = ('localhost', 7777) # 綁定ip tcpserver.bind(addr) # 開啟監聽 tcpserver.listen(5) # 接收客戶端請求 print(f'TCP 服務器已開啟:{addr}') while True: newSocket, clientAddr = tcpserver.accept() while True: data = newSocket.recv(1024) if len(data) > 0: print('receive from [%s]:%d, data: %s' % (*clientAddr, data.decode('utf-8'))) else: break newSocket.send('thank you!'.encode('utf-8')) newSocket.close() tcpserver.close()
-
-
客戶端
-
# coding: utf-8 import socket tcpclient = socket.socket(socket.AF_INET, socket.SOCK_STREAM) dest = ('localhost', 7777) tcpclient.connect(dest) while True: sendData = input('send: #some msg#') if len(sendData) > 0: tcpclient.send(sendData.encode('utf-8')) else: break recvData = tcpclient.recv(1024) print(recvData.decode('utf-8')) tcpclient.close()
-
5.2 TCP的數據包格式
-
源端口和目的端口:各占16bit=2字節
-
序列號(Seq):占32位=4字節 range=[0:2^32] 表示數據的第一個字節的序列號,TCP的數據交互式基於序列號(控制華東窗口),發送方通過序列號控制發送的數據,以及超時重傳,接收方通過序列號控制亂序重排。
接收方根據三次握手后確認的首字節序列號+數據長度,計算得到最后一個字節的序列號,並將其加1作為ack應答。
-
確認號(ACK):占4個字節,表示期望下次收到的序列號。比如服務器收到客戶端發來的報文段,其序列號字段值為501,並通過計算可知數據長度為200,所以服務器可以算出最后一個字節的序列號為700。這表明服務器正確收到了客戶端發送的序列號到700為止的數據,因此,服務器期望下次收到的序列號為701,並將其作為確認號放入應答報文段中
確認號和序列號范圍相同,當溢出時從0開始
-
數據偏移:占4bit, 表示TCP報文段的第一個數據距離報文段起始處有多遠。數據偏移代表的是4字節的倍數,由於4位二進制最大的可以表示15, 所以數據偏移最大值為4*15=60字節,即TCP報文首部最大長度。最小為20字節,偏移值=5。
-
保留 占6位,占未使用,可能是預留其他控制標志位,或者對齊字節位
-
控制位,用於說明報文段的性質。每個控制字段占1位
-
緊急URG:開啟時表示此數據包處於緊急狀態應優先處理
-
確認標志位ACK:開啟表明確認號有效,TCP規定連接建立后發送的所有報文段ACK位都必須置1
-
推送PSH:該控制位很少使用,因為TCP會自己決定什么時候應該使用PUSH操作。
-
復位RST:用於復位,表示連接出現錯誤,應當立即關閉。當TCP接收到復位報文段后會通知應用程序連接被復位,隨后關閉連接
-
同步SYN:連接建立的過程中用於同步序列號,告知對方自己的起始序列號。可以根據對方的序列號初始化緩沖區起點(滑動窗口)
SYN=1,ACK=0時表示一個連接請求報文段,SYN=1,ACK=1表示一個連接接收報文段
-
終止FIN:用於釋放連接,報文段中FIN控制位為1表示已經將數據發送完畢。等待關閉連接
-
窗口:占2個字節,表示發送該報文段的一方能夠接收的字節數,表明期望接受到的數據包字節數,用於擁塞控制。窗口值范圍為[0:2^16−1]
-
校驗和:占2個字節,用於檢驗報文段是否出錯。發送方根據發送的報文段計算檢驗和填入報文段首部,接收方根據接收的報文段重新計算,如果不匹配,表明報文段出錯
-
緊急指針:占2個字節,表示緊急數據的個數。在緊急狀態下(URG打開),指出窗口中緊急數據的位置(末端)。
-
選項:用於支持一些特殊的變量,比如最大分組長度(MSS),MSS指的是數據的最大長度而不是TCP報文段長度。在將數據發送之前,會根據MSS將數據進行合理的切分,即單次發送的報文段中的數據不能超過MSS,所以MSS應該適當調大一些以降低網絡中的報文段個數
查缺補漏
MSS(Maximum Segment Size):MSS 是TCP選項中最經常出現,也是最早出現的選項。MSS選項占4byte。MSS是每一個TCP報文段中數據字段的最大長度,注意:只是數據部分的字段,不包括TCP的頭部。TCP在三次握手中,每一方都會通告其期望收到的MSS(MSS只出現在SYN數據包中)如果一方不接受另一方的MSS值則定位默認值536byte。
MSS值太小或太大都是不合適。太小,例如MSS值只有1byte,那么為了傳輸這1byte數據,至少要消耗20字節IP頭部+20字節TCP頭部=40byte,這還不包括其二層頭部所需要的開銷,顯然這種數據傳輸效率是很低的。MSS過大,導致數據包可以封裝很大,那么在IP傳輸中分片的可能性就會增大,接受方在處理分片包所消耗的資源和處理時間都會增大,如果分片在傳輸中還發生了重傳,那么其網絡開銷也會增大。因此合理的MSS是至關重要的。MSS的合理值應為保證數據包不分片的最大值,對於以太網MSS可以達到1460byte,在IP層中有一個類似的概念,MTU(Maximum Transfer Unit)MTU=MSS+TCP Header + IP Header
為什么需要MSS?
主要是為了最大程度的保證傳輸的高效和穩定性
那么MTU和MSS又有什么必然聯系呢?雖然MTU限制了IP層的報文大小,但分層網絡模型本來不就是為了對上層提供透明的服務么?即使一個很大的TCP報文傳遞給IP層,IP層也應該可以經過分段等手段成功傳輸報文才對。
理論上來說是沒錯的,UDP中就不存在MSS,UDP生成任意大的UDP報文,然后包裝成IP報文根據底層網絡的MTU分段進行發送。MSS存在的本質原因就是TCP和UDP的根本不同:TCP提供穩定的連接。假設生成了很大的TCP報文,經過IP分段進行發送,而其中一個IP分段丟失了,則TCP協議需要重發整個TCP報文,造成了嚴重的網絡性能浪費,而相對的由於UDP無保證的性質,即使丟失了IP分段也不會進行重發。所以說,MSS存在的核心作用,就是避免由於IP層對TCP報文進行分段而導致的性能下降。
通常將MSS設置為MTU-40(20字節IP頭部+20字節TCP頭部),在TCP建立連接時由連接雙方商定,雙方得到的MSS值可能並不相同,建立MSS所基於MTU的值基於路徑MTU發現機制獲取。
參考:
TCP Maximum Segment Size (MSS)
TCP Maximum Segment Size (MSS) and Relationship to IP Datagram Size
-
5.3 TCP 三次握手
拋出疑惑:為什么是三次握手而不是二次或者四次握手?
TCP作為一種可靠傳輸控制協議,核心思想:既要保證數據可靠傳輸,又要提高傳輸的效率,而用三次就可以滿足以上兩方面的需求。
TCP的可靠性就是通過三次握手就是確認通信雙方數據原點的初始序列號 (Initial Sequence Number)。
通俗的描述:客戶端A發出連接請求,由操作系統動態隨機選取一個32位長的序列號(Initial Sequence Number), 假設A的初始序列號是1000, 以該序列號為原點,對自己將要發送的每個字節進行編號,1001, 1002..., 並把自己的初始序列號INS告訴B, 什么樣的編號的數據是合法的,方便服務端B對A的每一個編號的字節數據進行確認。如:如果A收到B確認編號為2001,則意味着字節編號為1001-2000,共1000個字節已經安全到達。
同理B也是類似的操作,假設B的初始序列號ISN為2000,以該序列號為原點,對自己將要發送的每個字節的數據進行編號,2001,2002,2003…,並把自己的初始序列號ISN告訴A,以便A可以確認B發送的每一個字節。如果B收到A確認編號為4001,則意味着字節編號為2001-4000,共2000個字節已經安全到達。
第一次握手:
客戶端向服務端發送連接請求報文段,報文段的頭部中SYN=1, ACK=0, seq=x。請求發送后,客戶端進入SYN-SENT狀態
- SYN=1, ACK=0 標識該報文段為連接請求報文
- seq=x, 標識本次TCP通信客戶端數據字節流的初始序列號
- TCP規定:SYN=1的報文段不能有數據部分,但要消耗掉一個字節,一個序號
第二次握手:
服務端處於監聽狀態LISTEN,收到連接請求報文后,如果同意連接,返回一個應答 SYN=1, ACK=1, seq=y, ack=x+1, 進入SYN-RCVD狀態
第三次握手:
當客戶端收到服務器的應答后,還要向服務端發送一個確認報文段,表示服務端發來的連接同意應答已經成功收到,且收到服務端的出示序列號y
確認報文為:ACK=1, seq=x+1, ack=y+1。
為什么連接建立需要三次握手,而不是2次握手?
防止失效的連接請求報文段被服務端接收,從而產生錯誤,失效的連接請求:若客戶端向服務端發送的連接請求丟失,客戶端等待應答超時后就會再次發送連接請求,此時,上一個連接請求就是『失效的』---《計算機網絡》謝希仁版
三次握手中存在的漏洞:SYN flood!,攻擊者通過向服務器發起大量的SYN報文,把服務器的SYN報文連接的隊列生生耗盡,導致正常的連接請求得不到處理,目前只能進行減緩,別沒有解決補丁
-
在web應用程序中可以使用安全的CSRF令牌環節問題。CSRF攻擊將在服務器造成持久的變化而沒有處理要求,除非使用了有效的CSRF令牌。
-
首保丟棄:可通過丟棄客戶端的第一個SYN報文來達到防御的目的,TCP是一種可靠的協議,為了確保所有的數據包都能到達服務器,設計了一個重傳機制。真實的客戶端訪問,在一定的時間內如果沒有收到服務器的回復,將會再次發送SYN報文。
-
內核層面進行緩解:
- 增大tcp_max_syn_backlog
- 減小tcp_synack_retries
- 啟用tcp_syncookies: 當啟用tcp_syncookies時,backlog滿了后,linux內核生成一個特定的n值,而不並把客戶的連接放到半連接的隊列backlog里(即沒有存儲任何關於這個連接的信息,不浪費內存)。當客戶端提交第三次握手的ACK包時,linux內核取出n值,進行校驗,如果通過,則認為這個是一個合法的連接。(加密的INS)
注:tcp_max_syn_backlog 在 syn_cookies 開啟時是無效的,這兩個選項存在沖突
5.4 TCP四次揮手
第一揮手
若A認為數據發送完成,就會向B發送連接釋放請求,該請求只有報文頭,頭重攜帶的主要參數為:FIN=1, seq=u, 此時A進入FIN-WAIT-1狀態
- FIN=1即TCP報文段中的控制位FIN置1表示該數據報為連接釋放請求
- seq=u, u-1是A向B發送的最后一個字節的序號
第二次揮手
B收到連接釋放請求后,會通知相應的應用程序,告訴它連接已經釋放,此時B進入CLOSE_WAIT狀態, 報文頭:ACK=1, seq=v, ack=u+1
- ACK=1, 除了TCP連接請求報文段以外,TCP通信過程中數據報的ACK控制為都為1
- seq=v, v-1表示B向A發送的最后一個字節的序號
- ack=u+1 表示希望收到第u+1個字節開始的報文段,已經成功接收了簽u個字節數據
A收到該應答后進入 FIN_WAIT_2狀態,等待B發送連接釋放請求
第二次揮手后,A->B方向的連接已經釋放,A不會再發送數據,但B->A方向的連接仍然存在。
此時會有一個孤兒連接的問題,可以參考文章TCP連接的性能優化
第三次揮手
當B向A發送完所有數據后,向A發送連接釋放請求,請求頭: FIN=1, ACK=1, seq=w, ack=u+1 B進入 LAST_ACK狀態
第四次揮手
A收到釋放請求后,向B發送確認應答,A進入TIME_WAIT狀態。該狀態會持續2MSL(Maximum Segment Lifetime)時間,(報文最大生存時間),若該時間段內B沒有發送請求的話,就進入CLOSED狀態,關閉TCP。當B收到確認應答后,也進入CLOSED狀態, 關閉TCP。
為什么A要先進入TIME-WAIT狀態,等待2MSL時間后才進入CLOSED狀態?
為了保證B能收到A的確認應答。
若A發完確認應答后直接進入CLOSED狀態,那么如果該應答丟失,B等待超時后就會重新發送連接釋放請求,但此時A已經關閉了,不會作出任何響應,因此B永遠無法正常關閉。在模擬tcpserver的時候,如果是服務器先close的時候,在2MSL中(也就是2-4分鍾之內並不會馬上釋放端口)不過在實際應用中可以通過設置 SO_REUSEADDR選項達到不必等待2MSL時間結束再使用此端口。
參考TCP 為什么是三次握手,而不是兩次或四次?-[大閑人柴毛毛]
參考:TCP的三次握手與四次揮手理解及面試題
5.5 TCP 中的半連接和全連接隊列
由於TCP
建立連接需要進行3次握手,一個新連接在到達ESTABLISHED
狀態可以被accept
系統調用返回給應用程序前,必須經過一個中間狀態SYN RECEIVED
(見上圖)。這意味着,TCP/IP
協議棧在實現backlog
隊列時,有兩種不同的選擇:
- 僅使用一個隊列,隊列規模由
listen
系統調用backlog
參數指定。當協議棧收到一個SYN
包時,響應SYN/ACK
包並且將連接加進該隊列。當相應的ACK
響應包收到后,連接變為ESTABLISHED
狀態,可以向應用程序返回。這意味着隊列里的連接可以有兩種不同的狀態:SEND RECEIVED
和ESTABLISHED
。只有后一種連接才能被accept
系統調用返回給應用程序。 - 使用兩個隊列——
SYN
隊列(待完成連接隊列)和accept
隊列(已完成連接隊列)。狀態為SYN RECEIVED
的連接進入SYN
隊列,后續當狀態變更為ESTABLISHED
時移到accept
隊列(即收到3次握手中最后一個ACK
包)。顧名思義,accept
系統調用就只是簡單地從accept
隊列消費新連接。在這種情況下,listen
系統調用backlog
參數決定accept
隊列的最大規模。
Linux
實現了第二種方案,使用兩個隊列——一個SYN
隊列,長度系統級別可設置以及一個accept
隊列長度由應用程序指定
如上圖所示,這里有兩個隊列:syns queue(半連接隊列);accept queue(全連接隊列)
三次握手中,在第一步server收到client的syn后,把相關信息放到半連接隊列中,同時回復syn+ack給client(第二步);
比如syn floods 攻擊就是針對半連接隊列的,攻擊方不停地建連接,但是建連接的時候只做第一步,第二步中攻擊方收到server的syn+ack后故意扔掉什么也不做,導致server上這個隊列滿其它正常請求無法進來
在accept
隊列已滿的情況下,內核會強制限制SYN
包的接收速率。如果有大量SYN
包待處理,它們其中的一些會被丟棄。這樣看來,就完全依靠客戶端重傳SYN
包了,這種行為跟BSD
實現一樣。
如何查看全連接隊列大小?
在服務端可以使用 ss
命令,來查看 TCP 全連接隊列的情況:
需要注意的是 ss命令獲取的
Recv-Q/Send-Q 在「LISTEN 狀態」和「非 LISTEN 狀態」所表達的含義是不同的
- 在LISTEN狀態時
- RECV-Q:當前,全連接隊列的大小,也就是當前已經完成三次握手並等待服務端
accept()
的TCP連接個數; - SEND-Q:當前全連接最大隊列長度,上面的輸出結果說明監聽8088端口的TCP服務進程,最大全連接長度為128;
- RECV-Q:當前,全連接隊列的大小,也就是當前已經完成三次握手並等待服務端
- 非LISTEN狀態時
- Recv-Q:已經收到但未被應用程序讀取的字節數
- Send-Q:已發送但未收到確認的字節數
全連接隊列溢出會發生什么?
從上面的輸出結果,可以發現當前 TCP 全連接隊列上升到了 129 大小,超過了最大 TCP 全連接隊列
當超過了 TCP 最大全連接隊列,服務端則會丟掉后續進來的 TCP 連接,丟掉的 TCP 連接的個數會被統計起來,我們可以使用 netstat -s
命令來查看:
上面看到的 41150 times
,表示全連接隊列溢出的次數,注意這個是累計值。可以隔幾秒鍾執行下,如果這個數字一直在增加的話肯定全連接隊列偶爾滿了。
當服務端並發處理大量請求時,如果 TCP 全連接隊列過小,就容易溢出。發生 TCP 全連接隊溢出的時候,后續的請求就會被丟棄,這樣就會出現服務端請求數量上不去的現象。
全連接隊列滿了,會做什么操作?
當全連接隊列滿了,Linux的默認行為是丟棄該連接,當然我們還可以選擇項客戶端發送RST復位報文,告訴客戶端連接已經建立失敗。
cat /proc/sys/net/ipv4/tcp_abort_on_overflow
0 # 默認值是0
tcp_abort_on_overflow
共兩個值,分別是0,1
- 0:表示如果全連接隊列滿了,那么server扔掉client發過來的ack
- 1:表示如果全連接隊列滿了,那么server發送一個reset包給client' 表示廢掉這個握手過程和這個連接
如果要想知道客戶端連接不上服務端,是不是服務端 TCP 全連接隊列滿的原因,那么可以把 tcp_abort_on_overflow
設置為 1,這時如果在客戶端異常中可以看到很多 connection reset by peer
的錯誤,那么就可以證明是由於服務端 TCP 全連接隊列溢出的問題。
通常情況下,應當把 tcp_abort_on_overflow
設置為 0,因為這樣更有利於應對突發流量。
舉個例子,當 TCP 全連接隊列滿導致服務器丟掉了 ACK,與此同時,客戶端的連接狀態卻是 ESTABLISHED,進程就在建立好的連接上發送請求。只要服務器沒有為請求回復 ACK,請求就會被多次重發。如果服務器上的進程只是短暫的繁忙造成 accept 隊列滿,那么當 TCP 全連接隊列有空位時,再次接收到的請求報文由於含有 ACK,仍然會觸發服務器端成功建立連接。
tcp_abort_on_overflow
設為 0 可以提高連接建立的成功率,只有你非常肯定 TCP 全連接隊列會長期溢出時,才能設置為 1 以盡快通知客戶端。
如何增大全連接隊列
TCP全連接隊列最大值取決於 somaxconn
和 backlog
之間的最小值,也就是min(somaxconn, backlog)
, 可以從內核代碼中得知
//Linux 2.6.35 net/socket.c
SYSCALL_DEFINE2(listen, int, fd, int, backlog) {
// ...
// /proc/sys/net/core/somaxconn
somaxconn = sock_net(sock->sk)->core.sysctl_somaxconn;
// TCP全連接隊列最大值 = min(somaxconn, backlog)
if ((unsigned)backlog > somaxconn)
backlog = somaxconn;
// ...
}
somaxconn
是Linux內核的參數, 默認是128, 可以通過/proc/sys/net/core/somaxconn
來修改值backlog
是listen(int, sockfd, int backlog)
函數中backlog
的大小, Nginx默認是511, 可以通過修改配置文件設置長度
修改全連接最大值
echo 5000 > /proc/sys/net/core/somaxconn
Nginx 的backlog 修改值為5000
# /usr/local/nginx/conf/nginx.conf
server {
listen 8088 default backlog=5000;
server_name localhost;
# ....
}
需要重啟Nginx服務, 因為要重新調用listen()函數, TCP全連接隊列才會重新初始化。
重啟完后 Nginx 服務后,服務端執行 ss
命令,查看 TCP 全連接隊列大小:
增大TCP全連接后,做壓測,使用wrk工具發出3萬個連接
服務端執行 ss
命令,查看 TCP 全連接隊列使用情況:
從上面的執行結果,可以發現全連接隊列使用增長的很快,但是一直都沒有超過最大值,所以就不會溢出,那么 netstat -s
就不會有 TCP 全連接隊列溢出個數的顯示:
說明 TCP 全連接隊列最大值從 128 增大到 5000 后,服務端抗住了 3 萬連接並發請求,也沒有發生全連接隊列溢出的現象了。
如果持續不斷地有連接因為 TCP 全連接隊列溢出被丟棄,就應該調大 backlog 以及 somaxconn 參數
如何查看半連接隊列大小?
半連接的長度,並沒有像全連接一樣的參數,但是可以通過查看處於SYN_RECV
狀態的TCP連接
# 查看當前tcp 半連接隊列的長度
netstat -natp | grep SYN_RECV | wc -l
256 # 表示當前處於半連接狀態的TCP連接有256個
可以使用hping3工具模擬SYN攻擊
當服務端受到 SYN 攻擊后,連接服務端 ssh 就會斷開了,無法再連上。只能在服務端主機上執行查看當前 TCP 半連接隊列大小:
同時,還可以通過 netstat -s
觀察半連接隊列溢出的情況:
netstat -s | grep "SYNs to LISTEN"
上面輸出的數值是累計值,表示共有多少個 TCP 連接因為半連接隊列溢出而被丟棄。隔幾秒執行幾次,如果有上升的趨勢,說明當前存在半連接隊列溢出的現象。
上面模擬 SYN 攻擊場景時,服務端的 tcp_max_syn_backlog
的默認值如下:
我個人的Ubuntu 查看是128,不同操作系統配置不同
但是在測試的時候發現,服務端最多只有 256 個半連接隊列,而不是 512,所以半連接隊列的最大長度不一定由 tcp_max_syn_backlog
值決定的。
TCP 第一次握手(收到 SYN 包)的 Linux 內核代碼如下,其中縮減了大量的代碼,只需要重點關注 TCP 半連接隊列溢出的處理邏輯:
從源碼中,我可以得出共有三個條件因隊列長度的關系而被丟棄的:
- 如果半連接隊列滿了,並且沒有開啟
tcp_syncookies
,則丟棄 - 如果全連接隊列滿了,且沒有重傳
SYN+ACK包
的連接請求多余1個,則會丟棄 - 如果沒有開啟
tcp_syncookies
, 並且max_syn_backlog
減去當前半連接隊列長度小於(max_syn_backlog>>2
),則會丟棄
- 全連接隊列的最大值是
sk_max_ack_backlog=min(somaxconn, backlog)
- 半連接隊列的最大值是
max_qlen_log
變量,查看源碼在哪里定義的這個變量
從上面的代碼中,我們可以算出 max_qlen_log
是 8,於是代入到 檢測半連接隊列是否滿的函數 reqsk_queue_is_full
:
也就是 qlen >> 8
什么時候為 1 就代表半連接隊列滿了。這計算這不難,很明顯是當 qlen 為 256 時,256 >> 8 = 1
。
至此,總算知道為什么上面模擬測試 SYN 攻擊的時候,服務端處於 SYN_RECV
連接最大只有 256 個。
可見,半連接隊列最大值不是單單由 max_syn_backlog
決定,還跟 somaxconn
和 backlog
有關系
在 Linux 2.6.32 內核版本,它們之間的關系,總體可以概況為:
- 當
max_syn_backlog > min(somaxconn, backlog)
時, 半連接隊列最大值是max_qlen_log = min(somaxconn, backlog) * 2
- 當
max_syn_backlog < min(somaxconn, backlog)
時, 半連接隊列的最大值是max_qlen_log = max_syn_backlog * 2
半連接隊列的大小與什么有關?
半連接隊列最大值 max_qlen_log
就表示服務端處於 SYN_REVC 狀態的最大個數嗎?
max_qlen_log
是理論半連接隊列最大值,並不一定代表服務端處於 SYN_REVC 狀態的最大個數。當觸發條件3的時候 如果沒有開啟
tcp_syncookies, 並且
max_syn_backlog減去當前半連接隊列長度小於(max_syn_backlog>>2),則會丟棄
服務端處於 SYN_RECV 狀態的最大個數是 193,正好是觸發了條件 3,所以處於 SYN_RECV 狀態的個數還沒到「理論半連接隊列最大值 256」,就已經把 SYN 包丟棄了,
所以,服務端處於 SYN_RECV 狀態的最大個數分為如下兩種情況:
- 如果「當前半連接隊列」沒超過「理論半連接隊列最大值」,但是超過
max_syn_backlog – (max_syn_backlog >> 2)
,那么處於 SYN_RECV 狀態的最大個數就是max_syn_backlog – (max_syn_backlog >> 2)
; - 如果「當前半連接隊列」超過「理論半連接隊列最大值」,那么處於 SYN_RECV 狀態的最大個數就是「理論半連接隊列最大值」;
每個Linux內核的理論半連接最大值計算方式不同,在上面我們是針對 Linux 2.6.32 版本分析的「理論」半連接最大值的算法,可能每個版本有些不同。比如在 Linux 5.0.0 的時候,「理論」半連接最大值就是全連接隊列最大值,但依然還是有隊列溢出的三個條件。
如果SYN半連接隊列已滿,只能丟棄鏈接嗎?
並不是這樣,開啟 syncookies
功能就可以在不使用 SYN 半連接隊列的情況下成功建立連接,在前面我們源碼分析也可以看到這點,當開啟了 syncookies
功能就不會丟棄連接。
syncookies
是這么做的:服務器根據當前狀態計算出一個值,放在己方發出的 SYN+ACK 報文中發出,當客戶端返回 ACK 報文時,取出該值驗證,如果合法,就認為連接建立成功,如下圖所示
syncookies
參數主要有以下三個值:
- 0 值,表示關閉該功能;
- 1 值,表示僅當 SYN 半連接隊列放不下時,再啟用它;
- 2 值,表示無條件開啟功能;
echo 1 > /proc/sys/net/ipv4/tcp_syncookies
如何防御SYN攻擊?
- 增大半連接隊列;
- 增大半連接隊列,半連接隊列大小跟
somaxconn
, 和blocklog
的值有關,所以三者都要增大
- 增大半連接隊列,半連接隊列大小跟
- 開啟
tcp_syncookies
功能 - 減少 SYN+ACK 重傳次數
- 當服務端受到 SYN 攻擊時,就會有大量處於 SYN_REVC 狀態的 TCP 連接,處於這個狀態的 TCP 會重傳 SYN+ACK ,當重傳超過次數達到上限后,就會斷開連接。那么針對 SYN 攻擊的場景,我們可以減少 SYN+ACK 的重傳次數,以加快處於 SYN_REVC 狀態的 TCP 連接斷開。
echo 1> /proc/sys/net/ipv4/tcp_synack_retries
設置重傳次數為1次
轉自-TCP 半連接隊列和全連接隊列滿了會發生什么?又該如何應對?
關於TCP 半連接隊列和全連接隊列
[譯文]深入理解Linux TCP backlog
TCP面試題(一)之TCP的三次握手和accept()的順序
TCP的accept發生在三次握手的哪個階段?
5.6 TCP 的流量控制和擁塞控制
流量控制
如果發送者發送數據過快,接收者來不及接收,那么就會有分組丟失。為了避免分組丟失,控制發送者的發送速度,使得接收者來得及接收,這就是流量控制。流量控制根本目的是防止分組丟失,它是構成TCP可靠性的一方面,流量控制由滑動窗口協議(連續ARQ協議)實現。滑動窗口協議既保證了分組無差錯、有序接收,也實現了流量控制。主要的方式就是接收方返回的 ACK 中會包含自己的接收窗口的大小,並且利用大小來控制發送方的數據發送。
TCP規定,即使設置為零窗口,也必須接收一下幾種報文
- 零窗口探測報文段
- 確認報文段
- 攜帶緊急數據的報文段
確認丟失和確認遲到
持續計時器
存在這樣一種情況:發送方接收到零窗口報文之后將發送窗口設置為0,停止發送數據。但等到接收方有足夠緩存,發送了非零窗口大小的報文,但是這個報文中途丟失,那么發送方的發送窗口就一直為0導致死鎖。
為此,TCP為每一個連接設有一個持續計時器(Persistence Timer):當TCP連接的一方收到對方的零窗口通知時就啟動持續計時器。若持續計時器時間到期,就發送一個零窗口探測報文段(攜有1字節的數據),那么收到這個報文段的一方就在確認這個探測報文段時給出了現在的窗口值。若窗口仍然是零,則收到這個報文段的一方就重新設置持續計時器;若窗口不是零,則死鎖的僵局就可以打破了。
延遲ACK
如果TCP對每個數據包都發送一個ACK確認,那么只是一個單獨的數據包為了發送一個ACK代價比較高,所以TCP會延遲一段時間,如果這段時間內有數據發送到對端,則捎帶發送ACK,如果在延遲ACK定時器觸發時候,發現ACK尚未發送,則立即單獨發送;
延遲ACK好處:
- 避免糊塗窗口綜合症。
- 發送數據的時候將ACK捎帶發送,不必單獨發送ACK。如果延遲時間內有多個數據段到達,那么允許協議棧發送一個ACK確認多個報文段。減少流量消耗。
糊塗窗口綜合症:TCP接收方的緩存已滿,而交互式的應用進程一次只從接收緩存中讀取1字節(這樣就使接收緩存空間僅騰出1字節),然后向發送方發送確認,並把窗口設置為1個字節(但發送的數據報為40字節的的話)。當發送方又發來1個字節的數據(發送方的IP數據報是41字節),接收方發回確認,仍然將窗口設置為1個字節。這樣,網絡的效率很低。要解決這個問題,可讓接收方等待一段時間,使得或者接收緩存已有足夠空間容納一個最長的報文段或者等到接收方緩存已有一半的空閑空間。只要出現這兩種情況,接收方就發回確認報文,並向發送方通知當前的窗口大小。此外,發送方也不要發送太小的報文段,而是把數據報積累成足夠大的報文段,或達到接收方緩存的空間的一半大小。
擁塞控制
擁塞控制與流量控制的區別 :
擁塞控制是防止過多的數據注入到網絡中,可以使網絡中的路由器或鏈路不致過載,是一個全局性的過程。
流量控制是點對點通信量的控制,是一個端到端的問題,主要就是抑制發送端發送數據的速率,以便接收端來得及接收。
- 慢啟動
發送方維持一個叫做擁塞窗口cwnd(congestion window)的狀態變量。擁塞窗口的大小取決於網絡的擁塞程度,並且動態地在變化。發送方讓自己的發送窗口等於擁塞窗口,另外考慮到接受方的接收能力,發送窗口可能小於擁塞窗口。
慢開始算法的思路就是,不要一開始就發送大量的數據,先探測一下網絡的擁塞程度,也就是說由小到大逐漸增加擁塞窗口的大小。
這里用報文段的個數作為擁塞窗口的大小舉例說明慢開始算法,實際的擁塞窗口大小是以字節為單位的。如下圖:
從上圖可以看到,一個傳輸輪次所經歷的時間其實就是往返時間RTT,而且沒經過一個傳輸輪次(transmission round),擁塞窗口cwnd就加倍。
為了防止cwnd增長過大引起網絡擁塞,還需設置一個慢開始門限ssthresh狀態變量。ssthresh的用法如下:當cwnd<ssthresh時,使用慢開始算法。
當cwnd>ssthresh時,改用擁塞避免算法。
當cwnd=ssthresh時,慢開始與擁塞避免算法任意
注意,這里的“慢”並不是指cwnd的增長速率慢,而是指在TCP開始發送報文段時先設置cwnd=1,然后逐漸增大,這當然比按照大的cwnd一下子把許多報文段突然注入到網絡中要“慢得多”
- 擁塞避免算法
擁塞避免算法讓擁塞窗口緩慢增長,即每經過一個往返時間RTT就把發送方的擁塞窗口cwnd加1,而不是加倍。這樣擁塞窗口按線性規律緩慢增長。
無論是在慢開始階段還是在擁塞避免階段,只要發送方判斷網絡出現擁塞(其根據就是沒有按時收到確認,雖然沒有收到確認可能是其他原因的分組丟失,但是因為無法判定,所以都當做擁塞來處理),就把慢開始門限ssthresh設置為出現擁塞時的發送窗口大小的一半(但不能小於2)。然后把擁塞窗口cwnd重新設置為1,執行慢開始算法。這樣做的目的就是要迅速減少主機發送到網絡中的分組數,使得發生擁塞的路由器有足夠時間把隊列中積壓的分組處理完畢。
整個擁塞控制的流程如下圖:
(1)擁塞窗口cwnd初始化為1個報文段,慢開始門限初始值為16
(2)執行慢開始算法,指數規律增長到第4輪,即cwnd=16=ssthresh,改為執行擁塞避免算法,擁塞窗口按線性規律增長
(3)假定cwnd=24時,網絡出現超時(擁塞),則更新后的ssthresh=12,cwnd重新設置為1,並執行慢開始算法。當cwnd=12=ssthresh時,改為執行擁塞避免算法
關於 乘法減小(Multiplicative Decrease)和加法增大(Additive Increase):
“乘法減小”指的是無論是在慢開始階段還是在擁塞避免階段,只要發送方判斷網絡出現擁塞,就把慢開始門限ssthresh設置為出現擁塞時的發送窗口大小的一半,並執行慢開始算法,所以當網絡頻繁出現擁塞時,ssthresh下降的很快,以大大減少注入到網絡中的分組數。“加法增大”是指執行擁塞避免算法后,使擁塞窗口緩慢增大,以防止過早出現擁塞。常合起來成為AIMD算法。
注意:“擁塞避免”並非完全能夠避免了阻塞,而是使網絡比較不容易出現擁塞。
- 快重傳算法
由於TCP采用的是累計確認機制,即當接收端收到比期望序號大的報文段時,便會重復發送最近一次確認的報文段的確認信號,我們稱之為冗余ACK(duplicate ACK)。
如圖所示,報文段1成功接收並被確認ACK 2,接收端的期待序號為2,當報文段2丟失,報文段3失序到來,與接收端的期望不匹配,接收端重復發送冗余ACK 2
這樣,如果在超時重傳定時器溢出之前,接收到連續的三個重復冗余ACK(其實是收到4個同樣的ACK,第一個是正常的,后三個才是冗余的),發送端便知曉哪個報文段在傳輸過程中丟失了,於是重發該報文段,不需要等待超時重傳定時器溢出,大大提高了效率。這便是快速重傳機制。
為什么是三次冗余?
首先要明白一點,即使發送端是按序發送,由於TCP包是封裝在IP包內,IP包在傳輸時亂序,意味着TCP包到達接收端也是亂序的,亂序的話也會造成接收端發送冗余ACK。那發送冗余ACK是由於亂序造成的還是包丟失造成的,這里便需要好好權衡一番,因為把3次冗余ACK作為判定丟失的准則其本身就是估計值
以上參看;TCP的快速重傳機制
快重傳要求接收方在收到一個失序的報文段后就立即發出重復確認(為的是使發送方及早知道有報文段沒有到達對方,可提高網絡吞吐量約20%)而不要等到自己發送數據時捎帶確認。快重傳算法規定,發送方只要一連收到三個重復確認就應當立即重傳對方尚未收到的報文段,而不必繼續等待設置的重傳計時器時間到期。如下圖:
ICMP會引起重新傳遞么?
答案是:不會,TCP會堅持用自己的定時器,但是TCP會保留下ICMP的錯誤並且通知用戶。
- 快恢復算法
快重傳配合使用的還有快恢復算法,有以下兩個要點:
當發送方連續收到三個重復確認時,就執行“乘法減小”算法,把ssthresh門限減半(為了預防網絡發生擁塞)。但是接下去並不執行慢開始算法,考慮到如果網絡出現擁塞的話就不會收到好幾個重復的確認,所以發送方現在認為網絡可能沒有出現擁塞。所以此時不執行慢開始算法,而是將cwnd設置為ssthresh減半后的值,然后執行擁塞避免算法,使cwnd緩慢增大。如下圖:TCP Reno版本是目前使用最廣泛的版本。
注意:在采用快恢復算法時,慢開始算法只是在TCP連接建立時和網絡出現超時時才使用
5.7 TCP 性能優化
5.8 為什么 TCP 協議有粘包問題
TCP 協議粘包問題是因為應用層協議開發者的錯誤設計導致的,他們忽略了 TCP 協議數據傳輸的核心機制 — 基於字節流,其本身不包含消息、數據包等概念,所有數據的傳輸都是流式的,需要應用層協議自己設計消息的邊界,即消息幀(Message Framing),我們重新回顧一下粘包問題出現的核心原因:
- TCP 協議是基於字節流的傳輸層協議,其中不存在消息和數據包的概念;
- 應用層協議沒有使用基於長度或者基於終結符的消息邊界,導致多個消息的粘連;
解決粘包問題的方法: 在應用層協議中,最常見的兩種解決方案就是基於長度或者基於終結符(Delimiter),HTTP 協議的消息邊界就是基於長度實現的:
HTTP/1.1 200 OK
Content-Type: text/html; charset=UTF-8
Content-Length: 138
有時間要看看謝希仁的計算機網絡這本書了