TCP/IP 狀態(tài)機,如下圖所示:

在TCP/IP協(xié)議中,TCP協(xié)議提供可靠的連接服務(wù),采用三次握手建立一個連接,如圖1所示。 (SYN包表示標(biāo)志位syn=1,ACK包表示標(biāo)志位ack=1,SYN+ACK包表示標(biāo)志位syn=1,ack=1)
(1) 第一次握手:建立連接時,客戶端A發(fā)送SYN包(SEQ_NUMBER=j)到服務(wù)器B,并進入SYN_SEND狀態(tài),等待服務(wù)器B確認。
(2) 第二次握手:服務(wù)器B收到SYN包,必須確認客戶A的SYN(ACK_NUMBER=j+1),同時自己也發(fā)送一個SYN包(SEQ_NUMBER=k),即SYN+ACK包,此時服務(wù)器B進入SYN_RECV狀態(tài)。
(3) 第三次握手:客戶端A收到服務(wù)器B的SYN+ACK包,向服務(wù)器B發(fā)送確認包ACK(ACK_NUMBER=k+1),此包發(fā)送完畢,客戶端A和服務(wù)器B進入ESTABLISHED狀態(tài),完成三次握手。
完成三次握手,客戶端與服務(wù)器開始傳送數(shù)據(jù)。

由于TCP連接是全雙工的,因此每個方向都必須單獨進行關(guān)閉。這個原則是當(dāng)一方完成它的數(shù)據(jù)發(fā)送任務(wù)后就能發(fā)送一個FIN來終止這個方向的連接。收到一個 FIN只意味著這一方向上沒有數(shù)據(jù)流動,一個TCP連接在收到一個FIN后仍能發(fā)送數(shù)據(jù)。首先進行關(guān)閉的一方將執(zhí)行主動關(guān)閉,而另一方執(zhí)行被動關(guān)閉。
(1)客戶端A發(fā)送一個FIN,用來關(guān)閉客戶A到服務(wù)器B的數(shù)據(jù)傳送(報文段4)。
(2)服務(wù)器B收到這個FIN,它發(fā)回一個ACK,確認序號為收到的序號加1(報文段5)。和SYN一樣,一個FIN將占用一個序號。
(3)服務(wù)器B關(guān)閉與客戶端A的連接,發(fā)送一個FIN給客戶端A(報文段6)。
(4)客戶端A發(fā)回ACK報文確認,并將確認序號設(shè)置為收到序號加1(報文段7)。
TCP采用四次揮手關(guān)閉連接如圖2所示。

PS:另一個關(guān)閉連接的圖

1.為什么建立連接協(xié)議是三次握手,而關(guān)閉連接卻是四次握手呢?
這是因為服務(wù)端的LISTEN狀態(tài)下的SOCKET當(dāng)收到SYN報文的連接請求后,它可以把ACK和SYN(ACK起應(yīng)答作用,而SYN起同步作用)放在一個報文里來發(fā)送。但關(guān)閉連接時,當(dāng)收到對方的FIN報文通知時,它僅僅表示對方?jīng)]有數(shù)據(jù)發(fā)送給你了;但未必你所有的數(shù)據(jù)都全部發(fā)送給對方了,所以你可能未必會馬上會關(guān)閉SOCKET,也即你可能還需要發(fā)送一些數(shù)據(jù)給對方之后,再發(fā)送FIN報文給對方來表示你同意現(xiàn)在可以關(guān)閉連接了,所以它這里的ACK報文和FIN報文多數(shù)情況下都是分開發(fā)送的。
2.為什么TIME_WAIT狀態(tài)還需要等2MSL后才能返回到CLOSED狀態(tài)?
這是因為雖然雙方都同意關(guān)閉連接了,而且握手的4個報文也都協(xié)調(diào)和發(fā)送完畢,按理可以直接回到CLOSED狀態(tài)(就好比從SYN_SEND狀態(tài)到ESTABLISH狀態(tài)那樣);但是因為我們必須要假想網(wǎng)絡(luò)是不可靠的,你無法保證你最后發(fā)送的ACK報文會一定被對方收到,因此對方處于LAST_ACK狀態(tài)下的SOCKET可能會因為超時未收到ACK報文,而重發(fā)FIN報文,所以這個TIME_WAIT狀態(tài)的作用就是用來重發(fā)可能丟失的ACK報文。
3. 為什么不能用兩次握手進行連接?
我們知道,3次握手完成兩個重要的功能,既要雙方做好發(fā)送數(shù)據(jù)的準(zhǔn)備工作(雙方都知道彼此已準(zhǔn)備好),也要允許雙方就初始序列號進行協(xié)商,這個序列號在握手過程中被發(fā)送和確認。 現(xiàn)在把三次握手改成僅需要兩次握手,死鎖是可能發(fā)生的。作為例子,考慮計算機S和C之間的通信,假定C給S發(fā)送一個連接請求分組,S收到了這個分組,并發(fā)送了確認應(yīng)答分組。按照兩次握手的協(xié)定,S認為連接已經(jīng)成功地建立了,可以開始發(fā)送數(shù)據(jù)分組??墒?,C在S的應(yīng)答分組在傳輸中被丟失的情況下,將不知道S是否已準(zhǔn)備好,不知道S建立什么樣的序列號,C甚至懷疑S是否收到自己的連接請求分組。在這種情況下,C認為連接還未建立成功,將忽略S發(fā)來的任何數(shù)據(jù)分組,只等待連接確認應(yīng)答分組。而S在發(fā)出的分組超時后,重復(fù)發(fā)送同樣的分組。這樣就形成了死鎖。
補充:
a. 默認情況下(不改變socket選項),當(dāng)你調(diào)用close( or closesocket,以下說close不再重復(fù))時,如果發(fā)送緩沖中還有數(shù)據(jù),TCP會繼續(xù)把數(shù)據(jù)發(fā)送完。
b. 發(fā)送了FIN只是表示這端不能繼續(xù)發(fā)送數(shù)據(jù)(應(yīng)用層不能再調(diào)用send發(fā)送),但是還可以接收數(shù)據(jù)。
c. 應(yīng)用層如何知道對端關(guān)閉?通常,在最簡單的阻塞模型中,當(dāng)你調(diào)用recv時,如果返回0,則表示對端關(guān)閉。在這個時候通常的做法就是也調(diào)用close,那么TCP層就發(fā)送FIN,繼續(xù)完成四次握手。如果你不調(diào)用close,那么對端就會處于FIN_WAIT_2狀態(tài),而本端則會處于CLOSE_WAIT狀態(tài)。這個可以寫代碼試試。
d. 在很多時候,TCP連接的斷開都會由TCP層自動進行,例如你CTRL+C終止你的程序,TCP連接依然會正常關(guān)閉,你可以寫代碼試試。
插曲:
特別的TIME_WAIT狀態(tài):
從以上TCP連接關(guān)閉的狀態(tài)轉(zhuǎn)換圖可以看出,主動關(guān)閉的一方在發(fā)送完對對方FIN報文的確認(ACK)報文后,會進入TIME_WAIT狀態(tài)。TIME_WAIT狀態(tài)也稱為2MSL狀態(tài)。
什么是2MSL?MSL即Maximum Segment Lifetime,也就是報文最大生存時間,引用《TCP/IP詳解》中的話:“它(MSL)是任何報文段被丟棄前在網(wǎng)絡(luò)內(nèi)的最長時間?!蹦敲矗?MSL也就是這個時間的2倍。其實我覺得沒必要把這個MSL的確切含義搞明白,你所需要明白的是,當(dāng)TCP連接完成四個報文段的交換時,主動關(guān)閉的一方將繼續(xù)等待一定時間(2-4分鐘),即使兩端的應(yīng)用程序結(jié)束。你可以寫代碼試試,然后用setstat查看下。
為什么需要2MSL?根據(jù)《TCP/IP詳解》和《The TCP/IP Guide》中的說法,有兩個原因:
其一,保證發(fā)送的ACK會成功發(fā)送到對方,如何保證?我覺得可能是通過超時計時器發(fā)送。這個就很難用代碼演示了。
其二,報文可能會被混淆,意思是說,其他時候的連接可能會被當(dāng)作本次的連接。直接引用《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狀態(tài)所帶來的影響:(1到4分鐘)
當(dāng)某個連接的一端處于TIME_WAIT狀態(tài)時,該連接將不能再被使用。事實上,對于我們比較有現(xiàn)實意義的是,這個端口將不能再被使用。某個端口處于TIME_WAIT狀態(tài)(其實應(yīng)該是這個連接)時,這意味著這個TCP連接并沒有斷開(完全斷開),那么,如果你bind這個端口,就會失敗。對于服務(wù)器而言,如果服務(wù)器突然crash掉了,那么它將無法在2MSL內(nèi)重新啟動,因為bind會失敗。解決這個問題的一個方法就是設(shè)置socket的SO_REUSEADDR選項。這個選項意味著你可以重用一個地址。
對于TIME_WAIT的插曲:
當(dāng)建立一個TCP連接時,服務(wù)器端會繼續(xù)用原有端口監(jiān)聽,同時用這個端口與客戶端通信。而客戶端默認情況下會使用一個隨機端口與服務(wù)器端的監(jiān)聽端口通信。有時候,為了服務(wù)器端的安全性,我們需要對客戶端進行驗證,即限定某個IP某個特定端口的客戶端??蛻舳丝梢允褂胋ind來使用特定的端口。對于服務(wù)器端,當(dāng)設(shè)置了SO_REUSEADDR選項時,它可以在2MSL內(nèi)啟動并listen成功。但是對于客戶端,當(dāng)使用bind并設(shè)置SO_REUSEADDR時,如果在2MSL內(nèi)啟動,雖然bind會成功,但是在windows平臺上connect會失敗。而在Linux上則不存在這個問題。(我的實驗平臺:winxp, ubuntu7.10)
要解決windows平臺的這個問題,可以設(shè)置SO_LINGER選項。SO_LINGER選項決定調(diào)用close時TCP的行為。SO_LINGER涉及到linger結(jié)構(gòu)體,如果設(shè)置結(jié)構(gòu)體中l_onoff為非0,l_linger為0,那么調(diào)用close時TCP連接會立刻斷開,TCP不會將發(fā)送緩沖中未發(fā)送的數(shù)據(jù)發(fā)送,而是立即發(fā)送一個RST報文給對方,這個時候TCP連接(關(guān)閉時)就不會進入TIME_WAIT狀態(tài)。如你所見,這樣做雖然解決了問題,但是并不安全。通過以上方式設(shè)置SO_LINGER狀態(tài),等同于設(shè)置SO_DONTLINGER狀態(tài)。
斷開連接時的意外:
這個算不上斷開連接時的意外,當(dāng)TCP連接發(fā)生一些物理上的意外情況時,例如網(wǎng)線斷開,linux上的TCP實現(xiàn)會依然認為該連接有效,而windows則會在一定時間后返回錯誤信息。這似乎可以通過設(shè)置SO_KEEPALIVE選項來解決,不過不知道這個選項是否對于所有平臺都有效。
轉(zhuǎn)載來源自:http://blog.csdn.net/lostyears/article/details/7104349