說說TCP的三次握手和四次揮手
本文轉(zhuǎn)載自微信公眾號「牧小農(nóng)」,作者牧小農(nóng)。轉(zhuǎn)載本文請聯(lián)系牧小農(nóng)公眾號。
一、傳輸控制協(xié)議TCP簡介
1.1 簡介
TCP(Transmission Control Protocol) 傳輸控制協(xié)議,是一種 面向連接的、可靠的、基于字節(jié)流的傳輸層 通信協(xié)議。
TCP是一種面向連接(連接導向)的、可靠的基于字節(jié)流的傳輸層通信協(xié)議。TCP將用戶數(shù)據(jù)打包成報文段,它發(fā)送后啟動一個定時器,另一端收到的數(shù)據(jù)進行確認、對失序的數(shù)據(jù)重新排序、丟棄重復數(shù)據(jù)。
TCP把連接作為最基本的對象,每一條TCP連接都有兩個端點,這種端點我們叫作套接字(socket),將端口號拼接到IP地址即構(gòu)成了套接字,例如 192.1.1.6:50030
1.2 特點
面向連接的、可靠的、基于字節(jié)流的 傳輸層 通信協(xié)議
將應用層的數(shù)據(jù)流分割成文段并發(fā)送給目標節(jié)點的TCP層
數(shù)據(jù)包都有序號,對方收到則發(fā)送ACK確認,未收到則重傳
使用校驗和來檢驗數(shù)據(jù)在傳輸過程中是否有誤
二、TCP報文頭
1、源端口(Source Port)/ 目的端口(Destination Port):他們各占2個字節(jié),標示該段報文來自哪里(源端口)以及要傳給哪個上層協(xié)議或應用程序(目的端口)。進行tcp通信時,一般client是通過系統(tǒng)自動選擇的臨時端口號,而服務器一般是使用知名服務端口號或者自己指定的端口號 (比如DNS協(xié)議對應端口53,HTTP協(xié)議對應80)
2、序號(Sequence Number):占據(jù)四個字節(jié),TCP是面向字節(jié)流的,TCP連接中傳送的字節(jié)流中的每個字節(jié)都按順序編號,例如如一段報文的序號字段值是 107,而攜帶的數(shù)據(jù)共有 100個字段,如果有下一個報文過來,那么序號就從 207(100+107)開始,整個要傳送的字節(jié)流的起始序號必須要在連接建立時設(shè)置。首部中的序號字段值指的是本報文段所發(fā)送的數(shù)據(jù)的第一個字節(jié)的序號
3、確認序號(Acknowledgment Number):4個字節(jié),是期望收到對方下一個報文段的第一個數(shù)據(jù)字節(jié)的序號,若確認號=N,則表明:到序號N-1為止的所有數(shù)據(jù)都已正確收到,例如:B收到A發(fā)送過來的報文,其序列號字段是 301,而數(shù)據(jù)長度是 200字節(jié),這表明了B正確的收到了A到序號 500(301+200-1)為止的數(shù)據(jù),因此B希望收到A的下一個數(shù)據(jù)序號是 501,于是B在發(fā)送給A的確認報文段中,會把ACK確認號設(shè)置為 501
4、數(shù)據(jù)偏移(Offset):4個字節(jié)。指出TCP報文段的數(shù)據(jù)起始處距離報文段的起始處有多遠,這個字段實際上是指出TCP報文段的首部長度。由于首部中還有長度不確定的選項字段,因此數(shù)據(jù)偏移字段是必要的。單位是32位字,也就是4字節(jié),4位二進制最大表示15,所以數(shù)據(jù)偏移也就是TCP首部最大60字節(jié)
5、保留(Reserved):6個字節(jié)。保留域
6、TCP Flags:控制位,由八個標志位組成,每個標志位表示控制的功能,我們主要來介紹TCP Flags中常用的六個,
- URG(緊急指針標志):當 URG=1時,表明緊急指針字段有效。它告訴系統(tǒng)此報文段中有緊急數(shù)據(jù),應盡快傳送(相當于高優(yōu)先級的數(shù)據(jù)),而不要按原來的排隊順序來傳送。例如,已經(jīng)發(fā)送了很長的一個程序在主機上運行。但后來發(fā)現(xiàn)了一些問題,需要取消該程序的運行。因此用戶從鍵盤發(fā)出中斷命令。如果不使用緊急數(shù)據(jù),那么這兩個字符將存儲在接收TCP的緩存末尾。只有在所有的數(shù)據(jù)被處理完畢后這兩個字符才被交付接收方的應用進程。這樣做就浪費了許多時間
- ACK(確認序號標志):當 ACK=1時確認號字段有效。當 ACK=0時,確認號無效。TCP規(guī)定,在連接建立后所有的傳送的報文段都必須把ACK置1
- PSH(push標志):當兩個應用進程進行交互式的通信時,有時在一端的應用進程希望在鍵入一個命令后立即就能收到對方的響應。在這種情況下,TCP就可以使用推送操作。這時,發(fā)送方TCP把PSH置1,并立即創(chuàng)建一個報文段發(fā)送出去。接收方TCP收到PSH=1的報文段,就盡快地交付接收應用進程,而不再等到整個緩存都填滿了后向上交付
- RST(重置連接標志):TCP連接中出現(xiàn)嚴重差錯(如由于主機崩潰或其他原因),必須釋放連接,然后再重新建立運輸連接,可以用來拒絕一個非法的報文段或拒絕打開一個連接
- SYN(同步序號,用于建立連接過程):在連接建立時用來同步序號。當 SYN=1而ACK=0時,表明這是一個連接請求報文段。對方若同意建立連接,則應在相應的報文段中使用 SYN=1和ACK=1。因此,SYN置為1就表示這是一個連接請求或連接接受保溫。
- FIN(finish標志,用于釋放連接):當 FIN=1時,表明此報文段的發(fā)送方的數(shù)據(jù)已發(fā)送完畢,并要求釋放運輸連接
7、窗口(Window):是TCP流量控制的一個手段。這里說的窗口,指的是接收通告窗口(Receiver Window,RWND)。它告訴對方本端的TCP接收緩沖區(qū)還能容納多少字節(jié)的數(shù)據(jù),這樣就可以控制發(fā)送數(shù)據(jù)的速度
8、檢驗和(Checksum):檢驗范圍包括首部和數(shù)據(jù)兩部分,由發(fā)送端填充,接收端對TCP報文段執(zhí)行CRC算法以檢驗TCP報文段在傳輸過程中是否損壞。這也是TCP可靠傳輸?shù)囊粋€重要保障
9、緊急指針(Urgent Pointer):緊急指針僅在URG=1時才有意義,它指出本報文段中的緊急數(shù)據(jù)的字節(jié)數(shù)(緊急數(shù)據(jù)結(jié)束后就是普通數(shù)據(jù))。因此,緊急指針指出了緊急數(shù)據(jù)的末尾在報文段中的位置。當所有緊急數(shù)據(jù)都處理完時,TCP就告訴應用程序恢復到正常操作。值得注意的是,即使窗口為零時也可發(fā)送緊急數(shù)據(jù)。
10、TCP可選項(TCP Options):長度可變,最長可達40字節(jié)。當沒有使用“選項”時,TCP的首部長度是20字節(jié)。
三、TCP的三次握手
所謂三次握手(Three-Way Handshake)即建立TCP連接,就是指建立一個TCP連接時,需要客戶端和服務端總共發(fā)送3個包以確認連接的建立。在socket編程中,這一過程由客戶端執(zhí)行connect來觸發(fā),整個流程如下圖所示:
在TCP/IP協(xié)議中,TCP協(xié)議提供可靠的連接服務,采用三次握手建立一個連接。
第一次握手: 建立連接時,客戶端發(fā)送SYN包(syn=j)到服務器,并進入SYN_SEND狀態(tài),等待服務器確認,SYN:同步序列編號(Synchronize Sequence Numbers)。
第二次握手: 服務器收到 SYN 包,必須確認客戶的 SYN(ack=j+1),同時自己也發(fā)送一個SYN包(syn=k),即SYN+ACK包,此時服務器進入SYN_RECV狀態(tài);
第三次握手: 客戶端收到服務器的SYN + ACK包,向服務器發(fā)送確認包ACK(ack=k+1),此包發(fā)送完畢,客戶端和服務器進入ESTABLISHED(TCP連接成功)狀態(tài),完成三次握手。
3.1 為什么需要三次握手才能建立連接
- 為了初始化Sequence Number 的初始值,實現(xiàn)可靠數(shù)據(jù)傳輸, TCP 協(xié)議的通信雙方, 都必須維護一個序列號, 以標識發(fā)送出去的數(shù)據(jù)包中, 哪些是已經(jīng)被對方收到的。三次握手的過程即是通信雙方相互告知序列號起始值, 并確認對方已經(jīng)收到了序列號起始值的必經(jīng)步驟
- 如果只是兩次握手, 至多只有連接發(fā)起方的起始序列號能被確認, 另一方選擇的序列號則得不到確認
3.2 首次握手的隱患——SYN超時
一、問題起因分析:
- 服務器收到客戶端的SYN,回復SYN和ACK的時候未收到ACK確認
- 服務器不斷重試直至超時,Linux默認等待63秒才斷開連接;(重復5次【不包括第一次】,從1秒開始,每次重試都翻倍:1+2+4+8+16+32=63秒)
二、針對SYN Flood的防護措施:
SYN隊列滿后,通過tcp_syncookies參數(shù)會發(fā)SYN cookie【源端口+目標端口+時間戳組成】
若為正常連接則Client會回發(fā)SYN Cookie,直接建立連接;
3.3 保活機制:
當我們建立連接后,Client出現(xiàn)故障怎么辦?
向?qū)Ψ桨l(fā)送?;钐綔y報文,如果未收到相應則繼續(xù)發(fā)送;
嘗試次數(shù)達到?;钐綔y數(shù)仍未收到相應則中斷連接;
四、TCP的四次揮手
所謂四次揮手(Four-Way Wavehand)即終止TCP連接,就是指斷開一個TCP連接時,需要客戶端和服務端總共發(fā)送4個包以確認連接的斷開。在socket編程中,這一過程由客戶端或服務端任一方執(zhí)行close來觸發(fā),整個流程如下圖所示:
由于TCP連接時全雙工的,因此,每個方向都必須要單獨進行關(guān)閉,這一原則是當一方完成數(shù)據(jù)發(fā)送任務后,發(fā)送一個FIN來終止這一方向的連接,收到一個FIN只是意味著這一方向上沒有數(shù)據(jù)流動了,即不會再收到數(shù)據(jù)了,但是在這個TCP連接上仍然能夠發(fā)送數(shù)據(jù),直到這一方向也發(fā)送了FIN。首先進行關(guān)閉的一方將執(zhí)行主動關(guān)閉,而另一方則執(zhí)行被動關(guān)閉。
- 第一次揮手: Client發(fā)送一個FIN,用來關(guān)閉Client到Server的數(shù)據(jù)傳送,Client進入FINWAIT1狀態(tài)
- 第二次揮手: Server收到FIN后,發(fā)送一個ACK給Client,確認序號為收到序號+1(與SYN相同,一個FIN占用一個序號),Server進入CLOSE_WAIT狀態(tài)
- 第三次揮手: Server發(fā)送一個FIN,用來關(guān)閉Server到Client的數(shù)據(jù)傳送,Server進入LAST_ACK狀態(tài)
- 第四次揮手: Client收到FIN后,Client進入TIME_WAIT狀態(tài),接著發(fā)送一個ACK給Server,確認序號為收到序號+1,Server進入CLOSED狀態(tài),完成四次揮手
一、為什么會有TIME_WAIT狀態(tài)
客戶端連接在收到服務器的結(jié)束報文段之后,不會直接進入CLOSED狀態(tài),而是轉(zhuǎn)移到TIME_WAIT狀態(tài)。在這個狀態(tài),客戶端連接要等待一段長為2MSL,即兩倍的報文段最大生存時間,才能完全關(guān)閉,其原因主要有兩點:
- 確保有足夠的時間放對方收到ACK包
- 避免新舊連接混淆
二、為什么需要四次握手才能斷開連接
因為TCP連接是全雙工的網(wǎng)絡協(xié)議,允許同時通信的雙方同時進行數(shù)據(jù)的收發(fā),同樣也允許收發(fā)兩個方向的連接被獨立關(guān)閉,以避免client數(shù)據(jù)發(fā)送完畢,向server發(fā)送FIN關(guān)閉連接,而server還有發(fā)送到client的數(shù)據(jù)沒有發(fā)送完畢的情況。所以關(guān)閉TCP連接需要進行四次握手,每次關(guān)閉一個方向上的連接需要FIN和ACK兩次握手,發(fā)送發(fā)和接收方都需要FIN報文和ACK報文
三、服務器出現(xiàn)大量CLOSE_WAIT狀態(tài)的原因
是由于對方關(guān)閉socket連接,我方忙于讀或?qū)?,沒有及時關(guān)閉連接
當客戶端因為某種原因先于服務端發(fā)出了FIN信號,就會導致服務端被動關(guān)閉,若服務端不主動關(guān)閉socket發(fā)FIN給Client,此時服務端Socket會處于CLOSEWAIT狀態(tài)(而不是LASTACK狀態(tài))。通常來說,一個CLOSEWAIT會維持至少2個小時的時間(系統(tǒng)默認超時時間的是7200秒,也就是2小時)。如果服務端程序因某個原因?qū)е孪到y(tǒng)造成一堆CLOSEWAIT消耗資源,那么通常是等不到釋放那一刻,系統(tǒng)就已崩潰
解決:1、檢查代碼,特別是釋放資源的代碼 2、檢查配置,特別是處理請求的線程配置
Linux的檢查代碼:
- netstat -n|awk '/^tcp/{++S[$NF]}END{for(a in S) print a,S[a]}'
五、總結(jié)
到這里TCP的三次握手四次揮手就講完了,好久都沒有寫技術(shù)文章了,寫了一下,感覺還挺好的,上面是博主的認識,有寫的不好的地方,大家可以在評論區(qū)討論或者提問。