TCP 詳解與優(yōu)化

轉(zhuǎn)自

面試官:換人!他連 TCP 這幾個(gè)參數(shù)都不懂

前言

TCP 性能的提升不僅考察 TCP 的理論知識(shí)雕沉,還考察了對(duì)于操作系統(tǒng)提供的內(nèi)核參數(shù)的理解與應(yīng)用集乔。

TCP 協(xié)議是由操作系統(tǒng)實(shí)現(xiàn),所以操作系統(tǒng)提供了不少調(diào)節(jié) TCP 的參數(shù)坡椒。

Linux TCP 參數(shù)

Linux TCP 參數(shù)

如何正確有效的使用這些參數(shù)扰路,來提高 TCP 性能是一個(gè)不那么簡(jiǎn)單事情。我們需要針對(duì) TCP 每個(gè)階段的問題來對(duì)癥下藥倔叼,而不是病急亂投醫(yī)汗唱。

接下來,將以三個(gè)角度來闡述提升 TCP 的策略缀雳,分別是:

  • TCP 三次握手的性能提升渡嚣;
  • TCP 四次揮手的性能提升;
  • TCP 數(shù)據(jù)傳輸?shù)男阅芴嵘?/li>
本節(jié)提綱

本節(jié)提綱


正文

01 TCP 三次握手的性能提升

TCP 是面向連接的肥印、可靠的识椰、雙向傳輸?shù)膫鬏攲油ㄐ艆f(xié)議,所以在傳輸數(shù)據(jù)之前需要經(jīng)過三次握手才能建立連接深碱。

三次握手與數(shù)據(jù)傳輸

三次握手與數(shù)據(jù)傳輸

那么腹鹉,三次握手的過程在一個(gè) HTTP 請(qǐng)求的平均時(shí)間占比 10% 以上,在網(wǎng)絡(luò)狀態(tài)不佳敷硅、高并發(fā)或者遭遇 SYN 攻擊等場(chǎng)景中功咒,如果不能有效正確的調(diào)節(jié)三次握手中的參數(shù)愉阎,就會(huì)對(duì)性能產(chǎn)生很多的影響。

如何正確有效的使用這些參數(shù)力奋,來提高 TCP 三次握手的性能榜旦,這就需要理解「三次握手的狀態(tài)變遷」,這樣當(dāng)出現(xiàn)問題時(shí)景殷,先用 netstat 命令查看是哪個(gè)握手階段出現(xiàn)了問題溅呢,再來對(duì)癥下藥,而不是病急亂投醫(yī)猿挚。

TCP 三次握手的狀態(tài)變遷

TCP 三次握手的狀態(tài)變遷

客戶端和服務(wù)端都可以針對(duì)三次握手優(yōu)化性能咐旧。主動(dòng)發(fā)起連接的客戶端優(yōu)化相對(duì)簡(jiǎn)單些,而服務(wù)端需要監(jiān)聽端口绩蜻,屬于被動(dòng)連接方铣墨,其間保持許多的中間狀態(tài),優(yōu)化方法相對(duì)復(fù)雜一些办绝。

所以伊约,客戶端(主動(dòng)發(fā)起連接方)和服務(wù)端(被動(dòng)連接方)優(yōu)化的方式是不同的,接下來分別針對(duì)客戶端和服務(wù)端優(yōu)化八秃。

客戶端優(yōu)化

三次握手建立連接的首要目的是「同步序列號(hào)」碱妆。

只有同步了序列號(hào)才有可靠傳輸,TCP 許多特性都依賴于序列號(hào)實(shí)現(xiàn)昔驱,比如流量控制、丟包重傳等上忍,這也是三次握手中的報(bào)文稱為 SYN 的原因骤肛,SYN 的全稱就叫 Synchronize Sequence Numbers(同步序列號(hào))。

TCP 頭部

TCP 頭部

SYN_SENT 狀態(tài)的優(yōu)化

客戶端作為主動(dòng)發(fā)起連接方窍蓝,首先它將發(fā)送 SYN 包腋颠,于是客戶端的連接就會(huì)處于 SYN_SENT 狀態(tài)。

客戶端在等待服務(wù)端回復(fù)的 ACK 報(bào)文吓笙,正常情況下淑玫,服務(wù)器會(huì)在幾毫秒內(nèi)返回 SYN+ACK ,但如果客戶端長(zhǎng)時(shí)間沒有收到 SYN+ACK 報(bào)文面睛,則會(huì)重發(fā) SYN 包絮蒿,重發(fā)的次數(shù)由 tcp_syn_retries 參數(shù)控制,默認(rèn)是 5 次:

image

通常叁鉴,第一次超時(shí)重傳是在 1 秒后土涝,第二次超時(shí)重傳是在 2 秒,第三次超時(shí)重傳是在 4 秒后幌墓,第四次超時(shí)重傳是在 8 秒后但壮,第五次是在超時(shí)重傳 16 秒后冀泻。沒錯(cuò),每次超時(shí)的時(shí)間是上一次的 2 倍蜡饵。

當(dāng)?shù)谖宕纬瑫r(shí)重傳后弹渔,會(huì)繼續(xù)等待 32 秒,如果仍然服務(wù)端沒有回應(yīng) ACK溯祸,客戶端就會(huì)終止三次握手捞附。

所以,總耗時(shí)是 1+2+4+8+16+32=63 秒您没,大約 1 分鐘左右鸟召。

SYN 超時(shí)重傳

SYN 超時(shí)重傳

你可以根據(jù)網(wǎng)絡(luò)的穩(wěn)定性和目標(biāo)服務(wù)器的繁忙程度修改 SYN 的重傳次數(shù),調(diào)整客戶端的三次握手時(shí)間上限氨鹏。比如內(nèi)網(wǎng)中通訊時(shí)欧募,就可以適當(dāng)調(diào)低重試次數(shù),盡快把錯(cuò)誤暴露給應(yīng)用程序仆抵。

服務(wù)端優(yōu)化

當(dāng)服務(wù)端收到 SYN 包后跟继,服務(wù)端會(huì)立馬回復(fù) SYN+ACK 包,表明確認(rèn)收到了客戶端的序列號(hào)镣丑,同時(shí)也把自己的序列號(hào)發(fā)給對(duì)方舔糖。

此時(shí),服務(wù)端出現(xiàn)了新連接莺匠,狀態(tài)是 SYN_RCV金吗。在這個(gè)狀態(tài)下,Linux 內(nèi)核就會(huì)建立一個(gè)「半連接隊(duì)列」來維護(hù)「未完成」的握手信息趣竣,當(dāng)半連接隊(duì)列溢出后摇庙,服務(wù)端就無法再建立新的連接。

半連接隊(duì)列與全連接隊(duì)列

半連接隊(duì)列與全連接隊(duì)列

SYN 攻擊遥缕,攻擊的是就是這個(gè)半連接隊(duì)列卫袒。

如何查看由于 SYN 半連接隊(duì)列已滿,而被丟棄連接的情況单匣?

我們可以通過該 netstat -s 命令給出的統(tǒng)計(jì)結(jié)果中夕凝, 可以得到由于半連接隊(duì)列已滿,引發(fā)的失敗次數(shù):

image

上面輸出的數(shù)值是累計(jì)值户秤,表示共有多少個(gè) TCP 連接因?yàn)榘脒B接隊(duì)列溢出而被丟棄码秉。隔幾秒執(zhí)行幾次,如果有上升的趨勢(shì)虎忌,說明當(dāng)前存在半連接隊(duì)列溢出的現(xiàn)象泡徙。

如何調(diào)整 SYN 半連接隊(duì)列大小?

要想增大半連接隊(duì)列声邦,不能只單純?cè)龃?tcp_max_syn_backlog 的值欢唾,還需一同增大 somaxconn 和 backlog殴玛,也就是增大 accept 隊(duì)列贸街。否則穷绵,只單純?cè)龃?tcp_max_syn_backlog 是無效的带斑。

增大 tcp_max_syn_backlog 和 somaxconn 的方法是修改 Linux 內(nèi)核參數(shù):

image

增大 backlog 的方式席噩,每個(gè) Web 服務(wù)都不同模捂,比如 Nginx 增大 backlog 的方法如下:

image

最后捶朵,改變了如上這些參數(shù)后,要重啟 Nginx 服務(wù)狂男,因?yàn)?SYN 半連接隊(duì)列和 accept 隊(duì)列都是在 listen()初始化的综看。

如果 SYN 半連接隊(duì)列已滿,只能丟棄連接嗎岖食?

并不是這樣红碑,開啟 syncookies 功能就可以在不使用 SYN 半連接隊(duì)列的情況下成功建立連接

syncookies 的工作原理:服務(wù)器根據(jù)當(dāng)前狀態(tài)計(jì)算出一個(gè)值泡垃,放在己方發(fā)出的 SYN+ACK 報(bào)文中發(fā)出析珊,當(dāng)客戶端返回 ACK 報(bào)文時(shí)蔑穴,取出該值驗(yàn)證哑姚,如果合法品山,就認(rèn)為連接建立成功涝登,如下圖所示剑刑。

開啟 syncookies 功能

開啟 syncookies 功能

syncookies 參數(shù)主要有以下三個(gè)值:

  • 0 值锌历,表示關(guān)閉該功能帆精;
  • 1 值表蝙,表示僅當(dāng) SYN 半連接隊(duì)列放不下時(shí)府蛇,再啟用它;
  • 2 值朴译,表示無條件開啟功能;

那么在應(yīng)對(duì) SYN 攻擊時(shí)红选,只需要設(shè)置為 1 即可:

image

SYN_RCV 狀態(tài)的優(yōu)化

當(dāng)客戶端接收到服務(wù)器發(fā)來的 SYN+ACK 報(bào)文后澜公,就會(huì)回復(fù) ACK 給服務(wù)器,同時(shí)客戶端連接狀態(tài)從 SYN_SENT 轉(zhuǎn)換為 ESTABLISHED,表示連接建立成功坟乾。

服務(wù)器端連接成功建立的時(shí)間還要再往后迹辐,等到服務(wù)端收到客戶端的 ACK 后,服務(wù)端的連接狀態(tài)才變?yōu)?ESTABLISHED甚侣。

如果服務(wù)器沒有收到 ACK明吩,就會(huì)重發(fā) SYN+ACK 報(bào)文,同時(shí)一直處于 SYN_RCV 狀態(tài)殷费。

當(dāng)網(wǎng)絡(luò)繁忙印荔、不穩(wěn)定時(shí),報(bào)文丟失就會(huì)變嚴(yán)重详羡,此時(shí)應(yīng)該調(diào)大重發(fā)次數(shù)仍律。反之則可以調(diào)小重發(fā)次數(shù)。修改重發(fā)次數(shù)的方法是实柠,調(diào)整 tcp_synack_retries 參數(shù)

image

tcp_synack_retries 的默認(rèn)重試次數(shù)是 5 次水泉,與客戶端重傳 SYN 類似,它的重傳會(huì)經(jīng)歷 1窒盐、2草则、4、8蟹漓、16 秒炕横,最后一次重傳后會(huì)繼續(xù)等待 32 秒,如果服務(wù)端仍然沒有收到 ACK牧牢,才會(huì)關(guān)閉連接看锉,故共需要等待 63 秒。

服務(wù)器收到 ACK 后連接建立成功塔鳍,此時(shí)伯铣,內(nèi)核會(huì)把連接從半連接隊(duì)列移除,然后創(chuàng)建新的完全的連接轮纫,并將其添加到 accept 隊(duì)列腔寡,等待進(jìn)程調(diào)用 accept 函數(shù)時(shí)把連接取出來。

如果進(jìn)程不能及時(shí)地調(diào)用 accept 函數(shù)掌唾,就會(huì)造成 accept 隊(duì)列(也稱全連接隊(duì)列)溢出放前,最終導(dǎo)致建立好的 TCP 連接被丟棄。

accept 隊(duì)列溢出

accept 隊(duì)列溢出

accept 隊(duì)列已滿糯彬,只能丟棄連接嗎凭语?

丟棄連接只是 Linux 的默認(rèn)行為,我們還可以選擇向客戶端發(fā)送 RST 復(fù)位報(bào)文撩扒,告訴客戶端連接已經(jīng)建立失敗似扔。打開這一功能需要將 tcp_abort_on_overflow 參數(shù)設(shè)置為 1。

image

tcp_abort_on_overflow 共有兩個(gè)值分別是 0 和 1,其分別表示:

  • 0 :如果 accept 隊(duì)列滿了炒辉,那么 server 扔掉 client 發(fā)過來的 ack 豪墅;
  • 1 :如果 accept 隊(duì)列滿了,server 發(fā)送一個(gè) RST 包給 client黔寇,表示廢掉這個(gè)握手過程和這個(gè)連接偶器;

如果要想知道客戶端連接不上服務(wù)端,是不是服務(wù)端 TCP 全連接隊(duì)列滿的原因缝裤,那么可以把 tcp_abort_on_overflow 設(shè)置為 1屏轰,這時(shí)如果在客戶端異常中可以看到很多 connection reset by peer的錯(cuò)誤,那么就可以證明是由于服務(wù)端 TCP 全連接隊(duì)列溢出的問題倘是。

通常情況下亭枷,應(yīng)當(dāng)把 tcp_abort_on_overflow 設(shè)置為 0,因?yàn)檫@樣更有利于應(yīng)對(duì)突發(fā)流量搀崭。

舉個(gè)例子叨粘,當(dāng) accept 隊(duì)列滿導(dǎo)致服務(wù)器丟掉了 ACK,與此同時(shí)瘤睹,客戶端的連接狀態(tài)卻是 ESTABLISHED升敲,客戶端進(jìn)程就在建立好的連接上發(fā)送請(qǐng)求。只要服務(wù)器沒有為請(qǐng)求回復(fù) ACK轰传,客戶端的請(qǐng)求就會(huì)被多次「重發(fā)」驴党。如果服務(wù)器上的進(jìn)程只是短暫的繁忙造成 accept 隊(duì)列滿,那么當(dāng) accept 隊(duì)列有空位時(shí)获茬,再次接收到的請(qǐng)求報(bào)文由于含有 ACK港庄,仍然會(huì)觸發(fā)服務(wù)器端成功建立連接。

tcp_abort_on_overflow 為 0 可以應(yīng)對(duì)突發(fā)流量

tcp_abort_on_overflow 為 0 可以應(yīng)對(duì)突發(fā)流量

所以恕曲,tcp_abort_on_overflow 設(shè)為 0 可以提高連接建立的成功率鹏氧,只有你非常肯定 TCP 全連接隊(duì)列會(huì)長(zhǎng)期溢出時(shí)佩谣,才能設(shè)置為 1 以盡快通知客戶端把还。

如何調(diào)整 accept 隊(duì)列的長(zhǎng)度呢?

accept 隊(duì)列的長(zhǎng)度取決于 somaxconn 和 backlog 之間的最小值茸俭,也就是 min(somaxconn, backlog)吊履,其中:

  • somaxconn 是 Linux 內(nèi)核的參數(shù),默認(rèn)值是 128调鬓,可以通過 net.core.somaxconn 來設(shè)置其值艇炎;
  • backlog 是 listen(int sockfd, int backlog) 函數(shù)中的 backlog 大小腾窝;

Tomcat冕臭、Nginx腺晾、Apache 常見的 Web 服務(wù)的 backlog 默認(rèn)值都是 511。

如何查看服務(wù)端進(jìn)程 accept 隊(duì)列的長(zhǎng)度辜贵?

可以通過 ss -ltn 命令查看:

image
  • Recv-Q:當(dāng)前 accept 隊(duì)列的大小,也就是當(dāng)前已完成三次握手并等待服務(wù)端 accept() 的 TCP 連接归形;
  • Send-Q:accept 隊(duì)列最大長(zhǎng)度托慨,上面的輸出結(jié)果說明監(jiān)聽 8088 端口的 TCP 服務(wù),accept 隊(duì)列的最大長(zhǎng)度為 128暇榴;

如何查看由于 accept 連接隊(duì)列已滿厚棵,而被丟棄的連接?

當(dāng)超過了 accept 連接隊(duì)列蔼紧,服務(wù)端則會(huì)丟掉后續(xù)進(jìn)來的 TCP 連接婆硬,丟掉的 TCP 連接的個(gè)數(shù)會(huì)被統(tǒng)計(jì)起來,我們可以使用 netstat -s 命令來查看:

image

上面看到的 41150 times 奸例,表示 accept 隊(duì)列溢出的次數(shù)彬犯,注意這個(gè)是累計(jì)值〔榈酰可以隔幾秒鐘執(zhí)行下谐区,如果這個(gè)數(shù)字一直在增加的話,說明 accept 連接隊(duì)列偶爾滿了逻卖。

如果持續(xù)不斷地有連接因?yàn)?accept 隊(duì)列溢出被丟棄宋列,就應(yīng)該調(diào)大 backlog 以及 somaxconn 參數(shù)。

如何繞過三次握手评也?

以上我們只是在對(duì)三次握手的過程進(jìn)行優(yōu)化炼杖,接下來我們看看如何繞過三次握手發(fā)送數(shù)據(jù)。

三次握手建立連接造成的后果就是盗迟,HTTP 請(qǐng)求必須在一個(gè) RTT(從客戶端到服務(wù)器一個(gè)往返的時(shí)間)后才能發(fā)送坤邪。

常規(guī) HTTP 請(qǐng)求

常規(guī) HTTP 請(qǐng)求

在 Linux 3.7 內(nèi)核版本之后,提供了 TCP Fast Open 功能诈乒,這個(gè)功能可以減少 TCP 連接建立的時(shí)延罩扇。

接下來說說,TCP Fast Open 功能的工作方式怕磨。

開啟 TCP Fast Open 功能

開啟 TCP Fast Open 功能

在客戶端首次建立連接時(shí)的過程:

  1. 客戶端發(fā)送 SYN 報(bào)文喂饥,該報(bào)文包含 Fast Open 選項(xiàng),且該選項(xiàng)的 Cookie 為空肠鲫,這表明客戶端請(qǐng)求 Fast Open Cookie员帮;
  2. 支持 TCP Fast Open 的服務(wù)器生成 Cookie,并將其置于 SYN-ACK 數(shù)據(jù)包中的 Fast Open 選項(xiàng)以發(fā)回客戶端导饲;
  3. 客戶端收到 SYN-ACK 后捞高,本地緩存 Fast Open 選項(xiàng)中的 Cookie氯材。

所以,第一次發(fā)起 HTTP GET 請(qǐng)求的時(shí)候硝岗,還是需要正常的三次握手流程氢哮。

之后,如果客戶端再次向服務(wù)器建立連接時(shí)的過程:

  1. 客戶端發(fā)送 SYN 報(bào)文型檀,該報(bào)文包含「數(shù)據(jù)」(對(duì)于非 TFO 的普通 TCP 握手過程冗尤,SYN 報(bào)文中不包含「數(shù)據(jù)」)以及此前記錄的 Cookie;
  2. 支持 TCP Fast Open 的服務(wù)器會(huì)對(duì)收到 Cookie 進(jìn)行校驗(yàn):如果 Cookie 有效胀溺,服務(wù)器將在 SYN-ACK 報(bào)文中對(duì) SYN 和「數(shù)據(jù)」進(jìn)行確認(rèn)裂七,服務(wù)器隨后將「數(shù)據(jù)」遞送至相應(yīng)的應(yīng)用程序;如果 Cookie 無效仓坞,服務(wù)器將丟棄 SYN 報(bào)文中包含的「數(shù)據(jù)」背零,且其隨后發(fā)出的 SYN-ACK 報(bào)文將只確認(rèn) SYN 的對(duì)應(yīng)序列號(hào);
  3. 如果服務(wù)器接受了 SYN 報(bào)文中的「數(shù)據(jù)」无埃,服務(wù)器可在握手完成之前發(fā)送「數(shù)據(jù)」徙瓶,這就減少了握手帶來的 1 個(gè) RTT 的時(shí)間消耗
  4. 客戶端將發(fā)送 ACK 確認(rèn)服務(wù)器發(fā)回的 SYN 以及「數(shù)據(jù)」录语,但如果客戶端在初始的 SYN 報(bào)文中發(fā)送的「數(shù)據(jù)」沒有被確認(rèn)倍啥,則客戶端將重新發(fā)送「數(shù)據(jù)」;
  5. 此后的 TCP 連接的數(shù)據(jù)傳輸過程和非 TFO 的正常情況一致澎埠。

所以虽缕,之后發(fā)起 HTTP GET 請(qǐng)求的時(shí)候,可以繞過三次握手蒲稳,這就減少了握手帶來的 1 個(gè) RTT 的時(shí)間消耗氮趋。

注:客戶端在請(qǐng)求并存儲(chǔ)了 Fast Open Cookie 之后,可以不斷重復(fù) TCP Fast Open 直至服務(wù)器認(rèn)為 Cookie 無效(通常為過期)江耀。

Linux 下怎么打開 TCP Fast Open 功能呢剩胁?

在 Linux 系統(tǒng)中,可以通過設(shè)置 tcp_fastopn 內(nèi)核參數(shù)祥国,來打開 Fast Open 功能

image

tcp_fastopn 各個(gè)值的意義:

  • 0 關(guān)閉
  • 1 作為客戶端使用 Fast Open 功能
  • 2 作為服務(wù)端使用 Fast Open 功能
  • 3 無論作為客戶端還是服務(wù)器昵观,都可以使用 Fast Open 功能

TCP Fast Open 功能需要客戶端和服務(wù)端同時(shí)支持,才有效果舌稀。

小結(jié)

本小結(jié)主要介紹了關(guān)于優(yōu)化 TCP 三次握手的幾個(gè) TCP 參數(shù)啊犬。

三次握手優(yōu)化策略

三次握手優(yōu)化策略

客戶端的優(yōu)化

當(dāng)客戶端發(fā)起 SYN 包時(shí),可以通過 tcp_syn_retries 控制其重傳的次數(shù)壁查。

服務(wù)端的優(yōu)化

當(dāng)服務(wù)端 SYN 半連接隊(duì)列溢出后觉至,會(huì)導(dǎo)致后續(xù)連接被丟棄,可以通過 netstat -s 觀察半連接隊(duì)列溢出的情況睡腿,如果 SYN 半連接隊(duì)列溢出情況比較嚴(yán)重语御,可以通過 tcp_max_syn_backlog峻贮、somaxconn、backlog參數(shù)來調(diào)整 SYN 半連接隊(duì)列的大小应闯。

服務(wù)端回復(fù) SYN+ACK 的重傳次數(shù)由 tcp_synack_retries 參數(shù)控制纤控。如果遭受 SYN 攻擊,應(yīng)把 tcp_syncookies 參數(shù)設(shè)置為 1孽锥,表示僅在 SYN 隊(duì)列滿后開啟 syncookie 功能嚼黔,可以保證正常的連接成功建立。

服務(wù)端收到客戶端返回的 ACK惜辑,會(huì)把連接移入 accpet 隊(duì)列,等待進(jìn)行調(diào)用 accpet() 函數(shù)取出連接疫赎。

可以通過 ss -lnt 查看服務(wù)端進(jìn)程的 accept 隊(duì)列長(zhǎng)度盛撑,如果 accept 隊(duì)列溢出,系統(tǒng)默認(rèn)丟棄 ACK捧搞,如果可以把 tcp_abort_on_overflow 設(shè)置為 1 抵卫,表示用 RST 通知客戶端連接建立失敗。

如果 accpet 隊(duì)列溢出嚴(yán)重胎撇,可以通過 listen 函數(shù)的 backlog 參數(shù)和 somaxconn 系統(tǒng)參數(shù)提高隊(duì)列大小介粘,accept 隊(duì)列長(zhǎng)度取決于 min(backlog, somaxconn)。

繞過三次握手

TCP Fast Open 功能可以繞過三次握手晚树,使得 HTTP 請(qǐng)求減少了 1 個(gè) RTT 的時(shí)間姻采,Linux 下可以通過 tcp_fastopen 開啟該功能,同時(shí)必須保證服務(wù)端和客戶端同時(shí)支持爵憎。


02 TCP 四次揮手的性能提升

接下來慨亲,我們一起看看針對(duì) TCP 四次揮手關(guān)不連接時(shí),如何優(yōu)化性能宝鼓。

在開始之前刑棵,我們得先了解四次揮手狀態(tài)變遷的過程。

客戶端和服務(wù)端雙方都可以主動(dòng)斷開連接愚铡,通常先關(guān)閉連接的一方稱為主動(dòng)方蛉签,后關(guān)閉連接的一方稱為被動(dòng)方。

客戶端主動(dòng)關(guān)閉

客戶端主動(dòng)關(guān)閉

可以看到沥寥,四次揮手過程只涉及了兩種報(bào)文碍舍,分別是 FIN 和 ACK

  • FIN 就是結(jié)束連接的意思,誰發(fā)出 FIN 報(bào)文营曼,就表示它將不會(huì)再發(fā)送任何數(shù)據(jù)乒验,關(guān)閉這一方向上的傳輸通道;
  • ACK 就是確認(rèn)的意思蒂阱,用來通知對(duì)方:你方的發(fā)送通道已經(jīng)關(guān)閉锻全;

四次揮手的過程:

  • 當(dāng)主動(dòng)方關(guān)閉連接時(shí)狂塘,會(huì)發(fā)送 FIN 報(bào)文,此時(shí)發(fā)送方的 TCP 連接將從 ESTABLISHED 變成 FIN_WAIT1鳄厌。
  • 當(dāng)被動(dòng)方收到 FIN 報(bào)文后荞胡,內(nèi)核會(huì)自動(dòng)回復(fù) ACK 報(bào)文,連接狀態(tài)將從 ESTABLISHED 變成 CLOSE_WAIT了嚎,表示被動(dòng)方在等待進(jìn)程調(diào)用 close 函數(shù)關(guān)閉連接泪漂。
  • 當(dāng)主動(dòng)方收到這個(gè) ACK 后,連接狀態(tài)由 FIN_WAIT1 變?yōu)?FIN_WAIT2歪泳,也就是表示主動(dòng)方的發(fā)送通道就關(guān)閉了萝勤。
  • 當(dāng)被動(dòng)方進(jìn)入 CLOSE_WAIT 時(shí),被動(dòng)方還會(huì)繼續(xù)處理數(shù)據(jù)呐伞,等到進(jìn)程的 read 函數(shù)返回 0 后敌卓,應(yīng)用程序就會(huì)調(diào)用 close 函數(shù),進(jìn)而觸發(fā)內(nèi)核發(fā)送 FIN 報(bào)文伶氢,此時(shí)被動(dòng)方的連接狀態(tài)變?yōu)?LAST_ACK趟径。
  • 當(dāng)主動(dòng)方收到這個(gè) FIN 報(bào)文后,內(nèi)核會(huì)回復(fù) ACK 報(bào)文給被動(dòng)方癣防,同時(shí)主動(dòng)方的連接狀態(tài)由 FIN_WAIT2 變?yōu)?TIME_WAIT蜗巧,在 Linux 系統(tǒng)下大約等待 1 分鐘后,TIME_WAIT 狀態(tài)的連接才會(huì)徹底關(guān)閉蕾盯。
  • 當(dāng)被動(dòng)方收到最后的 ACK 報(bào)文后幕屹,被動(dòng)方的連接就會(huì)關(guān)閉

你可以看到刑枝,每個(gè)方向都需要一個(gè) FIN 和一個(gè) ACK香嗓,因此通常被稱為四次揮手

這里一點(diǎn)需要注意是:主動(dòng)關(guān)閉連接的装畅,才有 TIME_WAIT 狀態(tài)靠娱。

主動(dòng)關(guān)閉方和被動(dòng)關(guān)閉方優(yōu)化的思路也不同,接下來分別說說如何優(yōu)化他們掠兄。

主動(dòng)方的優(yōu)化

關(guān)閉的連接的方式通常有兩種像云,分別是 RST 報(bào)文關(guān)閉和 FIN 報(bào)文關(guān)閉。

如果進(jìn)程異常退出了蚂夕,內(nèi)核就會(huì)發(fā)送 RST 報(bào)文來關(guān)閉迅诬,它可以不走四次揮手流程,是一個(gè)暴力關(guān)閉連接的方式婿牍。

安全關(guān)閉連接的方式必須通過四次揮手侈贷,它由進(jìn)程調(diào)用 closeshutdown 函數(shù)發(fā)起 FIN 報(bào)文(shutdown 參數(shù)須傳入 SHUT_WR 或者 SHUT_RDWR 才會(huì)發(fā)送 FIN)。

調(diào)用 close 函數(shù) 和 shutdown 函數(shù)有什么區(qū)別等脂?

調(diào)用了 close 函數(shù)意味著完全斷開連接俏蛮,完全斷開不僅指無法傳輸數(shù)據(jù)撑蚌,而且也不能發(fā)送數(shù)據(jù)。 此時(shí)搏屑,調(diào)用了 close 函數(shù)的一方的連接叫做「孤兒連接」争涌,如果你用 netstat -p 命令,會(huì)發(fā)現(xiàn)連接對(duì)應(yīng)的進(jìn)程名為空辣恋。

使用 close 函數(shù)關(guān)閉連接是不優(yōu)雅的。于是携狭,就出現(xiàn)了一種優(yōu)雅關(guān)閉連接的 shutdown 函數(shù)鳄逾,它可以控制只關(guān)閉一個(gè)方向的連接

image

第二個(gè)參數(shù)決定斷開連接的方式汽摹,主要有以下三種方式:

  • SHUT_RD(0):關(guān)閉連接的「讀」這個(gè)方向氏仗,如果接收緩沖區(qū)有已接收的數(shù)據(jù),則將會(huì)被丟棄昔头,并且后續(xù)再收到新的數(shù)據(jù),會(huì)對(duì)數(shù)據(jù)進(jìn)行 ACK淆两,然后悄悄地丟棄性含。也就是說辅鲸,對(duì)端還是會(huì)接收到 ACK独悴,在這種情況下根本不知道數(shù)據(jù)已經(jīng)被丟棄了管行。
  • SHUT_WR(1):關(guān)閉連接的「寫」這個(gè)方向荡陷,這就是常被稱為「半關(guān)閉」的連接徽龟。如果發(fā)送緩沖區(qū)還有未發(fā)送的數(shù)據(jù),將被立即發(fā)送出去唉地,并發(fā)送一個(gè) FIN 報(bào)文給對(duì)端据悔。
  • SHUT_RDWR(2):相當(dāng)于 SHUT_RD 和 SHUT_WR 操作各一次,關(guān)閉套接字的讀和寫兩個(gè)方向耘沼。

close 和 shutdown 函數(shù)都可以關(guān)閉連接极颓,但這兩種方式關(guān)閉的連接,不只功能上有差異群嗤,控制它們的 Linux 參數(shù)也不相同菠隆。

FIN_WAIT1 狀態(tài)的優(yōu)化

主動(dòng)方發(fā)送 FIN 報(bào)文后,連接就處于 FIN_WAIT1 狀態(tài),正常情況下骇径,如果能及時(shí)收到被動(dòng)方的 ACK躯肌,則會(huì)很快變?yōu)?FIN_WAIT2 狀態(tài)。

但是當(dāng)遲遲收不到對(duì)方返回的 ACK 時(shí)破衔,連接就會(huì)一直處于 FIN_WAIT1 狀態(tài)清女。此時(shí),內(nèi)核會(huì)定時(shí)重發(fā) FIN 報(bào)文晰筛,其中重發(fā)次數(shù)由 tcp_orphan_retries 參數(shù)控制(注意嫡丙,orphan 雖然是孤兒的意思,該參數(shù)卻不只對(duì)孤兒連接有效传惠,事實(shí)上迄沫,它對(duì)所有 FIN_WAIT1 狀態(tài)下的連接都有效),默認(rèn)值是 0卦方。

image

你可能會(huì)好奇羊瘩,這 0 表示幾次?實(shí)際上當(dāng)為 0 時(shí)盼砍,特指 8 次尘吗,從下面的內(nèi)核源碼可知:

image

如果 FIN_WAIT1 狀態(tài)連接很多,我們就需要考慮降低 tcp_orphan_retries 的值浇坐,當(dāng)重傳次數(shù)超過 tcp_orphan_retries 時(shí)睬捶,連接就會(huì)直接關(guān)閉掉。

對(duì)于普遍正常情況時(shí),調(diào)低 tcp_orphan_retries 就已經(jīng)可以了刮刑。如果遇到惡意攻擊毅戈,F(xiàn)IN 報(bào)文根本無法發(fā)送出去,這由 TCP 兩個(gè)特性導(dǎo)致的:

  • 首先介劫,TCP 必須保證報(bào)文是有序發(fā)送的,F(xiàn)IN 報(bào)文也不例外案淋,當(dāng)發(fā)送緩沖區(qū)還有數(shù)據(jù)沒有發(fā)送時(shí)座韵,F(xiàn)IN 報(bào)文也不能提前發(fā)送。
  • 其次踢京,TCP 有流量控制功能誉碴,當(dāng)接收方接收窗口為 0 時(shí),發(fā)送方就不能再發(fā)送數(shù)據(jù)瓣距。所以黔帕,當(dāng)攻擊者下載大文件時(shí),就可以通過接收窗口設(shè)為 0 蹈丸,這就會(huì)使得 FIN 報(bào)文都無法發(fā)送出去蹬屹,那么連接會(huì)一直處于 FIN_WAIT1 狀態(tài)侣背。

解決這種問題的方法,是調(diào)整 tcp_max_orphans 參數(shù)慨默,它定義了「孤兒連接」的最大數(shù)量

image

當(dāng)進(jìn)程調(diào)用了 close 函數(shù)關(guān)閉連接贩耐,此時(shí)連接就會(huì)是「孤兒連接」,因?yàn)樗鼰o法在發(fā)送和接收數(shù)據(jù)厦取。Linux 系統(tǒng)為了防止孤兒連接過多潮太,導(dǎo)致系統(tǒng)資源長(zhǎng)時(shí)間被占用,就提供了 tcp_max_orphans 參數(shù)虾攻。如果孤兒連接數(shù)量大于它铡买,新增的孤兒連接將不再走四次揮手,而是直接發(fā)送 RST 復(fù)位報(bào)文強(qiáng)制關(guān)閉霎箍。

FIN_WAIT2 狀態(tài)的優(yōu)化

當(dāng)主動(dòng)方收到 ACK 報(bào)文后奇钞,會(huì)處于 FIN_WAIT2 狀態(tài),就表示主動(dòng)方的發(fā)送通道已經(jīng)關(guān)閉漂坏,接下來將等待對(duì)方發(fā)送 FIN 報(bào)文景埃,關(guān)閉對(duì)方的發(fā)送通道。

這時(shí)顶别,如果連接是用 shutdown 函數(shù)關(guān)閉的谷徙,連接可以一直處于 FIN_WAIT2 狀態(tài),因?yàn)樗赡苓€可以發(fā)送或接收數(shù)據(jù)驯绎。但對(duì)于 close 函數(shù)關(guān)閉的孤兒連接完慧,由于無法在發(fā)送和接收數(shù)據(jù),所以這個(gè)狀態(tài)不可以持續(xù)太久剩失,而 tcp_fin_timeout 控制了這個(gè)狀態(tài)下連接的持續(xù)時(shí)長(zhǎng)屈尼,默認(rèn)值是 60 秒:

image

它意味著對(duì)于孤兒連接(調(diào)用 close 關(guān)閉的連接),如果在 60 秒后還沒有收到 FIN 報(bào)文拴孤,連接就會(huì)直接關(guān)閉鸿染。

這個(gè) 60 秒不是隨便決定的,它與 TIME_WAIT 狀態(tài)持續(xù)的時(shí)間是相同的乞巧,后面我們?cè)趤碚f說為什么是 60 秒。

TIME_WAIT 狀態(tài)的優(yōu)化

TIME_WAIT 是主動(dòng)方四次揮手的最后一個(gè)狀態(tài)摊鸡,也是最常遇見的狀態(tài)绽媒。

當(dāng)收到被動(dòng)方發(fā)來的 FIN 報(bào)文后,主動(dòng)方會(huì)立刻回復(fù) ACK免猾,表示確認(rèn)對(duì)方的發(fā)送通道已經(jīng)關(guān)閉是辕,接著就處于 TIME_WAIT 狀態(tài)。在 Linux 系統(tǒng)猎提,TIME_WAIT 狀態(tài)會(huì)持續(xù) 60 秒后才會(huì)進(jìn)入關(guān)閉狀態(tài)获三。

TIME_WAIT 狀態(tài)的連接,在主動(dòng)方看來確實(shí)快已經(jīng)關(guān)閉了。然后疙教,被動(dòng)方?jīng)]有收到 ACK 報(bào)文前棺聊,還是處于 LAST_ACK 狀態(tài)。如果這個(gè) ACK 報(bào)文沒有到達(dá)被動(dòng)方贞谓,被動(dòng)方就會(huì)重發(fā) FIN 報(bào)文限佩。重發(fā)次數(shù)仍然由前面介紹過的 tcp_orphan_retries 參數(shù)控制。

TIME-WAIT 的狀態(tài)尤其重要裸弦,主要是兩個(gè)原因:

  • 防止具有相同「四元組」的「舊」數(shù)據(jù)包被收到祟同;
  • 保證「被動(dòng)關(guān)閉連接」的一方能被正確的關(guān)閉,即保證最后的 ACK 能讓被動(dòng)關(guān)閉方接收理疙,從而幫助其正常關(guān)閉晕城;

原因一:防止舊連接的數(shù)據(jù)包

TIME-WAIT 的一個(gè)作用是防止收到歷史數(shù)據(jù),從而導(dǎo)致數(shù)據(jù)錯(cuò)亂的問題窖贤。

假設(shè) TIME-WAIT 沒有等待時(shí)間或時(shí)間過短砖顷,被延遲的數(shù)據(jù)包抵達(dá)后會(huì)發(fā)生什么呢?

接收到歷史數(shù)據(jù)的異常

接收到歷史數(shù)據(jù)的異常

  • 如上圖黃色框框服務(wù)端在關(guān)閉連接之前發(fā)送的 SEQ = 301 報(bào)文主之,被網(wǎng)絡(luò)延遲了择吊。
  • 這時(shí)有相同端口的 TCP 連接被復(fù)用后,被延遲的 SEQ = 301 抵達(dá)了客戶端槽奕,那么客戶端是有可能正常接收這個(gè)過期的報(bào)文几睛,這就會(huì)產(chǎn)生數(shù)據(jù)錯(cuò)亂等嚴(yán)重的問題。

所以粤攒,TCP 就設(shè)計(jì)出了這么一個(gè)機(jī)制所森,經(jīng)過 2MSL 這個(gè)時(shí)間,足以讓兩個(gè)方向上的數(shù)據(jù)包都被丟棄夯接,使得原來連接的數(shù)據(jù)包在網(wǎng)絡(luò)中都自然消失焕济,再出現(xiàn)的數(shù)據(jù)包一定都是新建立連接所產(chǎn)生的。

原因二:保證連接正確關(guān)閉

TIME-WAIT 的另外一個(gè)作用是等待足夠的時(shí)間以確保最后的 ACK 能讓被動(dòng)關(guān)閉方接收盔几,從而幫助其正常關(guān)閉晴弃。

假設(shè) TIME-WAIT 沒有等待時(shí)間或時(shí)間過短,斷開連接會(huì)造成什么問題呢逊拍?

沒有確保正常斷開的異常

沒有確保正常斷開的異常

  • 如上圖紅色框框客戶端四次揮手的最后一個(gè) ACK 報(bào)文如果在網(wǎng)絡(luò)中被丟失了上鞠,此時(shí)如果客戶端 TIME-WAIT 過短或沒有,則就直接進(jìn)入了 CLOSE 狀態(tài)了芯丧,那么服務(wù)端則會(huì)一直處在 LASE-ACK 狀態(tài)芍阎。
  • 當(dāng)客戶端發(fā)起建立連接的 SYN 請(qǐng)求報(bào)文后,服務(wù)端會(huì)發(fā)送 RST 報(bào)文給客戶端缨恒,連接建立的過程就會(huì)被終止谴咸。

我們?cè)倩剡^頭來看看轮听,為什么 TIME_WAIT 狀態(tài)要保持 60 秒呢?這與孤兒連接 FIN_WAIT2 狀態(tài)默認(rèn)保留 60 秒的原理是一樣的岭佳,因?yàn)檫@兩個(gè)狀態(tài)都需要保持 2MSL 時(shí)長(zhǎng)血巍。MSL 全稱是 Maximum Segment Lifetime,它定義了一個(gè)報(bào)文在網(wǎng)絡(luò)中的最長(zhǎng)生存時(shí)間(報(bào)文每經(jīng)過一次路由器的轉(zhuǎn)發(fā)驼唱,IP 頭部的 TTL 字段就會(huì)減 1藻茂,減到 0 時(shí)報(bào)文就被丟棄,這就限制了報(bào)文的最長(zhǎng)存活時(shí)間)玫恳。

為什么是 2 MSL 的時(shí)長(zhǎng)呢辨赐?這其實(shí)是相當(dāng)于至少允許報(bào)文丟失一次。比如京办,若 ACK 在一個(gè) MSL 內(nèi)丟失掀序,這樣被動(dòng)方重發(fā)的 FIN 會(huì)在第 2 個(gè) MSL 內(nèi)到達(dá),TIME_WAIT 狀態(tài)的連接可以應(yīng)對(duì)惭婿。

為什么不是 4 或者 8 MSL 的時(shí)長(zhǎng)呢不恭?你可以想象一個(gè)丟包率達(dá)到百分之一的糟糕網(wǎng)絡(luò),連續(xù)兩次丟包的概率只有萬分之一财饥,這個(gè)概率實(shí)在是太小了换吧,忽略它比解決它更具性價(jià)比。

因此钥星,TIME_WAIT 和 FIN_WAIT2 狀態(tài)的最大時(shí)長(zhǎng)都是 2 MSL沾瓦,由于在 Linux 系統(tǒng)中,MSL 的值固定為 30 秒谦炒,所以它們都是 60 秒贯莺。

雖然 TIME_WAIT 狀態(tài)有存在的必要,但它畢竟會(huì)消耗系統(tǒng)資源宁改。如果發(fā)起連接一方的 TIME_WAIT 狀態(tài)過多缕探,占滿了所有端口資源,則會(huì)導(dǎo)致無法創(chuàng)建新連接还蹲。

  • 客戶端受端口資源限制:如果客戶端 TIME_WAIT 過多爹耗,就會(huì)導(dǎo)致端口資源被占用,因?yàn)槎丝诰?5536個(gè)谜喊,被占滿就會(huì)導(dǎo)致無法創(chuàng)建新的連接潭兽;
  • 服務(wù)端受系統(tǒng)資源限制:由于一個(gè) 四元組表示TCP連接,理論上服務(wù)端可以建立很多連接锅论,服務(wù)端確實(shí)只監(jiān)聽一個(gè)端口 但是會(huì)把連接扔給處理線程,所以理論上監(jiān)聽的端口可以繼續(xù)監(jiān)聽楣号。但是線程池處理不了那么多一直不斷的連接了最易。所以當(dāng)服務(wù)端出現(xiàn)大量 TIME_WAIT 時(shí)怒坯,系統(tǒng)資源被占滿時(shí),會(huì)導(dǎo)致處理不過來新的連接藻懒;

另外剔猿,Linux 提供了 tcp_max_tw_buckets 參數(shù),當(dāng) TIME_WAIT 的連接數(shù)量超過該參數(shù)時(shí)嬉荆,新關(guān)閉的連接就不再經(jīng)歷 TIME_WAIT 而直接關(guān)閉:

image

當(dāng)服務(wù)器的并發(fā)連接增多時(shí)归敬,相應(yīng)地,同時(shí)處于 TIME_WAIT 狀態(tài)的連接數(shù)量也會(huì)變多鄙早,此時(shí)就應(yīng)當(dāng)調(diào)大 tcp_max_tw_buckets 參數(shù)汪茧,減少不同連接間數(shù)據(jù)錯(cuò)亂的概率。

tcp_max_tw_buckets 也不是越大越好限番,畢竟內(nèi)存和端口都是有限的舱污。

有一種方式可以在建立新連接時(shí),復(fù)用處于 TIME_WAIT 狀態(tài)的連接弥虐,那就是打開 tcp_tw_reuse 參數(shù)扩灯。但是需要注意,該參數(shù)是只用于客戶端(建立連接的發(fā)起方)霜瘪,因?yàn)槭窃谡{(diào)用 connect() 時(shí)起作用的珠插,而對(duì)于服務(wù)端(被動(dòng)連接方)是沒有用的。

image

tcp_tw_reuse 從協(xié)議角度理解是安全可控的颖对,可以復(fù)用處于 TIME_WAIT 的端口為新的連接所用捻撑。

什么是協(xié)議角度理解的安全可控呢?主要有兩點(diǎn):

  • 只適用于連接發(fā)起方惜互,也就是 C/S 模型中的客戶端布讹;
  • 對(duì)應(yīng)的 TIME_WAIT 狀態(tài)的連接創(chuàng)建時(shí)間超過 1 秒才可以被復(fù)用。

使用這個(gè)選項(xiàng)训堆,還有一個(gè)前提描验,需要打開對(duì) TCP 時(shí)間戳的支持(對(duì)方也要打開 ):

image

由于引入了時(shí)間戳,它能帶來了些好處:

  • 我們?cè)谇懊嫣岬降?2MSL 問題就不復(fù)存在了坑鱼,因?yàn)橹貜?fù)的數(shù)據(jù)包會(huì)因?yàn)闀r(shí)間戳過期被自然丟棄膘流;
  • 同時(shí),它還可以防止序列號(hào)繞回鲁沥,也是因?yàn)橹貜?fù)的數(shù)據(jù)包會(huì)由于時(shí)間戳過期被自然丟棄呼股;

老版本的 Linux 還提供了 tcp_tw_recycle 參數(shù),但是當(dāng)開啟了它画恰,就有兩個(gè)坑:

  • Linux 會(huì)加快客戶端和服務(wù)端 TIME_WAIT 狀態(tài)的時(shí)間彭谁,也就是它會(huì)使得 TIME_WAIT 狀態(tài)會(huì)小于 60 秒,很容易導(dǎo)致數(shù)據(jù)錯(cuò)亂允扇;
  • 另外缠局,Linux 會(huì)丟棄所有來自遠(yuǎn)端時(shí)間戳小于上次記錄的時(shí)間戳(由同一個(gè)遠(yuǎn)端發(fā)送的)的任何數(shù)據(jù)包则奥。就是說要使用該選項(xiàng),則必須保證數(shù)據(jù)包的時(shí)間戳是單調(diào)遞增的狭园。那么读处,問題在于,此處的時(shí)間戳并不是我們通常意義上面的絕對(duì)時(shí)間唱矛,而是一個(gè)相對(duì)時(shí)間罚舱。很多情況下,我們是沒法保證時(shí)間戳單調(diào)遞增的绎谦,比如使用了 NAT管闷,LVS 等情況;

所以燥滑,不建議設(shè)置為 1 渐北,建議關(guān)閉它:

image

在 Linux 4.12 版本后,Linux 內(nèi)核直接取消了這一參數(shù)铭拧。

另外赃蛛,我們可以在程序中設(shè)置 socket 選項(xiàng),來設(shè)置調(diào)用 close 關(guān)閉連接行為搀菩。

image

如果l_onoff為非 0呕臂, 且l_linger值為 0,那么調(diào)用close后肪跋,會(huì)立該發(fā)送一個(gè) RST 標(biāo)志給對(duì)端歧蒋,該 TCP 連接將跳過四次揮手,也就跳過了 TIME_WAIT 狀態(tài)州既,直接關(guān)閉谜洽。

但這為跨越 TIME_WAIT 狀態(tài)提供了一個(gè)可能,不過是一個(gè)非常危險(xiǎn)的行為吴叶,不值得提倡阐虚。

被動(dòng)方的優(yōu)化

當(dāng)被動(dòng)方收到 FIN 報(bào)文時(shí),內(nèi)核會(huì)自動(dòng)回復(fù) ACK蚌卤,同時(shí)連接處于 CLOSE_WAIT 狀態(tài)实束,顧名思義,它表示等待應(yīng)用進(jìn)程調(diào)用 close 函數(shù)關(guān)閉連接逊彭。

內(nèi)核沒有權(quán)利替代進(jìn)程去關(guān)閉連接咸灿,因?yàn)槿绻鲃?dòng)方是通過 shutdown 關(guān)閉連接,那么它就是想在半關(guān)閉連接上接收數(shù)據(jù)或發(fā)送數(shù)據(jù)侮叮。因此避矢,Linux 并沒有限制 CLOSE_WAIT 狀態(tài)的持續(xù)時(shí)間。

當(dāng)然,大多數(shù)應(yīng)用程序并不使用 shutdown 函數(shù)關(guān)閉連接审胸。所以分尸,當(dāng)你用 netstat 命令發(fā)現(xiàn)大量 CLOSE_WAIT 狀態(tài)。就需要排查你的應(yīng)用程序歹嘹,因?yàn)榭赡芤驗(yàn)閼?yīng)用程序出現(xiàn)了 Bug,read 函數(shù)返回 0 時(shí)孔庭,沒有調(diào)用 close 函數(shù)尺上。

處于 CLOSE_WAIT 狀態(tài)時(shí),調(diào)用了 close 函數(shù)圆到,內(nèi)核就會(huì)發(fā)出 FIN 報(bào)文關(guān)閉發(fā)送通道怎抛,同時(shí)連接進(jìn)入 LAST_ACK 狀態(tài),等待主動(dòng)方返回 ACK 來確認(rèn)連接關(guān)閉芽淡。

如果遲遲收不到這個(gè) ACK马绝,內(nèi)核就會(huì)重發(fā) FIN 報(bào)文,重發(fā)次數(shù)仍然由 tcp_orphan_retries 參數(shù)控制挣菲,這與主動(dòng)方重發(fā) FIN 報(bào)文的優(yōu)化策略一致富稻。

還有一點(diǎn)我們需要注意的,如果被動(dòng)方迅速調(diào)用 close 函數(shù)白胀,那么被動(dòng)方的 ACK 和 FIN 有可能在一個(gè)報(bào)文中發(fā)送椭赋,這樣看起來,四次揮手會(huì)變成三次揮手或杠,這只是一種特殊情況哪怔,不用在意。

如果連接雙方同時(shí)關(guān)閉連接向抢,會(huì)怎么樣认境?

由于 TCP 是雙全工的協(xié)議,所以是會(huì)出現(xiàn)兩方同時(shí)關(guān)閉連接的現(xiàn)象挟鸠,也就是同時(shí)發(fā)送了 FIN 報(bào)文叉信。

此時(shí),上面介紹的優(yōu)化策略仍然適用兄猩。兩方發(fā)送 FIN 報(bào)文時(shí)茉盏,都認(rèn)為自己是主動(dòng)方,所以都進(jìn)入了 FIN_WAIT1 狀態(tài)枢冤,F(xiàn)IN 報(bào)文的重發(fā)次數(shù)仍由 tcp_orphan_retries 參數(shù)控制鸠姨。

同時(shí)關(guān)閉

同時(shí)關(guān)閉

接下來,雙方在等待 ACK 報(bào)文的過程中淹真,都等來了 FIN 報(bào)文讶迁。這是一種新情況,所以連接會(huì)進(jìn)入一種叫做 CLOSING 的新狀態(tài)核蘸,它替代了 FIN_WAIT2 狀態(tài)巍糯。接著啸驯,雙方內(nèi)核回復(fù) ACK 確認(rèn)對(duì)方發(fā)送通道的關(guān)閉后,進(jìn)入 TIME_WAIT 狀態(tài)祟峦,等待 2MSL 的時(shí)間后罚斗,連接自動(dòng)關(guān)閉。

小結(jié)

針對(duì) TCP 四次揮手的優(yōu)化宅楞,我們需要根據(jù)主動(dòng)方和被動(dòng)方四次揮手狀態(tài)變化來調(diào)整系統(tǒng) TCP 內(nèi)核參數(shù)针姿。

四次揮手的優(yōu)化策略

四次揮手的優(yōu)化策略

主動(dòng)方的優(yōu)化

主動(dòng)發(fā)起 FIN 報(bào)文斷開連接的一方,如果遲遲沒收到對(duì)方的 ACK 回復(fù)厌衙,則會(huì)重傳 FIN 報(bào)文距淫,重傳的次數(shù)由 tcp_orphan_retries 參數(shù)決定。

當(dāng)主動(dòng)方收到 ACK 報(bào)文后婶希,連接就進(jìn)入 FIN_WAIT2 狀態(tài)榕暇,根據(jù)關(guān)閉的方式不同,優(yōu)化的方式也不同:

  • 如果這是 close 函數(shù)關(guān)閉的連接喻杈,那么它就是孤兒連接彤枢。如果 tcp_fin_timeout 秒內(nèi)沒有收到對(duì)方的 FIN 報(bào)文,連接就直接關(guān)閉筒饰。同時(shí)堂污,為了應(yīng)對(duì)孤兒連接占用太多的資源,tcp_max_orphans 定義了最大孤兒連接的數(shù)量龄砰,超過時(shí)連接就會(huì)直接釋放盟猖。
  • 反之是 shutdown 函數(shù)關(guān)閉的連接,則不受此參數(shù)限制换棚;

當(dāng)主動(dòng)方接收到 FIN 報(bào)文式镐,并返回 ACK 后,主動(dòng)方的連接進(jìn)入 TIME_WAIT 狀態(tài)固蚤。這一狀態(tài)會(huì)持續(xù) 1 分鐘娘汞,為了防止 TIME_WAIT 狀態(tài)占用太多的資源,tcp_max_tw_buckets 定義了最大數(shù)量夕玩,超過時(shí)連接也會(huì)直接釋放你弦。

當(dāng) TIME_WAIT 狀態(tài)過多時(shí),還可以通過設(shè)置 tcp_tw_reusetcp_timestamps 為 1 燎孟,將 TIME_WAIT 狀態(tài)的端口復(fù)用于作為客戶端的新連接禽作,注意該參數(shù)只適用于客戶端。

被動(dòng)方的優(yōu)化

被動(dòng)關(guān)閉的連接方應(yīng)對(duì)非常簡(jiǎn)單揩页,它在回復(fù) ACK 后就進(jìn)入了 CLOSE_WAIT 狀態(tài)旷偿,等待進(jìn)程調(diào)用 close 函數(shù)關(guān)閉連接。因此,出現(xiàn)大量 CLOSE_WAIT 狀態(tài)的連接時(shí)萍程,應(yīng)當(dāng)從應(yīng)用程序中找問題幢妄。

當(dāng)被動(dòng)方發(fā)送 FIN 報(bào)文后,連接就進(jìn)入 LAST_ACK 狀態(tài)茫负,在未等到 ACK 時(shí)蕉鸳,會(huì)在 tcp_orphan_retries參數(shù)的控制下重發(fā) FIN 報(bào)文。


03 TCP 傳輸數(shù)據(jù)的性能提升

在前面介紹的是三次握手和四次揮手的優(yōu)化策略忍法,接下來主要介紹的是 TCP 傳輸數(shù)據(jù)時(shí)的優(yōu)化策略置吓。

TCP 連接是由內(nèi)核維護(hù)的,內(nèi)核會(huì)為每個(gè)連接建立內(nèi)存緩沖區(qū):

  • 如果連接的內(nèi)存配置過小缔赠,就無法充分使用網(wǎng)絡(luò)帶寬,TCP 傳輸效率就會(huì)降低友题;
  • 如果連接的內(nèi)存配置過大嗤堰,很容易把服務(wù)器資源耗盡,這樣就會(huì)導(dǎo)致新連接無法建立度宦;

因此踢匣,我們必須理解 Linux 下 TCP 內(nèi)存的用途,才能正確地配置內(nèi)存大小戈抄。

滑動(dòng)窗口是如何影響傳輸速度的离唬?

TCP 會(huì)保證每一個(gè)報(bào)文都能夠抵達(dá)對(duì)方,它的機(jī)制是這樣:報(bào)文發(fā)出去后划鸽,必須接收到對(duì)方返回的確認(rèn)報(bào)文 ACK输莺,如果遲遲未收到,就會(huì)超時(shí)重發(fā)該報(bào)文裸诽,直到收到對(duì)方的 ACK 為止嫂用。

所以,TCP 報(bào)文發(fā)出去后丈冬,并不會(huì)立馬從內(nèi)存中刪除嘱函,因?yàn)橹貍鲿r(shí)還需要用到它。

由于 TCP 是內(nèi)核維護(hù)的埂蕊,所以報(bào)文存放在內(nèi)核緩沖區(qū)往弓。如果連接非常多,我們可以通過 free 命令觀察到 buff/cache 內(nèi)存是會(huì)增大蓄氧。

如果 TCP 是每發(fā)送一個(gè)數(shù)據(jù)函似,都要進(jìn)行一次確認(rèn)應(yīng)答。當(dāng)上一個(gè)數(shù)據(jù)包收到了應(yīng)答了喉童, 再發(fā)送下一個(gè)缴淋。這個(gè)模式就有點(diǎn)像我和你面對(duì)面聊天,你一句我一句,但這種方式的缺點(diǎn)是效率比較低的重抖。

按數(shù)據(jù)包進(jìn)行確認(rèn)應(yīng)答

按數(shù)據(jù)包進(jìn)行確認(rèn)應(yīng)答

所以露氮,這樣的傳輸方式有一個(gè)缺點(diǎn):數(shù)據(jù)包的往返時(shí)間越長(zhǎng),通信的效率就越低钟沛。

要解決這一問題不難畔规,并行批量發(fā)送報(bào)文,再批量確認(rèn)報(bào)文即刻恨统。

并行處理

并行處理

然而叁扫,這引出了另一個(gè)問題,發(fā)送方可以隨心所欲的發(fā)送報(bào)文嗎畜埋?當(dāng)然這不現(xiàn)實(shí)莫绣,我們還得考慮接收方的處理能力。

當(dāng)接收方硬件不如發(fā)送方悠鞍,或者系統(tǒng)繁忙对室、資源緊張時(shí),是無法瞬間處理這么多報(bào)文的咖祭。于是掩宜,這些報(bào)文只能被丟掉,使得網(wǎng)絡(luò)效率非常低么翰。

為了解決這種現(xiàn)象發(fā)生牺汤,TCP 提供一種機(jī)制可以讓「發(fā)送方」根據(jù)「接收方」的實(shí)際接收能力控制發(fā)送的數(shù)據(jù)量,這就是滑動(dòng)窗口的由來浩嫌。

接收方根據(jù)它的緩沖區(qū)檐迟,可以計(jì)算出后續(xù)能夠接收多少字節(jié)的報(bào)文,這個(gè)數(shù)字叫做接收窗口码耐。當(dāng)內(nèi)核接收到報(bào)文時(shí)锅减,必須用緩沖區(qū)存放它們,這樣剩余緩沖區(qū)空間變小伐坏,接收窗口也就變小了怔匣;當(dāng)進(jìn)程調(diào)用 read 函數(shù)后,數(shù)據(jù)被讀入了用戶空間桦沉,內(nèi)核緩沖區(qū)就被清空每瞒,這意味著主機(jī)可以接收更多的報(bào)文,接收窗口就會(huì)變大纯露。

因此剿骨,接收窗口并不是恒定不變的,接收方會(huì)把當(dāng)前可接收的大小放在 TCP 報(bào)文頭部中的窗口字段埠褪,這樣就可以起到窗口大小通知的作用浓利。

發(fā)送方的窗口等價(jià)于接收方的窗口嗎挤庇?如果不考慮擁塞控制,發(fā)送方的窗口大小「約等于」接收方的窗口大小贷掖,因?yàn)榇翱谕ㄖ獔?bào)文在網(wǎng)絡(luò)傳輸是存在時(shí)延的嫡秕,所以是約等于的關(guān)系。

TCP 頭部

TCP 頭部

從上圖中可以看到苹威,窗口字段只有 2 個(gè)字節(jié)昆咽,因此它最多能表達(dá) 65535 字節(jié)大小的窗口,也就是 64KB 大小牙甫。

這個(gè)窗口大小最大值凝化,在當(dāng)今高速網(wǎng)絡(luò)下杨帽,很明顯是不夠用的鸿捧。所以后續(xù)有了擴(kuò)充窗口的方法:在 TCP 選項(xiàng)字段定義了窗口擴(kuò)大因子铺然,用于擴(kuò)大 TCP 通告窗口,其值大小是 2^14且轨,這樣就使 TCP 的窗口大小從 16 位擴(kuò)大為 30 位(2^16 * 2^ 14 = 2^30)浮声,所以此時(shí)窗口的最大值可以達(dá)到 1GB。

Linux 中打開這一功能殖告,需要把 tcp_window_scaling 配置設(shè)為 1(默認(rèn)打開):

image

要使用窗口擴(kuò)大選項(xiàng),通訊雙方必須在各自的 SYN 報(bào)文中發(fā)送這個(gè)選項(xiàng):

  • 主動(dòng)建立連接的一方在 SYN 報(bào)文中發(fā)送這個(gè)選項(xiàng)雳锋;
  • 而被動(dòng)建立連接的一方只有在收到帶窗口擴(kuò)大選項(xiàng)的 SYN 報(bào)文之后才能發(fā)送這個(gè)選項(xiàng)黄绩。

這樣看來,只要進(jìn)程能及時(shí)地調(diào)用 read 函數(shù)讀取數(shù)據(jù)玷过,并且接收緩沖區(qū)配置得足夠大爽丹,那么接收窗口就可以無限地放大,發(fā)送方也就無限地提升發(fā)送速度辛蚊。

這是不可能的粤蝎,因?yàn)榫W(wǎng)絡(luò)的傳輸能力是有限的,當(dāng)發(fā)送方依據(jù)發(fā)送窗口袋马,發(fā)送超過網(wǎng)絡(luò)處理能力的報(bào)文時(shí)初澎,路由器會(huì)直接丟棄這些報(bào)文。因此虑凛,緩沖區(qū)的內(nèi)存并不是越大越好碑宴。

如果確定最大傳輸速度?

在前面我們知道了 TCP 的傳輸速度桑谍,受制于發(fā)送窗口與接收窗口延柠,以及網(wǎng)絡(luò)設(shè)備傳輸能力。其中锣披,窗口大小由內(nèi)核緩沖區(qū)大小決定贞间。如果緩沖區(qū)與網(wǎng)絡(luò)傳輸能力匹配贿条,那么緩沖區(qū)的利用率就達(dá)到了最大化。

問題來了增热,如何計(jì)算網(wǎng)絡(luò)的傳輸能力呢整以?

相信大家都知道網(wǎng)絡(luò)是有「帶寬」限制的,帶寬描述的是網(wǎng)絡(luò)傳輸能力钓葫,它與內(nèi)核緩沖區(qū)的計(jì)量單位不同:

  • 帶寬是單位時(shí)間內(nèi)的流量悄蕾,表達(dá)是「速度」,比如常見的帶寬 100 MB/s础浮;
  • 緩沖區(qū)單位是字節(jié)帆调,當(dāng)網(wǎng)絡(luò)速度乘以時(shí)間才能得到字節(jié)數(shù);

這里需要說一個(gè)概念豆同,就是帶寬時(shí)延積番刊,它決定網(wǎng)絡(luò)中飛行報(bào)文的大小,它的計(jì)算方式:

image

比如最大帶寬是 100 MB/s影锈,網(wǎng)絡(luò)時(shí)延(RTT)是 10ms 時(shí)芹务,意味著客戶端到服務(wù)端的網(wǎng)絡(luò)一共可以存放 100MB/s * 0.01s = 1MB 的字節(jié)。

這個(gè) 1MB 是帶寬和時(shí)延的乘積鸭廷,所以它就叫「帶寬時(shí)延積」(縮寫為 BDP枣抱,Bandwidth Delay Product)。同時(shí)辆床,這 1MB 也表示「飛行中」的 TCP 報(bào)文大小佳晶,它們就在網(wǎng)絡(luò)線路、路由器等網(wǎng)絡(luò)設(shè)備上讼载。如果飛行報(bào)文超過了 1 MB轿秧,就會(huì)導(dǎo)致網(wǎng)絡(luò)過載,容易丟包咨堤。

由于發(fā)送緩沖區(qū)大小決定了發(fā)送窗口的上限菇篡,而發(fā)送窗口又決定了「已發(fā)送未確認(rèn)」的飛行報(bào)文的上限。因此一喘,發(fā)送緩沖區(qū)不能超過「帶寬時(shí)延積」驱还。

發(fā)送緩沖區(qū)與帶寬時(shí)延積的關(guān)系:

  • 如果發(fā)送緩沖區(qū)「超過」帶寬時(shí)延積,超出的部分就沒辦法有效的網(wǎng)絡(luò)傳輸凸克,同時(shí)導(dǎo)致網(wǎng)絡(luò)過載铝侵,容易丟包;
  • 如果發(fā)送緩沖區(qū)「小于」帶寬時(shí)延積触徐,就不能很好的發(fā)揮出網(wǎng)絡(luò)的傳輸效率咪鲜。

所以,發(fā)送緩沖區(qū)的大小最好是往帶寬時(shí)延積靠近撞鹉。

怎樣調(diào)整緩沖區(qū)大信北颖侄?

在 Linux 中發(fā)送緩沖區(qū)和接收緩沖都是可以用參數(shù)調(diào)節(jié)的。設(shè)置完后享郊,Linux 會(huì)根據(jù)你設(shè)置的緩沖區(qū)進(jìn)行動(dòng)態(tài)調(diào)節(jié)览祖。

調(diào)節(jié)發(fā)送緩沖區(qū)范圍

先來看看發(fā)送緩沖區(qū),它的范圍通過 tcp_wmem 參數(shù)配置炊琉;

image

上面三個(gè)數(shù)字單位都是字節(jié)展蒂,它們分別表示:

  • 第一個(gè)數(shù)值是動(dòng)態(tài)范圍的最小值,4096 byte = 4K苔咪;
  • 第二個(gè)數(shù)值是初始默認(rèn)值锰悼,87380 byte ≈ 86K;
  • 第三個(gè)數(shù)值是動(dòng)態(tài)范圍的最大值团赏,4194304 byte = 4096K(4M)箕般;

發(fā)送緩沖區(qū)是自行調(diào)節(jié)的,當(dāng)發(fā)送方發(fā)送的數(shù)據(jù)被確認(rèn)后舔清,并且沒有新的數(shù)據(jù)要發(fā)送丝里,就會(huì)把發(fā)送緩沖區(qū)的內(nèi)存釋放掉。

調(diào)節(jié)接收緩沖區(qū)范圍

而接收緩沖區(qū)的調(diào)整就比較復(fù)雜一些体谒,先來看看設(shè)置接收緩沖區(qū)范圍的 tcp_rmem 參數(shù):

image

上面三個(gè)數(shù)字單位都是字節(jié)杯聚,它們分別表示:

  • 第一個(gè)數(shù)值是動(dòng)態(tài)范圍的最小值,表示即使在內(nèi)存壓力下也可以保證的最小接收緩沖區(qū)大小抒痒,4096 byte = 4K幌绍;
  • 第二個(gè)數(shù)值是初始默認(rèn)值,87380 byte ≈ 86K评汰;
  • 第三個(gè)數(shù)值是動(dòng)態(tài)范圍的最大值纷捞,6291456 byte = 6144K(6M)痢虹;

接收緩沖區(qū)可以根據(jù)系統(tǒng)空閑內(nèi)存的大小來調(diào)節(jié)接收窗口:

  • 如果系統(tǒng)的空閑內(nèi)存很多被去,就可以自動(dòng)把緩沖區(qū)增大一些,這樣傳給對(duì)方的接收窗口也會(huì)變大奖唯,因而提升發(fā)送方發(fā)送的傳輸數(shù)據(jù)數(shù)量惨缆;
  • 反正,如果系統(tǒng)的內(nèi)存很緊張丰捷,就會(huì)減少緩沖區(qū)坯墨,這雖然會(huì)降低傳輸效率,可以保證更多的并發(fā)連接正常工作病往;

發(fā)送緩沖區(qū)的調(diào)節(jié)功能是自動(dòng)開啟的捣染,而接收緩沖區(qū)則需要配置 tcp_moderate_rcvbuf 為 1 來開啟調(diào)節(jié)功能

image

調(diào)節(jié) TCP 內(nèi)存范圍

接收緩沖區(qū)調(diào)節(jié)時(shí),怎么知道當(dāng)前內(nèi)存是否緊張或充分呢停巷?這是通過 tcp_mem 配置完成的:

image

上面三個(gè)數(shù)字單位不是字節(jié)耍攘,而是「頁面大小」榕栏,1 頁表示 4KB,它們分別表示:

  • 當(dāng) TCP 內(nèi)存小于第 1 個(gè)值時(shí)蕾各,不需要進(jìn)行自動(dòng)調(diào)節(jié)扒磁;
  • 在第 1 和第 2 個(gè)值之間時(shí),內(nèi)核開始調(diào)節(jié)接收緩沖區(qū)的大惺角妨托;
  • 大于第 3 個(gè)值時(shí),內(nèi)核不再為 TCP 分配新內(nèi)存吝羞,此時(shí)新連接是無法建立的兰伤;

一般情況下這些值是在系統(tǒng)啟動(dòng)時(shí)根據(jù)系統(tǒng)內(nèi)存數(shù)量計(jì)算得到的。根據(jù)當(dāng)前 tcp_mem 最大內(nèi)存頁面數(shù)是 177120脆贵,當(dāng)內(nèi)存為 (177120 * 4) / 1024K ≈ 692M 時(shí)医清,系統(tǒng)將無法為新的 TCP 連接分配內(nèi)存,即 TCP 連接將被拒絕卖氨。

根據(jù)實(shí)際場(chǎng)景調(diào)節(jié)的策略

在高并發(fā)服務(wù)器中会烙,為了兼顧網(wǎng)速與大量的并發(fā)連接,我們應(yīng)當(dāng)保證緩沖區(qū)的動(dòng)態(tài)調(diào)整的最大值達(dá)到帶寬時(shí)延積筒捺,而最小值保持默認(rèn)的 4K 不變即可柏腻。而對(duì)于內(nèi)存緊張的服務(wù)而言,調(diào)低默認(rèn)值是提高并發(fā)的有效手段系吭。

同時(shí)五嫂,如果這是網(wǎng)絡(luò) IO 型服務(wù)器,那么肯尺,調(diào)大 tcp_mem 的上限可以讓 TCP 連接使用更多的系統(tǒng)內(nèi)存沃缘,這有利于提升并發(fā)能力。需要注意的是则吟,tcp_wmem 和 tcp_rmem 的單位是字節(jié)槐臀,而 tcp_mem 的單位是頁面大小。而且氓仲,千萬不要在 socket 上直接設(shè)置 SO_SNDBUF 或者 SO_RCVBUF水慨,這樣會(huì)關(guān)閉緩沖區(qū)的動(dòng)態(tài)調(diào)整功能。

小結(jié)

本節(jié)針對(duì) TCP 優(yōu)化數(shù)據(jù)傳輸?shù)姆绞骄纯福隽艘恍┙榻B晰洒。

數(shù)據(jù)傳輸?shù)膬?yōu)化策略

數(shù)據(jù)傳輸?shù)膬?yōu)化策略

TCP 可靠性是通過 ACK 確認(rèn)報(bào)文實(shí)現(xiàn)的,又依賴滑動(dòng)窗口提升了發(fā)送速度也兼顧了接收方的處理能力啥箭。

可是谍珊,默認(rèn)的滑動(dòng)窗口最大值只有 64 KB,不滿足當(dāng)今的高速網(wǎng)絡(luò)的要求急侥,要想要想提升發(fā)送速度必須提升滑動(dòng)窗口的上限砌滞,在 Linux 下是通過設(shè)置 tcp_window_scaling 為 1 做到的炼七,此時(shí)最大值可高達(dá) 1GB。

滑動(dòng)窗口定義了網(wǎng)絡(luò)中飛行報(bào)文的最大字節(jié)數(shù)布持,當(dāng)它超過帶寬時(shí)延積時(shí)豌拙,網(wǎng)絡(luò)過載,就會(huì)發(fā)生丟包题暖。而當(dāng)它小于帶寬時(shí)延積時(shí)按傅,就無法充分利用網(wǎng)絡(luò)帶寬。因此胧卤,滑動(dòng)窗口的設(shè)置唯绍,必須參考帶寬時(shí)延積。

內(nèi)核緩沖區(qū)決定了滑動(dòng)窗口的上限枝誊,緩沖區(qū)可分為:發(fā)送緩沖區(qū) tcp_wmem 和接收緩沖區(qū) tcp_rmem况芒。

Linux 會(huì)對(duì)緩沖區(qū)動(dòng)態(tài)調(diào)節(jié),我們應(yīng)該把緩沖區(qū)的上限設(shè)置為帶寬時(shí)延積叶撒。發(fā)送緩沖區(qū)的調(diào)節(jié)功能是自動(dòng)打開的绝骚,而接收緩沖區(qū)需要把 tcp_moderate_rcvbuf 設(shè)置為 1 來開啟。其中祠够,調(diào)節(jié)的依據(jù)是 TCP 內(nèi)存范圍 tcp_mem压汪。

但需要注意的是,如果程序中的 socket 設(shè)置 SO_SNDBUF 和 SO_RCVBUF古瓤,則會(huì)關(guān)閉緩沖區(qū)的動(dòng)態(tài)整功能止剖,所以不建議在程序設(shè)置它倆,而是交給內(nèi)核自動(dòng)調(diào)整比較好落君。

有效配置這些參數(shù)后穿香,既能夠最大程度地保持并發(fā)性,也能讓資源充裕時(shí)連接傳輸速度達(dá)到最大值绎速。


巨人的肩膀

[1] 系統(tǒng)性能調(diào)優(yōu)必知必會(huì).陶輝.極客時(shí)間.

[2] 網(wǎng)絡(luò)編程實(shí)戰(zhàn)專欄.盛延敏.極客時(shí)間.

[3] http://www.blogjava.net/yongboy/archive/2013/04/11/397677.html

[4] http://blog.itpub.net/31559359/viewspace-2284113/

[5] https://blog.51cto.com/professor/1909022


好文推薦

本篇是 TCP 系列的「終章」了皮获,往期的 TCP 圖解文章如下:

TCP 半連接隊(duì)列和全連接隊(duì)列滿了會(huì)發(fā)生什么?又該如何應(yīng)對(duì)朝氓?

實(shí)戰(zhàn)魔市!我用“大白鯊”讓你看見 TCP

你還在為 TCP 重傳主届、滑動(dòng)窗口赵哲、流量控制、擁塞控制發(fā)愁嗎君丁?看完圖解就不愁了

硬不硬你說了算枫夺!近 40 張圖解被問千百遍的 TCP 三次握手和四次揮手面試題

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市绘闷,隨后出現(xiàn)的幾起案子橡庞,更是在濱河造成了極大的恐慌较坛,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,406評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件扒最,死亡現(xiàn)場(chǎng)離奇詭異丑勤,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)吧趣,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門法竞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人强挫,你說我怎么就攤上這事岔霸。” “怎么了俯渤?”我有些...
    開封第一講書人閱讀 163,711評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵呆细,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我八匠,道長(zhǎng)絮爷,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,380評(píng)論 1 293
  • 正文 為了忘掉前任梨树,我火速辦了婚禮略水,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘劝萤。我一直安慰自己渊涝,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,432評(píng)論 6 392
  • 文/花漫 我一把揭開白布床嫌。 她就那樣靜靜地躺著跨释,像睡著了一般。 火紅的嫁衣襯著肌膚如雪厌处。 梳的紋絲不亂的頭發(fā)上鳖谈,一...
    開封第一講書人閱讀 51,301評(píng)論 1 301
  • 那天,我揣著相機(jī)與錄音阔涉,去河邊找鬼缆娃。 笑死,一個(gè)胖子當(dāng)著我的面吹牛瑰排,可吹牛的內(nèi)容都是我干的贯要。 我是一名探鬼主播,決...
    沈念sama閱讀 40,145評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼椭住,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼崇渗!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,008評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤宅广,失蹤者是張志新(化名)和其女友劉穎葫掉,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體跟狱,經(jīng)...
    沈念sama閱讀 45,443評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡俭厚,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,649評(píng)論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了驶臊。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片套腹。...
    茶點(diǎn)故事閱讀 39,795評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖资铡,靈堂內(nèi)的尸體忽然破棺而出电禀,到底是詐尸還是另有隱情,我是刑警寧澤笤休,帶...
    沈念sama閱讀 35,501評(píng)論 5 345
  • 正文 年R本政府宣布尖飞,位于F島的核電站,受9級(jí)特大地震影響店雅,放射性物質(zhì)發(fā)生泄漏政基。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,119評(píng)論 3 328
  • 文/蒙蒙 一闹啦、第九天 我趴在偏房一處隱蔽的房頂上張望沮明。 院中可真熱鬧,春花似錦窍奋、人聲如沸荐健。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽江场。三九已至,卻和暖如春窖逗,著一層夾襖步出監(jiān)牢的瞬間址否,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工碎紊, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留佑附,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,899評(píng)論 2 370
  • 正文 我出身青樓仗考,卻偏偏與公主長(zhǎng)得像音同,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子痴鳄,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,724評(píng)論 2 354