在TCP/IP協議中,TCP協議提供可靠的連接服務,采用三次握手建立一個連接。
第一次握手:建立連接時,客戶端發送syn包(syn=j)到服務器,並進入SYN_SEND狀態,等待服務器確認;
第二次握手:服務器收到syn包,必須確認客戶的SYN(ack=j+1),同時自己也發送一個SYN包(syn=k),即SYN+ACK包,此時服務器進入SYN_RECV狀態;
第三次握手:客戶端收到服務器的SYN+ACK包,向服務器發送確認包ACK(ack=k+1),此包發送完畢,客戶端和服務器進入ESTABLISHED狀態,完成三次握手。
完成三次握手,客戶端與服務器開始傳送數據,在上述過程中,還有一些重要的概念:
未連接隊列:在三次握手協議中,服務器維護一個未連接隊列,該隊列為每個客戶端的SYN包(syn=j)開設一個條目,該條目表明服務器已收到SYN 包,並向客戶發出確認,正在等待客戶的確認包。這些條目所標識的連接在服務器處於Syn_RECV狀態,當服務器收到客戶的確認包時,刪除該條目,服務器 進入ESTABLISHED狀態。
Backlog參數:表示未連接隊列的最大容納數目。
SYN-ACK 重傳次數 服務器發送完SYN-ACK包,如果未收到客戶確認包,服務器進行首次重傳,等待一段時間仍未收到客戶確認包,進行第二次重傳,如果重傳次數超 過系統規定的最大重傳次數,系統將該連接信息從半連接隊列中刪除。注意,每次重傳等待的時間不一定相同。
半連接存活時間:是指半連接隊列的條目存活的最長時間,也即服務從收到SYN包到確認這個報文無效的最長時間,該時間值是所有重傳請求包的最長等待時間總和。有時我們也稱半連接存活時間為Timeout時間、SYN_RECV存活時間。
====================================================================
現在,我們來看一個完整的流程,在一個TCP socket上系統調用connect究竟是如何建立起一個到對端的連接的。我們還是以實驗環境172.16.48.2向172.16.48.1的端口5002發起連接請求為例。
第一步,172.16.48.2向172.16.48.1發起連接請求,發送一個SYN段,指明目的端口5002,通告自己的初始序號(ISN,由協議棧 隨機產生的一個32位數),設置確認序號為0(因為還沒有收到過對端的數據),通告自己的滑動窗口大小為5840(對端是5792,這似乎有問題,有待進 一步細查),窗口擴大因子為2(在首部選項中),通告最大報文段長度為1460(本地局域網),下面是數據內容(已剝去鏈路層的以太網首部和網絡層的IP 首部):
數據內容 含義
基本首部
80 0e 源端口(32782)
13 8a 目的端口(5002)
00 00 07 bc 初始序號ISN
00 00 00 00 確認序號
a 首部長度
0 02 標志位,SYN=1
16 d0 滑動窗口大小(5840)
64 9e 校驗和
00 00 緊急指針
TCP選項
02 04 05 b4 最大報文段長度(1460)
04 02 允許SACK
08 0a 00 0a 79 14 00 00 00 00 時間戳(0x000a7914),回顯時間戳(0)
01 占位。
03 03 02 窗口擴大因子(2)
第二步,172.16.48.1收到請求包,檢查標志位,發現SYN=1,認為這是一個初始化連接的請求,回應這個SYN,同時也發送自己的SYN段(即 ACK,SYN同時置位)。因為SYN本身要占用一個序號(還有標志FIN也要占用一個序號)。所以,確認序號設置為172.16.48.2的ISN加1 (即172.16.48.1期望收到來自172.16.48.2的下一個包的第一個序號為0x07bd。同時也要通告自己的初始序號,滑動窗口大小,窗口 擴大因子,最大報文段長度等,下面是數據內容:
數據內容 含義
基本TCP首部
13 8a 源端口(5002)
80 0e 目的端口(32782)
98 8e 40 91 初始序號ISN
00 00 07 bd 確認序號(對端ISN+1)
a 首部長度
0 12 標志位,ACK=1, SYN=1
16 a0 滑動窗口大小
65 d7 校驗和
00 00 緊急指針
TCP選項
02 04 05 b4 最大報文段長度(1460)
04 02 允許SACK
08 0a 00 3c 25 8a 00 0a 79 14 時間戳(0x003c258a),回顯時間戳(000a7914)
01 占位
03 03 02 窗口擴大因子(2)
第三步,172.16.48.2對來自172.16.48.1的SYN段進行確認,至此,TCP三次握手協議完成,連接建立,在172.16.48.2收 到SYN段時,將自己對應的socket的狀態由TCP_SYN_SENT改為TCP_ESTABLISHED,進入連接建立狀態,下面是數據內容:
數據內容 含義
80 0e 源端口(32782)
13 8a 目的端口(5002)
00 00 07 bd 序號(已不是ISN了)
98 8e 40 92 確認序號(對端ISN+1)
8 首部長度(8*4=32,有12字節的選項)
0 10 標志,ACK=1
05 b4 滑動窗口大小(1460,有問題?待確認)
a5 8a 校驗和
00 00 緊急指針
01 占位
01 占位
08 0a 00 0a 79 14 00 3c 25 8a 時間戳(0x0a007914), 回顯時間戳(0x003c258a)
=====================================================================
7、簡述TCP三次握手過程,並說明為什么要3次握手
TCP 三次握手
TCP 連接是通過三次握手進行初始化的。三次握手的目的是同步連接雙方的序列號和確認號並交換 TCP 窗口大小信息。以下步驟概述了通常情況下客戶端計算機聯系服務器計算機的過程:
1. 客戶端向服務器發送一個SYN置位的TCP報文,其中包含連接的初始序列號x和一個窗口大小(表示客戶端上用來存儲從服務器發送來的傳入段的緩沖區的大小)。
2. 服務器收到客戶端發送過來的SYN報文后,向客戶端發送一個SYN和ACK都置位的TCP報文,其中包含它選擇的初始序列號y、對客戶端的序列號的確認x+1和一個窗口大小(表示服務器上用來存儲從客戶端發送來的傳入段的緩沖區的大小)。
3. .客戶端接收到服務器端返回的SYN+ACK報文后,向服務器端返回一個確認號y+1和序號x+1的ACK報文,一個標准的TCP連接完成。
TCP 使用類似的握手過程來結束連接。這可確保兩個主機均能完成傳輸並確保所有的數據均得以接收
TCP Client Flags TCP Server
1 Send SYN (seq=x) ----SYN---> SYN Received
2 SYN/ACK Received <---SYN/ACK---- Send SYN (seq=y), ACK (x+1)
3 Send ACK (y+1) ----ACK---> ACK Received, Connection Established
w: ISN (Initial Sequence Number) of the Client
x: ISN of the Server
==========================================================
握手階段:
序號 方向 seq ack
1 A->B 10000 0
2 B->A 20000 10000+1=10001
3 A->B 10001 20000+1=20001
解釋:
1:A向B發起連接請求,以一個隨機數初始化A的seq,這里假設為10000,此時ACK=0
2:B收到A的連接請求后,也以一個隨機數初始化B的seq,這里假設為20000,意思是:你的請求我已收到,我這方的數據流就從這個數開始。B的ACK是A的seq加1,即10000+1=10001
3:A收到B的回復后,它的seq是它的上個請求的seq加1,即10000+1=10001,意思也是:你的回復我收到了,我這方的數據流就從這個數開始。A此時的ACK是B的seq加1,即20000+1=20001
數據傳輸階段:
序號 方向 seq ack size
23 A->B 40000 70000 1514
24 B->A 70000 40000+1514-54=41460 54
25 A->B 41460 70000+54-54=70000 1514
26 B->A 70000 41460+1514-54=42920 54
解釋:
23:B接收到A發來的seq=40000,ack=30000,size=1514的數據包
24:於是B向A也發一個數據包,告訴B,你的上個包我收到了。B的seq就以它收到的數據包的ACK填充,ACK是它收到的數據包的SEQ加上數據包的大小(不包括以太網協議頭,IP頭,TCP頭),以證實B發過來的數據全收到了。
25:A在收到B發過來的seq為41460的數據包時,一看到41460,正好是它的上個數據包的seq加上包的大小,就明白,上次發送的數據包已安全 到達。於是它再發一個數據包給B。這個正在發送的數據包的seq也以它收到的數據包的ACK填充,ACK就以它收到的數據包的seq(70000)加上包 的size(54)填充,即ack=70000+54-54(全是頭長,沒數據項)。
26:一樣的啊
在TCP/IP協議中,TCP協議提供可靠的連接服務,采用三次握手建立一個連接,如圖1所示。
(1) 第一次握手:建立連接時,客戶端A發送SYN包(SYN=j)到服務器B,並進入SYN_SEND狀態,等待服務器B確認。
(2) 第二次握手:服務器B收到SYN包,必須確認客戶A的SYN(ACK=j+1),同時自己也發送一個SYN包(SYN=k),即SYN+ACK包,此時服務器B進入SYN_RECV狀態。
(3) 第三次握手:客戶端A收到服務器B的SYN+ACK包,向服務器B發送確認包ACK(ACK=k+1),此包發送完畢,客戶端A和服務器B進入ESTABLISHED狀態,完成三次握手。
完成三次握手,客戶端與服務器開始傳送數據。
圖1 TCP三次握手建立連接
由於TCP連接是全雙工的,因此每個方向都必須單獨進行關閉。這個原則是當一方完成它的數據發送任務后就能發送一個FIN來終止這個方向的連接。收到一個 FIN只意味着這一方向上沒有數據流動,一個TCP連接在收到一個FIN后仍能發送數據。首先進行關閉的一方將執行主動關閉,而另一方執行被動關閉。
(1)客戶端A發送一個FIN,用來關閉客戶A到服務器B的數據傳送(報文段4)。
(2)服務器B收到這個FIN,它發回一個ACK,確認序號為收到的序號加1(報文段5)。和SYN一樣,一個FIN將占用一個序號。
(3)服務器B關閉與客戶端A的連接,發送一個FIN給客戶端A(報文段6)。
(4)客戶端A發回ACK報文確認,並將確認序號設置為收到序號加1(報文段7)。
TCP采用四次揮手關閉連接如圖2所示。
圖2 TCP四次揮手關閉連接
1.為什么建立連接協議是三次握手,而關閉連接卻是四次握手呢?
這是因為服務端的LISTEN狀態下的SOCKET當收到SYN報文的連接請求后,它可以把ACK和SYN(ACK起應答作用,而SYN起同步作用)放在一個報文里來發送。但關閉連接時,當收到對方的FIN報文通知時,它僅僅表示對方沒有數據發送給你了;但未必你所有的數據都全部發送給對方了,所以你可能未必會馬上會關閉SOCKET,也即你可能還需要發送一些數據給對方之后,再發送FIN報文給對方來表示你同意現在可以關閉連接了,所以它這里的ACK報文和FIN報文多數情況下都是分開發送的。
2.為什么TIME_WAIT狀態還需要等2MSL后才能返回到CLOSED狀態?
TIME_WAIT狀態由兩個存在的理由。
(1)可靠的實現TCP全雙工鏈接的終止
這是因為雖然雙方都同意關閉連接了,而且握手的4個報文也都協調和發送完畢,按理可以直接回到CLOSED狀態(就好比從SYN_SEND狀態到ESTABLISH狀態那樣);但是因為我們必須要假想網絡是不可靠的,你無法保證你最后發送的ACK報文會一定被對方收到,因此對方處於LAST_ACK狀態下的SOCKET可能會因為超時未收到ACK報文,而重發FIN報文,所以這個TIME_WAIT狀態的作用就是用來重發可能丟失的ACK報文。
(2)允許老的重復的分節在網絡中消逝
假設在12.106.32.254的1500端口和206.168.1.112.219的21端口之間有一個TCP連接。我們關閉這個鏈接,過一段時間后在相同的IP地址和端口建立另一個連接。后一個鏈接成為前一個的化身。因為它們的IP地址和端口號都相同。TCP必須防止來自某一個連接的老的重復分組在連接已經終止后再現,從而被誤解成屬於同一鏈接的某一個某一個新的化身。為做到這一點,TCP將不給處於TIME_WAIT狀態的鏈接發起新的化身。既然TIME_WAIT狀態的持續時間是MSL的2倍,這就足以讓某個方向上的分組最多存活msl秒即被丟棄,另一個方向上的應答最多存活msl秒也被丟棄。通過實施這個規則,我們就能保證每成功建立一個TCP連接時。來自該鏈接先前化身的重復分組都已經在網絡中消逝了。
3. 為什么不能用兩次握手進行連接?
我們知道,3次握手完成兩個重要的功能,既要雙方做好發送數據的准備工作(雙方都知道彼此已准備好),也要允許雙方就初始序列號進行協商,這個序列號在握手過程中被發送和確認。
現在把三次握手改成僅需要兩次握手,死鎖是可能發生的。作為例子,考慮計算機S和C之間的通信,假定C給S發送一個連接請求分組,S收到了這個分組,並發 送了確認應答分組。按照兩次握手的協定,S認為連接已經成功地建立了,可以開始發送數據分組。可是,C在S的應答分組在傳輸中被丟失的情況下,將不知道S 是否已准備好,不知道S建立什么樣的序列號,C甚至懷疑S是否收到自己的連接請求分組。在這種情況下,C認為連接還未建立成功,將忽略S發來的任何數據分 組,只等待連接確認應答分組。而S在發出的分組超時后,重復發送同樣的分組。這樣就形成了死鎖。
補充:
a. 默認情況下(不改變socket選項),當你調用close( or closesocket,以下說close不再重復)時,如果發送緩沖中還有數據,TCP會繼續把數據發送完。
b. 發送了FIN只是表示這端不能繼續發送數據(應用層不能再調用send發送),但是還可以接收數據。
c. 應用層如何知道對端關閉?通常,在最簡單的阻塞模型中,當你調用recv時,如果返回0,則表示對端關閉。在這個時候通常的做法就是也調用close,那么TCP層就發送FIN,繼續完成四次握手。如果你不調用close,那么對端就會處於FIN_WAIT_2狀態,而本端則會處於CLOSE_WAIT狀態。這個可以寫代碼試試。
d. 在很多時候,TCP連接的斷開都會由TCP層自動進行,例如你CTRL+C終止你的程序,TCP連接依然會正常關閉,你可以寫代碼試試。
插曲:
特別的TIME_WAIT狀態:
從以上TCP連接關閉的狀態轉換圖可以看出,主動關閉的一方在發送完對對方FIN報文的確認(ACK)報文后,會進入TIME_WAIT狀態。TIME_WAIT狀態也稱為2MSL狀態。什么是2MSL?MSL即Maximum Segment Lifetime,也就是報文最大生存時間,引用《TCP/IP詳解》中的話:“它(MSL)是任何報文段被丟棄前在網絡內的最長時間。”那么,2MSL也就是這個時間的2倍。其實我覺得沒必要把這個MSL的確切含義搞明白,你所需要明白的是,當TCP連接完成四個報文段的交換時,主動關閉的一方將繼續等待一定時間(2-4分鍾),即使兩端的應用程序結束。你可以寫代碼試試,然后用netstat查看下。
為什么需要2MSL?根據《TCP/IP詳解》和《The TCP/IP Guide》中的說法,有兩個原因:
其一,保證發送的ACK會成功發送到對方,如何保證?我覺得可能是通過超時計時器發送。這個就很難用代碼演示了。
其二,報文可能會被混淆,意思是說,其他時候的連接可能會被當作本次的連接。直接引用《The TCP/IP Guide》的說法:The second is to provide a “buffering period”between the end of this connection and any subsequent ones. If not for this period, it is possible that packets from different connections could be mixed, creating confusion.
TIME_WAIT狀態所帶來的影響:
當某個連接的一端處於TIME_WAIT狀態時,該連接將不能再被使用。事實上,對於我們比較有現實意義的是,這個端口將不能再被使用。某個端口處於TIME_WAIT狀態(其實應該是這個連接)時,這意味着這個TCP連接並沒有斷開(完全斷開),那么,如果你bind這個端口,就會失敗。對於服務器而言,如果服務器突然crash掉了,那么它將無法再2MSL內重新啟動,因為bind會失敗。解決這個問題的一個方法就是設置socket的SO_REUSEADDR選項。這個選項意味着你可以重用一個地址。
對於TIME_WAIT的插曲:
當建立一個TCP連接時,服務器端會繼續用原有端口監聽,同時用這個端口與客戶端通信。而客戶端默認情況下會使用一個隨機端口與服務器端的監聽端口通信。有時候,為了服務器端的安全性,我們需要對客戶端進行驗證,即限定某個IP某個特定端口的客戶端。客戶端可以使用bind來使用特定的端口。對於服務器端,當設置了SO_REUSEADDR選項時,它可以在2MSL內啟動並listen成功。但是對於客戶端,當使
用bind並設置SO_REUSEADDR時,如果在2MSL內啟動,雖然bind會成功,但是在windows平台上connect會失敗。而在linux上則不存在這個問題。(我的實驗平台:winxp, ubuntu7.10)
要解決windows平台的這個問題,可以設置SO_LINGER選項。SO_LINGER選項決定調用close時TCP的行為。SO_LINGER涉及到linger結構體,如果設置結構體中l_onoff為非0,l_linger為0,那么調用close時TCP連接會立刻斷開,TCP不會將發送緩沖中未發送的數據發送,而是立即發送一個RST報文給對方,這個時候TCP連接就不會進入TIME_WAIT狀態。如你所見,這樣做雖然解決了問題,但是並不安全。通過以上方式設置SO_LINGER狀態,等同於設置SO_DONTLINGER狀態。
斷開連接時的意外:
這個算不上斷開連接時的意外,當TCP連接發生一些物理上的意外情況時,例如網線斷開,linux上的TCP實現會依然認為該連接有效,而windows則會在一定時間后返回錯誤信息。這似乎可以通過設置SO_KEEPALIVE選項來解決,不過不知道這個選項是否對於所有平台都有效。