來自公眾號(hào):小林coding
作者小林coding
前言
不管面試 Java 、C/C++问词、Python 等開發(fā)崗位督函, TCP
的知識(shí)點(diǎn)可以說是的必問的了。
任 TCP 虐我千百遍激挪,我仍待 TCP 如初戀辰狡。
遙想小林當(dāng)年校招時(shí)常因 TCP
面試題被刷,真是又愛又狠….
過去不會(huì)沒關(guān)系垄分,今天就讓我們來消除這份恐懼宛篇,微笑著勇敢的面對(duì)它吧!
所以小林整理了關(guān)于 TCP 三次握手和四次揮手的面試題型薄湿,跟大家一起探討探討些己。
- TCP 基本認(rèn)識(shí)
- TCP 連接建立
- TCP 連接斷開
- Socket 編程
PS:本次文章不涉及 TCP 流量控制、擁塞控制嘿般、可靠性傳輸?shù)确矫嬷R(shí)段标,這些留在下篇哈!
正文
01 TCP 基本認(rèn)識(shí)
瞧瞧 TCP 頭格式
我們先來看看 TCP 頭的格式炉奴,標(biāo)注顏色的表示與本文關(guān)聯(lián)比較大的字段逼庞,其他字段不做詳細(xì)闡述。
序列號(hào):在建立連接時(shí)由計(jì)算機(jī)生成的隨機(jī)數(shù)作為其初始值瞻赶,通過 SYN 包傳給接收端主機(jī)赛糟,每發(fā)送一次數(shù)據(jù)派任,就「累加」一次該「數(shù)據(jù)字節(jié)數(shù)」的大小。用來解決網(wǎng)絡(luò)包亂序問題璧南。
確認(rèn)應(yīng)答號(hào):指下一次「期望」收到的數(shù)據(jù)的序列號(hào)掌逛,發(fā)送端收到這個(gè)確認(rèn)應(yīng)答以后可以認(rèn)為在這個(gè)序號(hào)以前的數(shù)據(jù)都已經(jīng)被正常接收。用來解決不丟包的問題司倚。
控制位:
ACK:該位為
1
時(shí)豆混,「確認(rèn)應(yīng)答」的字段變?yōu)橛行В琓CP 規(guī)定除了最初建立連接時(shí)的SYN
包之外該位必須設(shè)置為1
动知。RST:該位為
1
時(shí)皿伺,表示 TCP 連接中出現(xiàn)異常必須強(qiáng)制斷開連接。SYC:該位為
1
時(shí)盒粮,表示希望建立連鸵鸥,并在其「序列號(hào)」的字段進(jìn)行序列號(hào)初始值的設(shè)定。FIN:該位為
1
時(shí)丹皱,表示今后不會(huì)再有數(shù)據(jù)發(fā)送妒穴,希望斷開連接。當(dāng)通信結(jié)束希望斷開連接時(shí)摊崭,通信雙方的主機(jī)之間就可以相互交換FIN
位置為 1 的 TCP 段讼油。
為什么需要 TCP 協(xié)議?TCP 工作在哪一層爽室?
IP
層是「不可靠」的汁讼,它不保證網(wǎng)絡(luò)包的交付、不保證網(wǎng)絡(luò)包的按序交付阔墩、也不保證網(wǎng)絡(luò)包中的數(shù)據(jù)的完整性嘿架。
如果需要保障網(wǎng)絡(luò)數(shù)據(jù)包的可靠性,那么就需要由上層(傳輸層)的 TCP
協(xié)議來負(fù)責(zé)啸箫。
因?yàn)?TCP 是一個(gè)工作在傳輸層的可靠數(shù)據(jù)傳輸?shù)姆?wù)耸彪,它能確保接收端接收的網(wǎng)絡(luò)包是無損壞、無間隔忘苛、非冗余和按序的蝉娜。
什么是 TCP ?
TCP 是面向連接的扎唾、可靠的召川、基于字節(jié)流的傳輸層通信協(xié)議。
面向連接:一定是「一對(duì)一」才能連接胸遇,不能像 UDP 協(xié)議 可以一個(gè)主機(jī)同時(shí)向多個(gè)主機(jī)發(fā)送消息荧呐,也就是一對(duì)多是無法做到的;
可靠的:無論的網(wǎng)絡(luò)鏈路中出現(xiàn)了怎樣的鏈路變化,TCP 都可以保證一個(gè)報(bào)文一定能夠到達(dá)接收端倍阐;
字節(jié)流:消息是「沒有邊界」的概疆,所以無論我們消息有多大都可以進(jìn)行傳輸。并且消息是「有序的」峰搪,當(dāng)「前一個(gè)」消息沒有收到的時(shí)候岔冀,即使它先收到了后面的字節(jié)已經(jīng)收到,那么也不能扔給應(yīng)用層去處理概耻,同時(shí)對(duì)「重復(fù)」的報(bào)文會(huì)自動(dòng)丟棄使套。
什么是 TCP 連接?
我們來看看 RFC 793 是如何定義「連接」的:
*Connections: *
*The reliability and flow control mechanisms described above requirethat TCPs initialize and maintain certain status information foreach data stream. *
The combination of this information, includingsockets, sequence numbers, and window sizes, is called a connection.
簡(jiǎn)單來說就是咐蚯,用于保證可靠性和流量控制維護(hù)的某些狀態(tài)信息童漩,這些信息的組合弄贿,包括Socket春锋、序列號(hào)和窗口大小稱為連接。
所以我們可以知道差凹,建立一個(gè) TCP 連接是需要客戶端與服務(wù)器端達(dá)成上述三個(gè)信息的共識(shí)期奔。
Socket:由 IP 地址和端口號(hào)組成
序列號(hào):用來解決亂序問題等
窗口大小:用來做流量控制
如何唯一確定一個(gè) TCP 連接呢?
TCP 四元組可以唯一的確定一個(gè)連接危尿,四元組包括如下:
源地址
源端口
目的地址
目的端口
源地址和目的地址的字段(32位)是在 IP 頭部中呐萌,作用是通過 IP 協(xié)議發(fā)送報(bào)文給對(duì)方主機(jī)。
源端口和目的端口的字段(16位)是在 TCP 頭部中谊娇,作用是告訴 TCP 協(xié)議應(yīng)該把報(bào)文發(fā)給哪個(gè)進(jìn)程肺孤。
有一個(gè) IP 的服務(wù)器監(jiān)聽了一個(gè)端口,它的 TCP 的最大連接數(shù)是多少济欢?
服務(wù)器通常固定在某個(gè)本地端口上監(jiān)聽赠堵,等待客戶端的連接請(qǐng)求。
因此法褥,客戶端 IP 和 端口是可變的茫叭,其理論值計(jì)算公式如下:
對(duì) IPv4,客戶端的 IP 數(shù)最多為 2
的 32
次方半等,客戶端的端口數(shù)最多為 2
的 16
次方揍愁,也就是服務(wù)端單機(jī)最大 TCP 連接數(shù),約為 2
的 48
次方杀饵。
當(dāng)然莽囤,服務(wù)端最大并發(fā) TCP 連接數(shù)遠(yuǎn)不能達(dá)到理論上限。
首先主要是文件描述符限制切距,Socket 都是文件朽缎,所以首先要通過
ulimit
配置文件描述符的數(shù)目;另一個(gè)是內(nèi)存限制,每個(gè) TCP 連接都要占用一定內(nèi)存饵沧,操作系統(tǒng)是有限的锨络。
UDP 和 TCP 有什么區(qū)別呢?分別的應(yīng)用場(chǎng)景是狼牺?
UDP 不提供復(fù)雜的控制機(jī)制羡儿,利用 IP 提供面向「無連接」的通信服務(wù)。
UDP 協(xié)議真的非常簡(jiǎn)是钥,頭部只有 8
個(gè)字節(jié)( 64 位)掠归,UDP 的頭部格式如下:
目標(biāo)和源端口:主要是告訴 UDP 協(xié)議應(yīng)該把報(bào)文發(fā)給哪個(gè)進(jìn)程。
包長(zhǎng)度:該字段保存了 UDP 首部的長(zhǎng)度跟數(shù)據(jù)的長(zhǎng)度之和悄泥。
校驗(yàn)和:校驗(yàn)和是為了提供可靠的 UDP 首部和數(shù)據(jù)而設(shè)計(jì)虏冻。
TCP 和 UDP 區(qū)別:
1. 連接
TCP 是面向連接的傳輸層協(xié)議,傳輸數(shù)據(jù)前先要建立連接弹囚。
UDP 是不需要連接厨相,即刻傳輸數(shù)據(jù)。
2. 服務(wù)對(duì)象
TCP 是一對(duì)一的兩點(diǎn)服務(wù)鸥鹉,即一條連接只有兩個(gè)端點(diǎn)蛮穿。
UDP 支持一對(duì)一、一對(duì)多毁渗、多對(duì)多的交互通信
3. 可靠性
TCP 是可靠交付數(shù)據(jù)的践磅,數(shù)據(jù)可以無差錯(cuò)、不丟失灸异、不重復(fù)府适、按需到達(dá)。
UDP 是盡最大努力交付肺樟,不保證可靠交付數(shù)據(jù)檐春。
4. 擁塞控制、流量控制
TCP 有擁塞控制和流量控制機(jī)制儡嘶,保證數(shù)據(jù)傳輸?shù)陌踩浴?/p>
UDP 則沒有喇聊,即使網(wǎng)絡(luò)非常擁堵了,也不會(huì)影響 UDP 的發(fā)送速率蹦狂。
5. 首部開銷
TCP 首部長(zhǎng)度較長(zhǎng)誓篱,會(huì)有一定的開銷,首部在沒有使用「選項(xiàng)」字段時(shí)是
20
個(gè)字節(jié)凯楔,如果使用了「選項(xiàng)」字段則會(huì)變長(zhǎng)的窜骄。UDP 首部只有 8 個(gè)字節(jié),并且是固定不變的摆屯,開銷較小邻遏。
TCP 和 UDP 應(yīng)用場(chǎng)景:
由于 TCP 是面向連接糠亩,能保證數(shù)據(jù)的可靠性交付,因此經(jīng)常用于:
FTP
文件傳輸HTTP
/HTTPS
由于 UDP 面向無連接准验,它可以隨時(shí)發(fā)送數(shù)據(jù)赎线,再加上UDP本身的處理既簡(jiǎn)單又高效,因此經(jīng)常用于:
包總量較少的通信糊饱,如
DNS
垂寥、SNMP
等視頻、音頻等多媒體通信
廣播通信
為什么 UDP 頭部沒有「首部長(zhǎng)度」字段另锋,而 TCP 頭部有「首部長(zhǎng)度」字段呢滞项?
原因是 TCP 有可變長(zhǎng)的「選項(xiàng)」字段,而 UDP 頭部長(zhǎng)度則是不會(huì)變化的夭坪,無需多一個(gè)字段去記錄 UDP 的首部長(zhǎng)度文判。
為什么 UDP 頭部有「包長(zhǎng)度」字段,而 TCP 頭部則沒有「包長(zhǎng)度」字段呢室梅?
先說說 TCP 是如何計(jì)算負(fù)載數(shù)據(jù)長(zhǎng)度:
其中 IP 總長(zhǎng)度 和 IP 首部長(zhǎng)度戏仓,在 IP 首部格式是已知的。TCP 首部長(zhǎng)度竞惋,則是在 TCP 首部格式已知的柜去,所以就可以求得 TCP 數(shù)據(jù)的長(zhǎng)度灰嫉。
大家這時(shí)就奇怪了問:“ UDP 也是基于 IP 層的呀拆宛,那 UDP 的數(shù)據(jù)長(zhǎng)度也可以通過這個(gè)公式計(jì)算呀?為何還要有「包長(zhǎng)度」呢讼撒?”
這么一問浑厚,確實(shí)感覺 UDP 「包長(zhǎng)度」是冗余的。
因?yàn)闉榱司W(wǎng)絡(luò)設(shè)備硬件設(shè)計(jì)和處理方便根盒,首部長(zhǎng)度需要是 4
字節(jié)的整數(shù)倍钳幅。
如果去掉 UDP 「包長(zhǎng)度」字段,那 UDP 首部長(zhǎng)度就不是 4
字節(jié)的整數(shù)倍了炎滞,所以小林覺得這可能是為了補(bǔ)全 UDP 首部長(zhǎng)度是 4
字節(jié)的整數(shù)倍敢艰,才補(bǔ)充了「包長(zhǎng)度」字段。
02 TCP 連接建立
TCP 三次握手過程和狀態(tài)變遷
TCP 是面向連接的協(xié)議册赛,所以使用 TCP 前必須先建立連接钠导,而建立連接是通過三次握手而進(jìn)行的。
- 一開始森瘪,客戶端和服務(wù)端都處于
CLOSED
狀態(tài)牡属。先是服務(wù)端主動(dòng)監(jiān)聽某個(gè)端口,處于LISTEN
狀態(tài)
- 客戶端會(huì)隨機(jī)初始化序號(hào)(
client_isn
)扼睬,將此序號(hào)置于 TCP 首部的「序號(hào)」字段中逮栅,同時(shí)把SYN
標(biāo)志位置為1
,表示SYN
報(bào)文。接著把第一個(gè) SYN 報(bào)文發(fā)送給服務(wù)端措伐,表示向服務(wù)端發(fā)起連接特纤,該報(bào)文不包含應(yīng)用層數(shù)據(jù),之后客戶端處于SYN-SENT
狀態(tài)侥加。
- 服務(wù)端收到客戶端的
SYN
報(bào)文后叫潦,首先服務(wù)端也隨機(jī)初始化自己的序號(hào)(server_isn
),將此序號(hào)填入 TCP 首部的「序號(hào)」字段中官硝,其次把 TCP 首部的「確認(rèn)應(yīng)答號(hào)」字段填入client_isn + 1
, 接著把SYN
和ACK
標(biāo)志位置為1
矗蕊。最后把該報(bào)文發(fā)給客戶端,該報(bào)文也不包含應(yīng)用層數(shù)據(jù)氢架,之后服務(wù)端處于SYN-RCVD
狀態(tài)傻咖。
客戶端收到服務(wù)端報(bào)文后,還要向服務(wù)端回應(yīng)最后一個(gè)應(yīng)答報(bào)文岖研,首先該應(yīng)答報(bào)文 TCP 首部
ACK
標(biāo)志位置為1
卿操,其次「確認(rèn)應(yīng)答號(hào)」字段填入server_isn + 1
,最后把報(bào)文發(fā)送給服務(wù)端孙援,這次報(bào)文可以攜帶客戶到服務(wù)器的數(shù)據(jù)害淤,之后客戶端處于ESTABLISHED
狀態(tài)。服務(wù)器收到客戶端的應(yīng)答報(bào)文后拓售,也進(jìn)入
ESTABLISHED
狀態(tài)窥摄。
從上面的過程可以發(fā)現(xiàn)第三次握手是可以攜帶數(shù)據(jù)的,前兩次握手是不可以攜帶數(shù)據(jù)的础淤,這也是面試常問的題崭放。
一旦完成三次握手,雙方都處于 ESTABLISHED
狀態(tài)鸽凶,此致連接就已建立完成币砂,客戶端和服務(wù)端就可以相互發(fā)送數(shù)據(jù)了。
如何在 Linux 系統(tǒng)中查看 TCP 狀態(tài)玻侥?
TCP 的連接狀態(tài)查看决摧,在 Linux 可以通過 netstat -napt
命令查看。
為什么是三次握手凑兰?不是兩次掌桩、四次?
相信大家比較称币。回答的是:“因?yàn)槿挝帐植拍鼙WC雙方具有接收和發(fā)送的能力拘鞋『惺梗”
這回答是沒問題唧垦,但這回答是片面的,并沒有說出主要的原因胰耗。
在前面我們知道了什么是 TCP 連接:
- 用于保證可靠性和流量控制維護(hù)的某些狀態(tài)信息,這些信息的組合隔躲,包括Socket摩梧、序列號(hào)和窗口大小稱為連接。
所以宣旱,重要的是為什么三次握手才可以初始化Socket仅父、序列號(hào)和窗口大小并建立 TCP 連接。
接下來以三個(gè)方面分析三次握手的原因:
三次握手才可以阻止歷史重復(fù)連接的初始化(主要原因)
三次握手才可以同步雙方的初始序列號(hào)
三次握手才可以避免資源浪費(fèi)
原因一:避免歷史連接
我們來看看 RFC 793 指出的 TCP 連接使用三次握手的首要原因:
The principle reason for the three-way handshake is to prevent old duplicate connection initiations from causing confusion.
簡(jiǎn)單來說浑吟,三次握手的首要原因是為了防止舊的重復(fù)連接初始化造成混亂笙纤。
網(wǎng)絡(luò)環(huán)境是錯(cuò)綜復(fù)雜的,往往并不是如我們期望的一樣组力,先發(fā)送的數(shù)據(jù)包省容,就先到達(dá)目標(biāo)主機(jī),反而它很騷燎字,可能會(huì)由于網(wǎng)絡(luò)擁堵等亂七八糟的原因腥椒,會(huì)使得舊的數(shù)據(jù)包,先到達(dá)目標(biāo)主機(jī)候衍,那么這種情況下 TCP 三次握手是如何避免的呢笼蛛?
客戶端連續(xù)發(fā)送多次 SYN 建立連接的報(bào)文,在網(wǎng)絡(luò)擁堵等情況下:
一個(gè)「舊 SYN 報(bào)文」比「最新的 SYN 」 報(bào)文早到達(dá)了服務(wù)端蛉鹿;
那么此時(shí)服務(wù)端就會(huì)回一個(gè)
SYN + ACK
報(bào)文給客戶端滨砍;客戶端收到后可以根據(jù)自身的上下文,判斷這是一個(gè)歷史連接(序列號(hào)過期或超時(shí))榨为,那么客戶端就會(huì)發(fā)送
RST
報(bào)文給服務(wù)端惨好,表示中止這一次連接。
如果是兩次握手連接随闺,就不能判斷當(dāng)前連接是否是歷史連接,三次握手則可以在客戶端(發(fā)送方)準(zhǔn)備發(fā)送第三次報(bào)文時(shí)蔓腐,客戶端因有足夠的上下文來判斷當(dāng)前連接是否是歷史連接:
如果是歷史連接(序列號(hào)過期或超時(shí))矩乐,則第三次握手發(fā)送的報(bào)文是
RST
報(bào)文,以此中止歷史連接回论;如果不是歷史連接散罕,則第三次發(fā)送的報(bào)文是
ACK
報(bào)文,通信雙方就會(huì)成功建立連接傀蓉;
所以欧漱, TCP 使用三次握手建立連接的最主要原因是防止歷史連接初始化了連接。
原因二:同步雙方初始序列號(hào)
TCP 協(xié)議的通信雙方葬燎, 都必須維護(hù)一個(gè)「序列號(hào)」误甚, 序列號(hào)是可靠傳輸?shù)囊粋€(gè)關(guān)鍵因素缚甩,它的作用:
接收方可以去除重復(fù)的數(shù)據(jù);
接收方可以根據(jù)數(shù)據(jù)包的序列號(hào)按序接收窑邦;
可以標(biāo)識(shí)發(fā)送出去的數(shù)據(jù)包中擅威, 哪些是已經(jīng)被對(duì)方收到的;
可見冈钦,序列號(hào)在 TCP 連接中占據(jù)著非常重要的作用郊丛,所以當(dāng)客戶端發(fā)送攜帶「初始序列號(hào)」的 SYN
報(bào)文的時(shí)候,需要服務(wù)端回一個(gè) ACK
應(yīng)答報(bào)文瞧筛,表示客戶端的 SYN 報(bào)文已被服務(wù)端成功接收厉熟,那當(dāng)服務(wù)端發(fā)送「初始序列號(hào)」給客戶端的時(shí)候,依然也要得到客戶端的應(yīng)答回應(yīng)较幌,這樣一來一回庆猫,才能確保雙方的初始序列號(hào)能被可靠的同步。
四次握手其實(shí)也能夠可靠的同步雙方的初始化序號(hào)绅络,但由于第二步和第三步可以優(yōu)化成一步月培,所以就成了「三次握手」。
而兩次握手只保證了一方的初始序列號(hào)能被對(duì)方成功接收恩急,沒辦法保證雙方的初始序列號(hào)都能被確認(rèn)接收杉畜。
原因三:避免資源浪費(fèi)
如果只有「兩次握手」,當(dāng)客戶端的 SYN
請(qǐng)求連接在網(wǎng)絡(luò)中阻塞衷恭,客戶端沒有接收到 ACK
報(bào)文此叠,就會(huì)重新發(fā)送 SYN
,由于沒有第三次握手随珠,服務(wù)器不清楚客戶端是否收到了自己發(fā)送的建立連接的 ACK
確認(rèn)信號(hào)灭袁,所以每收到一個(gè) SYN
就只能先主動(dòng)建立一個(gè)連接,這會(huì)造成什么情況呢窗看?
如果客戶端的 SYN
阻塞了茸歧,重復(fù)發(fā)送多次 SYN
報(bào)文,那么服務(wù)器在收到請(qǐng)求后就會(huì)建立多個(gè)冗余的無效鏈接显沈,造成不必要的資源浪費(fèi)软瞎。
即兩次握手會(huì)造成消息滯留情況下,服務(wù)器重復(fù)接受無用的連接請(qǐng)求 SYN
報(bào)文拉讯,而造成重復(fù)分配資源涤浇。
小結(jié)
TCP 建立連接時(shí),通過三次握手能防止歷史連接的建立魔慷,能減少雙方不必要的資源開銷只锭,能幫助雙方同步初始化序列號(hào)。序列號(hào)能夠保證數(shù)據(jù)包不重復(fù)院尔、不丟棄和按序傳輸蜻展。
不使用「兩次握手」和「四次握手」的原因:
「兩次握手」:無法防止歷史連接的建立喉誊,會(huì)造成雙方資源的浪費(fèi),也無法可靠的同步雙方序列號(hào)铺呵;
「四次握手」:三次握手就已經(jīng)理論上最少可靠連接建立裹驰,所以不需要使用更多的通信次數(shù)。
為什么客戶端和服務(wù)端的初始序列號(hào) ISN 是不相同的片挂?
因?yàn)榫W(wǎng)絡(luò)中的報(bào)文會(huì)延遲幻林、會(huì)復(fù)制重發(fā)、也有可能丟失音念,這樣會(huì)造成的不同連接之間產(chǎn)生互相影響沪饺,所以為了避免互相影響,客戶端和服務(wù)端的初始序列號(hào)是隨機(jī)且不同的闷愤。
初始序列號(hào) ISN 是如何隨機(jī)產(chǎn)生的整葡?
起始 ISN
是基于時(shí)鐘的,每 4 毫秒 + 1讥脐,轉(zhuǎn)一圈要 4.55 個(gè)小時(shí)遭居。
RFC1948 中提出了一個(gè)較好的初始化序列號(hào) ISN 隨機(jī)生成算法。
ISN = M + F (localhost, localport, remotehost, remoteport)
M
是一個(gè)計(jì)時(shí)器旬渠,這個(gè)計(jì)時(shí)器每隔 4 毫秒加 1俱萍。F
是一個(gè) Hash 算法,根據(jù)源 IP告丢、目的 IP枪蘑、源端口、目的端口生成一個(gè)隨機(jī)數(shù)值岖免。要保證 Hash 算法不能被外部輕易推算得出岳颇,用 MD5 算法是一個(gè)比較好的選擇。
既然 IP 層會(huì)分片颅湘,為什么 TCP 層還需要 MSS 呢话侧?
我們先來認(rèn)識(shí)下 MTU 和 MSS
MTU
:一個(gè)網(wǎng)絡(luò)包的最大長(zhǎng)度,以太網(wǎng)中一般為1500
字節(jié)栅炒;MSS
:除去 IP 和 TCP 頭部之后掂摔,一個(gè)網(wǎng)絡(luò)包所能容納的 TCP 數(shù)據(jù)的最大長(zhǎng)度;
如果TCP 的整個(gè)報(bào)文(頭部 + 數(shù)據(jù))交給 IP 層進(jìn)行分片赢赊,會(huì)有什么異常呢?
當(dāng) IP 層有一個(gè)超過 MTU
大小的數(shù)據(jù)(TCP 頭部 + TCP 數(shù)據(jù))要發(fā)送级历,那么 IP 層就要進(jìn)行分片释移,把數(shù)據(jù)分片成若干片,保證每一個(gè)分片都小于 MTU寥殖。把一份 IP 數(shù)據(jù)報(bào)進(jìn)行分片以后玩讳,由目標(biāo)主機(jī)的 IP 層來進(jìn)行重新組裝后涩蜘,在交給上一層 TCP 傳輸層。
這看起來井然有序熏纯,但這存在隱患的同诫,那么當(dāng)如果一個(gè) IP 分片丟失,整個(gè) IP 報(bào)文的所有分片都得重傳樟澜。
因?yàn)?IP 層本身沒有超時(shí)重傳機(jī)制误窖,它由傳輸層的 TCP 來負(fù)責(zé)超時(shí)和重傳。
當(dāng)接收方發(fā)現(xiàn) TCP 報(bào)文(頭部 + 數(shù)據(jù))的某一片丟失后秩贰,則不會(huì)響應(yīng) ACK 給對(duì)方霹俺,那么發(fā)送方的 TCP 在超時(shí)后,就會(huì)重發(fā)「整個(gè) TCP 報(bào)文(頭部 + 數(shù)據(jù))」毒费。
因此丙唧,可以得知由 IP 層進(jìn)行分片傳輸,是非常沒有效率的觅玻。
所以想际,為了達(dá)到最佳的傳輸效能 TCP 協(xié)議在建立連接的時(shí)候通常要協(xié)商雙方的 MSS 值,當(dāng) TCP 層發(fā)現(xiàn)數(shù)據(jù)超過 MSS 時(shí)溪厘,則就先會(huì)進(jìn)行分片胡本,當(dāng)然由它形成的 IP 包的長(zhǎng)度也就不會(huì)大于 MTU ,自然也就不用 IP 分片了桩匪。
經(jīng)過 TCP 層分片后打瘪,如果一個(gè) TCP 分片丟失后,進(jìn)行重發(fā)時(shí)也是以 MSS 為單位傻昙,而不用重傳所有的分片闺骚,大大增加了重傳的效率。
什么是 SYN 攻擊妆档?如何避免 SYN 攻擊僻爽?
SYN 攻擊
我們都知道 TCP 連接建立是需要三次握手,假設(shè)攻擊者短時(shí)間偽造不同 IP 地址的 SYN
報(bào)文贾惦,服務(wù)端每接收到一個(gè) SYN
報(bào)文胸梆,就進(jìn)入SYN_RCVD
狀態(tài),但服務(wù)端發(fā)送出去的 ACK + SYN
報(bào)文须板,無法得到未知 IP 主機(jī)的 ACK
應(yīng)答碰镜,久而久之就會(huì)占滿服務(wù)端的 SYN 接收隊(duì)列(未連接隊(duì)列),使得服務(wù)器不能為正常用戶服務(wù)习瑰。
避免 SYN 攻擊方式一
其中一種解決方式是通過修改 Linux 內(nèi)核參數(shù)绪颖,控制隊(duì)列大小和當(dāng)隊(duì)列滿時(shí)應(yīng)做什么處理。
- 當(dāng)網(wǎng)卡接收數(shù)據(jù)包的速度大于內(nèi)核處理的速度時(shí)甜奄,會(huì)有一個(gè)隊(duì)列保存這些數(shù)據(jù)包柠横∏钥睿控制該隊(duì)列的最大值如下參數(shù):
net.core.netdev_max_backlog
- SYN_RCVD 狀態(tài)連接的最大個(gè)數(shù):
net.ipv4.tcp_max_syn_backlog
- 超出處理能時(shí),對(duì)新的 SYN 直接回 RST牍氛,丟棄連接:
net.ipv4.tcp_abort_on_overflow
避免 SYN 攻擊方式二
我們先來看下Linux 內(nèi)核的 SYN
(未完成連接建立)隊(duì)列與 Accpet
(已完成連接建立)隊(duì)列是如何工作的晨继?
正常流程:
當(dāng)服務(wù)端接收到客戶端的 SYN 報(bào)文時(shí),會(huì)將其加入到內(nèi)核的「 SYN 隊(duì)列」搬俊;
接著發(fā)送 SYN + ACK 給客戶端紊扬,等待客戶端回應(yīng) ACK 報(bào)文;
服務(wù)端接收到 ACK 報(bào)文后悠抹,從「 SYN 隊(duì)列」移除放入到「 Accept 隊(duì)列」珠月;
應(yīng)用通過調(diào)用
accpet()
socket 接口,從「 Accept 隊(duì)列」取出的連接楔敌。
應(yīng)用程序過慢:
- 如果應(yīng)用程序過慢時(shí)啤挎,就會(huì)導(dǎo)致「 Accept 隊(duì)列」被占滿。
受到 SYN 攻擊:
- 如果不斷受到 SYN 攻擊卵凑,就會(huì)導(dǎo)致「 SYN 隊(duì)列」被占滿庆聘。
tcp_syncookies
的方式可以應(yīng)對(duì) SYN 攻擊的方法:
net.ipv4.tcp_syncookies = 1
當(dāng) 「 SYN 隊(duì)列」?jié)M之后,后續(xù)服務(wù)器收到 SYN 包勺卢,不進(jìn)入「 SYN 隊(duì)列」伙判;
計(jì)算出一個(gè)
cookie
值,再以 SYN + ACK 中的「序列號(hào)」返回客戶端黑忱,服務(wù)端接收到客戶端的應(yīng)答報(bào)文時(shí)宴抚,服務(wù)器會(huì)檢查這個(gè) ACK 包的合法性。如果合法甫煞,直接放入到「 Accept 隊(duì)列」菇曲。
最后應(yīng)用通過調(diào)用
accpet()
socket 接口,從「 Accept 隊(duì)列」取出的連接抚吠。
03 TCP 連接斷開
TCP 四次揮手過程和狀態(tài)變遷
天下沒有不散的宴席常潮,對(duì)于 TCP 連接也是這樣, TCP 斷開連接是通過四次揮手方式楷力。
雙方都可以主動(dòng)斷開連接喊式,斷開連接后主機(jī)中的「資源」將被釋放。
客戶端打算關(guān)閉連接萧朝,此時(shí)會(huì)發(fā)送一個(gè) TCP 首部
FIN
標(biāo)志位被置為1
的報(bào)文岔留,也即FIN
報(bào)文,之后客戶端進(jìn)入FIN_WAIT_1
狀態(tài)检柬。服務(wù)端收到該報(bào)文后贸诚,就向客戶端發(fā)送
ACK
應(yīng)答報(bào)文,接著服務(wù)端進(jìn)入CLOSED_WAIT
狀態(tài)厕吉。客戶端收到服務(wù)端的
ACK
應(yīng)答報(bào)文后酱固,之后進(jìn)入FIN_WAIT_2
狀態(tài)。等待服務(wù)端處理完數(shù)據(jù)后头朱,也向客戶端發(fā)送
FIN
報(bào)文运悲,之后服務(wù)端進(jìn)入LAST_ACK
狀態(tài)。客戶端收到服務(wù)端的
FIN
報(bào)文后项钮,回一個(gè)ACK
應(yīng)答報(bào)文班眯,之后進(jìn)入TIME_WAIT
狀態(tài)服務(wù)器收到了
ACK
應(yīng)答報(bào)文后,就進(jìn)入了CLOSE
狀態(tài)烁巫,至此服務(wù)端已經(jīng)完成連接的關(guān)閉署隘。客戶端在經(jīng)過
2MSL
一段時(shí)間后,自動(dòng)進(jìn)入CLOSE
狀態(tài)亚隙,至此客戶端也完成連接的關(guān)閉磁餐。
你可以看到,每個(gè)方向都需要一個(gè) FIN 和一個(gè) ACK阿弃,因此通常被稱為四次揮手诊霹。
這里一點(diǎn)需要注意是:主動(dòng)關(guān)閉連接的,才有 TIME_WAIT 狀態(tài)渣淳。
為什么揮手需要四次脾还?
再來回顧下四次揮手雙方發(fā) FIN
包的過程,就能理解為什么需要四次了入愧。
關(guān)閉連接時(shí)鄙漏,客戶端向服務(wù)端發(fā)送
FIN
時(shí),僅僅表示客戶端不再發(fā)送數(shù)據(jù)了但是還能接收數(shù)據(jù)棺蛛。服務(wù)器收到客戶端的
FIN
報(bào)文時(shí)怔蚌,先回一個(gè)ACK
應(yīng)答報(bào)文,而服務(wù)端可能還有數(shù)據(jù)需要處理和發(fā)送鞠值,等服務(wù)端不再發(fā)送數(shù)據(jù)時(shí)媚创,才發(fā)送FIN
報(bào)文給客戶端來表示同意現(xiàn)在關(guān)閉連接。
從上面過程可知彤恶,服務(wù)端通常需要等待完成數(shù)據(jù)的發(fā)送和處理钞钙,所以服務(wù)端的 ACK
和 FIN
一般都會(huì)分開發(fā)送,從而比三次握手導(dǎo)致多了一次声离。
為什么 TIME_WAIT 等待的時(shí)間是 2MSL芒炼?
MSL
是 Maximum Segment Lifetime,報(bào)文最大生存時(shí)間术徊,它是任何報(bào)文在網(wǎng)絡(luò)上存在的最長(zhǎng)時(shí)間本刽,超過這個(gè)時(shí)間報(bào)文將被丟棄。因?yàn)?TCP 報(bào)文基于是 IP 協(xié)議的,而 IP 頭中有一個(gè) TTL
字段子寓,是 IP 數(shù)據(jù)報(bào)可以經(jīng)過的最大路由數(shù)暗挑,每經(jīng)過一個(gè)處理他的路由器此值就減 1,當(dāng)此值為 0 則數(shù)據(jù)報(bào)將被丟棄斜友,同時(shí)發(fā)送 ICMP 報(bào)文通知源主機(jī)炸裆。
MSL 與 TTL 的區(qū)別:MSL 的單位是時(shí)間,而 TTL 是經(jīng)過路由跳數(shù)鲜屏。所以 MSL 應(yīng)該要大于等于 TTL 消耗為 0 的時(shí)間烹看,以確保報(bào)文已被自然消亡。
TIME_WAIT 等待 2 倍的 MSL洛史,比較合理的解釋是:網(wǎng)絡(luò)中可能存在來自發(fā)送方的數(shù)據(jù)包惯殊,當(dāng)這些發(fā)送方的數(shù)據(jù)包被接收方處理后又會(huì)向?qū)Ψ桨l(fā)送響應(yīng),所以一來一回需要等待 2 倍的時(shí)間也殖。
比如土思,如果被動(dòng)關(guān)閉方?jīng)]有收到斷開連接的最后的 ACK 報(bào)文,就會(huì)觸發(fā)超時(shí)重發(fā) Fin 報(bào)文毕源,另一方接收到 FIN 后浪漠,會(huì)重發(fā) ACK 給被動(dòng)關(guān)閉方, 一來一去正好 2 個(gè) MSL霎褐。
2MSL
的時(shí)間是從客戶端接收到 FIN 后發(fā)送 ACK 開始計(jì)時(shí)的址愿。如果在 TIME-WAIT 時(shí)間內(nèi),因?yàn)榭蛻舳说?ACK 沒有傳輸?shù)椒?wù)端冻璃,客戶端又接收到了服務(wù)端重發(fā)的 FIN 報(bào)文响谓,那么 2MSL 時(shí)間將重新計(jì)時(shí)。
在 Linux 系統(tǒng)里 2MSL
默認(rèn)是 60
秒省艳,那么一個(gè) MSL
也就是 30
秒娘纷。Linux 系統(tǒng)停留在 TIME_WAIT 的時(shí)間為固定的 60 秒。
其定義在 Linux 內(nèi)核代碼里的名稱為 TCP_TIMEWAIT_LEN:
#define TCP_TIMEWAIT_LEN (60*HZ) /* how long to wait to destroy TIME-WAIT
state, about 60 seconds */
如果要修改 TIME_WAIT 的時(shí)間長(zhǎng)度跋炕,只能修改 Linux 內(nèi)核代碼里 TCP_TIMEWAIT_LEN 的值赖晶,并重新編譯 Linux 內(nèi)核。
為什么需要 TIME_WAIT 狀態(tài)辐烂?
主動(dòng)發(fā)起關(guān)閉連接的一方遏插,才會(huì)有 TIME-WAIT
狀態(tài)。
需要 TIME-WAIT 狀態(tài)纠修,主要是兩個(gè)原因:
防止具有相同「四元組」的「舊」數(shù)據(jù)包被收到胳嘲;
保證「被動(dòng)關(guān)閉連接」的一方能被正確的關(guān)閉,即保證最后的 ACK 能讓被動(dòng)關(guān)閉方接收扣草,從而幫助其正常關(guān)閉了牛;
原因一:防止舊連接的數(shù)據(jù)包
假設(shè) TIME-WAIT 沒有等待時(shí)間或時(shí)間過短颜屠,被延遲的數(shù)據(jù)包抵達(dá)后會(huì)發(fā)生什么呢?
如上圖黃色框框服務(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)閉
在 RFC 793 指出 TIME-WAIT 另一個(gè)重要的作用是:
TIME-WAIT - represents waiting for enough time to pass to be sure the remote TCP received the acknowledgment of its connection termination request.
也就是說颤介,TIME-WAIT 作用是等待足夠的時(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ì)被終止嘹履。
如果 TIME-WAIT 等待足夠長(zhǎng)的情況就會(huì)遇到兩種情況:
服務(wù)端正常收到四次揮手的最后一個(gè)
ACK
報(bào)文,則服務(wù)端正常關(guān)閉連接堪藐。服務(wù)端沒有收到四次揮手的最后一個(gè)
ACK
報(bào)文時(shí)届榄,則會(huì)重發(fā)FIN
關(guān)閉連接報(bào)文并等待新的ACK
報(bào)文浅乔。
所以客戶端在 TIME-WAIT
狀態(tài)等待 2MSL
時(shí)間后,就可以保證雙方的連接都可以正常的關(guān)閉铝条。
TIME_WAIT 過多有什么危害靖苇?
如果服務(wù)器有處于 TIME-WAIT 狀態(tài)的 TCP,則說明是由服務(wù)器方主動(dòng)發(fā)起的斷開請(qǐng)求攻晒。
過多的 TIME-WAIT 狀態(tài)主要的危害有兩種:
第一是內(nèi)存資源占用顾复;
第二是對(duì)端口資源的占用,一個(gè) TCP 連接至少消耗一個(gè)本地端口鲁捏;
第二個(gè)危害是會(huì)造成嚴(yán)重的后果的芯砸,要知道萧芙,端口資源也是有限的,一般可以開啟的端口為 32768~61000
假丧,也可以通過如下參數(shù)設(shè)置指定
net.ipv4.ip_local_port_range
如果服務(wù)端 TIME_WAIT 狀態(tài)過多双揪,占滿了所有端口資源,則會(huì)導(dǎo)致無法創(chuàng)建新連接包帚。
如何優(yōu)化 TIME_WAIT渔期?
這里給出優(yōu)化 TIME-WAIT 的幾個(gè)方式,都是有利有弊:
打開 net.ipv4.tcp_tw_reuse 和 net.ipv4.tcp_timestamps 選項(xiàng)渴邦;
net.ipv4.tcp_max_tw_buckets
程序中使用 SO_LINGER 疯趟,應(yīng)用強(qiáng)制使用 RST 關(guān)閉。
方式一:net.ipv4.tcp_tw_reuse 和 tcp_timestamps
如下的 Linux 內(nèi)核參數(shù)開啟后谋梭,則可以復(fù)用處于 TIME_WAIT 的 socket 為新的連接所用信峻。
net.ipv4.tcp_tw_reuse = 1
使用這個(gè)選項(xiàng),還有一個(gè)前提瓮床,需要打開對(duì) TCP 時(shí)間戳的支持盹舞,即
net.ipv4.tcp_timestamps=1(默認(rèn)即為 1)
這個(gè)時(shí)間戳的字段是在 TCP 頭部的「選項(xiàng)」里,用于記錄 TCP 發(fā)送方的當(dāng)前時(shí)間戳和從對(duì)端接收到的最新時(shí)間戳隘庄。
由于引入了時(shí)間戳踢步,我們?cè)谇懊嫣岬降?2MSL
問題就不復(fù)存在了,因?yàn)橹貜?fù)的數(shù)據(jù)包會(huì)因?yàn)闀r(shí)間戳過期被自然丟棄丑掺。
溫馨提醒:net.ipv4.tcp_tw_reuse
要慎用获印,因?yàn)槭褂昧怂捅厝灰蜷_時(shí)間戳的支持 net.ipv4.tcp_timestamps
,當(dāng)客戶端與服務(wù)端主機(jī)時(shí)間不同步時(shí)吼鱼,客戶端的發(fā)送的消息會(huì)被直接拒絕掉蓬豁。小林在工作中就遇到過。菇肃。地粪。排查了非常的久
方式二:net.ipv4.tcp_max_tw_buckets
這個(gè)值默認(rèn)為 18000,當(dāng)系統(tǒng)中處于 TIME_WAIT 的連接一旦超過這個(gè)值時(shí)琐谤,系統(tǒng)就會(huì)將所有的 TIME_WAIT 連接狀態(tài)重置蟆技。
這個(gè)方法過于暴力,而且治標(biāo)不治本斗忌,帶來的問題遠(yuǎn)比解決的問題多质礼,不推薦使用。
方式三:程序中使用 SO_LINGER
我們可以通過設(shè)置 socket 選項(xiàng)织阳,來設(shè)置調(diào)用 close 關(guān)閉連接行為眶蕉。
struct linger so_linger;
so_linger.l_onoff = 1;
so_linger.l_linger = 0;
setsockopt(s, SOL_SOCKET, SO_LINGER, &so_linger,sizeof(so_linger));
如果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)的行為谐丢,不值得提倡爽航。
如果已經(jīng)建立了連接,但是客戶端突然出現(xiàn)故障了怎么辦乾忱?
TCP 有一個(gè)機(jī)制是奔フ洌活機(jī)制。這個(gè)機(jī)制的原理是這樣的:
定義一個(gè)時(shí)間段饭耳,在這個(gè)時(shí)間段內(nèi)串述,如果沒有任何連接相關(guān)的活動(dòng),TCP 蹦ぃ活機(jī)制會(huì)開始作用,每隔一個(gè)時(shí)間間隔衰腌,發(fā)送一個(gè)探測(cè)報(bào)文新蟆,該探測(cè)報(bào)文包含的數(shù)據(jù)非常少,如果連續(xù)幾個(gè)探測(cè)報(bào)文都沒有得到響應(yīng)右蕊,則認(rèn)為當(dāng)前的 TCP 連接已經(jīng)死亡琼稻,系統(tǒng)內(nèi)核將錯(cuò)誤信息通知給上層應(yīng)用程序。
在 Linux 內(nèi)核可以有對(duì)應(yīng)的參數(shù)可以設(shè)置比那簦活時(shí)間帕翻、保活探測(cè)的次數(shù)萝风、编值В活探測(cè)的時(shí)間間隔,以下都為默認(rèn)值:
net.ipv4.tcp_keepalive_time=7200
net.ipv4.tcp_keepalive_intvl=75
net.ipv4.tcp_keepalive_probes=9
tcp_keepalive_time=7200:表示惫娑瑁活時(shí)間是 7200 秒(2小時(shí))睬塌,也就 2 小時(shí)內(nèi)如果沒有任何連接相關(guān)的活動(dòng),則會(huì)啟動(dòng)毙颍活機(jī)制
tcp_keepalive_intvl=75:表示每次檢測(cè)間隔 75 秒揩晴;
tcp_keepalive_probes=9:表示檢測(cè) 9 次無響應(yīng),認(rèn)為對(duì)方是不可達(dá)的贪磺,從而中斷本次的連接硫兰。
也就是說在 Linux 系統(tǒng)中,最少需要經(jīng)過 2 小時(shí) 11 分 15 秒才可以發(fā)現(xiàn)一個(gè)「死亡」連接寒锚。
這個(gè)時(shí)間是有點(diǎn)長(zhǎng)的劫映,我們也可以根據(jù)實(shí)際的需求违孝,對(duì)以上的保活相關(guān)的參數(shù)進(jìn)行設(shè)置苏研。
如果開啟了 TCP 钡茸牵活,需要考慮以下幾種情況:
第一種摹蘑,對(duì)端程序是正常工作的筹燕。當(dāng) TCP 保活的探測(cè)報(bào)文發(fā)送給對(duì)端, 對(duì)端會(huì)正常響應(yīng)衅鹿,這樣 TCP 比鲎伲活時(shí)間會(huì)被重置,等待下一個(gè) TCP 贝蟛常活時(shí)間的到來制妄。
第二種,對(duì)端程序崩潰并重啟泵三。當(dāng) TCP 备蹋活的探測(cè)報(bào)文發(fā)送給對(duì)端后,對(duì)端是可以響應(yīng)的烫幕,但由于沒有該連接的有效信息俺抽,會(huì)產(chǎn)生一個(gè) RST 報(bào)文,這樣很快就會(huì)發(fā)現(xiàn) TCP 連接已經(jīng)被重置较曼。
第三種磷斧,是對(duì)端程序崩潰,或?qū)Χ擞捎谄渌驅(qū)е聢?bào)文不可達(dá)捷犹。當(dāng) TCP 背诜梗活的探測(cè)報(bào)文發(fā)送給對(duì)端后,石沉大海萍歉,沒有響應(yīng)侣颂,連續(xù)幾次,達(dá)到贝滂耄活探測(cè)次數(shù)后横蜒,TCP 會(huì)報(bào)告該 TCP 連接已經(jīng)死亡。
03 Socket 編程
針對(duì) TCP 應(yīng)該如何 Socket 編程销凑?
服務(wù)端和客戶端初始化
socket
丛晌,得到文件描述符;服務(wù)端調(diào)用
bind
斗幼,將綁定在 IP 地址和端口;服務(wù)端調(diào)用
listen
澎蛛,進(jìn)行監(jiān)聽;服務(wù)端調(diào)用
accept
蜕窿,等待客戶端連接谋逻;客戶端調(diào)用
connect
呆馁,向服務(wù)器端的地址和端口發(fā)起連接請(qǐng)求;服務(wù)端
accept
返回用于傳輸?shù)?socket
的文件描述符毁兆;客戶端調(diào)用
write
寫入數(shù)據(jù)浙滤;服務(wù)端調(diào)用read
讀取數(shù)據(jù);客戶端斷開連接時(shí)气堕,會(huì)調(diào)用
close
纺腊,那么服務(wù)端read
讀取數(shù)據(jù)的時(shí)候,就會(huì)讀取到了EOF
茎芭,待處理完數(shù)據(jù)后揖膜,服務(wù)端調(diào)用close
,表示連接關(guān)閉梅桩。
這里需要注意的是壹粟,服務(wù)端調(diào)用 accept
時(shí),連接成功了會(huì)返回一個(gè)已完成連接的 socket宿百,后續(xù)用來傳輸數(shù)據(jù)趁仙。
所以,監(jiān)聽的 socket 和真正用來傳送數(shù)據(jù)的 socket垦页,是「兩個(gè)」 socket幸撕,一個(gè)叫作監(jiān)聽 socket,一個(gè)叫作已完成連接 socket外臂。
成功連接建立之后,雙方開始通過 read 和 write 函數(shù)來讀寫數(shù)據(jù)律胀,就像往一個(gè)文件流里面寫東西一樣宋光。
listen 時(shí)候參數(shù) backlog 的意義?
Linux內(nèi)核中會(huì)維護(hù)兩個(gè)隊(duì)列:
未完成連接隊(duì)列(SYN 隊(duì)列):接收到一個(gè) SYN 建立連接請(qǐng)求炭菌,處于 SYN_RCVD 狀態(tài)罪佳;
已完成連接隊(duì)列(Accpet 隊(duì)列):已完成 TCP 三次握手過程,處于 ESTABLISHED 狀態(tài)黑低;
int listen (int socketfd, int backlog)
參數(shù)一 socketfd 為 socketfd 文件描述符
參數(shù)二 backlog赘艳,這參數(shù)在歷史有一定的變化
在早期 Linux 內(nèi)核 backlog 是 SYN 隊(duì)列大小,也就是未完成的隊(duì)列大小克握。
在 Linux 內(nèi)核 2.2 之后蕾管,backlog 變成 accept 隊(duì)列,也就是已完成連接建立的隊(duì)列長(zhǎng)度菩暗,所以現(xiàn)在通常認(rèn)為 backlog 是 accept 隊(duì)列掰曾。
accept 發(fā)送在三次握手的哪一步?
我們先看看客戶端連接服務(wù)端時(shí)停团,發(fā)送了什么旷坦?
客戶端的協(xié)議棧向服務(wù)器端發(fā)送了 SYN 包掏熬,并告訴服務(wù)器端當(dāng)前發(fā)送序列號(hào) client_isn,客戶端進(jìn)入 SYNC_SENT 狀態(tài)秒梅;
服務(wù)器端的協(xié)議棧收到這個(gè)包之后旗芬,和客戶端進(jìn)行 ACK 應(yīng)答,應(yīng)答的值為 client_isn+1捆蜀,表示對(duì) SYN 包 client_isn 的確認(rèn)疮丛,同時(shí)服務(wù)器也發(fā)送一個(gè) SYN 包,告訴客戶端當(dāng)前我的發(fā)送序列號(hào)為 server_isn漱办,服務(wù)器端進(jìn)入 SYNC_RCVD 狀態(tài)这刷;
客戶端協(xié)議棧收到 ACK 之后,使得應(yīng)用程序從
connect
調(diào)用返回娩井,表示客戶端到服務(wù)器端的單向連接建立成功暇屋,客戶端的狀態(tài)為 ESTABLISHED,同時(shí)客戶端協(xié)議棧也會(huì)對(duì)服務(wù)器端的 SYN 包進(jìn)行應(yīng)答洞辣,應(yīng)答數(shù)據(jù)為 server_isn+1咐刨;應(yīng)答包到達(dá)服務(wù)器端后,服務(wù)器端協(xié)議棧使得
accept
阻塞調(diào)用返回扬霜,這個(gè)時(shí)候服務(wù)器端到客戶端的單向連接也建立成功定鸟,服務(wù)器端也進(jìn)入 ESTABLISHED 狀態(tài)。
從上面的描述過程著瓶,我們可以得知客戶端 connect 成功返回是在第二次握手联予,服務(wù)端 accept 成功返回是在三次握手成功之后。
客戶端調(diào)用 close 了材原,連接是斷開的流程是什么沸久?
我們看看客戶端主動(dòng)調(diào)用了 close
,會(huì)發(fā)生什么余蟹?
客戶端調(diào)用
close
卷胯,表明客戶端沒有數(shù)據(jù)需要發(fā)送了,則此時(shí)會(huì)向服務(wù)端發(fā)送 FIN 報(bào)文威酒,進(jìn)入 FIN_WAIT_1 狀態(tài)窑睁;服務(wù)端接收到了 FIN 報(bào)文,TCP 協(xié)議棧會(huì)為 FIN 包插入一個(gè)文件結(jié)束符
EOF
到接收緩沖區(qū)中葵孤,應(yīng)用程序可以通過read
調(diào)用來感知這個(gè) FIN 包担钮。這個(gè)EOF
會(huì)被放在已排隊(duì)等候的其他已接收的數(shù)據(jù)之后,這就意味著服務(wù)端需要處理這種異常情況佛呻,因?yàn)?EOF 表示在該連接上再無額外數(shù)據(jù)到達(dá)裳朋。此時(shí),服務(wù)端進(jìn)入 CLOSE_WAIT 狀態(tài);接著鲤嫡,當(dāng)處理完數(shù)據(jù)后送挑,自然就會(huì)讀到
EOF
,于是也調(diào)用close
關(guān)閉它的套接字暖眼,這會(huì)使得會(huì)發(fā)出一個(gè) FIN 包惕耕,之后處于 LAST_ACK 狀態(tài);客戶端接收到服務(wù)端的 FIN 包诫肠,并發(fā)送 ACK 確認(rèn)包給服務(wù)端司澎,此時(shí)客戶端將進(jìn)入 TIME_WAIT 狀態(tài);
服務(wù)端收到 ACK 確認(rèn)包后栋豫,就進(jìn)入了最后的 CLOSE 狀態(tài)挤安;
客戶端進(jìn)過
2MSL
時(shí)間之后,也進(jìn)入 CLOSED 狀態(tài)丧鸯;
巨人的肩膀
[1] 趣談網(wǎng)絡(luò)協(xié)議專欄.劉超.極客時(shí)間.
[2] 網(wǎng)絡(luò)編程實(shí)戰(zhàn)專欄.盛延敏.極客時(shí)間.
[3] 計(jì)算機(jī)網(wǎng)絡(luò)-自頂向下方法.陳鳴 譯.機(jī)械工業(yè)出版社
[4] TCP/IP詳解 卷1:協(xié)議.范建華 譯.機(jī)械工業(yè)出版社
[5] 圖解TCP/IP.竹下隆史.人民郵電出版社
[6] https://www.rfc-editor.org/rfc/rfc793.html
[7] https://draveness.me/whys-the-design-tcp-three-way-handshake