主要部分,四次握手:
斷開(kāi)連接其實(shí)從我的角度看不區分客戶(hù)端和服務(wù)器端,任何一方都可以調用close(or closesocket)之類(lèi)
的函數開(kāi)始主動(dòng)終止一個(gè)連接。這里先暫時(shí)說(shuō)正常情況。當調用close函數斷開(kāi)一個(gè)連接時(shí),主動(dòng)斷開(kāi)的
一方發(fā)送FIN(finish報文給對方。有了之前的經(jīng)驗,我想你應該明白我說(shuō)的FIN報文時(shí)什么東西。也就是
一個(gè)設置了FIN標志位的報文段。FIN報文也可能附加用戶(hù)數據,如果這一方還有數據要發(fā)送時(shí),將數據附
加到這個(gè)FIN報文時(shí)完全正常的。之后你會(huì )看到,這種附加報文還會(huì )有很多,例如ACK報文。我們所要把握
的原則是,TCP肯定會(huì )力所能及地達到最大效率,所以你能夠想到的優(yōu)化方法,我想TCP都會(huì )想到。
當被動(dòng)關(guān)閉的一方收到FIN報文時(shí),它會(huì )發(fā)送ACK確認報文(對于A(yíng)CK這個(gè)東西你應該很熟悉了)。這里有個(gè)
東西要注意,因為T(mén)CP是雙工的,也就是說(shuō),你可以想象一對TCP連接上有兩條數據通路。當發(fā)送FIN報文
時(shí),意思是說(shuō),發(fā)送FIN的一端就不能發(fā)送數據,也就是關(guān)閉了其中一條數據通路。被動(dòng)關(guān)閉的一端發(fā)送
了ACK后,應用層通常就會(huì )檢測到這個(gè)連接即將斷開(kāi),然后被動(dòng)斷開(kāi)的應用層調用close關(guān)閉連接。
我可以告訴你,一旦當你調用close(or closesocket),這一端就會(huì )發(fā)送FIN報文。也就是說(shuō),現在被動(dòng)
關(guān)閉的一端也發(fā)送FIN給主動(dòng)關(guān)閉端。有時(shí)候,被動(dòng)關(guān)閉端會(huì )將ACK和FIN兩個(gè)報文合在一起發(fā)送。主動(dòng)
關(guān)閉端收到FIN后也發(fā)送ACK,然后整個(gè)連接關(guān)閉(事實(shí)上還沒(méi)完全關(guān)閉,只是關(guān)閉需要交換的報文發(fā)送
完畢),四次握手完成。如你所見(jiàn),因為被動(dòng)關(guān)閉端可能會(huì )將ACK和FIN合到一起發(fā)送,所以這也算不上
嚴格的四次握手---四個(gè)報文段。
在前面的文章中,我一直沒(méi)提TCP的狀態(tài)轉換。在這里我還是在猶豫是不是該將那張四處通用的圖拿出來(lái),
不過(guò),這里我只給出斷開(kāi)連接時(shí)的狀態(tài)轉換圖,摘自<The TCP/IP Guide>:
給出一個(gè)正常關(guān)閉時(shí)的windump信息:
14:00:38.819856 IP cd-zhangmin.1748 > 220.181.37.55.80: F 1:1(0) ack 1 win 65535
14:00:38.863989 IP 220.181.37.55.80 > cd-zhangmin.1748: F 1:1(0) ack 2 win 2920
14:00:38.864412 IP cd-zhangmin.1748 > 220.181.37.55.80: . ack 2 win 65535
補充細節:
關(guān)于以上的四次握手,我補充下細節:
1. 默認情況下(不改變socket選項),當你調用close( or closesocket,以下說(shuō)close不再重復)時(shí),如果
發(fā)送緩沖中還有數據,TCP會(huì )繼續把數據發(fā)送完。
2. 發(fā)送了FIN只是表示這端不能繼續發(fā)送數據(應用層不能再調用send發(fā)送),但是還可以接收數據。
3. 應用層如何知道對端關(guān)閉?通常,在最簡(jiǎn)單的阻塞模型中,當你調用recv時(shí),如果返回0,則表示對端
關(guān)閉。在這個(gè)時(shí)候通常的做法就是也調用close,那么TCP層就發(fā)送FIN,繼續完成四次握手。如果你不調用
close,那么對端就會(huì )處于FIN_WAIT_2狀態(tài),而本端則會(huì )處于CLOSE_WAIT狀態(tài)。這個(gè)可以寫(xiě)代碼試試。
4. 在很多時(shí)候,TCP連接的斷開(kāi)都會(huì )由TCP層自動(dòng)進(jìn)行,例如你CTRL+C終止你的程序,TCP連接依然會(huì )正常關(guān)
閉,你可以寫(xiě)代碼試試。
特別的TIME_WAIT狀態(tài):
從以上TCP連接關(guān)閉的狀態(tài)轉換圖可以看出,主動(dòng)關(guān)閉的一方在發(fā)送完對對方FIN報文的確認(ACK)報文后,
會(huì )進(jìn)入TIME_WAIT狀態(tài)。TIME_WAIT狀態(tài)也稱(chēng)為2MSL狀態(tài)。
什么是2MSL?MSL即Maximum Segment Lifetime,也就是報文最大生存時(shí)間,引用<TCP/IP詳解>中的話(huà):“
它(MSL)是任何報文段被丟棄前在網(wǎng)絡(luò )內的最長(cháng)時(shí)間?!蹦敲?,2MSL也就是這個(gè)時(shí)間的2倍。其實(shí)我覺(jué)得沒(méi)
必要把這個(gè)MSL的確切含義搞明白,你所需要明白的是,當TCP連接完成四個(gè)報文段的交換時(shí),主動(dòng)關(guān)閉的
一方將繼續等待一定時(shí)間(2-4分鐘),即使兩端的應用程序結束。你可以寫(xiě)代碼試試,然后用netstat查看下。
為什么需要2MSL?根據<TCP/IP詳解>和<The TCP/IP Guide>中的說(shuō)法,有兩個(gè)原因:
其一,保證發(fā)送的ACK會(huì )成功發(fā)送到對方,如何保證?我覺(jué)得可能是通過(guò)超時(shí)計時(shí)器發(fā)送。這個(gè)就很難用
代碼演示了。
其二,報文可能會(huì )被混淆,意思是說(shuō),其他時(shí)候的連接可能會(huì )被當作本次的連接。直接引用<The TCP/IP Guide>
的說(shuō)法: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)所帶來(lái)的影響:
當某個(gè)連接的一端處于TIME_WAIT狀態(tài)時(shí),該連接將不能再被使用。事實(shí)上,對于我們比較有現實(shí)意義的
是,這個(gè)端口將不能再被使用。某個(gè)端口處于TIME_WAIT狀態(tài)(其實(shí)應該是這個(gè)連接)時(shí),這意味著(zhù)這個(gè)TCP
連接并沒(méi)有斷開(kāi)(完全斷開(kāi)),那么,如果你bind這個(gè)端口,就會(huì )失敗。
對于服務(wù)器而言,如果服務(wù)器突然crash掉了,那么它將無(wú)法再2MSL內重新啟動(dòng),因為bind會(huì )失敗。解決這
個(gè)問(wèn)題的一個(gè)方法就是設置socket的SO_REUSEADDR選項。這個(gè)選項意味著(zhù)你可以重用一個(gè)地址。
對于TIME_WAIT的插曲:
當建立一個(gè)TCP連接時(shí),服務(wù)器端會(huì )繼續用原有端口監聽(tīng),同時(shí)用這個(gè)端口與客戶(hù)端通信。而客戶(hù)端默認情況
下會(huì )使用一個(gè)隨機端口與服務(wù)器端的監聽(tīng)端口通信。有時(shí)候,為了服務(wù)器端的安全性,我們需要對客戶(hù)端進(jìn)行
驗證,即限定某個(gè)IP某個(gè)特定端口的客戶(hù)端??蛻?hù)端可以使用bind來(lái)使用特定的端口。
對于服務(wù)器端,當設置了SO_REUSEADDR選項時(shí),它可以在2MSL內啟動(dòng)并listen成功。但是對于客戶(hù)端,當使
用bind并設置SO_REUSEADDR時(shí),如果在2MSL內啟動(dòng),雖然bind會(huì )成功,但是在windows平臺上connect會(huì )失敗。
而在linux上則不存在這個(gè)問(wèn)題。(我的實(shí)驗平臺:winxp, ubuntu7.10)
要解決windows平臺的這個(gè)問(wèn)題,可以設置SO_LINGER選項。SO_LINGER選項決定調用close時(shí),TCP的行為。
SO_LINGER涉及到linger結構體,如果設置結構體中l_onoff為非0,l_linger為0,那么調用close時(shí)TCP連接
會(huì )立刻斷開(kāi),TCP不會(huì )將發(fā)送緩沖中未發(fā)送的數據發(fā)送,而是立即發(fā)送一個(gè)RST報文給對方,這個(gè)時(shí)候TCP連
接就不會(huì )進(jìn)入TIME_WAIT狀態(tài)。
如你所見(jiàn),這樣做雖然解決了問(wèn)題,但是并不安全。通過(guò)以上方式設置SO_LINGER狀態(tài),等同于設置SO_DONTLINGER
狀態(tài)。
斷開(kāi)連接時(shí)的意外:
這個(gè)算不上斷開(kāi)連接時(shí)的意外,當TCP連接發(fā)生一些物理上的意外情況時(shí),例如網(wǎng)線(xiàn)斷開(kāi),linux上的TCP實(shí)現
會(huì )依然認為該連接有效,而windows則會(huì )在一定時(shí)間后返回錯誤信息。
這似乎可以通過(guò)設置SO_KEEPALIVE選項來(lái)解決,不過(guò)不知道這個(gè)選項是否對于所有平臺都有效。