面試反客為主 TCP
本文轉(zhuǎn)載自微信公眾號(hào)「sowhat1412」,作者SoWhat1412。轉(zhuǎn)載本文請(qǐng)聯(lián)系sowhat1412公眾號(hào)。
3 傳輸層 TCP/UDP
承接上文 HTTP,數(shù)據(jù)經(jīng)過應(yīng)用層就到傳輸層,但數(shù)據(jù)到傳輸層之前需要先獲得服務(wù)端的 IP 地址,這就涉及到 DNS 域名解析。
3.1 DNS
3.1.1 DNS 講解
主機(jī)的真正地址是 IP ,問題是 IP 地址不方便人們記憶,就像你拿手機(jī)給張三打電話,難道你能瞬間說出張三電話號(hào)碼么,手機(jī)里做一個(gè)名字跟電話的映射即可,想通話時(shí)直接從通訊錄找到張三就可以找到對(duì)應(yīng)的手機(jī)號(hào),在網(wǎng)絡(luò)請(qǐng)求時(shí)候也是需要映射的,而域名服務(wù)器 Domain Name System 就是干這個(gè)事的,深入講 DNS 前先了解下域名。
我們?cè)跒g覽器地址欄中輸入的每一個(gè)地址都是一個(gè)域名,比如 www.baidu.com。域名是由.和不同級(jí)別域的域名組成。通常我們?cè)跁鴮憰r(shí)會(huì)省略根域名,即域名結(jié)尾的.,如www.baidu.com.。由于域名是老外發(fā)明的所以從左到右范圍逐步變大且以.分割。
DNS分層
由上到下域名之間相互包含跟嵌套,根域名服務(wù)器是關(guān)鍵,必須是眾所周知的,找到了它,下面的各級(jí)域名服務(wù)器才能找到,否則域名解析就無從談起了。我們看下請(qǐng)求 www.baidu.com 的 DNS 解析流程:
- 首先訪問根域名服務(wù)器,根域名是不做域名解析,只是給你指路用的,現(xiàn)在你獲取 com 頂級(jí)域名服務(wù)器的地址。
- 請(qǐng)求 com 頂級(jí)域名服務(wù)器,返回 baidu.com 域名服務(wù)器的地址。
- 然后請(qǐng)求 baidu.com 域名服務(wù)器,返回 www.baidu.com 的地址。
這樣進(jìn)行 DNS 的流程是OK的,但問題是全球數(shù)十億的PC電腦,如果每個(gè)電腦請(qǐng)求上網(wǎng)都按照上面流程走一波,那上面的 DNS 核心解析系統(tǒng)瞬間爆炸!解決辦法就是用緩存,很多大公司跟運(yùn)營(yíng)商都會(huì)搭建自己的 DNS 服務(wù)器來代替用戶請(qǐng)求核心 DNS 系統(tǒng),如果查到的話可以緩存查詢記錄,再次收到請(qǐng)求的號(hào)如果有緩存結(jié)果或者緩存未過期,則直接返回原來的緩存結(jié)果,知名的 Google 8.8.8.8 DNS 解析服務(wù)器,就是 Google 自建的非權(quán)威域名服務(wù)器 。除了非權(quán)威域名服務(wù)器,我們經(jīng)常看到的有瀏覽器緩存,操作系統(tǒng)緩存,比如 /etc/hosts文件等。
3.1.2 DNS 樣例
DNS域名解析
- 用戶輸入網(wǎng)址先看下瀏覽器的DNS緩存是否過期,沒過期直接拿來用。過期了看看本地操作系統(tǒng)緩存 /etc/hosts 文件等。
- 請(qǐng)求本地配置的 非權(quán)威域名服務(wù)器 DNS resolver。
- DNS resolver 將網(wǎng)址轉(zhuǎn)發(fā)到根域名請(qǐng)求,返回 com 域名地址。
- DNS resolver 將網(wǎng)址轉(zhuǎn)發(fā)到 com 域名的服務(wù)器請(qǐng)求,返回跟 baidu.com 相關(guān)的 權(quán)威DNS解析器。
- DNS resolver 將網(wǎng)址轉(zhuǎn)發(fā)權(quán)威 DNS 解析器繼續(xù)請(qǐng)求,然后返回真正的目標(biāo)域名IP。
- DNS resolver 最終將目標(biāo) IP 返回給用戶繼續(xù)接下來的訪問請(qǐng)求。
3.2 TCP
3.2.1 TCP 頭部講解
TCP 是一個(gè)是面向連接的、可靠的、基于字節(jié)流的、工作在傳輸層的數(shù)據(jù)傳輸服務(wù)。用 TCP 傳輸數(shù)據(jù)能確保接收端接收的網(wǎng)絡(luò)包是無損壞、無間隔、非冗余、有序。這里需注意 TCP 是一對(duì)一連接的。
TCP頭部 + HTTP
- 發(fā)送端口:是一個(gè)大于 1023 的 16 位數(shù)字,由基于TCP應(yīng)用程序的用戶進(jìn)程隨機(jī)選擇。
- 目的端口:指明接收者所用的端口號(hào),一般由應(yīng)用程序來指定。
- 序列號(hào):建立連接時(shí)客戶端生成隨機(jī)數(shù)作為初始值,通過 SYN 包傳給接收端主機(jī),每發(fā)送一次數(shù)據(jù),就累加一次。序列號(hào)達(dá)到最大值會(huì)出現(xiàn)序列號(hào)回繞,再次從0 開始。核心作用就是 接收方去重?cái)?shù)據(jù) + 按序接收。
- 確認(rèn)號(hào):用來解決不丟包的問題,指定下一次希望收到的數(shù)據(jù)的序列號(hào),發(fā)送端收到這個(gè)確認(rèn)應(yīng)答以后可以認(rèn)為在這個(gè)序號(hào)以前的數(shù)據(jù)都已經(jīng)被正常接收。
- 數(shù)據(jù)偏移:表示 TCP 報(bào)文段的首部長(zhǎng)度,4 位二進(jìn)制最大表示15,由于TCP首部包含個(gè)可變長(zhǎng)度選項(xiàng),需要指定這個(gè) TCP 報(bào)文段到底有多長(zhǎng)。它指出 TCP 報(bào)文段的數(shù)據(jù)起始處距離 TCP 報(bào)文段的起始處有多遠(yuǎn)。該字段的單位是4字節(jié),所以TCP首部最大15*4 = 60字節(jié)。
- 保留:保留6位,未使用,應(yīng)置零。
下面的7~12是控制位,用來表示說明報(bào)文段的性質(zhì)
7.URG:表示本報(bào)文段中發(fā)送的數(shù)據(jù)是否包含緊急數(shù)據(jù)。只有當(dāng) URG=1 時(shí)后面的緊急指針字段urgent pointer才有效。
8.ACK:表示是否前面確認(rèn)號(hào)字段是否有效。只有當(dāng) ACK=1 時(shí),前面的確認(rèn)號(hào)字段才有效。TCP 規(guī)定連接建立后 ACK=1,帶 ACK 標(biāo)志的TCP報(bào)文段稱為確認(rèn)報(bào)文段。
9.PSH:提示接收端需立即從 TCP 接收緩沖區(qū)中讀走數(shù)據(jù),為接收后續(xù)數(shù)據(jù)騰出空間。為1表示對(duì)方應(yīng)當(dāng)立即把數(shù)據(jù)提交給上層應(yīng)用,如果應(yīng)用程序不將接收到的數(shù)據(jù)讀走,就會(huì)一直停留在 TCP 接收緩沖區(qū)中。
10.RST:收到一個(gè) RST=1 的報(bào)文說明與主機(jī)的連接出現(xiàn)了嚴(yán)重錯(cuò)誤,必須釋放連接,然后再重新建立連接。或者說明上次發(fā)送給主機(jī)的數(shù)據(jù)有問題,主機(jī)拒絕響應(yīng),帶 RST 標(biāo)志的 TCP 報(bào)文段稱為復(fù)位報(bào)文段。
11.SYN:建立連接時(shí)用來同步序號(hào)。SYN=1 說明這是一個(gè)請(qǐng)求建立連接或同意建立連接的報(bào)文。只有在前兩次握手中 SYN 才置為1,帶 SYN 標(biāo)志的 TCP 報(bào)文段稱為同步報(bào)文段。
- 當(dāng) SYN=1 且 ACK=0 時(shí)表示這是一個(gè)請(qǐng)求建立連接的報(bào)文段。
- 當(dāng) SYN=1 且 ACK=1 時(shí)表示對(duì)方同意建立連接。
12FIN:通知對(duì)方本端要關(guān)閉連接了,標(biāo)記數(shù)據(jù)是否發(fā)送完畢。如果 FIN=1 告訴對(duì)方釋放連接,帶FIN標(biāo)志的TCP報(bào)文段稱為結(jié)束報(bào)文段。
13.窗口大?。罕硎粳F(xiàn)在允許對(duì)方發(fā)送的數(shù)據(jù)量,告訴對(duì)方從本報(bào)文段的確認(rèn)號(hào)開始允許對(duì)方發(fā)送的數(shù)據(jù)量,達(dá)到此值,需要ACK確認(rèn)后才能再繼續(xù)傳送后面數(shù)據(jù)。
14.校驗(yàn)和:提供額外的可靠性。
15.緊急指針:標(biāo)記緊急數(shù)據(jù)在數(shù)據(jù)字段中的位。
16.選項(xiàng)部分:選項(xiàng)部分的最大長(zhǎng)度可根據(jù)TCP首部長(zhǎng)度進(jìn)行推算。TCP首部長(zhǎng)度用4位表示,選項(xiàng)部分最長(zhǎng)為:(2^4-1)*4-20=40 字節(jié)。
TCP 只規(guī)定了一種選項(xiàng),即TCP報(bào)文段最大長(zhǎng)度 MSS,通常是1460字節(jié),整個(gè)TCP報(bào)文段的長(zhǎng)度 = 數(shù)據(jù)字段的長(zhǎng)度 + TCP 首部的長(zhǎng)度 。
17.填充:這里需注意,為了網(wǎng)絡(luò)設(shè)備硬件設(shè)計(jì)和處理方便, 數(shù)據(jù)傳輸過程中首部長(zhǎng)度必須是 4 字節(jié)的整數(shù)倍。
3.2.2 TCP 三次握手
TCP三次握手
- 一開始客戶端跟服務(wù)器都處于 closed 狀態(tài),然后服務(wù)端主動(dòng)監(jiān)聽某個(gè)客戶端端口,此時(shí)服務(wù)端處于listen 狀態(tài)。
- 客戶端隨機(jī)初始化序列號(hào) seq = client_isn,同時(shí)將 SYN = 1 表示這是SYN 報(bào)文,接著把該 SYN 報(bào)文發(fā)給服務(wù)器,注意此時(shí)報(bào)文不包含引用層數(shù)據(jù),客戶端處于 syn-sent狀態(tài)。
- 服務(wù)端收到客戶端的 SYN報(bào)文后也隨機(jī)初始化個(gè)序號(hào) seq = server_isn,并且將確認(rèn)序號(hào) ack = client_isn + 1,接著把 SYN = 1跟 ACK = 1,然后該報(bào)文發(fā)送給客戶端,服務(wù)器處于 syn-rcvd狀態(tài)。
- 客戶端收到服務(wù)器的報(bào)文后,將 ACK = 1,確認(rèn)應(yīng)答號(hào) ack = server_isn + 1,然后把報(bào)文發(fā)送給服務(wù)器,本次報(bào)文可發(fā)送數(shù)據(jù),同時(shí)客戶端處于established 狀態(tài)。
- 服務(wù)器收到客戶端的應(yīng)答報(bào)文后,也進(jìn)入 established 狀態(tài)。
- 客戶端和服務(wù)端建立好了連接,可以相互發(fā)送數(shù)據(jù)。
這里你可能發(fā)現(xiàn)了客戶端跟服務(wù)器的初始化序列號(hào)是各自隨機(jī)的,原因是網(wǎng)絡(luò)中的報(bào)文會(huì)重發(fā)、會(huì)延遲、也有可能丟失,為避免相互影響干脆各用各的為好。同時(shí)通過流程發(fā)現(xiàn)前兩次握手是不帶數(shù)據(jù)的,第三次可攜帶數(shù)據(jù)。
3.2.3 TCP 數(shù)據(jù)傳輸大致流程
前面在HTTP時(shí)候就說過了,數(shù)據(jù)到TCP層跟IP層都會(huì)拆分發(fā)送,有人可能會(huì)問:既然IP會(huì)分幀,那為什么TCP層還分層呢?原因是如果TCP不分層,只用IP層分幀數(shù)據(jù)發(fā)送,如果有一幀出現(xiàn)丟失則會(huì)導(dǎo)致整個(gè)IP報(bào)文分幀全部重傳。本質(zhì)在于IP層沒有重傳機(jī)制而TCP層可以實(shí)現(xiàn)數(shù)據(jù)的超時(shí)重傳、丟失重傳。
信息傳輸大致流程
3.2.4 TCP 狀態(tài)查詢
服務(wù)器一般用 netstat 查看 tcp,udp 的端口和進(jìn)程等相關(guān)情況。netstat -tunlp | grep 端口號(hào)
- -t (tcp) 僅顯示tcp相關(guān)選項(xiàng)
- -u (udp) 僅顯示udp相關(guān)選項(xiàng)
- -n 拒絕顯示別名,能顯示數(shù)字的全部轉(zhuǎn)化為數(shù)字
- -l 僅列出在Listen(監(jiān)聽)的服務(wù)狀態(tài)
- -p 顯示建立相關(guān)鏈接的程序名
netstat樣例
3.2.5 TCP 為啥三次握手
TCP是不區(qū)分客戶端和服務(wù)端,連接的建立是雙向的過程。所以客戶端要給服務(wù)器通訊的話兩次握手是必須的。
- 第一次握手客戶端發(fā)個(gè)連接請(qǐng)求給服務(wù)端,服務(wù)端收到后知道自己可以跟客戶端連接了,
- 但是此時(shí)客戶端不知道啊,所以必須的執(zhí)行第二次握手,反饋下信息給客戶端。
- 第一次握手請(qǐng)求連接如果因?yàn)榫W(wǎng)絡(luò)導(dǎo)致延遲,直到連接釋放后信息才到達(dá)服務(wù)端,那此時(shí)服務(wù)端也會(huì)給客戶端進(jìn)行第二次握手回復(fù),關(guān)鍵是客戶端已經(jīng)不要這個(gè)連接了,此時(shí)服務(wù)端會(huì)一直在等待接收客戶端信息,造成資源浪費(fèi)。
- 如果用了三次握手則客戶端會(huì)發(fā)送 RST 報(bào)文告知服務(wù)端請(qǐng)終止本次舊連接。
如果還不太理解,我們用個(gè)生活常識(shí)說明下。晚上你在小區(qū)里散步,不遠(yuǎn)處看見一位漂亮妹子迎面而來,因?yàn)槁窡粲悬c(diǎn)暗不能100%確認(rèn),所以要通過招手的方式來確定對(duì)方是否認(rèn)識(shí)自己。
1. 你首先向妹子 招手 syn。
2. 妹子看到你向自己招手后,向你點(diǎn)頭 微笑 ack。
3. 她也需要確認(rèn)一下你有沒有可能你是在看別人呢,妹子也向你 招手 syn。
4. 你看到妹子 微笑ack 后確認(rèn)了妹子成功辨認(rèn)出了自己,進(jìn)入 established 狀態(tài)。
5. 妹子給你 招手 syn 了,你也 微笑 ack 回復(fù),妹子收到后也進(jìn)入 established 狀態(tài)。
因?yàn)槊米舆B續(xù)進(jìn)行了兩個(gè)動(dòng)作,先是點(diǎn)頭微笑,然后再次招手,所以可以將這兩個(gè)動(dòng)作合成一個(gè)動(dòng)作,招手的同時(shí)點(diǎn)頭和微笑。于是這四個(gè)動(dòng)作就簡(jiǎn)化成了三個(gè)動(dòng)作。
你與妹子的相識(shí)
3.2.6 TCP 三次握手的意義
1.避免歷史連接
客戶端建立連接時(shí)發(fā)送多次 SYN 報(bào)文,由于網(wǎng)絡(luò)擁堵可能舊的 SYN 報(bào)文比新的 SYN 報(bào)文先到服務(wù)器,服務(wù)器不管新舊,收到就回復(fù) SYN + ACK 給客戶端,三次握手情況下客戶端可以根據(jù)序列號(hào)或超時(shí)時(shí)間判斷回復(fù)的連接是否是歷史連接,如果是歷史連接直接發(fā)送 RST 報(bào)文給服務(wù)端來終止連接。
2.同步雙方初始序列號(hào)
TCP 協(xié)議的通信雙方都在維護(hù)各自的序列號(hào),且必須要讓對(duì)方知道。只有通過三次握手才可以實(shí)現(xiàn)。
3.避免服務(wù)端資源浪費(fèi)
二次握手情況下,如果客戶端的 SYN 阻塞導(dǎo)致重復(fù)發(fā)送多次 SYN 報(bào)文,那么服務(wù)器在收到請(qǐng)求后就會(huì)建立多個(gè)冗余的無效鏈接,造成不必要的資源浪費(fèi)。而三次握手發(fā)現(xiàn)無效鏈接可在第三次給服務(wù)器端發(fā)送終止指令。
3.2.7 TCP 連接中客戶端忽然掛掉咋辦?
TCP還設(shè)有一個(gè)?;钣?jì)時(shí)器,服務(wù)器每收到一次客戶端的請(qǐng)求后都會(huì)重新復(fù)位這個(gè)計(jì)時(shí)器,時(shí)間通常是設(shè)置為2小時(shí),若兩小時(shí)還沒有收到客戶端的任何數(shù)據(jù),服務(wù)器就會(huì)發(fā)送一個(gè)探測(cè)報(bào)文段,以后每隔75秒發(fā)送一次。若一連發(fā)送10個(gè)探測(cè)報(bào)文仍然沒反應(yīng),服務(wù)器就認(rèn)為客戶端出了故障,接著就關(guān)閉連接。
3.2.8 TCP 如何避免 SYN 攻擊
TCP 連接時(shí)會(huì)經(jīng)過三次握手,在第一次握手后,服務(wù)端收到 SYN 報(bào)文 就會(huì)發(fā)出 ACK + SYN 報(bào)文 同時(shí)進(jìn)入 SYN_RCVD 狀態(tài),如果有黑客偽造 n 個(gè)不同 IP 發(fā)出請(qǐng)求,會(huì)導(dǎo)致服務(wù)器的 SYN_RCVD 隊(duì)列 爆滿,最終無法對(duì)外提供服務(wù)。
解決方法:
- 設(shè)置 SYN_RCVD 最大值:服務(wù)端超過處理能力時(shí),直接將新的 SYN 請(qǐng)求 RST 丟棄。
- 可縮短 SYN Timeout 時(shí)間:通過縮短從接收到 SYN 報(bào)文到確定這個(gè)報(bào)文無效并丟棄該連接的時(shí)間,可以降低服務(wù)器負(fù)荷。
- 設(shè)置SYN Cookie:給每個(gè)請(qǐng)求連接的 IP 地址分配一個(gè) Cookie,如果短時(shí)間內(nèi)收到同一個(gè) IP 的重復(fù) SYN 報(bào)文,則以后從這個(gè) IP 地址來的包會(huì)被丟棄。
3.2.9 TCP 四次揮手
客戶端跟服務(wù)端都可以發(fā)出端口請(qǐng)求,TCP 斷開連接是通過四次揮手方式。
TCP四次揮手
- 客戶端停止發(fā)送數(shù)據(jù)并且發(fā)出釋放連接的報(bào)文,報(bào)文中FIN = 1,F(xiàn)IN報(bào)文段即使不攜帶數(shù)據(jù),也要消耗一個(gè)序號(hào),此時(shí)序列號(hào)seq = u ,u = 前面已經(jīng)傳輸過來數(shù)據(jù)最后一個(gè)字節(jié)序號(hào)加1,客戶端進(jìn)入FIN-WAIT-1狀態(tài)。
- 服務(wù)器收到連接釋放報(bào)文,發(fā)出確認(rèn)報(bào)文,ACK=1,應(yīng)答確認(rèn)好 ack=u+1,并且?guī)献约旱男蛄刑?hào)seq=v,此時(shí)服務(wù)端就進(jìn)入了CLOSE-WAIT 狀態(tài)。TCP 服務(wù)器通知高層的應(yīng)用進(jìn)程進(jìn)入半閉狀態(tài),即客戶端已經(jīng)沒有數(shù)據(jù)要發(fā)送了,但是服務(wù)器若發(fā)送數(shù)據(jù),客戶端依然要接受。這個(gè)狀態(tài)還要持續(xù)一段時(shí)間,也就是整個(gè) CLOSE-WAIT狀態(tài)持續(xù)的時(shí)間。
- 客戶端收到服務(wù)器的確認(rèn)請(qǐng)求后,此時(shí)客戶端就進(jìn)入FIN-WAIT-2 狀態(tài),等待服務(wù)器發(fā)送連接釋放報(bào)文,在這之前還需要接受服務(wù)器發(fā)送的最后的數(shù)據(jù)。
- 服務(wù)器將最后的數(shù)據(jù)發(fā)送完畢后,就向客戶端發(fā)送連接釋放報(bào)文,F(xiàn)IN=1,ack=u+1,由于在半關(guān)閉狀態(tài),服務(wù)器很可能又發(fā)送了一些數(shù)據(jù),假定此時(shí)的序列號(hào)為 seq=w,此時(shí)服務(wù)器就進(jìn)入了LAST-ACK 狀態(tài),等待客戶端的確認(rèn)。
- 客戶端收到服務(wù)器的連接釋放報(bào)文后必須發(fā)出確認(rèn),ACK=1,ack=w+1,seq=u+1,此時(shí)客戶端就進(jìn)入了TIME-WAIT狀態(tài)。注意此時(shí) TCP 連接還沒有釋放,必須經(jīng)過最長(zhǎng)報(bào)文段壽命 2MSL 的時(shí)間后,當(dāng)客戶端撤銷相應(yīng)的 TCB 后,才進(jìn)入 CLOSED 狀態(tài)。
- 服務(wù)器只要收到了客戶端發(fā)出的確認(rèn),立即進(jìn)入 CLOSED 狀態(tài)。同樣撤銷 TCB后,就結(jié)束了這次的 TCP 連接,可以看到服務(wù)器結(jié)束TCP連接的時(shí)間要比客戶端早一些。
我們還以你跟妹子碰面交流為例,你倆彼此確認(rèn)后交流幾分鐘后,你打算結(jié)束這個(gè)談話,畢竟交流太久沒老婆發(fā)現(xiàn)就涼了。
你跟妹子揮手離別
3.2.10 TCP 為什么四次揮手
其實(shí)分析下整個(gè)關(guān)閉的流程就知道為什么必須是四次揮手而不是三次揮手了。
- 關(guān)閉連接時(shí),客戶端向服務(wù)端發(fā)送 FIN 時(shí),僅僅表示客戶端不再發(fā)送數(shù)據(jù)出去了但是還是能接收數(shù)據(jù)。
- 服務(wù)器收到客戶端的 FIN 報(bào)文時(shí),先回一個(gè) ACK應(yīng)答報(bào)文,意思是不再接受數(shù)據(jù)了,但服務(wù)端可能還有數(shù)據(jù)需往外發(fā)送,等服務(wù)端不再發(fā)送數(shù)據(jù)時(shí)才發(fā)送 FIN 報(bào)文給客戶端來表示同意現(xiàn)在關(guān)閉連接。這里注意服務(wù)端的 ACK 跟 FIN 是分開發(fā)的。
- 客戶端收到服務(wù)端的 ACK 后,再給服務(wù)端發(fā)送 ACK,最終客戶端跟服務(wù)器都進(jìn)入close 狀態(tài)。
3.2.11 TCP 揮手為什么需要 TIME_WAIT 狀態(tài)
MSL 定義:
Maximum Segment Lifetime 報(bào)文最大生存時(shí)間,意思是網(wǎng)絡(luò)傳輸?shù)膱?bào)文在網(wǎng)絡(luò)上存在的最長(zhǎng)時(shí)間,超過這個(gè)時(shí)間報(bào)文將被丟棄。而數(shù)據(jù)之所以可以被拋棄是因?yàn)門CP層的下面的IP層有個(gè)TTL來記錄報(bào)文傳輸過程中經(jīng)過的最大路由次數(shù)。
TIME_WAIT 定義:
TIME_WAIT = 2* MSL,原因是 發(fā)送方數(shù)據(jù)到接受方后,接收方會(huì)返回響應(yīng),這樣一來一回正好2 倍的MSL。
Time_wait 是從客戶端接收到 FIN 后發(fā)送 ACK 開始計(jì)時(shí)的,如果在 TIME-WAIT 時(shí)間內(nèi),客戶端的 ACK 沒有傳輸?shù)椒?wù)端,客戶端又接收到了服務(wù)端重發(fā)的 FIN 報(bào)文,那 2MSL 將重新計(jì)時(shí)。
TIME_WAIT 存在意義:
防止舊連接的數(shù)據(jù)包被重新消費(fèi):上一次連接時(shí)候如果有網(wǎng)絡(luò)震蕩導(dǎo)致服務(wù)端數(shù)據(jù)在網(wǎng)絡(luò)游蕩,如果因?yàn)?time_wait 時(shí)間太短,新的連接可能會(huì)重新接受到游蕩的消息。有了延遲時(shí)間可以避免消耗游蕩的數(shù)據(jù)。
確保連接正確關(guān)閉:TIME-WAIT 作用是等待足夠的時(shí)間以確保最后的 ACK 能讓被動(dòng)關(guān)閉方接收,從而幫助其正常關(guān)閉。
TIME_WAIT 發(fā)生場(chǎng)景:
在高并發(fā)短連接的 TCP 服務(wù)器上,當(dāng)服務(wù)器處理完請(qǐng)求后立刻主動(dòng)正常關(guān)閉連接。這個(gè)場(chǎng)景下會(huì)出現(xiàn)大量 socket 處于 TIME_WAIT 狀態(tài)。如果客戶端的并發(fā)量持續(xù)很高,因?yàn)槎丝谟邢?,?nèi)存有限,會(huì)導(dǎo)致此時(shí)部分客戶端顯示連接不上。
在Linux內(nèi)核中 TIME_WAIT = 60秒。
避免 TIME_WAIT 過多:
- 取消短連接,改用長(zhǎng)連接方式,
- 設(shè)定閾值,一旦超過閾值系統(tǒng)會(huì)將所有time_wait 連接重置。
- 修改客戶端程序代碼。
3.2.12 TCP 如何保證數(shù)據(jù)傳輸可靠
- 校驗(yàn)和:發(fā)送跟接受數(shù)據(jù)都會(huì)進(jìn)行檢驗(yàn)的,如果不一致,那么傳輸有誤。
- 確認(rèn)應(yīng)答序列號(hào):TCP進(jìn)行傳輸時(shí)數(shù)據(jù)都進(jìn)行了編號(hào),每次接收方返回ACK都有確認(rèn)序列號(hào)。
- 超時(shí)重傳:如果發(fā)送方發(fā)送數(shù)據(jù)一段時(shí)間后沒有收到ACK,那么就重發(fā)數(shù)據(jù)。并且自帶去重功能。
- 連接管理:三次握手和四次揮手的過程。
- 流量控制:TCP協(xié)議報(bào)頭包含16位的窗口大小,接收方會(huì)在返回ACK時(shí)同時(shí)把自己的即時(shí)窗口填入,發(fā)送方就根據(jù)報(bào)文中窗口的大小控制發(fā)送速度。
- 擁塞控制:剛開始發(fā)送數(shù)據(jù)的時(shí)候,擁塞窗口是1,以后每次收到ACK,則擁塞窗口+1,然后將擁塞窗口和收到的窗口取較小值作為實(shí)際發(fā)送的窗口,如果發(fā)生超時(shí)重傳,擁塞窗口重置為1。這樣做的目的就是為了保證傳輸過程的高效性和可靠性。
3.3 UDP
UDP 為應(yīng)用程序提供了一種無需建立連接就可以發(fā)送封裝的 IP 數(shù)據(jù)包的方法,它的協(xié)議很簡(jiǎn)單,頭部只有八個(gè)字節(jié):
UDP頭部
- 兩個(gè)十六位的端口號(hào):分別為源端口和目標(biāo)端口。
- 包長(zhǎng)度:該字段 = UDP首部長(zhǎng)度 + 數(shù)據(jù)長(zhǎng)度。
- 校驗(yàn)和:整個(gè)數(shù)據(jù)報(bào)文的檢驗(yàn)和,該字段用于發(fā)現(xiàn)頭部信息和數(shù)據(jù)中的錯(cuò)誤。
3.3.1 UDP 特點(diǎn)
UDP有不提供數(shù)據(jù)包分組、組裝和不能對(duì)數(shù)據(jù)包進(jìn)行排序的缺點(diǎn),當(dāng)報(bào)文發(fā)送之后,是無法得知其是否安全完整到達(dá)的。
1.面向無連接
UDP 不會(huì)進(jìn)行三次握手建立連接,想建立連接就建立連接,并且也只是數(shù)據(jù)報(bào)文的搬運(yùn)工,不會(huì)對(duì)數(shù)據(jù)報(bào)文進(jìn)行任何拆分和拼接操作。
在發(fā)送端,應(yīng)用層將數(shù)據(jù)傳遞給傳輸層的 UDP 協(xié)議,UDP 只會(huì)給數(shù)據(jù)增加一個(gè) UDP 頭標(biāo)識(shí)下是 UDP 協(xié)議,然后就傳遞給網(wǎng)絡(luò)層了。
在接收端,網(wǎng)絡(luò)層將數(shù)據(jù)傳遞給傳輸層,UDP 只去除 IP 報(bào)文頭就傳遞給應(yīng)用層,不會(huì)任何拼接操作
2.有單播、多播、廣播的功能
UDP 不止支持一對(duì)一的傳輸方式,同樣支持一對(duì)多,多對(duì)多,多對(duì)一的方式,也就是說 UDP 提供了單播,多播,廣播的功能。
3.UDP面向報(bào)文
發(fā)送方的UDP對(duì)應(yīng)用程序交下來的報(bào)文,在添加首部后就向下交付IP層。UDP對(duì)應(yīng)用層交下來的報(bào)文,既不合并,也不拆分,而是保留這些報(bào)文的邊界。因此應(yīng)用程序必須選擇合適大小的報(bào)文
4.不可靠性
不可靠性體現(xiàn)在無連接上,通信都不需要建立連接,想發(fā)就發(fā),這樣的情況肯定不可靠。
收到什么數(shù)據(jù)就傳遞什么數(shù)據(jù),并且也不會(huì)備份數(shù)據(jù),發(fā)送數(shù)據(jù)也不會(huì)關(guān)心對(duì)方是否已經(jīng)正確接收到數(shù)據(jù)了。
沒有擁塞控制,一直會(huì)以恒定的速度發(fā)送數(shù)據(jù)。網(wǎng)絡(luò)不好可能導(dǎo)致丟包,在某些實(shí)時(shí)性要求高的場(chǎng)景,比如視頻電話就需要使用 UDP。
5.頭部開銷小
UDP 的頭部開銷小,只有八字節(jié),相比 TCP 的至少二十字節(jié)要少得多,在傳輸數(shù)據(jù)報(bào)文時(shí)是很高效的
3.3.2 TCP 跟 UDP 對(duì)比
3.3.3 TCP UDP 共有端口
你可能經(jīng)常被問到,TCP 和 UDP 為何可以共用同一端口?這是因?yàn)閺木W(wǎng)絡(luò)層的角度來看,它是不知道端口這個(gè)概念的,TCP/UDP 都是包裹在 IP 協(xié)議內(nèi)的,IP 協(xié)議只需要知道 IP 對(duì)應(yīng)的硬件地址就可以把遠(yuǎn)端的網(wǎng)絡(luò)包發(fā)送到目的主機(jī)上。
端口這個(gè)概念是由操作系統(tǒng)劃分的。因?yàn)閮?nèi)核不可能把所有網(wǎng)絡(luò)數(shù)據(jù)都發(fā)送給所有的進(jìn)程,所以為了區(qū)分哪些數(shù)據(jù)該劃分給哪些進(jìn)程,便在傳輸層的協(xié)議中定義了端口。而TCP和UDP協(xié)議中的端口號(hào)占位都是16位,所以操作系統(tǒng)能綁定的端口也就只有65535個(gè)。
如果你查看 C 語言有關(guān) Socket 編程中的 socket 跟 bind 函數(shù)你會(huì)發(fā)現(xiàn),系統(tǒng)是以 協(xié)議 + ip + 端口來綁定端口的,所以不同協(xié)議相同的ip和端口也是可以綁定成功的。
4 TCP 進(jìn)階
4.1 TCP 重傳機(jī)制
為保證數(shù)據(jù)安全到達(dá)接受端,TCP引入了超時(shí)重傳、快速重傳、SACK、D-SACK。
4.1.1 超時(shí)重傳
以時(shí)間為基準(zhǔn),在發(fā)送數(shù)據(jù)時(shí)設(shè)置個(gè)定時(shí)器,如果期限內(nèi)沒收到接受者的ACK就會(huì)重新發(fā)送數(shù)據(jù),一般數(shù)據(jù)包丟失或確認(rèn)應(yīng)答丟失會(huì)導(dǎo)致超時(shí)重傳,這里先普及兩個(gè)跟時(shí)間相關(guān)的參數(shù)跟一些規(guī)則。
- RTT:Round-Trip Time 往返時(shí)間,指的是數(shù)據(jù)從發(fā)送到接受的耗時(shí)時(shí)間。
- RTO :Retransmission Timeout 超時(shí)重傳時(shí)間。
- 動(dòng)態(tài):RTT 收到網(wǎng)絡(luò)波動(dòng)是動(dòng)態(tài)變化的,同理RTO也是動(dòng)態(tài)變化的。
- RTO翻倍:每遇到一次超時(shí)重傳,系統(tǒng)都會(huì)將下一次RTO翻倍。
RTO跟RTT
RTT跟RTO之間的關(guān)系十分微妙。
- RTO 較小時(shí)可能導(dǎo)致數(shù)據(jù)本來就沒丟失只是還沒被響應(yīng), 又重發(fā)會(huì)增加網(wǎng)絡(luò)擁塞,導(dǎo)致更多的超時(shí)重發(fā)。
- RTO較大時(shí)候可能導(dǎo)致數(shù)據(jù)已經(jīng)丟了好久才重發(fā)數(shù)據(jù)。
所以離線情況下 RTO 稍微大于 RTT是最好的。具體規(guī)則有興趣的可自行百度。
4.1.2 快速重傳
TCP有累計(jì)確認(rèn)機(jī)制,當(dāng)接收端收到比期望序號(hào)大的報(bào)文段時(shí),便會(huì)重復(fù)發(fā)送最近一次確認(rèn)的報(bào)文段的確認(rèn)信號(hào),我們稱之為冗余ACK(duplicate ACK)。
如圖所示,報(bào)文段1成功接收并被確認(rèn)ACK 2,接收端的期待序號(hào)為2,當(dāng)報(bào)文段2丟失,報(bào)文段3失序到來,與接收端的期望不匹配,接收端重復(fù)發(fā)送冗余ACK 2。
快速重傳機(jī)制
發(fā)送端如果在超時(shí)重傳定時(shí)器溢出之前,接收到連續(xù)的三個(gè)重復(fù)冗余ACK(其實(shí)是收到4個(gè)同樣的ACK,第一個(gè)是正常的,后三個(gè)才是冗余的),發(fā)送端便知曉哪個(gè)報(bào)文段在傳輸過程中丟失了,于是重發(fā)該報(bào)文段,不需要等待超時(shí)重傳定時(shí)器溢出,最后客戶端收到 2,因?yàn)?45已經(jīng)回復(fù)過了,返回ACK6。
為啥是3次呢? 你要明白發(fā)送端即使按序發(fā)送,接收端也是會(huì)出現(xiàn)亂序的。亂序也會(huì)造成冗余ACK發(fā)送,那冗余ACK是亂序?qū)е逻€是丟包導(dǎo)致呢?經(jīng)過權(quán)衡把3次冗余ACK作為判定丟失的準(zhǔn)則其本身就是估計(jì)值。
數(shù)據(jù)接收情況
A為發(fā)送端,B為接收端,A的待發(fā)報(bào)文段序號(hào)為 【N-1,N,N+1,N+2】,假設(shè)報(bào)文段N-1成功到達(dá)。
- 在沒丟失的情況下,有40%的可能出現(xiàn)3次冗余ACK,在亂序的情況下必定是2次冗余ACK。
- 在丟失的情況下,必定出現(xiàn)3次冗余ACK。
基于這樣的概率,選定3次冗余ACK作為閾值也算是合理的。實(shí)際抓包時(shí)大多數(shù)的快速重傳都會(huì)在大于3次冗余ACK后發(fā)生。
快速重傳解決了超時(shí)問題,可是重傳時(shí)是重傳之前的一個(gè),還是重傳所有它是定不了的。
4.1.3 SACK
既然快速重傳搞不定,就用 Selective Acknowledgment 選擇性確認(rèn),原理也很簡(jiǎn)單,服務(wù)端給客戶端回復(fù)的時(shí)候多加個(gè)字段SACK,SACK的內(nèi)容就是告知發(fā)送端服務(wù)端收到了哪些。這樣服務(wù)端可以根據(jù)收到的信息選擇性發(fā)送丟失的包。
4.1.4 D-SACK
DSACK是在SACK的基礎(chǔ)上做了一些擴(kuò)展,主要用于對(duì)收到的重復(fù)報(bào)文進(jìn)行了處理。DSACK同樣使用了與SACK一樣的報(bào)文格式。核心關(guān)注點(diǎn)是發(fā)送的時(shí)候出問題了還是回復(fù)的時(shí)候出問題了。
如果發(fā)送端發(fā)送數(shù)據(jù)A延時(shí)而觸發(fā)了快速重傳機(jī)制,快速重傳機(jī)制發(fā)送過來的信息新A,然后老A又到了,接收端會(huì)回復(fù)SACK 意思是網(wǎng)絡(luò)震蕩導(dǎo)致的。
如果服務(wù)端的ACK 客戶端沒收到,客戶端重發(fā)的時(shí)候,服務(wù)端會(huì)回復(fù)SACK,意思就是你的數(shù)據(jù)發(fā)送重復(fù)了。
4.2 TCP 滑動(dòng)窗口
如果沒有滑動(dòng)窗口的機(jī)制:傳輸N份文件,就需要等待N次應(yīng)答時(shí)間。
總的傳輸時(shí)間 = N份傳輸時(shí)間 + N份應(yīng)答傳輸時(shí)間。
保證可靠性的前提下TCP 引入了窗口概念,滑動(dòng)窗口可以讓我們進(jìn)一步提高傳輸效率。在窗口內(nèi)的數(shù)據(jù)無需等待確認(rèn)應(yīng)答就可以繼續(xù)發(fā)送數(shù)據(jù)。窗口的本質(zhì)是OS開辟的一個(gè)緩存空間,然后進(jìn)行批量傳輸,只要接收方?jīng)]確認(rèn)應(yīng)答那么緩存中會(huì)一直存在。
總的傳輸時(shí)間 = N分?jǐn)?shù)據(jù)傳輸時(shí)間疊加成一份時(shí)間,N份應(yīng)答傳輸時(shí)間,重疊成一份時(shí)間
窗口大小為4000字節(jié)
窗口大小一般是接收方來決定的,接收方會(huì)告知發(fā)送方自己有多少緩存可接受數(shù)據(jù),如果超過這個(gè)數(shù)據(jù)量接收方就無法接收了。
4.2.1 發(fā)送滑動(dòng)窗口
滑動(dòng)窗口
在一的狀態(tài)下發(fā)送方收到一個(gè)請(qǐng)求序列號(hào)2001的確認(rèn)應(yīng)答ACK,則2001前數(shù)據(jù)被標(biāo)記為傳輸完畢,系統(tǒng)會(huì)進(jìn)行窗口滑動(dòng)變?yōu)槎臉幼印?/p>
- 窗口左邊是已經(jīng)發(fā)送并且受到服務(wù)器的ACK的數(shù)據(jù),這些數(shù)據(jù)可以從緩存刪除。
- 窗口內(nèi)的數(shù)據(jù)其實(shí)也分為兩類,一類是發(fā)送還沒接收到ACK的,一類是還未發(fā)送的。在收到整個(gè)窗口的確認(rèn)應(yīng)答ACK之前,如果數(shù)據(jù)有丟失,發(fā)送端仍然需要重傳。所以發(fā)送端需要有緩存保留可能被重傳的數(shù)據(jù),直到收到服務(wù)端ACK。
- 收到服務(wù)端ACK后,發(fā)送端會(huì)將窗口滑動(dòng)到確認(rèn)應(yīng)答中的序列號(hào)的位置。這樣可以順序地將多個(gè)段同時(shí)發(fā)送提高通信性能。這種機(jī)制也別稱為滑動(dòng)窗口控制。
- 窗口模式下發(fā)送方也會(huì)根據(jù)接收方的能力來進(jìn)行發(fā)送數(shù)據(jù)來進(jìn)行流量控制。
4.2.2 窗口數(shù)據(jù)丟失
這里的數(shù)據(jù)丟失其實(shí)跟前面說到的重傳機(jī)制類似,主要分為兩種
接收端收到信息但是返回ACK失敗了:如果丟失ACK,不需要做任何處理,如3001這個(gè)ACK丟了,但是4001ACK卻已經(jīng)發(fā)送給主機(jī)A,說明2001~3000這個(gè)數(shù)據(jù)也順利到達(dá),3001ACK丟了無所謂,只要當(dāng)前序號(hào)開始,就說明之前的數(shù)據(jù)已經(jīng)正確傳輸?shù)竭_(dá)主機(jī)B
收到數(shù)據(jù)但ACK丟失
發(fā)送端發(fā)送數(shù)據(jù)中途數(shù)據(jù)丟失了:如下圖1001-2000數(shù)據(jù)包丟了,而2001-3000,3001-4000都順利到達(dá),此時(shí)接收方反饋的ACK確認(rèn)序號(hào)始終是1001,發(fā)送方如果發(fā)現(xiàn)接收方連續(xù)發(fā)送ACK都是1001,接收方就明白1001-2000這個(gè)數(shù)據(jù)丟包,就會(huì)重新傳送,當(dāng)接收方重新收到丟失的1001-2000數(shù)據(jù)后,直接返回ACK4001,因?yàn)?001-4000已經(jīng)接受過放到緩存區(qū)了,接下來ACK直接從4001開始。
發(fā)送時(shí)丟失
4.3 擁塞控制
前面說到的流量控制只是單純的對(duì)于發(fā)送方跟接受方而已,但是我們要知道網(wǎng)絡(luò)一般都是公用的,別的服務(wù)器也可以能將網(wǎng)絡(luò)搞阻塞,因?yàn)樽枞麑?dǎo)致重發(fā),然后重發(fā)導(dǎo)致更阻塞,最后陷入惡性循環(huán)。
為了控制發(fā)送方的數(shù)據(jù)量避免數(shù)據(jù)阻塞整個(gè)網(wǎng)絡(luò),發(fā)送方維護(hù)著一個(gè)叫擁塞窗口的東西,前面說到過發(fā)送窗口跟接受窗口,現(xiàn)在由于有了擁塞窗口,此時(shí) 發(fā)送窗口swnd = min(擁塞窗口cwnd,接受窗口rwnd)。擁塞窗口的大小是動(dòng)態(tài)變化的,當(dāng)網(wǎng)絡(luò)沒阻塞就會(huì)變大,網(wǎng)絡(luò)中有阻塞就會(huì)變小。判斷阻塞的依據(jù)就是如果發(fā)送方在指定時(shí)間內(nèi)沒收到數(shù)據(jù)那就是阻塞了。
擁塞控制主要通過慢開始,快重傳,快恢復(fù)和避免擁塞來實(shí)現(xiàn)的。
4.3.1 慢開始
TCP建立連接后系統(tǒng)有個(gè)慢啟動(dòng)的過程,意思就是一點(diǎn)一點(diǎn)的提高發(fā)送數(shù)據(jù)包的數(shù)量,慢啟動(dòng)的原則就是當(dāng)發(fā)送方每收到一個(gè) ACK,擁塞窗口 cwnd 的大小就會(huì)加 1。有一個(gè)叫慢啟動(dòng)門限 slow start threshold 狀態(tài)變量來充當(dāng)最大值。
cwnd < ssthresh 時(shí),使用慢啟動(dòng)算法。
cwnd >= ssthresh 時(shí),使用擁塞避免算法。
4.3.2 擁塞避免算法
一般情況下 slow start threshold = 65535字節(jié),系統(tǒng)進(jìn)入擁塞避免算法后,每當(dāng)收到一個(gè) ACK 時(shí),擁塞窗口就增加 1/擁塞窗口。擁塞避免算法存在的意義就是將慢開始的那種指數(shù)增長(zhǎng)變化為線程增長(zhǎng)。
4.3.3 快重傳
進(jìn)入擁塞避免算法后的數(shù)據(jù)隨著不斷增長(zhǎng)最終會(huì)導(dǎo)致網(wǎng)絡(luò)阻塞,最終引發(fā)丟包。然后會(huì)采用前面說到的超時(shí)重傳跟快速重傳。
- 超時(shí)重傳:ssthresh = cwnd/2 同時(shí) cwnd 重置為 1,然后重新開始慢啟動(dòng),回到了起點(diǎn)。
- 快速重傳:cwnd = cwnd/2 同時(shí) ssthresh = cwnd 然后進(jìn)入快速恢復(fù)算法。
4.3.4 快恢復(fù)
快恢復(fù)與快重傳配合使用,當(dāng)發(fā)送方接收到連續(xù)三個(gè)重復(fù)確認(rèn)請(qǐng)求,為了避免網(wǎng)絡(luò)擁塞,執(zhí)行快速重傳(cwnd = cwnd/2 同時(shí) ssthresh = cwnd ),執(zhí)行快速恢復(fù)算法。
- cwnd = ssthresh + 3
- 重傳丟失的數(shù)據(jù)包
- 收到重復(fù)ACK則 cwnd 累加 1。
- 收到新ACK后設(shè)置 cwnd = ssthresh,進(jìn)入擁塞避免算法。
5 參考
科技哥網(wǎng)絡(luò):https://t.1yb.co/gJRx
小林網(wǎng)絡(luò):https://t.1yb.co/fQG3
TCP/IP講解:https://developer.51cto.com/art/201906/597961.htm
快速重傳:https://blog.csdn.net/whgtheone/article/details/80983882