傳輸控制協(xié)議(TCP,Transmission Control Protocol)是一種面向連接的、可靠的、基于字節(jié)流的傳輸層通信協(xié)議
基于TCP應(yīng)用層協(xié)議
- HTTP
- HTTPS
- SSH
- Telnet
- FTP
- SMTP
TCP協(xié)議段格式
- 源/目的端口號(hào): 表示數(shù)據(jù)是從哪個(gè)進(jìn)程來, 到哪個(gè)進(jìn)程去
- 32位序號(hào)/確認(rèn)序號(hào):TCP的確認(rèn)應(yīng)答機(jī)制要使用到的字段,保證TCP的可靠性
- 4位首部長度:表示TCP報(bào)頭的長度,以4字節(jié)為單位
- 6位保留字段:暫時(shí)未使用的6個(gè)比特位,以后可能會(huì)擴(kuò)用
- 16位窗口大?。篢CP的流量控制機(jī)制要使用字段,保證TCP的可靠性。
- 16位校驗(yàn)和:發(fā)送端填充, CRC校驗(yàn)(CRC,循環(huán)冗余校驗(yàn)碼). 接收端校驗(yàn)不通過, 則認(rèn)為數(shù)據(jù)有問題. 此處的檢驗(yàn)和不光包含TCP首部, 也包含TCP數(shù)據(jù)部分
- 16位緊急指針:標(biāo)識(shí)哪部分?jǐn)?shù)據(jù)是緊急數(shù)據(jù)
- TCP報(bào)頭當(dāng)中允許攜帶額外的選項(xiàng)字段,最多40字節(jié)
- 6位標(biāo)志位
-
UGR(URGent,緊急)緊急指針是否有效
-
ACK(ACKnowlegment,確認(rèn)) 確認(rèn)序號(hào)是否有效
-
PSH(PuSH,推送) 提示接收端應(yīng)用程序立刻把數(shù)據(jù)從接收緩沖區(qū)里讀走
-
RST(ReSeT,復(fù)位) 對(duì)方要求重新建立連接,我們稱攜帶RST標(biāo)識(shí)的為復(fù)位報(bào)文段
-
SYN(SYNchronization,同步)請(qǐng)求建立連接,我們稱攜帶SYN標(biāo)識(shí)的為同步報(bào)文段
-
FIN(FINish,終止)通知對(duì)方,本端要關(guān)閉了,我們稱攜帶FIN標(biāo)識(shí)的為結(jié)束報(bào)文段
TCP如何將報(bào)頭與有效載荷進(jìn)行分離?
-
在UDP中,使用的是8個(gè)字節(jié)的定長報(bào)頭,在讀取完報(bào)頭的8個(gè)字節(jié)后,剩下的就是有效載荷。而在TCP中,由于多加了“選項(xiàng)”這個(gè)字段,并不能像UDP那樣直接讀取。
在“選項(xiàng)”這個(gè)字段之前的報(bào)頭是固定20字節(jié),利用其中的4位首部長度,就可以求出報(bào)頭的總大小 size,再拿size - 20 就能得到選項(xiàng)字段的大小。再讀取這部分大小的內(nèi)容后,剩下的就是有效載荷。
TCP報(bào)頭中,4位首部長度描述的基本單位是4個(gè)字節(jié),4位首部不帶單位時(shí)的取值范圍是 0000 ~ 1111 (二進(jìn)制),轉(zhuǎn)化為十進(jìn)制后是0 ~ 15
由于基本單位是4個(gè)字節(jié),所以取值范圍是0 ~ 60。因?yàn)門CP報(bào)頭的最大長度為60 字節(jié),而“選項(xiàng)”之前的報(bào)頭占了20字節(jié),所以報(bào)頭中“選項(xiàng)”字段的長度最多為40字節(jié)
假如現(xiàn)在“選項(xiàng)”大小為0字節(jié),那么4位首部長度的值就應(yīng)該為 20 / 4 = 5,也就說0101(二進(jìn)制)
TCP如何決定將有效載荷交付給上層的哪一個(gè)協(xié)議?
這里與UDP的決策一樣:因?yàn)閼?yīng)用層的每個(gè)網(wǎng)絡(luò)進(jìn)程都必須綁定端口號(hào),服務(wù)端進(jìn)程顯示綁定端口號(hào),客戶端進(jìn)程由系統(tǒng)動(dòng)態(tài)綁定端口號(hào),TCP報(bào)文中包含了目的端口號(hào),就可以找出對(duì)應(yīng)的應(yīng)用層進(jìn)程(端口號(hào)和進(jìn)程ID采用哈希映射的方法),進(jìn)而把有效載荷交給應(yīng)用層進(jìn)行處理。
談?wù)勈裁词?“可靠” 和 “不可靠”
我們經(jīng)常說UDP協(xié)議是一種不可靠的傳輸協(xié)議,而TCP協(xié)議是可靠的傳輸協(xié)議。
所謂的不可靠指的是,由于傳輸距離的問題,雙方在傳輸數(shù)據(jù)時(shí),數(shù)據(jù)包可能出現(xiàn)丟包、亂序、重復(fù)、校驗(yàn)失敗、發(fā)送太快/太慢,網(wǎng)絡(luò)出現(xiàn)異常造成的一系列情況等問題。這也是UDP協(xié)議在擁有簡單、快速這些特點(diǎn)的同時(shí),對(duì)應(yīng)的不足的地方。
而TCP協(xié)議就是為了解決傳輸不可靠的問題,因此TCP一定會(huì)比UDP復(fù)雜。
TCP雖然復(fù)雜,但是效率不一定比UDP低,TCP當(dāng)中不僅有保證可靠性的機(jī)制,還有保證傳輸性能的各種機(jī)制。
UDP之間的優(yōu)點(diǎn)和缺點(diǎn), 不能簡單, 絕對(duì)的進(jìn)行比較
- TCP用于可靠傳輸?shù)那闆r, 應(yīng)用于文件傳輸, 重要狀態(tài)更新等場景;
- UDP用于對(duì)高速傳輸和實(shí)時(shí)性要求較高的通信領(lǐng)域, 例如, 早期的QQ, 視頻傳輸?shù)? 另外UDP可以用于廣播;
歸根結(jié)底, TCP和UDP都是程序員的工具, 什么時(shí)機(jī)用, 具體怎么用, 還是要根據(jù)具體的需求場景去判定。
TCP協(xié)議段——序號(hào)與確認(rèn)序號(hào)
什么是真正的可靠?
在進(jìn)行網(wǎng)絡(luò)通信時(shí),一方發(fā)出數(shù)據(jù)后,它不能保證另一方一定能成功接收到數(shù)據(jù),數(shù)據(jù)在傳輸過程中可能會(huì)出現(xiàn)各種錯(cuò)誤,只有對(duì)方發(fā)來響應(yīng),該主機(jī)才能保證上次發(fā)出的消息被對(duì)方成功接收到了,這是可靠性的體現(xiàn)。
圖中的實(shí)線代表該數(shù)據(jù)能被對(duì)方可靠地接收到,虛線代表對(duì)方不一定能接收到。
但是TCP要保證的是通信雙方的可靠性,此時(shí)主機(jī)A發(fā)給主機(jī)B的消息要得到響應(yīng)(表示主機(jī)A發(fā)的消息被主機(jī)B成功接收了),假設(shè)主機(jī)A也確實(shí)收到了主機(jī)B的響應(yīng),保證了主機(jī)A的發(fā)消息的可靠性。但是為了保證雙方的可靠性,主機(jī)B給主機(jī)A發(fā)的消息,主機(jī)B也要收到對(duì)應(yīng)的響應(yīng),表示主機(jī)B發(fā)送的消息成功被主機(jī)A收到了。主機(jī)A給主機(jī)B響應(yīng)后,主機(jī)A也要知道上一條發(fā)的消息是否被接收了,于是就陷入了死循環(huán)…
因?yàn)橹挥挟?dāng)一端收到對(duì)方的響應(yīng)消息后,才能保證自己上一次發(fā)送的數(shù)據(jù)被可靠地接收到了,但通信雙方總有一條消息是最新的消息,因此無法保證百分百的可靠性。
所以從嚴(yán)格意義上來講,網(wǎng)絡(luò)上的通信是做不到百分百的可靠性的,總有一條最新的消息得不到響應(yīng),但是實(shí)際上就沒有必要保證所有消息的可靠性,我們只要保證通信雙方通信時(shí)發(fā)送的每一個(gè)核心數(shù)據(jù)都有對(duì)應(yīng)的響應(yīng)就可以了。而對(duì)于一些無關(guān)緊要的數(shù)據(jù)(如響應(yīng)數(shù)據(jù)),我們沒有必要保證它的可靠性。因?yàn)閷?duì)端如果沒有收到這個(gè)響應(yīng)數(shù)據(jù),會(huì)判定上一次發(fā)送的報(bào)文丟失了,此時(shí)對(duì)端可以將上一次發(fā)送的數(shù)據(jù)進(jìn)行重傳。
這種策略在TCP當(dāng)中叫做確認(rèn)應(yīng)答機(jī)制。需要注意的是,確認(rèn)應(yīng)答機(jī)制不是保證雙方通信全部消息的可靠性,而是只要一方收到了另一方的應(yīng)答消息,就說明它上一次發(fā)送的數(shù)據(jù)被另一方可靠的接收到了。
32位序號(hào)
雙方在進(jìn)行通信時(shí),只有發(fā)送的數(shù)據(jù)被響應(yīng)了,才能發(fā)下一條數(shù)據(jù),這種串行的通信效率是不夠高的。
實(shí)際上雙方在進(jìn)行網(wǎng)絡(luò)通信時(shí),允許一方向另一方發(fā)送多條數(shù)據(jù),主要保證發(fā)送的每個(gè)報(bào)文都能得到對(duì)應(yīng)的響應(yīng)就可以了,此時(shí)也就能保證這些報(bào)文被對(duì)方收到了。
但是在連續(xù)發(fā)送報(bào)文時(shí),可能傳輸原因,主機(jī)B收到的報(bào)文的先后順序和主機(jī)A發(fā)送報(bào)文的先后順序可能不一致。但是保證報(bào)文有序也是可靠性的一種,因此TCP報(bào)頭中的32位序號(hào)的作用就是用來保證報(bào)文的有序性的。
TCP是面向字節(jié)流傳輸?shù)膮f(xié)議,它將發(fā)送出去的每個(gè)字節(jié)數(shù)據(jù)都進(jìn)行了編號(hào),即為序號(hào)。
-
比如現(xiàn)在發(fā)送端要發(fā)送3000字節(jié)的數(shù)據(jù),每次發(fā)送1000字節(jié),那么就需要三個(gè)TCP報(bào)頭來發(fā)送這3000字節(jié)數(shù)據(jù)。
-
此時(shí)這三個(gè)TCP報(bào)頭中的32位序號(hào)填的就是發(fā)送數(shù)據(jù)中首個(gè)字節(jié)的序列號(hào)。分別填的是1、1001、2001
-
接收端收到了這三個(gè)TCP報(bào)頭后,就可以根據(jù)TCP報(bào)頭當(dāng)中的32位序號(hào)對(duì)這三個(gè)報(bào)文進(jìn)行順序重排,重排后將其放在TCP緩沖區(qū)當(dāng)中,此時(shí)接收到的報(bào)文就跟發(fā)送的報(bào)文順序是一致的了。
32位確認(rèn)序號(hào)
TCP報(bào)頭中的32位確認(rèn)序號(hào),是由接收方告訴發(fā)送方,我當(dāng)前已經(jīng)收到了那些數(shù)據(jù),你下一次該從哪里開始發(fā)
比如,當(dāng)主機(jī)B收到主機(jī)A發(fā)送過來的報(bào)文時(shí),從中解析出32位序號(hào)的值為1。由于該報(bào)文中含有1000個(gè)字節(jié)的數(shù)據(jù),因此主機(jī)B已經(jīng)收到1-1000字節(jié)的數(shù)據(jù),于是主機(jī)B發(fā)給主機(jī)A的響應(yīng)數(shù)據(jù)的報(bào)頭中32位確認(rèn)序號(hào)的的值就是1001
- 一方面是告訴主機(jī)A,序號(hào)1001之前的數(shù)據(jù)我已經(jīng)接收到了。
- 另一方面是告訴主機(jī)A,下次發(fā)送的數(shù)據(jù)應(yīng)該從1001字節(jié)開始發(fā)送。
以此類推,主機(jī)A再次發(fā)送1001-2000字節(jié)的數(shù)據(jù)時(shí),主機(jī)B的響應(yīng)數(shù)據(jù)報(bào)文里的確認(rèn)序號(hào)值為2001
注意:發(fā)送的都是完整的TCP報(bào)文,雙方會(huì)從報(bào)文中解析出相應(yīng)的數(shù)據(jù)。
報(bào)文丟失的情況
如果這三個(gè)報(bào)文在網(wǎng)絡(luò)傳輸?shù)倪^程中出現(xiàn)了丟包的情況,比如只有序號(hào)為1和2001的報(bào)文被主機(jī)B接收到了,那么當(dāng)主機(jī)B對(duì)報(bào)文進(jìn)行順序重排時(shí),就會(huì)發(fā)現(xiàn)只收到了1-1000字節(jié)和2001-3000字節(jié)的數(shù)據(jù),1001-2000字節(jié)的數(shù)據(jù)丟失了,此時(shí)主機(jī)B在給主機(jī)A進(jìn)行響應(yīng)時(shí),其響應(yīng)數(shù)據(jù)的TCP報(bào)文中的確認(rèn)序號(hào)填的值為1001,告訴主機(jī)A下次向我發(fā)送時(shí)發(fā)送的數(shù)據(jù)應(yīng)該從序號(hào)1001的字節(jié)數(shù)據(jù)開始發(fā)送。
- 當(dāng)主機(jī)B直接收到2001-3000字節(jié)的數(shù)據(jù)時(shí),此時(shí)因?yàn)?001-2000字節(jié)的數(shù)據(jù)之前是沒有收到的,響應(yīng)數(shù)據(jù)報(bào)文里的確認(rèn)序號(hào)不能寫成3001。因?yàn)?001字節(jié)是在3001字節(jié)的前面的,如果寫成3001,表示3001字節(jié)之前數(shù)據(jù)主機(jī)B都收到了。
- 所以主機(jī)B發(fā)送的報(bào)文確認(rèn)序號(hào)只能寫成1001,當(dāng)主機(jī)A收到這個(gè)響應(yīng)以后,就會(huì)判斷序號(hào)為1001字節(jié)的數(shù)據(jù)丟包了,此時(shí)主機(jī)A就可以選擇進(jìn)行數(shù)據(jù)重傳。
因此發(fā)送端可以根據(jù)對(duì)端發(fā)來的確認(rèn)序號(hào),來判斷自己發(fā)送的某個(gè)報(bào)文在傳輸過程是否丟失。
為什么TCP要使用兩套序號(hào)機(jī)制
如果通信雙方只是一端發(fā)送數(shù)據(jù),一端接收數(shù)據(jù),那么只需要一套序號(hào)就好了。
- 發(fā)送端在發(fā)送數(shù)據(jù)時(shí),將該序號(hào)看作是32位序號(hào)
- 接收端在對(duì)發(fā)送端發(fā)來的數(shù)據(jù)響應(yīng)時(shí),將該序號(hào)看作是32位確認(rèn)序號(hào)
但是由于TCP是全雙工的,雙方是可以同時(shí)發(fā)送消息和接收消息的。
- 雙方發(fā)出去的報(bào)文中,不僅需要填充32位序號(hào)來表明自己當(dāng)前發(fā)出去的數(shù)據(jù)序號(hào)
- 還需要填充32位確認(rèn)序號(hào),對(duì)對(duì)方上一次發(fā)送的數(shù)據(jù)進(jìn)行確認(rèn),告訴對(duì)方下一次應(yīng)該從哪一個(gè)字節(jié)處開始發(fā)送。
因此在進(jìn)行TCP通信時(shí),雙方都需要有確認(rèn)應(yīng)答機(jī)制,此時(shí)一套序號(hào)就無法滿足要求了,因此TCP報(bào)頭中出現(xiàn)了兩套序號(hào)。
總結(jié):
- 32位序號(hào)的作用是,保證數(shù)據(jù)的按序到達(dá),同時(shí)這個(gè)序號(hào)也是作為對(duì)端發(fā)送報(bào)文時(shí)填充32位確認(rèn)序號(hào)的根據(jù)。
- 32位確認(rèn)序號(hào)的作用是,告訴對(duì)端當(dāng)前收到的字節(jié)數(shù)據(jù)有那些,對(duì)端下一次發(fā)送數(shù)據(jù)時(shí)應(yīng)該從哪一字節(jié)序號(hào)開始進(jìn)行發(fā)送
- 序號(hào)和確認(rèn)序號(hào)是確認(rèn)應(yīng)答機(jī)制的數(shù)據(jù)化表示,確認(rèn)應(yīng)答機(jī)制就是由序號(hào)和確認(rèn)序號(hào)來保證的
- 此外,通過序號(hào)和確認(rèn)序號(hào)還可以判斷某個(gè)報(bào)文是否丟失
TCP協(xié)議段——窗口大小
TCP的接收緩沖區(qū)和發(fā)送緩沖區(qū)
TCP本身是具有接收緩沖區(qū)和發(fā)送緩沖區(qū)的:
- 接收緩沖區(qū)用來暫時(shí)保存接收到的數(shù)據(jù)。
-
發(fā)送緩沖區(qū)用來暫時(shí)保存還未發(fā)送的數(shù)據(jù)。
-
這兩個(gè)緩沖區(qū)都是在TCP傳輸層內(nèi)部實(shí)現(xiàn)的
-
TCP發(fā)送緩沖區(qū)當(dāng)中的數(shù)據(jù)由上層應(yīng)用應(yīng)用層進(jìn)行寫入。當(dāng)上層調(diào)用write/send這樣的系統(tǒng)調(diào)用接口時(shí),實(shí)際不是將數(shù)據(jù)直接發(fā)送到了網(wǎng)絡(luò)當(dāng)中,而是將數(shù)據(jù)從應(yīng)用層拷貝到了TCP的發(fā)送緩沖區(qū)當(dāng)中。
-
TCP接收緩沖區(qū)當(dāng)中的數(shù)據(jù)最終也是由應(yīng)用層來讀取的。當(dāng)上層調(diào)用read/recv這樣的系統(tǒng)調(diào)用接口時(shí),實(shí)際也不是直接從網(wǎng)絡(luò)當(dāng)中讀取數(shù)據(jù),而是將數(shù)據(jù)從TCP的接收緩沖區(qū)拷貝到了應(yīng)用層而已。
當(dāng)數(shù)據(jù)寫入到TCP的發(fā)送緩沖區(qū)后,對(duì)應(yīng)的write/send函數(shù)就可以返回了,至于發(fā)送緩沖區(qū)當(dāng)中的數(shù)據(jù)具體什么時(shí)候發(fā),怎么發(fā)等問題實(shí)際都是由TCP決定的
我們之所以稱TCP為傳輸層控制協(xié)議,就是因?yàn)樽罱K數(shù)據(jù)的發(fā)送和接收方式,以及傳輸數(shù)據(jù)時(shí)遇到的各種問題應(yīng)該如何解決,都是由TCP自己決定的,用戶只需要將數(shù)據(jù)拷貝到TCP的發(fā)送緩沖區(qū),以及從TCP的接收緩沖區(qū)當(dāng)中讀取數(shù)據(jù)即可。
需要注意的是,通信雙方的TCP層都是一樣的,因此通信雙方的TCP層都是既有發(fā)送緩沖區(qū)又有接收緩沖區(qū)。
TCP的發(fā)送緩沖區(qū)和接收緩沖區(qū)存在的意義
發(fā)送緩沖區(qū)和接收緩沖區(qū)的作用:
- 數(shù)據(jù)在網(wǎng)絡(luò)中傳輸時(shí)可能會(huì)出現(xiàn)某些錯(cuò)誤,此時(shí)就可能要求發(fā)送端進(jìn)行數(shù)據(jù)重傳,因此TCP必須提供一個(gè)發(fā)送緩沖區(qū)來暫時(shí)保存發(fā)送出去的數(shù)據(jù),以免需要進(jìn)行數(shù)據(jù)重傳。只有當(dāng)發(fā)出去的數(shù)據(jù)被對(duì)端可靠的收到后,發(fā)送緩沖區(qū)中的這部分?jǐn)?shù)據(jù)才可以被覆蓋掉。
- 接收端處理數(shù)據(jù)的速度是有限的,為了保證沒來得及處理的數(shù)據(jù)不會(huì)被迫丟棄,因此TCP必須提供一個(gè)接收緩沖區(qū)來暫時(shí)保存未被處理的數(shù)據(jù),因?yàn)閿?shù)據(jù)傳輸是需要耗費(fèi)資源的,我們不能隨意丟棄正確的報(bào)文。此外,TCP的數(shù)據(jù)重排也是在接收緩沖區(qū)當(dāng)中進(jìn)行的。
窗口大小
當(dāng)發(fā)送端要將數(shù)據(jù)發(fā)送給對(duì)端時(shí),本質(zhì)是把自己發(fā)送緩沖區(qū)當(dāng)中的數(shù)據(jù)發(fā)送到對(duì)端的接收緩沖區(qū)當(dāng)中。但緩沖區(qū)是有大小的,如果接收端處理數(shù)據(jù)的速度小于發(fā)送端發(fā)送數(shù)據(jù)的速度,那么總有一個(gè)時(shí)刻接收端的接收緩沖區(qū)會(huì)被打滿,這時(shí)發(fā)送端再發(fā)送數(shù)據(jù)過來就會(huì)造成數(shù)據(jù)丟包,進(jìn)而引起丟包重傳等一系列的連鎖反應(yīng)。
因此TCP報(bào)頭當(dāng)中就有了16位的窗口大小,這個(gè)16位窗口大小當(dāng)中填的是自身接收緩沖區(qū)中剩余空間的大小,也就是當(dāng)前主機(jī)接收數(shù)據(jù)的能力。
接收端在對(duì)發(fā)送端發(fā)來的數(shù)據(jù)進(jìn)行響應(yīng)時(shí),就可以通過16位窗口大小告知發(fā)送端自己當(dāng)前接收緩沖區(qū)剩余空間的大小,此時(shí)發(fā)送端就可以根據(jù)這個(gè)窗口大小字段來調(diào)整自己發(fā)送數(shù)據(jù)的速度。
- 窗口大小字段越大,說明接收端接收數(shù)據(jù)的能力越強(qiáng),此時(shí)發(fā)送端可以提高發(fā)送數(shù)據(jù)的速度。
- 窗口大小字段越小,說明接收端接收數(shù)據(jù)的能力越弱,此時(shí)發(fā)送端可以減小發(fā)送數(shù)據(jù)的速度。
- 如果窗口大小的值為0,說明接收端接收緩沖區(qū)已經(jīng)被打滿了,此時(shí)發(fā)送端就不應(yīng)該再發(fā)送數(shù)據(jù)了。
TCP協(xié)議段 —— 六個(gè)標(biāo)志位
為什么會(huì)存在標(biāo)志位?
- TCP報(bào)文的種類多種多樣,除了正常通信時(shí)發(fā)送的普通報(bào)文,還有建立連接時(shí)發(fā)送的請(qǐng)求建立連接的報(bào)文,以及斷開連接時(shí)發(fā)送的斷開連接的報(bào)文等等。
- 收到不同種類的報(bào)文時(shí)完美需要對(duì)應(yīng)執(zhí)行動(dòng)作,比如正常通信的報(bào)文需要放到接收緩沖區(qū)當(dāng)中等待上層應(yīng)用進(jìn)行讀取,而建立和斷開連接的報(bào)文本質(zhì)不是交給用戶處理的,而是需要讓操作系統(tǒng)在TCP層執(zhí)行對(duì)應(yīng)的握手和揮手動(dòng)作。
- 也就是說不同種類的報(bào)文對(duì)應(yīng)的是不同的處理邏輯,所以我們要能夠區(qū)分報(bào)文的種類。而TCP就是使用報(bào)頭當(dāng)中的六個(gè)標(biāo)志字段來進(jìn)行區(qū)分的,這六個(gè)標(biāo)志位都只占用一個(gè)比特位,為0表示假,為1表示真。
SYN
報(bào)文當(dāng)中的SYN被設(shè)置為1,表明該報(bào)文是一個(gè)連接建立的請(qǐng)求報(bào)文。
只有在連接建立階段,SYN才被設(shè)置,正常通信時(shí)SYN不會(huì)被設(shè)置。
ACK
報(bào)文當(dāng)中的ACK被設(shè)置為1,表明該報(bào)文可以對(duì)收到的報(bào)文進(jìn)行確認(rèn)。
一般除了第一個(gè)請(qǐng)求報(bào)文沒有設(shè)置ACK以外,其余報(bào)文基本都會(huì)設(shè)置ACK,因?yàn)榘l(fā)送出去的數(shù)據(jù)本身就對(duì)對(duì)方發(fā)送過來的數(shù)據(jù)具有一定的確認(rèn)能力,因此雙方在進(jìn)行數(shù)據(jù)通信時(shí),可以順便對(duì)對(duì)方上一次發(fā)送的數(shù)據(jù)進(jìn)行響應(yīng)。
FIN
報(bào)文當(dāng)中的FIN被設(shè)置為1,表明該報(bào)文是一個(gè)連接斷開的請(qǐng)求報(bào)文。
只有在斷開連接階段,F(xiàn)IN才被設(shè)置,正常通信時(shí)FIN不會(huì)被設(shè)置
URG
雙方在進(jìn)行網(wǎng)絡(luò)通信的時(shí)候,由于TCP是保證數(shù)據(jù)按序到達(dá)的,即便發(fā)送端將要發(fā)送的數(shù)據(jù)分成了若干個(gè)TCP報(bào)文進(jìn)行發(fā)送,最終到達(dá)接收端時(shí)這些數(shù)據(jù)也都是有序的,因?yàn)門CP可以通過序號(hào)來對(duì)這些TCP報(bào)文進(jìn)行順序重排,最終就能保證數(shù)據(jù)到達(dá)對(duì)端接收緩沖區(qū)中時(shí)是有序的。
TCP按序到達(dá)本身也是我們的目的,此時(shí)對(duì)端上層在從接收緩沖區(qū)讀取數(shù)據(jù)時(shí)也必須是按順序讀取的。但是有時(shí)候發(fā)送端可能發(fā)送了一些“緊急數(shù)據(jù)”,這些數(shù)據(jù)需要讓對(duì)方上層提取進(jìn)行讀取,此時(shí)應(yīng)該怎么辦呢?
此時(shí)就需要用到URG標(biāo)志位,以及TCP報(bào)頭當(dāng)中的16位緊急指針。
當(dāng)URG標(biāo)志位被設(shè)置為1時(shí),需要通過TCP報(bào)頭當(dāng)中的16位緊急指針來找到緊急數(shù)據(jù),否則一般情況下不需要關(guān)注TCP報(bào)頭當(dāng)中的16位緊急指針。
16位緊急指針代表的就是緊急數(shù)據(jù)在報(bào)文中的偏移量。
因?yàn)榫o急指針只有一個(gè),它只能標(biāo)識(shí)數(shù)據(jù)段中的一個(gè)位置,因此緊急數(shù)據(jù)只能發(fā)送一個(gè)字節(jié),而至于這一個(gè)字節(jié)的具體含義這里就不展開討論了。
PSH
報(bào)文當(dāng)中的PSH被設(shè)置為1,是在告訴對(duì)方盡快將你的接收緩沖區(qū)當(dāng)中的數(shù)據(jù)交付給上層。
RST
報(bào)文當(dāng)中的RST被設(shè)置為1,表示需要讓對(duì)方重新建立連接。
在通信雙方在連接未建立好的情況下,一方向另一方發(fā)數(shù)據(jù),此時(shí)另一方發(fā)送的響應(yīng)報(bào)文當(dāng)中的RST標(biāo)志位就會(huì)被置1,表示要求對(duì)方重新建立連接。
在雙方建立好連接進(jìn)行正常通信時(shí),如果通信中途發(fā)現(xiàn)之前建立好的連接出現(xiàn)了異常也會(huì)要求重新建立連接。
確認(rèn)應(yīng)答機(jī)制(ACK)
TCP保證可靠性的機(jī)制之一就是確認(rèn)應(yīng)答機(jī)制。
確認(rèn)應(yīng)答機(jī)制就是由TCP報(bào)頭當(dāng)中的,32位序號(hào)和32位確認(rèn)序號(hào)來保證的。需要再次強(qiáng)調(diào)的是,確認(rèn)應(yīng)答機(jī)制不是保證雙方通信的全部消息的可靠性,而是通過收到對(duì)方的應(yīng)答消息,來保證自己曾經(jīng)發(fā)送給對(duì)方的某一條消息被對(duì)方可靠的收到了。
TCP將每個(gè)字節(jié)的數(shù)據(jù)都進(jìn)行了編號(hào). 即為序列號(hào)
每一個(gè)ACK都帶有對(duì)應(yīng)的確認(rèn)序列號(hào), 意思是告訴發(fā)送者, 我已經(jīng)收到了哪些數(shù)據(jù); 下一次你從哪里開始發(fā).
超時(shí)重傳機(jī)制
主機(jī)A發(fā)送數(shù)據(jù)給B之后, 可能因?yàn)榫W(wǎng)絡(luò)擁堵等原因, 數(shù)據(jù)無法到達(dá)主機(jī)B;
如果主機(jī)A在一個(gè)特定時(shí)間間隔內(nèi)沒有收到B發(fā)來的確認(rèn)應(yīng)答, 就會(huì)進(jìn)行重發(fā);
但是, 主機(jī)A未收到B發(fā)來的確認(rèn)應(yīng)答, 也可能是因?yàn)锳CK丟失了;
因此主機(jī)B會(huì)收到很多重復(fù)數(shù)據(jù). 那么TCP協(xié)議需要能夠識(shí)別出那些包是重復(fù)的包, 并且把重復(fù)的丟棄掉.
這時(shí)候我們可以利用前面提到的序列號(hào), 就可以很容易做到去重的效果.
那么, 如果超時(shí)的時(shí)間如何確定?
-
最理想的情況下, 找到一個(gè)最小的時(shí)間, 保證 “確認(rèn)應(yīng)答一定能在這個(gè)時(shí)間內(nèi)返回”.
-
但是這個(gè)時(shí)間的長短, 隨著網(wǎng)絡(luò)環(huán)境的不同, 是有差異的.
-
如果超時(shí)時(shí)間設(shè)的太長, 會(huì)影響整體的重傳效率;
-
如果超時(shí)時(shí)間設(shè)的太短, 有可能會(huì)頻繁發(fā)送重復(fù)的包;
TCP為了保證無論在任何環(huán)境下都能比較高性能的通信, 因此會(huì)動(dòng)態(tài)計(jì)算這個(gè)最大超時(shí)時(shí)間
-
Linux中(BSD Unix和Windows也是如此), 超時(shí)以500ms為一個(gè)單位進(jìn)行控制, 每次判定超時(shí)重發(fā)的超時(shí)
時(shí)間都是500ms的整數(shù)倍. -
如果重發(fā)一次之后, 仍然得不到應(yīng)答, 等待 2*500ms 后再進(jìn)行重傳.
-
如果仍然得不到應(yīng)答, 等待 4*500ms 進(jìn)行重傳. 依次類推, 以指數(shù)形式遞增.
-
累計(jì)到一定的重傳次數(shù), TCP認(rèn)為網(wǎng)絡(luò)或者對(duì)端主機(jī)出現(xiàn)異常, 強(qiáng)制關(guān)閉連接
連接管理機(jī)制
TCP是面向連接的
TCP的各種可靠性機(jī)制實(shí)際都不是從主機(jī)到主機(jī)的,而是基于連接的,與連接是強(qiáng)相關(guān)的。比如一臺(tái)服務(wù)器啟動(dòng)后可能有多個(gè)客戶端前來訪問,如果TCP不是基于連接的,也就意味著服務(wù)器端只有一個(gè)接收緩沖區(qū),此時(shí)各個(gè)客戶端發(fā)來的數(shù)據(jù)都會(huì)拷貝到這個(gè)接收緩沖區(qū)當(dāng)中,此時(shí)這些數(shù)據(jù)就可能會(huì)互相干擾。
而我們?cè)谶M(jìn)行TCP通信之前需要先建立連接,就是因?yàn)門CP的各種可靠性保證都是基于連接的,要保證傳輸數(shù)據(jù)的可靠性的前提就是先建立好連接。
操作系統(tǒng)對(duì)連接的管理
面向連接是TCP可靠性的一種,只有在通信建立好連接才會(huì)有各種可靠性的保證,而一臺(tái)機(jī)器上可能會(huì)存在大量的連接,此時(shí)操作系統(tǒng)就不得不對(duì)這些連接進(jìn)行管理。
- 操作系統(tǒng)在管理這些連接時(shí)需要“先描述,再組織”,在操作系統(tǒng)中一定有一個(gè)描述連接的結(jié)構(gòu)體,該結(jié)構(gòu)體當(dāng)中包含了連接的各種屬性字段,所有定義出來的連接結(jié)構(gòu)體最終都會(huì)以某種數(shù)據(jù)結(jié)構(gòu)組織起來,此時(shí)操作系統(tǒng)對(duì)連接的管理就變成了對(duì)該數(shù)據(jù)結(jié)構(gòu)的增刪查改。
- 建立連接,實(shí)際就是在操作系統(tǒng)中用該結(jié)構(gòu)體定義一個(gè)結(jié)構(gòu)體變量,然后填充連接的各種屬性字段,最后將其插入到管理連接的數(shù)據(jù)結(jié)構(gòu)當(dāng)中即可。
- 斷開連接,實(shí)際就是將某個(gè)連接從管理連接的數(shù)據(jù)結(jié)構(gòu)當(dāng)中刪除,釋放該連接曾經(jīng)占用的各種資源。
- 因此連接的管理也是有成本的,這個(gè)成本就是管理連接結(jié)構(gòu)體的時(shí)間成本,以及存儲(chǔ)連接結(jié)構(gòu)體的空間成本
TCP 的三次握手
三次握手的過程
雙方在進(jìn)行TCP通信之前需要先建立連接,建立連接的這個(gè)過程我們稱之為三次握手。
這里以服務(wù)器和客戶端為例,當(dāng)客戶端想要與服務(wù)器進(jìn)行通信時(shí),需要先于服務(wù)器建立連接,此時(shí)客戶端會(huì)主動(dòng)向服務(wù)器發(fā)送連接建立請(qǐng)求,然后雙方TCP在底層會(huì)自動(dòng)進(jìn)行三次握手
- 第一次握手:客戶端向服務(wù)器發(fā)送的報(bào)文當(dāng)中的SYN位置為1,表示請(qǐng)求服務(wù)器建立連接
- 第二次握手:服務(wù)器收到客戶端發(fā)來的連接請(qǐng)求報(bào)文后,緊接著向客戶端發(fā)起連接建立,并對(duì)客戶端發(fā)來的連接請(qǐng)求進(jìn)行響應(yīng),此時(shí)服務(wù)器向客戶端發(fā)的報(bào)文中SYN位和ACK位都被置為1
- 第三次握手:客戶端收到服務(wù)器發(fā)來的報(bào)文后,得知服務(wù)器收到了自己發(fā)送的連接請(qǐng)求,并請(qǐng)求和自己連接,最后客戶端再想服務(wù)器進(jìn)行響應(yīng)。
需要注意的是,客戶端向服務(wù)器發(fā)起的連接建立請(qǐng)求,是請(qǐng)求建立從客戶端到服務(wù)器方向的通信連接,而TCP是全雙工通信,因此服務(wù)器在收到客戶端發(fā)來的連接建立請(qǐng)求后,服務(wù)器也需要向客戶端發(fā)起連接建立請(qǐng)求,請(qǐng)求建立從服務(wù)器到客戶端方法的通信連接。
為什么是三次握手?兩次可以嗎?四次以上可以嗎?
首先我們需要知道,連接建立不是百分之百能成功的,通信雙方在進(jìn)行三次握手時(shí),其中前兩次握手能夠保證被對(duì)方收到,因?yàn)榍皟纱挝帐侄加袑?duì)應(yīng)的下一次握手對(duì)其進(jìn)行響應(yīng),但第三次握手是沒有對(duì)應(yīng)的響應(yīng)報(bào)文的,如果第三次握手時(shí)客戶端發(fā)送的ACK報(bào)文丟失了,那么連接建立就會(huì)失敗。
雖然客戶端發(fā)起第三次握手后就完成了三次握手,但服務(wù)器卻沒有收到客戶端發(fā)來的第三次握手,此時(shí)服務(wù)器端就不會(huì)建立對(duì)應(yīng)的連接。所以建立連接時(shí)不管采用幾次握手,最后一次握手的可靠性都是不能保證的。
既然連接的建立都不是百分之百成功的,因此建立連接時(shí)具體采用幾次握手的依據(jù),實(shí)際是看幾次握手時(shí)的優(yōu)點(diǎn)更多。
三次握手是驗(yàn)證雙方通信信道的最小次數(shù):
- 因?yàn)門CP是全雙工通信的,因此連接建立的核心要?jiǎng)?wù)實(shí)際是,驗(yàn)證雙方的通信信道是否是連通的。
- 而三次握手恰好是驗(yàn)證雙方通信信道的最小次數(shù),通過三次握手后雙方就都能知道自己和對(duì)方是否都能夠正常發(fā)送和接收數(shù)據(jù)。
- 在客戶端看來,當(dāng)它收到服務(wù)器發(fā)來第二次握手時(shí),說明自己發(fā)出的第一次握手被對(duì)方可靠的收到了,證明自己能發(fā)以及服務(wù)器能收,同時(shí)當(dāng)自己收到服務(wù)器發(fā)來的第二次握手時(shí),也就證明服務(wù)器能發(fā)以及自己能收,此時(shí)就證明自己和服務(wù)器都是能發(fā)能收的。
- 在服務(wù)器看來,當(dāng)它收到客戶端發(fā)來第一次握手時(shí),證明客戶端能發(fā)以及自己能收,而當(dāng)它收到客戶端發(fā)來的第三次握手時(shí),說明自己發(fā)出的第二次握手被對(duì)方可靠的收到了,也就證明自己能發(fā)以及客戶端能收,此時(shí)就證明自己和客戶端都是能發(fā)能收的。
- 既然三次握手已經(jīng)能夠驗(yàn)證雙方通信信道是否正常了,那么三次以上的握手當(dāng)然也是可以驗(yàn)證的,但既然三次已經(jīng)能驗(yàn)證了就沒有必要再進(jìn)行更多次的握手了。
三次握手能夠保證連接建立時(shí)的異常連接掛在客戶端:
- 當(dāng)客戶端收到服務(wù)器發(fā)來的第二次握手時(shí),客戶端就已經(jīng)證明雙方通信信道是連通的了,因此當(dāng)客戶端發(fā)出第三次握手后,這個(gè)連接就已經(jīng)在客戶端建立了。
- 而只有當(dāng)服務(wù)器收到客戶端發(fā)來的第三次握手后,服務(wù)器才知道雙方通信信道是連通的,此時(shí)在服務(wù)器端才會(huì)建立對(duì)應(yīng)的連接。
- 因此雙方在進(jìn)行三次握手建立連接時(shí),雙方建立連接的時(shí)間點(diǎn)是不一樣的。如果客戶端最后發(fā)出的第三次握手丟包了,此時(shí)在服務(wù)器端就不會(huì)建立對(duì)應(yīng)的連接,而在客戶端就需要短暫的維護(hù)一個(gè)異常的連接。
- 而維護(hù)連接是需要時(shí)間成本和空間成本的,因此三次握手還有一個(gè)好處就是能夠保證連接建立異常時(shí),這個(gè)異常連接是掛在客戶端的,而不會(huì)影響到服務(wù)器。
- 雖然此時(shí)客戶端也需要短暫維護(hù)這個(gè)異常,但客戶端的異常連接不會(huì)特別多,不像服務(wù)器,一旦多個(gè)客戶端建立連接時(shí)都建立失敗了,此時(shí)服務(wù)器端就需要耗費(fèi)大量資源來維護(hù)這些異常連接。
- 此外,建立連接失敗時(shí)的異常連接不會(huì)一直維護(hù)下去。如果服務(wù)器端長時(shí)間收不到客戶端發(fā)來的第三次握手,就會(huì)將第二次握手進(jìn)行超時(shí)重傳,此時(shí)客戶端就有機(jī)會(huì)重新發(fā)出第三次握手?;蛘弋?dāng)客戶端認(rèn)為連接建立好后向服務(wù)器發(fā)送數(shù)據(jù)時(shí),此時(shí)服務(wù)器會(huì)發(fā)現(xiàn)沒有和該客戶端建立連接而要求客戶端重新建立連接。
TCP三次握手的漏洞
在TCP的三次握手過程中,當(dāng)服務(wù)端收到客戶端的SYN連接請(qǐng)求后,會(huì)返回一個(gè)SYN-ACK確認(rèn),并且分配一定的資源等待客戶端的ACK確認(rèn)以完成握手。這個(gè)等待ACK的連接會(huì)在一定時(shí)間內(nèi)(通常60-75秒)保持在半開放(half-open)狀態(tài)。洪水攻擊就是利用這個(gè)機(jī)制,攻擊者發(fā)送大量偽造的SYN請(qǐng)求,迫使服務(wù)器分配大量的資源去處理這些不存在的客戶端確認(rèn),從而耗盡服務(wù)器的資源,使得正常的連接請(qǐng)求無法得到響應(yīng)。因?yàn)檫@些SYN請(qǐng)求通常偽造了源IP地址,所以服務(wù)器發(fā)出的SYN-ACK并不能得到正確的ACK確認(rèn),服務(wù)器就會(huì)在一段時(shí)間后關(guān)閉這個(gè)半開放的連接并釋放資源,但在此期間,正常的服務(wù)請(qǐng)求可能因?yàn)橘Y源不足而得不到處理。
解決方案:延緩TCB分配方法,無效鏈接監(jiān)視釋放,防火墻。
因此,這里給出兩個(gè)建立連接時(shí)采用三次握手的理由:
- 三次握手是驗(yàn)證雙方通信信道的最小次數(shù),能夠讓能建立的連接盡快建立起來。
- 三次握手能夠保證連接建立時(shí)的異常連接掛在客戶端(風(fēng)險(xiǎn)轉(zhuǎn)移)
三次握手時(shí)的狀態(tài)變化
三次握手時(shí)的狀態(tài)變化如下:
- 最開始時(shí)客戶端和服務(wù)器都處于CLOSED狀態(tài)。
- 服務(wù)器為了能夠接收客戶端發(fā)來的連接請(qǐng)求,需要由CLOSED狀態(tài)變?yōu)長ISTEN狀態(tài)。
- 此時(shí)客戶端就可以向服務(wù)器發(fā)起三次握手了,當(dāng)客戶端發(fā)起第一次握手后,狀態(tài)變?yōu)镾YN_SENT狀態(tài)。
- 處于LISTEN狀態(tài)的服務(wù)器收到客戶端的連接請(qǐng)求后,將該連接放入內(nèi)核等待隊(duì)列中,并向客戶端發(fā)起第二次握手,此時(shí)服務(wù)器的狀態(tài)變?yōu)镾YN_RCVD。
- 當(dāng)客戶端收到服務(wù)器發(fā)來的第二次握手后,緊接著向服務(wù)器發(fā)送最后一次握手,此時(shí)客戶端的連接已經(jīng)建立,狀態(tài)變?yōu)镋STABLISHED。
- 而服務(wù)器收到客戶端發(fā)來的最后一次握手后,連接也建立成功,此時(shí)服務(wù)器的狀態(tài)也變成ESTABLISHED。
至此三次握手結(jié)束,通信雙方可以開始進(jìn)行數(shù)據(jù)交互了。
套接字和三次握手之間的關(guān)系
- 在客戶端發(fā)起連接建立請(qǐng)求之前,服務(wù)器需要先進(jìn)入LISTEN狀態(tài),此時(shí)就需要服務(wù)器調(diào)用對(duì)應(yīng)listen函數(shù)。
- 當(dāng)服務(wù)器進(jìn)入LISTEN狀態(tài)后,客戶端就可以向服務(wù)器發(fā)起三次握手了,此時(shí)客戶端對(duì)應(yīng)調(diào)用的就是connect函數(shù)。
- 需要注意的是,connect函數(shù)不參與底層的三次握手,connect函數(shù)的作用只是發(fā)起三次握手。當(dāng)connect函數(shù)返回時(shí),要么是底層已經(jīng)成功完成了三次握手連接建立成功,要么是底層三次握手失敗。
- 如果服務(wù)器端與客戶端成功完成了三次握手,此時(shí)在服務(wù)器端就會(huì)建立一個(gè)連接,但這個(gè)連接在內(nèi)核的等待隊(duì)列當(dāng)中,服務(wù)器端需要通過調(diào)用accept函數(shù)將這個(gè)建立好的連接獲取上來。
- 當(dāng)服務(wù)器端將建立好的連接獲取上來后,雙方就可以通過調(diào)用read/recv函數(shù)和write/send函數(shù)進(jìn)行數(shù)據(jù)交互了。
四次揮手
四次揮手的過程
由于雙方維護(hù)連接都是需要成本的,因此當(dāng)雙方TCP通信結(jié)束之后就需要斷開連接,斷開連接的這個(gè)過程我們稱之為四次揮手。
還是以服務(wù)器和客戶端為例,當(dāng)客戶端與服務(wù)器通信結(jié)束后,需要與服務(wù)器斷開連接,此時(shí)就需要進(jìn)行四次揮手。
- 第一次揮手:客戶端向服務(wù)器發(fā)送的報(bào)文當(dāng)中的FIN位被設(shè)置為1,表示請(qǐng)求與服務(wù)器斷開連接。
- 第二次揮手:服務(wù)器收到客戶端發(fā)來的斷開連接請(qǐng)求后對(duì)其進(jìn)行響應(yīng)。
- 第三次揮手:服務(wù)器收到客戶端斷開連接的請(qǐng)求,且已經(jīng)沒有數(shù)據(jù)需要發(fā)送給客戶端的時(shí)候,服務(wù)器就會(huì)向客戶端發(fā)起斷開連接請(qǐng)求。
- 第四次揮手:客戶端收到服務(wù)器發(fā)來的斷開連接請(qǐng)求后對(duì)其進(jìn)行響應(yīng)。
四次揮手結(jié)束后雙方的連接才算真正斷開。
為什么是四次揮手?
-
由于TCP是全雙工的,建立連接的時(shí)候需要建立雙方的連接,斷開連接時(shí)也同樣如此。在斷開連接時(shí)不僅要斷開從客戶端到服務(wù)器方向的通信信道,也要斷開從服務(wù)器到客戶端的通信信道,其中每兩次揮手對(duì)應(yīng)就是關(guān)閉一個(gè)方向的通信信道,因此斷開連接時(shí)需要進(jìn)行四次揮手。
-
需要注意的是,四次揮手當(dāng)中的第二次和第三次揮手不能合并在一起,因?yàn)榈谌挝帐质欠?wù)器端想要與客戶端斷開連接時(shí)發(fā)給客戶端的請(qǐng)求,而當(dāng)服務(wù)器收到客戶端斷開連接的請(qǐng)求并響應(yīng)后,服務(wù)器不一定會(huì)馬上發(fā)起第三次揮手,因?yàn)榉?wù)器可能還有某些數(shù)據(jù)要發(fā)送給客戶端,只有當(dāng)服務(wù)器端將這些數(shù)據(jù)發(fā)送完后才會(huì)向客戶端發(fā)起第三次揮手。
在用某些抓包工具時(shí),四次揮手可能會(huì)合并成三次揮手,因?yàn)樵诎l(fā)送ACK之前可以檢查有沒有數(shù)據(jù)要發(fā)送,如果沒有數(shù)據(jù)要發(fā)送,服務(wù)器端要直接關(guān)閉連接的話,可以把ACK和FIN合并到一個(gè)包發(fā)送。
四次揮手時(shí)的狀態(tài)變化
- 在揮手前客戶端和服務(wù)器都處于連接建立后的ESTABLISHED狀態(tài)。
- 客戶端為了與服務(wù)器斷開連接主動(dòng)向服務(wù)器發(fā)起連接斷開請(qǐng)求,此時(shí)客戶端的狀態(tài)變?yōu)镕IN_WAIT_1。
- 服務(wù)器收到客戶端發(fā)來的連接斷開請(qǐng)求后對(duì)其進(jìn)行響應(yīng),此時(shí)服務(wù)器的狀態(tài)變?yōu)镃LOSE_WAIT。
- 當(dāng)服務(wù)器沒有數(shù)據(jù)需要發(fā)送給客戶端的時(shí),服務(wù)器會(huì)向客戶端發(fā)起斷開連接請(qǐng)求,等待最后一個(gè)ACK到來,此時(shí)服務(wù)器的狀態(tài)變?yōu)長AST_ACK。
- 客戶端收到服務(wù)器發(fā)來的第三次揮手后,會(huì)向服務(wù)器發(fā)送最后一個(gè)響應(yīng)報(bào)文,此時(shí)客戶端進(jìn)入TIME_WAIT狀態(tài)。
- 當(dāng)服務(wù)器收到客戶端發(fā)來的最后一個(gè)響應(yīng)報(bào)文時(shí),服務(wù)器會(huì)徹底關(guān)閉連接,變?yōu)镃LOSED狀態(tài)。
- 而客戶端則會(huì)等待一個(gè)2MSL(Maximum Segment Lifetime,報(bào)文最大生存時(shí)間)才會(huì)進(jìn)入CLOSED狀態(tài)。
至此四次揮手結(jié)束,通信雙方成功斷開連接。
套接字和四次揮手之間的關(guān)系
- 客戶端發(fā)起斷開連接請(qǐng)求,對(duì)應(yīng)就是客戶端主動(dòng)調(diào)用close函數(shù)。
- 服務(wù)器發(fā)起斷開連接請(qǐng)求,對(duì)應(yīng)就是服務(wù)器主動(dòng)調(diào)用close函數(shù)。
- 一個(gè)close對(duì)應(yīng)的就是兩次揮手,雙方都要調(diào)用close,因此就是四次揮手。
TCP三次握手四次揮手總結(jié)圖
CLOSE_WAIT
- 雙方在進(jìn)行四次揮手時(shí),如果只有客戶端調(diào)用了close函數(shù),而服務(wù)器不調(diào)用close函數(shù),此時(shí)服務(wù)器就會(huì)進(jìn)入CLOSE_WAIT狀態(tài),而客戶端則會(huì)進(jìn)入到FIN_WAIT_2狀態(tài)。
- 但只有完成四次揮手后連接才算真正斷開,此時(shí)雙方才會(huì)釋放對(duì)應(yīng)的連接資源。如果服務(wù)器沒有主動(dòng)關(guān)閉不需要的文件描述符,此時(shí)在服務(wù)器端就會(huì)存在大量處于CLOSE_WAIT狀態(tài)的連接,而每個(gè)連接都會(huì)占用服務(wù)器的資源,最終就會(huì)導(dǎo)致服務(wù)器可用資源越來越少。
- 因此如果不及時(shí)關(guān)閉不用的文件描述符,除了會(huì)造成文件描述符泄漏以外,可能也會(huì)導(dǎo)致連接資源沒有完全釋放,這其實(shí)也是一種內(nèi)存泄漏的問題。
- 因此在編寫網(wǎng)絡(luò)套接字代碼時(shí),如果發(fā)現(xiàn)服務(wù)器端存在大量處于CLOSE_WAIT狀態(tài)的連接,此時(shí)就可以檢查一下是不是服務(wù)器沒有及時(shí)調(diào)用close函數(shù)關(guān)閉對(duì)應(yīng)的文件描述符。
TIME_WAIT
四次揮手中前三次揮手丟包時(shí)的解決方法:
- 第一次揮手丟包:客戶端收不到服務(wù)器的應(yīng)答,進(jìn)而進(jìn)行超時(shí)重傳。
- 第二次揮手丟包:客戶端收不到服務(wù)器的應(yīng)答,進(jìn)而進(jìn)行超時(shí)重傳。
- 第三次揮手丟包:服務(wù)器收不到客戶端的應(yīng)答,進(jìn)而進(jìn)行超時(shí)重傳。
- 第四次揮手丟包:服務(wù)器收不到客戶端的應(yīng)答,進(jìn)而進(jìn)行超時(shí)重傳。
如果客戶端在發(fā)出第四次揮手后立即進(jìn)入CLOSED狀態(tài),此時(shí)服務(wù)器雖然進(jìn)行了超時(shí)重傳,但已經(jīng)得不到客戶端的響應(yīng)了,因?yàn)榭蛻舳艘呀?jīng)將連接關(guān)閉了。
服務(wù)器在經(jīng)過若干次超時(shí)重發(fā)后得不到響應(yīng),最終也一定會(huì)將對(duì)應(yīng)的連接關(guān)閉,但在服務(wù)器不斷進(jìn)行超時(shí)重傳期間還需要維護(hù)這條廢棄的連接,這樣對(duì)服務(wù)器是非常不友好的。
為了避免這種情況,因此客戶端在四次揮手后沒有立即進(jìn)入CLOSED狀態(tài),而是進(jìn)入到了TIME_WAIT狀態(tài)進(jìn)行等待,此時(shí)要是第四次揮手的報(bào)文丟包了,客戶端也能收到服務(wù)器重發(fā)的報(bào)文然后進(jìn)行響應(yīng)。
TIME_WAIT狀態(tài)存在的必要性:
- 客戶端在進(jìn)行四次揮手后進(jìn)入TIME_WAIT狀態(tài),如果第四次揮手的報(bào)文丟包了,客戶端在一段時(shí)間內(nèi)仍然能夠接收服務(wù)器重發(fā)的FIN報(bào)文并對(duì)其進(jìn)行響應(yīng),能夠較大概率保證最后一個(gè)ACK被服務(wù)器收到。
- 客戶端發(fā)出最后一次揮手時(shí),雙方歷史通信的數(shù)據(jù)可能還沒有發(fā)送到對(duì)方。因此客戶端四次揮手后進(jìn)入TIME_WAIT狀態(tài),還可以保證雙方通信信道上的數(shù)據(jù)在網(wǎng)絡(luò)中盡可能的消散。
實(shí)際第四次揮手丟包后,可能雙方網(wǎng)絡(luò)狀態(tài)出現(xiàn)了問題,盡管客戶端還沒有關(guān)閉連接,也收不到服務(wù)器重發(fā)的連接斷開請(qǐng)求,此時(shí)客戶端TIME_WAIT等若干時(shí)間最終會(huì)關(guān)閉連接,而服務(wù)器經(jīng)過多次超時(shí)重傳后也會(huì)關(guān)閉連接。這種情況雖然也讓服務(wù)器維持了閑置的連接,但畢竟是少數(shù),引入TIME_WAIT狀態(tài)就是爭取讓主動(dòng)發(fā)起四次揮手的客戶端維護(hù)這個(gè)成本。
因此TCP并不能完全保證建立連接和斷開連接的可靠性,TCP保證的是建立連接之后,以及斷開連接之前雙方通信數(shù)據(jù)的可靠性。
TIME_WAIT的等待時(shí)長是多少?
TIME_WAIT的等待時(shí)長既不能太長也不能太短。
- 太長會(huì)讓等待方維持一個(gè)較長的時(shí)間的TIME_WAIT狀態(tài),在這個(gè)時(shí)間內(nèi)等待方也需要花費(fèi)成本來維護(hù)這個(gè)連接,這也是一種浪費(fèi)資源的現(xiàn)象。
- 太短可能沒有達(dá)到我們最初目的,沒有保證ACK被對(duì)方較大概率收到,也沒有保證數(shù)據(jù)在網(wǎng)絡(luò)中消散,此時(shí)TIME_WAIT的意義也就沒有了。
TCP協(xié)議規(guī)定,主動(dòng)關(guān)閉連接的一方在四次揮手后要處于TIME_WAIT狀態(tài),等待兩個(gè)MSL(Maximum Segment Lifetime,報(bào)文最大生存時(shí)間)的時(shí)間才能進(jìn)入CLOSED狀態(tài)。
MSL在RFC1122中規(guī)定為兩分鐘,但是各個(gè)操作系統(tǒng)的實(shí)現(xiàn)不同,比如在Centos7上默認(rèn)配置的值是60s。我們可以通過cat /proc/sys/net/ipv4/tcp_fin_timeout命令來查看MSL的值。
TIME_WAIT的等待時(shí)長設(shè)置為兩個(gè)MSL的原因:
- MSL是TCP報(bào)文的最大生存時(shí)間,因此TIME_WAIT狀態(tài)持續(xù)存在2MSL的話,就能保證在兩個(gè)傳輸方向上的尚未被接收或遲到的報(bào)文段都已經(jīng)消失。
- 同時(shí)也是在理論上保證最后一個(gè)報(bào)文可靠到達(dá)的時(shí)間
滑動(dòng)窗口
剛才我們討論了確認(rèn)應(yīng)答策略, 對(duì)每一個(gè)發(fā)送的數(shù)據(jù)段, 都要給一個(gè)ACK確認(rèn)應(yīng)答. 收到ACK后再發(fā)送下一個(gè)數(shù)據(jù)段.
這樣做有一個(gè)比較大的缺點(diǎn), 就是性能較差. 尤其是數(shù)據(jù)往返的時(shí)間較長的時(shí)候.
既然這樣一發(fā)一收的方式性能較低, 那么我們一次發(fā)送多條數(shù)據(jù), 就可以大大的提高性能(其實(shí)是將多個(gè)段的等待時(shí)間重疊在一起了).
- 窗口大小指的是無需等待確認(rèn)應(yīng)答而可以繼續(xù)發(fā)送數(shù)據(jù)的最大值. 上圖的窗口大小就是4000個(gè)字節(jié)(四個(gè)段).
- 發(fā)送前四個(gè)段的時(shí)候, 不需要等待任何ACK, 直接發(fā)送
- 收到第一個(gè)ACK后, 滑動(dòng)窗口向后移動(dòng), 繼續(xù)發(fā)送第五個(gè)段的數(shù)據(jù); 依次類推;
- 操作系統(tǒng)內(nèi)核為了維護(hù)這個(gè)滑動(dòng)窗口, 需要開辟 發(fā)送緩沖區(qū) 來記錄當(dāng)前還有哪些數(shù)據(jù)沒有應(yīng)答; 只有確認(rèn)應(yīng)答過的數(shù)據(jù), 才能從緩沖區(qū)刪掉;
- 窗口越大, 則網(wǎng)絡(luò)的吞吐率就越高;
那么如果出現(xiàn)了丟包, 如何進(jìn)行重傳? 這里分兩種情況討論
情況一: 數(shù)據(jù)包已經(jīng)抵達(dá), ACK被丟了
這種情況下, 部分ACK丟了并不要緊,因?yàn)榭梢酝ㄟ^后續(xù)的ACK進(jìn)行確認(rèn)
情況二: 數(shù)據(jù)包就直接丟了.
當(dāng)某一段報(bào)文段丟失之后, 發(fā)送端會(huì)一直收到 1001 這樣的ACK, 就像是在提醒發(fā)送端 “我想要的是 1001” 一樣;
如果發(fā)送端主機(jī)連續(xù)三次收到了同樣一個(gè) “1001” 這樣的應(yīng)答, 就會(huì)將對(duì)應(yīng)的數(shù)據(jù) 1001 - 2000 重新發(fā)送;
這個(gè)時(shí)候接收端收到了 1001 之后, 再次返回的ACK就是7001了(因?yàn)?001 - 7000)接收端其實(shí)之前就已經(jīng)收到了, 被放到了接收端操作系統(tǒng)內(nèi)核的接收緩沖區(qū)中
這種機(jī)制被稱為 “高速重發(fā)控制”(也叫 “快重傳”)
流量控制
接收端處理數(shù)據(jù)的速度是有限的. 如果發(fā)送端發(fā)的太快, 導(dǎo)致接收端的緩沖區(qū)被打滿, 這個(gè)時(shí)候如果發(fā)送端繼續(xù)發(fā)送就會(huì)造成丟包, 繼而引起丟包重傳等等一系列連鎖反應(yīng).
因此TCP支持根據(jù)接收端的處理能力, 來決定發(fā)送端的發(fā)送速度. 這個(gè)機(jī)制就叫做流量控制(Flow Control);
- 接收端將自己可以接收的緩沖區(qū)大小放入 TCP 首部中的 “窗口大小” 字段, 通過ACK端通知發(fā)送端
- 窗口大小字段越大, 說明網(wǎng)絡(luò)的吞吐量越高
- 接收端一旦發(fā)現(xiàn)自己的緩沖區(qū)快滿了, 就會(huì)將窗口大小設(shè)置成一個(gè)更小的值通知給發(fā)送端
- 發(fā)送端接受到這個(gè)窗口之后, 就會(huì)減慢自己的發(fā)送速度
- 如果接收端緩沖區(qū)滿了, 就會(huì)將窗口置為0; 這時(shí)發(fā)送方不再發(fā)送數(shù)據(jù), 但是需要定期發(fā)送一個(gè)窗口探測數(shù)據(jù)的,使接收端把窗口大小告訴發(fā)送端
接收端如何把窗口大小告訴發(fā)送端呢? 回憶我們的TCP首部中, 有一個(gè)16位窗口字段, 就是存放了窗口大小信息;
那么問題來了, 16位數(shù)字最大表示65535, 那么TCP窗口最大就是65535字節(jié)么?
實(shí)際上, TCP首部40字節(jié)選項(xiàng)中還包含了一個(gè)窗口擴(kuò)大因子M, 實(shí)際窗口大小是窗口字段的值左移 M 位;
擁塞控制
雖然TCP有了滑動(dòng)窗口這個(gè)大殺器, 能夠高效可靠的發(fā)送大量的數(shù)據(jù). 但是如果在剛開始階段就發(fā)送大量的數(shù)據(jù), 仍然可能引發(fā)問題.
因?yàn)榫W(wǎng)絡(luò)上有很多的計(jì)算機(jī), 可能當(dāng)前的網(wǎng)絡(luò)狀態(tài)就已經(jīng)比較擁堵. 在不清楚當(dāng)前網(wǎng)絡(luò)狀態(tài)下, 貿(mào)然發(fā)送大量的數(shù)據(jù),是很有可能引起雪上加霜的.
TCP引入 慢啟動(dòng) 機(jī)制, 先發(fā)少量的數(shù)據(jù), 探探路, 摸清當(dāng)前的網(wǎng)絡(luò)擁堵狀態(tài), 再?zèng)Q定按照多大的速度傳輸數(shù)據(jù);
-
此處引入一個(gè)概念程為擁塞窗口
-
發(fā)送開始的時(shí)候, 定義擁塞窗口大小為1;
-
每次收到一個(gè)ACK應(yīng)答, 擁塞窗口加1;
-
每次發(fā)送數(shù)據(jù)包的時(shí)候, 將擁塞窗口和接收端主機(jī)反饋的窗口大小做比較, 取較小的值作為實(shí)際發(fā)送的窗口;
像上面這樣的擁塞窗口增長速度, 是指數(shù)級(jí)別的. “慢啟動(dòng)” 只是指初使時(shí)慢, 但是增長速度非???
為了不增長的那么快, 因此不能使擁塞窗口單純的加倍.
此處引入一個(gè)叫做慢啟動(dòng)的閾值
當(dāng)擁塞窗口超過這個(gè)閾值的時(shí)候, 不再按照指數(shù)方式增長, 而是按照線性方式增長
- 當(dāng)TCP開始啟動(dòng)的時(shí)候, 慢啟動(dòng)閾值等于窗口最大值;
- 在每次超時(shí)重發(fā)的時(shí)候, 慢啟動(dòng)閾值會(huì)變成原來的一半, 同時(shí)擁塞窗口置回1
少量的丟包, 我們僅僅是觸發(fā)超時(shí)重傳; 大量的丟包, 我們就認(rèn)為網(wǎng)絡(luò)擁塞;
當(dāng)TCP通信開始后, 網(wǎng)絡(luò)吞吐量會(huì)逐漸上升; 隨著網(wǎng)絡(luò)發(fā)生擁堵, 吞吐量會(huì)立刻下降;
擁塞控制, 歸根結(jié)底是TCP協(xié)議想盡可能快的把數(shù)據(jù)傳輸給對(duì)方, 但是又要避免給網(wǎng)絡(luò)造成太大壓力的折中方案.
延遲應(yīng)答
如果接收數(shù)據(jù)的主機(jī)立刻返回ACK應(yīng)答, 這時(shí)候返回的窗口可能比較小
-
假設(shè)接收端緩沖區(qū)為1M. 一次收到了500K的數(shù)據(jù); 如果立刻應(yīng)答, 返回的窗口就是500K;
-
但實(shí)際上可能處理端處理的速度很快, 10ms之內(nèi)就把500K數(shù)據(jù)從緩沖區(qū)消費(fèi)掉了;
-
在這種情況下, 接收端處理還遠(yuǎn)沒有達(dá)到自己的極限, 即使窗口再放大一些, 也能處理過來;
-
如果接收端稍微等一會(huì)再應(yīng)答, 比如等待200ms再應(yīng)答, 那么這個(gè)時(shí)候返回的窗口大小就是1M;
一定要記得, 窗口越大, 網(wǎng)絡(luò)吞吐量就越大, 傳輸效率就越高. 我們的目標(biāo)是在保證網(wǎng)絡(luò)不擁塞的情況下盡量提高傳輸效率
那么所有的包都可以延遲應(yīng)答么? 肯定也不是;
-
數(shù)量限制: 每隔N個(gè)包就應(yīng)答一次;
-
時(shí)間限制: 超過最大延遲時(shí)間就應(yīng)答一次
具體的數(shù)量和超時(shí)時(shí)間, 依操作系統(tǒng)不同也有差異; 一般N取2, 超時(shí)時(shí)間取200ms;
捎帶應(yīng)答
在延遲應(yīng)答的基礎(chǔ)上, 我們發(fā)現(xiàn), 很多情況下, 客戶端服務(wù)器在應(yīng)用層也是 “一發(fā)一收” 的. 意味著客戶端給服務(wù)器說了 “How are you”, 服務(wù)器也會(huì)給客戶端回一個(gè) “Fine, thank you”;
那么這個(gè)時(shí)候ACK就可以搭順風(fēng)車, 和服務(wù)器回應(yīng)的 “Fine, thank you” 一起回給客戶端
面向字節(jié)流
創(chuàng)建一個(gè)TCP的socket, 同時(shí)在內(nèi)核中創(chuàng)建一個(gè) 發(fā)送緩沖區(qū) 和一個(gè) 接收緩沖區(qū);
-
調(diào)用write時(shí), 數(shù)據(jù)會(huì)先寫入發(fā)送緩沖區(qū)中;
-
如果發(fā)送的字節(jié)數(shù)太長, 會(huì)被拆分成多個(gè)TCP的數(shù)據(jù)包發(fā)出;
-
如果發(fā)送的字節(jié)數(shù)太短, 就會(huì)先在緩沖區(qū)里等待, 等到緩沖區(qū)長度差不多了, 或者其他合適的時(shí)機(jī)發(fā)送出去;
-
接收數(shù)據(jù)的時(shí)候, 數(shù)據(jù)也是從網(wǎng)卡驅(qū)動(dòng)程序到達(dá)內(nèi)核的接收緩沖區(qū);
-
然后應(yīng)用程序可以調(diào)用read從接收緩沖區(qū)拿數(shù)據(jù);
-
另一方面, TCP的一個(gè)連接, 既有發(fā)送緩沖區(qū), 也有接收緩沖區(qū), 那么對(duì)于這一個(gè)連接, 既可以讀數(shù)據(jù), 也可以寫數(shù)據(jù). 這個(gè)概念叫做 全雙工
由于緩沖區(qū)的存在, TCP程序的讀和寫不需要一一匹配, 例如: -
寫100個(gè)字節(jié)數(shù)據(jù)時(shí), 可以調(diào)用一次write寫100個(gè)字節(jié), 也可以調(diào)用100次write, 每次寫一個(gè)字節(jié);
-
讀100個(gè)字節(jié)數(shù)據(jù)時(shí), 也完全不需要考慮寫的時(shí)候是怎么寫的, 既可以一次read 100個(gè)字節(jié), 也可以一次read一個(gè)字節(jié), 重復(fù)100次;
粘包問題
-
首先要明確, 粘包問題中的 “包” , 是指的應(yīng)用層的數(shù)據(jù)包.
-
在TCP的協(xié)議頭中, 沒有如同UDP一樣的 “報(bào)文長度” 這樣的字段, 但是有一個(gè)序號(hào)這樣的字段.
-
站在傳輸層的角度, TCP是一個(gè)一個(gè)報(bào)文過來的. 按照序號(hào)排好序放在緩沖區(qū)中.
-
站在應(yīng)用層的角度, 看到的只是一串連續(xù)的字節(jié)數(shù)據(jù).
-
那么應(yīng)用程序看到了這么一連串的字節(jié)數(shù)據(jù), 就不知道從哪個(gè)部分開始到哪個(gè)部分, 是一個(gè)完整的應(yīng)用層數(shù)據(jù)包
那么如何避免粘包問題呢? 歸根結(jié)底就是一句話, 明確兩個(gè)包之間的邊界.
-
對(duì)于定長的包, 保證每次都按固定大小讀取即可;
-
對(duì)于變長的包, 可以在包頭的位置, 約定一個(gè)包總長度的字段, 從而就知道了包的結(jié)束位置;
-
對(duì)于變長的包, 還可以在包和包之間使用明確的分隔符(應(yīng)用層協(xié)議, 是程序員自己來定的, 只要保證分隔符不和正文沖突即可)
對(duì)于UDP協(xié)議來說, 是否也存在 “粘包問題” 呢?
對(duì)于UDP, 如果還沒有上層交付數(shù)據(jù), UDP的報(bào)文長度仍然在. 同時(shí), UDP是一個(gè)一個(gè)把數(shù)據(jù)交付給應(yīng)用層. 就有很明確的數(shù)據(jù)邊界.
站在應(yīng)用層的站在應(yīng)用層的角度, 使用UDP的時(shí)候, 要么收到完整的UDP報(bào)文, 要么不收. 不會(huì)出現(xiàn)"半個(gè)"的情況
TCP異常情況
-
進(jìn)程終止: 進(jìn)程終止會(huì)釋放文件描述符, 仍然可以發(fā)送FIN. 和正常關(guān)閉沒有什么區(qū)別.
-
機(jī)器重啟: 和進(jìn)程終止的情況相同.
-
機(jī)器掉電/網(wǎng)線斷開: 接收端認(rèn)為連接還在, 一旦接收端有寫入操作, 接收端發(fā)現(xiàn)連接已經(jīng)不在了, 就會(huì)進(jìn)行reset. 即使沒有寫入操作, TCP自己也內(nèi)置了一個(gè)?;疃〞r(shí)器, 會(huì)定期詢問對(duì)方是否還在. 如果對(duì)方不在, 也會(huì)把連接釋放.
-
另外, 應(yīng)用層的某些協(xié)議, 也有一些這樣的檢測機(jī)制. 例如HTTP長連接中, 也會(huì)定期檢測對(duì)方的狀態(tài). 例如QQ, 在QQ斷線之后, 也會(huì)定期嘗試重新連接.
TCP小結(jié)
什么TCP這么復(fù)雜? 因?yàn)橐WC可靠性, 同時(shí)又盡可能的提高性能.
可靠性:
- 校驗(yàn)和
- 序號(hào)/確認(rèn)序號(hào)
- 確認(rèn)應(yīng)答
- 超時(shí)重發(fā)
- 連接管理
- 流量控制
- 擁塞控制
提高性能:
- 滑動(dòng)窗口
- 快速重傳
- 延遲應(yīng)答
- 捎帶應(yīng)答
其他:文章來源:http://www.zghlxwxcb.cn/news/detail-625186.html
定時(shí)器(超時(shí)重傳定時(shí)器, ?;疃〞r(shí)器, TIME_WAIT定時(shí)器等)文章來源地址http://www.zghlxwxcb.cn/news/detail-625186.html
到了這里,關(guān)于【Linux 網(wǎng)絡(luò)】 傳輸層協(xié)議之TCP協(xié)議 && TCP的三次握手和四次揮手的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!