一、TCP概述
每一條TCP連接都有兩個端點,這種端點我們叫作套接字(socket),它的定義為端口號拼接到IP地址即構成了套接字,
例如,若IP地址為192.0.0.1 而端口號為8000,那么得到的套接字為192.0.0.1:8000
二、TCP報文格式
ACK、SYN和FIN這些大寫的單詞表示標志位,其值要么是1,要么是0;ack、seq小寫的單詞表示序號
同步SYN:(Synchronize ),SYN=1表示這是一個連接請求報文,或連接接受報文。SYN這個標志位只有在TCP建產連接時才會被置1,握手完成后SYN標志位被置0
確認ACK:僅當ACK=1時,確認號字段才有效。ACK=0時,確認號無效。如:當SYN=1,ACK=0時表示這是一個連接請求報文段,若同意連接,則在響應報文段中使得SYN=1,ACK=1
終止FIN:用來釋放一個連接。FIN=1表示:此報文段的發送方的數據已經發送完畢,並要求釋放
序列號seq:(Sequence Number),占4個字節,表示報文段攜帶數據的第一個字節的編號,TCP連接中傳送的字節流中的每個字節都按順序編號。例如,一段報文的序號值是 301 ,而攜帶的數據共有100字段,顯然下一個報文段(如果還有的話)的數據序號應該從401開始;,圖中的 x 和 y,
確認號ack:占4個字節,期待收到對方下一個報文段的第一個數據字節的序號,例如,B收到了A發送過來的報文段,其序列號seq是1,而數據長度是100字節,這表明B正確的收到了A發送的到序號從1到100為止的數據。因此,B期望收到A的下一個數據序號是100+1,於是B在發送給A的確認報文段中把確認號置為101
三、三次握手,四次揮手
3.1 TCP連接的建立過程——三次握手
建立雙向通道的過程稱之為三次握手,建立通道的發起者可以是客戶端也可以是服務端,下面我們就以客戶端先主動發起為例
-
客戶端會朝服務端發送一個請求詢問服務端:"我能不能挖一條通往你家的地道"
-
服務端收到請求,回復說:"好吧 你挖吧",由於TCP是雙向通道,客戶端挖向服務端的通道只能給客戶端朝服務端發消息使用,服務端要向給客戶端發消息是沒辦法走這一條通道的,需要自己挖一條通往客戶端的通道
所以服務端在回復同意客戶端挖通道的同時還會問一句:"那我能不能也挖一條通往你家的通道"
-
客戶端收到服務端請求后客戶端到服務端的通道就挖成功了,然后也會同意服務端的請求,服務端挖向客戶端的通道也會成功
- 1.服務器准備:TCP服務器進程先創建傳輸控制塊TCB,時刻准備接受客戶進程的連接請求,此時服務器就進入了LISTEN(監聽)狀態
- 2.客戶端准備:TCP客戶進程也是先創建傳輸控制塊TCB
-
3.第一次握手:客戶端向服務器發出連接請求報文,報文首部中的同步標志SYN=1,同時生成一個初始序列號 seq=x ,此時,TCP客戶端進程進入了 SYN-SENT(同步已發送狀態)狀態。TCP規定,SYN報文段(SYN=1的報文段)不能攜帶數據,但需要消耗掉一個序號
-
4.第二次握手:TCP服務器收到請求報文后,如果同意連接,則發出確認報文。確認報文中確認標志 ACK=1,SYN=1,確認號是ack=x+1,同時也要為自己初始化一個序列號 seq=y,此時,TCP服務器進程進入了SYN-RCVD(同步收到)狀態。這個報文也不能攜帶數據,但是同樣要消耗一個序號
-
5.第三次握手:TCP客戶端收到確認后,還要向服務器再次給出確認。確認報文的確認標志ACK=1,確認號ack=y+1,自己的序列號seq=x+1,此時,TCP連接建立,客戶端進入ESTABLISHED(已建立連接)狀態。TCP規定,ACK報文段可以攜帶數據,但是如果不攜帶數據則不消耗序號
- 6.連接成功:當服務器收到客戶端的確認后也進入ESTABLISHED狀態,此后雙方就可以開始通信了

LISTEN - 偵聽來自遠方TCP端口的連接請求; SYN-SENT -在發送連接請求后等待匹配的連接請求; SYN-RECEIVED - 在收到和發送一個連接請求后等待對連接請求的確認; ESTABLISHED- 代表一個打開的連接,數據可以傳送給用戶; FIN-WAIT-1 - 等待遠程TCP的連接中斷請求,或先前的連接中斷請求的確認; FIN-WAIT-2 - 從遠程TCP等待連接中斷請求; CLOSE-WAIT - 等待從本地用戶發來的連接中斷請求; CLOSING -等待遠程TCP對連接中斷的確認; LAST-ACK - 等待原來發向遠程TCP的連接中斷請求的確認; TIME-WAIT -等待足夠的時間以確保遠程TCP接收到連接中斷請求的確認; CLOSED - 沒有任何連接狀態;
3.2 TCP鏈接的釋放過程——四次揮手
建立一個連接需要三次握手,而終止一個連接要經過四次握手
當服務端或者客戶端不想再與對方進行通信之后,雙方任意一方都可以主動發起斷開鏈接的請求,我們還是以客戶端主動發起為例
-
客戶端由於已經沒有任何需要發送給服務端的消息了,所以發起斷開客戶端到服務端的通道請求
-
服務端收到該請求后同意了 至此客戶端到服務端的單項通道斷開
-
服務端這個時候不會立刻朝客戶端發器請求說那我也斷開到你家的通道吧,服務端需要想想我手上還有沒有需要發送給客戶端的消息,如果還有的話,那我不能立馬斷開,先把數據發完才能斷
等服務端檢查完畢之后發送也沒有數據要給客戶端了,這個時候就會朝客戶端發起斷開服務端到客戶端的通道請求
-
客戶端同意該請求,至此四次揮手完成
數據發送之后用戶端發出分手請求,服務端會立馬同意,但是服務端自己不會立即發出分手請求,因為可能還有數據傳輸沒有完成,檢測數據是否發送后才會提出分手,一次中間兩次不能合並
四、常見面試問題
【問題1】
肯定不行,三次握手可以有效避免已經失效的連接請求報文突然又傳送到了服務器,從而產生錯誤和資源浪費。
如果采用兩次握手建立連接,假設有這樣一種場景,客戶端發送了第一個請求連接並且沒有丟失,只是因為在網絡結點中滯留的時間太長了,由於TCP的客戶端遲遲沒有收到確認報文,以為服務器沒有收到,此時重新向服務器發送這條報文,此后客戶端和服務器經過兩次握手完成連接,傳輸數據,然后關閉連接。此時此前滯留的那一次請求連接,網絡通暢了到達了服務器,這個報文本該是失效的,但是,兩次握手的機制將會讓客戶端和服務器再次建立連接,這將導致不必要的錯誤和資源的浪費。
如果采用的是三次握手,就算是那一次失效的報文傳送到服務端,服務端接受到了那條失效報文並且回復了確認報文,但是客戶端很清楚自己並沒有發送額外請求,知道這是個作廢的請求,因此不會再次發出確認。由於服務器收不到確認,就知道客戶端並沒有請求連接,
從另外一個角度講,只有三次握手才能確認彼此的接受與發送能力是否正常,而兩次卻不可以
-
-
第二次:服務器發給客戶端,客戶端知道自己發送、接收正常,服務器接收、發送正常
-
第三次:客戶端發給服務器:服務器知道客戶端發送,接收正常,自己接收,發送也正常
【問題2】三次握手過程中可以攜帶數據嗎?
第一次、第二次握手不可以攜帶數據,而第三次握手是可以攜帶數據的。
第一次不可以攜帶數據,也不可以先將數據緩存下來,等握手成功再提交給應用程序,這樣會放大SYN FLOOD攻擊。如果有人要惡意攻擊服務器,那他每次都在第一次握手中的 SYN 報文中放入大量的數據,因為攻擊者根本就不理服務器的接收、發送能力是否正常,然后瘋狂着重復發 SYN 報文的話,這會讓服務器花費很多時間、內存空間來接收這些報文。也就是說,第一次握手可以放數據的話,其中一個簡單的原因就是會讓服務器更加容易受到攻擊了。
第三次的話,能夠發出第三次握手報文的主機,肯定接收到第二次握手報文,因為偽造IP的主機是不會接收到第二次報文的。所以,能夠發出第三次握手報文的,應該是合法的用戶。盡管服務器側的狀態還沒有“established”,接收到第三次握手的瞬間,狀態就會切換為“established”,里面攜帶的數據按照正常流程走就好
【問題3】如果已經建立了連接,但是客戶端突然出現故障了怎么辦?
TCP還設有一個保活計時器SYN Timeout,顯然,客戶端如果出現故障,服務器不能一直等下去,白白浪費資源。服務器每收到一次客戶端的請求后都會重新復位這個計時器,時間通常是設置為2小時,若兩小時還沒有收到客戶端的任何數據,服務器就會發送一個探測報文段,以后每隔75秒鍾發送一次。若一連發送10個探測報文仍然沒反應,服務器就認為客戶端出了故障,接着就關閉連接。
【問題4】SYN洪水攻擊原理
利用TCP協議缺陷,發送大量偽造的TCP連接請求,使被攻擊方資源耗盡(CPU滿負荷或內存不足)的攻擊方式
雖然按道理,四個報文都發送完畢,我們可以直接進入CLOSE狀態了,但是網絡是不可靠的,有可能最后一個ACK丟失。
在Client發送出最后的ACK回復后,該ACK可能丟失。所以TIME_WAIT狀態就是用來確認對方是否收到ACK,服務器如果沒有收到ACK,不知道客戶端是否接收完全部數據不敢關閉自己的通道,將不斷重復第三次揮手發送FIN片段。所以客戶端不能立即關閉,它必須確認Server接收到了該ACK。
Client會在發送出ACK之后進入到TIME_WAIT狀態。Client會設置一個計時器,等待2MSL的時間。如果在該時間內再次收到FIN,那么Client會重發ACK並再次等待2MSL。所謂的2MSL是兩倍的MSL。MSL指一個片段在網絡中最大的存活時間,2MSL就是一個發送和一個回復所需的最大時間。如果直到2MSL,Client都沒有再次收到FIN,那么Client推斷ACK已經被成功接收,則結束TCP連接
【問題6】RFC793明確規定,除了第一個握手報文SYN除外,其它所有報文必須將ACK = 1,RFC規定的背后肯定有合理性的一面,能否深究一下原因?
TCP報文是在IP網絡上傳輸,丟包是家常便飯,接收方要抓住一切的機會,把消息告訴發送方。最方便的方式就是,任何我方發送的TCP報文,都要捎帶着ACK狀態位。
ACK狀態位單獨能承擔這個消息傳遞的任務嗎?
不能!需要有確認號ack配合才行。
如客戶端序列號seq=x,則服務端接收完全部數據后應該告訴對面ack=x+1,客戶端就知道服務端已經全部接收到
如果我方發出的ack= 10001,那意味着序列號10000及之前的字節已經成功接收
【問題7】(ISN)seq是固定的嗎
三次握手的一個重要功能是客戶端和服務端交換ISN(Initial Sequence Number), 以便讓對方知道接下來接收數據的時候如何按序列號組裝數據。
如果ISN是固定的,攻擊者很容易猜出后續的確認號,因此 ISN 是動態生成的。
【問題8】關於SYN-ACK 重傳次數的問題
服務器發送完SYN-ACK包,如果未收到客戶確認包,服務器進行首次重傳,等待一段時間仍未收到客戶確認包,進行第二次重傳,如果重傳次數超 過系統規定的最大重傳次數,系統將該連接信息從半連接隊列中刪除。注意,每次重傳等待的時間不一定相同,一般會是指數增長,例如間隔時間為 1s, 2s, 4s, 8s, ….