## 簡述TCP的三次握手、四次揮手
TCP是一種面向連接的、可靠的、基于字節流的傳輸層通信協議,在發送數據前,通信雙方必須在彼此間建立一條連接。所謂的“連接”,其實是客戶端和服務端保存的一份關于對方的信息,如ip地址、端口號等。
TCP可以看成是一種字節流,它會處理IP層或以下的層的丟包、重復以及錯誤問題。在連接的建立過程中,雙方需要交換一些連接的參數。這些參數可以放在TCP頭部。
一個TCP連接由一個4元組構成,分別是兩個IP地址和兩個端口號。一個TCP連接通常分為三個階段:連接、數據傳輸、退出(關閉)。**通過三次握手建立一個鏈接,通過四次揮手來關閉一個連接。**
**當一個連接被建立或被終止時,交換的報文段只包含TCP頭部,而沒有數據。**
### TCP報文的頭部結構
在了解TCP連接之前先來了解一下TCP報文的頭部結構。

上圖中有幾個字段需要重點介紹下:
(1)序號:seq序號,占32位,用來標識從TCP源端向目的端發送的字節流,發起方發送數據時對此進行標記。
(2)確認序號:ack序號,占32位,只有ACK標志位為1時,確認序號字段才有效,ack=seq+1。
(3)標志位:共6個,即URG、ACK、PSH、RST、SYN、FIN等,具體含義如下:
* ACK:確認序號有效。
* FIN:釋放一個連接。
* PSH:接收方應該盡快將這個報文交給應用層。
* RST:重置連接。
* SYN:發起一個新連接。
* URG:緊急指針(urgent pointer)有效。
需要注意的是:
* 不要將確認序號ack與標志位中的ACK搞混了。
* 確認方ack=發起方seq+1,兩端配對。
### 三次握手
三次握手的本質是確認通信雙方收發數據的能力
首先,我讓信使運輸一份信件給對方,**對方收到了,那么他就知道了我的發件能力和他的收件能力是可以的。**
于是他給我回信,**我若收到了,我便知我的發件能力和他的收件能力是可以的,并且他的發件能力和我的收件能力是可以。**
然而此時他還不知道他的發件能力和我的收件能力到底可不可以,于是我最后回饋一次**,他若收到了,他便清楚了他的發件能力和我的收件能力是可以的。**
這,就是三次握手,這樣說,你理解了嗎?

* 第一次握手:客戶端要向服務端發起連接請求,首先客戶端隨機生成一個起始序列號ISN(比如是100),那客戶端向服務端發送的報文段包含SYN標志位(也就是SYN=1),序列號seq=100。
* 第二次握手:服務端收到客戶端發過來的報文后,發現SYN=1,知道這是一個連接請求,于是將客戶端的起始序列號100存起來,并且隨機生成一個服務端的起始序列號(比如是300)。然后給客戶端回復一段報文,回復報文包含SYN和ACK標志(也就是SYN=1,ACK=1)、序列號seq=300、確認號ack=101(客戶端發過來的序列號+1)。
* 第三次握手:客戶端收到服務端的回復后發現ACK=1并且ack=101,于是知道服務端已經收到了序列號為100的那段報文;同時發現SYN=1,知道了服務端同意了這次連接,于是就將服務端的序列號300給存下來。然后客戶端再回復一段報文給服務端,報文包含ACK標志位(ACK=1)、ack=301(服務端序列號+1)、seq=101(第一次握手時發送報文是占據一個序列號的,所以這次seq就從101開始,需要注意的是不攜帶數據的ACK報文是不占據序列號的,所以后面第一次正式發送數據時seq還是101)。當服務端收到報文后發現ACK=1并且ack=301,就知道客戶端收到序列號為300的報文了,就這樣客戶端和服務端通過TCP建立了連接。
### 四次揮手
四次揮手的目的是關閉一個連接

比如客戶端初始化的序列號ISA=100,服務端初始化的序列號ISA=300。TCP連接成功后客戶端總共發送了1000個字節的數據,服務端在客戶端發FIN報文前總共回復了2000個字節的數據。
* 第一次揮手:當客戶端的數據都傳輸完成后,客戶端向服務端發出連接釋放報文(當然數據沒發完時也可以發送連接釋放報文并停止發送數據),釋放連接報文包含FIN標志位(FIN=1)、序列號seq=1101(100+1+1000,其中的1是建立連接時占的一個序列號)。需要注意的是客戶端發出FIN報文段后只是不能發數據了,但是還可以正常收數據;另外FIN報文段即使不攜帶數據也要占據一個序列號。
* 第二次揮手:服務端收到客戶端發的FIN報文后給客戶端回復確認報文,確認報文包含ACK標志位(ACK=1)、確認號ack=1102(客戶端FIN報文序列號1101+1)、序列號seq=2300(300+2000)。此時服務端處于關閉等待狀態,而不是立馬給客戶端發FIN報文,這個狀態還要持續一段時間,因為服務端可能還有數據沒發完。
* 第三次揮手:服務端將最后數據(比如50個字節)發送完畢后就向客戶端發出連接釋放報文,報文包含FIN和ACK標志位(FIN=1,ACK=1)、確認號和第二次揮手一樣ack=1102、序列號seq=2350(2300+50)。
* 第四次揮手:客戶端收到服務端發的FIN報文后,向服務端發出確認報文,確認報文包含ACK標志位(ACK=1)、確認號ack=2351、序列號seq=1102。注意客戶端發出確認報文后不是立馬釋放TCP連接,而是要經過2MSL(最長報文段壽命的2倍時長)后才釋放TCP連接。而服務端一旦收到客戶端發出的確認報文就會立馬釋放TCP連接,所以服務端結束TCP連接的時間要比客戶端早一些。
### 常見面試題
### 為什么TCP連接的時候是3次?2次不可以嗎?
因為需要考慮連接時丟包的問題,如果只握手2次,第二次握手時如果服務端發給客戶端的確認報文段丟失,此時服務端已經準備好了收發數(可以理解服務端已經連接成功)據,而客戶端一直沒收到服務端的確認報文,所以客戶端就不知道服務端是否已經準備好了(可以理解為客戶端未連接成功),這種情況下客戶端不會給服務端發數據,也會忽略服務端發過來的數據。
如果是三次握手,即便發生丟包也不會有問題,比如如果第三次握手客戶端發的確認ack報文丟失,服務端在一段時間內沒有收到確認ack報文的話就會重新進行第二次握手,也就是服務端會重發SYN報文段,客戶端收到重發的報文段后會再次給服務端發送確認ack報文。
### 為什么TCP連接的時候是3次,關閉的時候卻是4次?
因為只有在客戶端和服務端都沒有數據要發送的時候才能斷開TCP。而客戶端發出FIN報文時只能保證客戶端沒有數據發了,服務端還有沒有數據發客戶端是不知道的。而服務端收到客戶端的FIN報文后只能先回復客戶端一個確認報文來告訴客戶端我服務端已經收到你的FIN報文了,但我服務端還有一些數據沒發完,等這些數據發完了服務端才能給客戶端發FIN報文(所以不能一次性將確認報文和FIN報文發給客戶端,就是這里多出來了一次)。
### 為什么客戶端發出第四次揮手的確認報文后要等2MSL的時間才能釋放TCP連接?
這里同樣是要考慮丟包的問題,如果第四次揮手的報文丟失,服務端沒收到確認ack報文就會重發第三次揮手的報文,這樣報文一去一回最長時間就是2MSL,所以需要等這么長時間來確認服務端確實已經收到了。
### 如果已經建立了連接,但是客戶端突然出現故障了怎么辦?
TCP設有一個保活計時器,客戶端如果出現故障,服務器不能一直等下去,白白浪費資源。服務器每收到一次客戶端的請求后都會重新復位這個計時器,時間通常是設置為2小時,若兩小時還沒有收到客戶端的任何數據,服務器就會發送一個探測報文段,以后每隔75秒鐘發送一次。若一連發送10個探測報文仍然沒反應,服務器就認為客戶端出了故障,接著就關閉連接。
- PHP篇
- 函數傳值和傳引用的區別
- 簡述PHP的垃圾回收機制
- 簡述CGI、FAST-CGI、PHP-FPM的關系
- 常見正則表達式
- 多進程寫文件,如何保證都寫成功
- php支持回調函數的數組函數
- MySQL篇
- MySQL的兩種存儲引擎區別
- 事務的四大特性
- 數據庫事務隔離級別
- 什么是索引
- 索引有哪些數據結構,優缺點
- 索引的一些潛規則
- SQL的優化方案
- 簡述MySQL的鎖機制
- 死鎖是怎么產生的?怎么解決?
- 簡述MySQL的主從復制過程,延遲問題怎么解決
- 分布式事務的解決方案
- 數據庫中間件MyCat
- Linux篇
- Linux常用命令
- 對日志文件的IP出現的次數進行統計,并顯示次數最多的前5名
- WEB篇
- 跨域是怎么產生的,如何解決跨域
- Redis篇
- redis介紹
- redis和memcached區別
- redis的持久化方案
- 緩存穿透、擊穿、雪崩、預熱、更新、降級
- 網絡篇
- 計算機網絡體系結構
- 簡述TCP的三次握手、四次揮手過程
- UDP、TCP 區別,適用場景
- HTTP常見狀態碼含義
- 設計模式篇
- 單例模式
- 簡單工廠模式
- 抽象工廠模式
- 觀察者模式
- 策略模式
- 注冊模式
- 適配器模式
- 安全篇
- 跨站腳本攻擊(XSS)
- 跨站點請求偽造(CSRF)
- SQL 注入
- 應用層拒絕服務攻擊
- PHP安全
- 運維篇
- docker面試題
- 消息隊列篇
- 架構篇
- 數據結構與算法篇