相對(duì)于SOCKET開發(fā)者,TCP創(chuàng)建過程和鏈接折除過程是由TCP/IP協(xié)議棧自動(dòng)創(chuàng)建的.因此開發(fā)者并不需要控制這個(gè)過程.但是對(duì)于理解TCP底層運(yùn)作機(jī)制,相當(dāng)有幫助.
TCP三次握手
所謂三次握手(Three-way Handshake),是指建立一個(gè)TCP連接時(shí),需要客戶端和服務(wù)器總共發(fā)送3個(gè)包。
三次握手的目的是連接服務(wù)器指定端口,建立TCP連接,并同步連接雙方的序列號(hào)和確認(rèn)號(hào)并交換 TCP 窗口大小信息.在socket編程中,客戶端執(zhí)行connect()時(shí)。將觸發(fā)三次握手。
第一次握手:
客戶端發(fā)送一個(gè)TCP的SYN標(biāo)志位置1的包指明客戶打算連接的服務(wù)器的端口,以及初始序號(hào)X,保存在包頭的序列號(hào)(Sequence Number)字段里。

第二次握手:
服務(wù)器發(fā)回確認(rèn)包(ACK)應(yīng)答。即SYN標(biāo)志位和ACK標(biāo)志位均為1同時(shí),將確認(rèn)序號(hào)(Acknowledgement Number)設(shè)置為客戶的I S N加1以.即X+1。

第三次握手.
客戶端再次發(fā)送確認(rèn)包(ACK) SYN標(biāo)志位為0,ACK標(biāo)志位為1.并且把服務(wù)器發(fā)來ACK的序號(hào)字段+1,放在確定字段中發(fā)送給對(duì)方.并且在數(shù)據(jù)段放寫ISN的+1
SYN攻擊
在三次握手過程中,服務(wù)器發(fā)送SYN-ACK之后,收到客戶端的ACK之前的TCP連接稱為半連接(half-open connect).此時(shí)服務(wù)器處于Syn_RECV狀態(tài).當(dāng)收到ACK后,服務(wù)器轉(zhuǎn)入ESTABLISHED狀態(tài).
Syn攻擊就是 攻擊客戶端 在短時(shí)間內(nèi)偽造大量不存在的IP地址,向服務(wù)器不斷地發(fā)送syn包,服務(wù)器回復(fù)確認(rèn)包,并等待客戶的確認(rèn),由于源地址是不存在的,服務(wù)器需要不斷的重發(fā)直 至超時(shí),這些偽造的SYN包將長時(shí)間占用未連接隊(duì)列,正常的SYN請(qǐng)求被丟棄,目標(biāo)系統(tǒng)運(yùn)行緩慢,嚴(yán)重者引起網(wǎng)絡(luò)堵塞甚至系統(tǒng)癱瘓。
Syn攻擊是一個(gè)典型的DDOS攻擊。檢測SYN攻擊非常的方便,當(dāng)你在服務(wù)器上看到大量的半連接狀態(tài)時(shí),特別是源IP地址是隨機(jī)的,基本上可以斷定這是一次SYN攻擊.在Linux下可以如下命令檢測是否被Syn攻擊
netstat -n -p TCP | grep SYN_RECV
一般較新的TCP/IP協(xié)議棧都對(duì)這一過程進(jìn)行修正來防范Syn攻擊,修改tcp協(xié)議實(shí)現(xiàn)。主要方法有SynAttackProtect保護(hù)機(jī)制、SYN cookies技術(shù)、增加最大半連接和縮短超時(shí)時(shí)間等.
但是不能完全防范syn攻擊。
TCP 四次揮手
TCP的連接的拆除需要發(fā)送四個(gè)包,因此稱為四次揮手(four-way handshake)??蛻舳嘶蚍?wù)器均可主動(dòng)發(fā)起揮手動(dòng)作,在socket編程中,任何一方執(zhí)行close()操作即可產(chǎn)生揮手操作。
參見wireshark抓包,實(shí)測的抓包結(jié)果并沒有嚴(yán)格按揮手時(shí)序。我估計(jì)是時(shí)間間隔太短造成。
第二部分:補(bǔ)充tcp連接過程
在TCP/IP協(xié)議中,TCP協(xié)議提供可靠的連接服務(wù),采用三次握手建立一個(gè)連接,如圖1所示。
(1) 第一次握手:建立連接時(shí),客戶端A發(fā)送SYN包(SYN=j)到服務(wù)器B,并進(jìn)入SYN_SEND狀態(tài),等待服務(wù)器B確認(rèn)。
(2) 第二次握手:服務(wù)器B收到SYN包,必須確認(rèn)客戶A的SYN(ACK=j+1),同時(shí)自己也發(fā)送一個(gè)SYN包(SYN=k),即SYN+ACK包,此時(shí)服務(wù)器B進(jìn)入SYN_RECV狀態(tài)。
(3) 第三次握手:客戶端A收到服務(wù)器B的SYN+ACK包,向服務(wù)器B發(fā)送確認(rèn)包ACK(ACK=k+1),此包發(fā)送完畢,客戶端A和服務(wù)器B進(jìn)入ESTABLISHED狀態(tài),完成三次握手。
完成三次握手,客戶端與服務(wù)器開始傳送數(shù)據(jù)。
圖1 TCP三次握手建立連接
由于TCP連接是全雙工的,因此每個(gè)方向都必須單獨(dú)進(jìn)行關(guān)閉。這個(gè)原則是當(dāng)一方完成它的數(shù)據(jù)發(fā)送任務(wù)后就能發(fā)送一個(gè)FIN來終止這個(gè)方向的連接。收到一個(gè) FIN只意味著這一方向上沒有數(shù)據(jù)流動(dòng),一個(gè)TCP連接在收到一個(gè)FIN后仍能發(fā)送數(shù)據(jù)。首先進(jìn)行關(guān)閉的一方將執(zhí)行主動(dòng)關(guān)閉,而另一方執(zhí)行被動(dòng)關(guān)閉。
(1)客戶端A發(fā)送一個(gè)FIN,用來關(guān)閉客戶A到服務(wù)器B的數(shù)據(jù)傳送(報(bào)文段4)。
(2)服務(wù)器B收到這個(gè)FIN,它發(fā)回一個(gè)ACK,確認(rèn)序號(hào)為收到的序號(hào)加1(報(bào)文段5)。和SYN一樣,一個(gè)FIN將占用一個(gè)序號(hào)。
(3)服務(wù)器B關(guān)閉與客戶端A的連接,發(fā)送一個(gè)FIN給客戶端A(報(bào)文段6)。
(4)客戶端A發(fā)回ACK報(bào)文確認(rèn),并將確認(rèn)序號(hào)設(shè)置為收到序號(hào)加1(報(bào)文段7)。
TCP采用四次揮手關(guān)閉連接如圖2所示。
圖2 TCP四次揮手關(guān)閉連接
1.為什么建立連接協(xié)議是三次握手,而關(guān)閉連接卻是四次握手呢?
這是因?yàn)榉?wù)端的LISTEN狀態(tài)下的SOCKET當(dāng)收到SYN報(bào)文的連接請(qǐng)求后,它可以把ACK和SYN(ACK起應(yīng)答作用,而SYN起同步作用)放在一個(gè)報(bào)文里來發(fā)送。但關(guān)閉連接時(shí),當(dāng)收到對(duì)方的FIN報(bào)文通知時(shí),它僅僅表示對(duì)方?jīng)]有數(shù)據(jù)發(fā)送給你了;但未必你所有的數(shù)據(jù)都全部發(fā)送給對(duì)方了,所以你可能未必會(huì)馬上會(huì)關(guān)閉SOCKET,也即你可能還需要發(fā)送一些數(shù)據(jù)給對(duì)方之后,再發(fā)送FIN報(bào)文給對(duì)方來表示你同意現(xiàn)在可以關(guān)閉連接了,所以它這里的ACK報(bào)文和FIN報(bào)文多數(shù)情況下都是分開發(fā)送的。
2.為什么TIME_WAIT狀態(tài)還需要等2MSL后才能返回到CLOSED狀態(tài)?
這個(gè)問題可以參考《unix 網(wǎng)絡(luò)編程》(第三版,2.7 TIME_WAIT狀態(tài))。
TIME_WAIT狀態(tài)由兩個(gè)存在的理由。
****(1)可靠的實(shí)現(xiàn)TCP全雙工鏈接的終止。****
這是因?yàn)殡m然雙方都同意關(guān)閉連接了,而且握手的4個(gè)報(bào)文也都協(xié)調(diào)和發(fā)送完畢,按理可以直接回到CLOSED狀態(tài)(就好比從SYN_SEND狀態(tài)到ESTABLISH狀態(tài)那樣);但是因?yàn)槲覀儽仨氁傧刖W(wǎng)絡(luò)是不可靠的,你無法保證你最后發(fā)送的ACK報(bào)文會(huì)一定被對(duì)方收到,因此對(duì)方處于LAST_ACK狀態(tài)下的SOCKET可能會(huì)因?yàn)槌瑫r(shí)未收到ACK報(bào)文,而重發(fā)FIN報(bào)文,所以這個(gè)TIME_WAIT狀態(tài)的作用就是用來重發(fā)可能丟失的ACK報(bào)文。
(2)允許老的重復(fù)的分節(jié)在網(wǎng)絡(luò)中消逝。
假 設(shè)在12.106.32.254的1500端口和206.168.1.112.219的21端口之間有一個(gè)TCP連接。我們關(guān)閉這個(gè)鏈接,過一段時(shí)間后在 相同的IP地址和端口建立另一個(gè)連接。后一個(gè)鏈接成為前一個(gè)的化身。因?yàn)樗鼈兊腎P地址和端口號(hào)都相同。TCP必須防止來自某一個(gè)連接的老的重復(fù)分組在連 接已經(jīng)終止后再現(xiàn),從而被誤解成屬于同一鏈接的某一個(gè)某一個(gè)新的化身。為做到這一點(diǎn),TCP將不給處于TIME_WAIT狀態(tài)的鏈接發(fā)起新的化身。既然 TIME_WAIT狀態(tài)的持續(xù)時(shí)間是MSL的2倍,這就足以讓某個(gè)方向上的分組最多存活msl秒即被丟棄,另一個(gè)方向上的應(yīng)答最多存活msl秒也被丟棄。 通過實(shí)施這個(gè)規(guī)則,我們就能保證每成功建立一個(gè)TCP連接時(shí)。來自該鏈接先前化身的重復(fù)分組都已經(jīng)在網(wǎng)絡(luò)中消逝了。
3. 為什么不能用兩次握手進(jìn)行連接?
我們知道,3次握手完成兩個(gè)重要的功能,既要雙方做好發(fā)送數(shù)據(jù)的準(zhǔn)備工作(雙方都知道彼此已準(zhǔn)備好),也要允許雙方就初始序列號(hào)進(jìn)行協(xié)商,這個(gè)序列號(hào)在握手過程中被發(fā)送和確認(rèn)。
現(xiàn)在把三次握手改成僅需要兩次握手,死鎖是可能發(fā)生的。作為例子,考慮計(jì)算機(jī)S和C之間的通信,假定C給S發(fā)送一個(gè)連接請(qǐng)求分組,S收到了這個(gè)分組,并發(fā) 送了確認(rèn)應(yīng)答分組。按照兩次握手的協(xié)定,S認(rèn)為連接已經(jīng)成功地建立了,可以開始發(fā)送數(shù)據(jù)分組??墒牵珻在S的應(yīng)答分組在傳輸中被丟失的情況下,將不知道S 是否已準(zhǔn)備好,不知道S建立什么樣的序列號(hào),C甚至懷疑S是否收到自己的連接請(qǐng)求分組。在這種情況下,C認(rèn)為連接還未建立成功,將忽略S發(fā)來的任何數(shù)據(jù)分 組,只等待連接確認(rèn)應(yīng)答分組。而S在發(fā)出的分組超時(shí)后,重復(fù)發(fā)送同樣的分組。這樣就形成了死鎖。
補(bǔ)充:
a. 默認(rèn)情況下(不改變socket選項(xiàng)),當(dāng)你調(diào)用close( or closesocket,以下說close不再重復(fù))時(shí),如果發(fā)送緩沖中還有數(shù)據(jù),TCP會(huì)繼續(xù)把數(shù)據(jù)發(fā)送完。
b. 發(fā)送了FIN只是表示這端不能繼續(xù)發(fā)送數(shù)據(jù)(應(yīng)用層不能再調(diào)用send發(fā)送),但是還可以接收數(shù)據(jù)。
c. 應(yīng)用層如何知道對(duì)端關(guān)閉?通常,在最簡單的阻塞模型中,當(dāng)你調(diào)用recv時(shí),如果返回0,則表示對(duì)端關(guān)閉。在這個(gè)時(shí)候通常的做法就是也調(diào)用close,那么TCP層就發(fā)送FIN,繼續(xù)完成四次握手。如果你不調(diào)用close,那么對(duì)端就會(huì)處于FIN_WAIT_2狀態(tài),而本端則會(huì)處于CLOSE_WAIT狀態(tài)。這個(gè)可以寫代碼試試。
d. 在很多時(shí)候,TCP連接的斷開都會(huì)由TCP層自動(dòng)進(jìn)行,例如你CTRL+C終止你的程序,TCP連接依然會(huì)正常關(guān)閉,你可以寫代碼試試。
插曲:
特別的TIME_WAIT狀態(tài):
從以上TCP連接關(guān)閉的狀態(tài)轉(zhuǎn)換圖可以看出,主動(dòng)關(guān)閉的一方在發(fā)送完對(duì)對(duì)方FIN報(bào)文的確認(rèn)(ACK)報(bào)文后,會(huì)進(jìn)入TIME_WAIT狀態(tài)。TIME_WAIT狀態(tài)也稱為2MSL狀態(tài)。
什么是2MSL?MSL即Maximum Segment Lifetime,也就是報(bào)文最大生存時(shí)間,引用《TCP/IP詳解》中的話:“它(MSL)是任何報(bào)文段被丟棄前在網(wǎng)絡(luò)內(nèi)的最長時(shí)間?!蹦敲矗?MSL也就是這個(gè)時(shí)間的2倍。其實(shí)我覺得沒必要把這個(gè)MSL的確切含義搞明白,你所需要明白的是,當(dāng)TCP連接完成四個(gè)報(bào)文段的交換時(shí),主動(dòng)關(guān)閉的一方將繼續(xù)等待一定時(shí)間(2-4分鐘),即使兩端的應(yīng)用程序結(jié)束。你可以寫代碼試試,然后用setstat查看下。
為什么需要2MSL?根據(jù)《TCP/IP詳解》和《The TCP/IP Guide》中的說法,有兩個(gè)原因:
其一,保證發(fā)送的ACK會(huì)成功發(fā)送到對(duì)方,如何保證?我覺得可能是通過超時(shí)計(jì)時(shí)器發(fā)送。這個(gè)就很難用代碼演示了。
其二,報(bào)文可能會(huì)被混淆,意思是說,其他時(shí)候的連接可能會(huì)被當(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)所帶來的影響:
當(dāng)某個(gè)連接的一端處于TIME_WAIT狀態(tài)時(shí),該連接將不能再被使用。事實(shí)上,對(duì)于我們比較有現(xiàn)實(shí)意義的是,這個(gè)端口將不能再被使用。某個(gè)端口處于TIME_WAIT狀態(tài)(其實(shí)應(yīng)該是這個(gè)連接)時(shí),這意味著這個(gè)TCP連接并沒有斷開(完全斷開),那么,如果你bind這個(gè)端口,就會(huì)失敗。對(duì)于服務(wù)器而言,如果服務(wù)器突然crash掉了,那么它將無法再2MSL內(nèi)重新啟動(dòng),因?yàn)閎ind會(huì)失敗。解決這個(gè)問題的一個(gè)方法就是設(shè)置socket的SO_REUSEADDR選項(xiàng)。這個(gè)選項(xiàng)意味著你可以重用一個(gè)地址。
對(duì)于TIME_WAIT的插曲:
當(dāng)建立一個(gè)TCP連接時(shí),服務(wù)器端會(huì)繼續(xù)用原有端口監(jiān)聽,同時(shí)用這個(gè)端口與客戶端通信。而客戶端默認(rèn)情況下會(huì)使用一個(gè)隨機(jī)端口與服務(wù)器端的監(jiān)聽端口通信。有時(shí)候,為了服務(wù)器端的安全性,我們需要對(duì)客戶端進(jìn)行驗(yàn)證,即限定某個(gè)IP某個(gè)特定端口的客戶端??蛻舳丝梢允褂胋ind來使用特定的端口。對(duì)于服務(wù)器端,當(dāng)設(shè)置了SO_REUSEADDR選項(xiàng)時(shí),它可以在2MSL內(nèi)啟動(dòng)并listen成功。但是對(duì)于客戶端,當(dāng)使
用bind并設(shè)置SO_REUSEADDR時(shí),如果在2MSL內(nèi)啟動(dòng),雖然bind會(huì)成功,但是在windows平臺(tái)上connect會(huì)失敗。而在linux上則不存在這個(gè)問題。(我的實(shí)驗(yàn)平臺(tái):winxp, ubuntu7.10)
要解決windows平臺(tái)的這個(gè)問題,可以設(shè)置SO_LINGER選項(xiàng)。SO_LINGER選項(xiàng)決定調(diào)用close時(shí)TCP的行為。SO_LINGER涉及到linger結(jié)構(gòu)體,如果設(shè)置結(jié)構(gòu)體中l(wèi)_onoff為非0,l_linger為0,那么調(diào)用close時(shí)TCP連接會(huì)立刻斷開,TCP不會(huì)將發(fā)送緩沖中未發(fā)送的數(shù)據(jù)發(fā)送,而是立即發(fā)送一個(gè)RST報(bào)文給對(duì)方,這個(gè)時(shí)候TCP連接就不會(huì)進(jìn)入TIME_WAIT狀態(tài)。如你所見,這樣做雖然解決了問題,但是并不安全。通過以上方式設(shè)置SO_LINGER狀態(tài),等同于設(shè)置SO_DONTLINGER狀態(tài)。
斷開連接時(shí)的意外:
這個(gè)算不上斷開連接時(shí)的意外,當(dāng)TCP連接發(fā)生一些物理上的意外情況時(shí),例如網(wǎng)線斷開,linux上的TCP實(shí)現(xiàn)會(huì)依然認(rèn)為該連接有效,而windows則會(huì)在一定時(shí)間后返回錯(cuò)誤信息。這似乎可以通過設(shè)置SO_KEEPALIVE選項(xiàng)來解決,不過不知道這個(gè)選項(xiàng)是否對(duì)于所有平臺(tái)都有效。
第三部分:常見面試題
TCP協(xié)議和UDP協(xié)議的區(qū)別是什么
TCP協(xié)議是有連接的,有連接的意思是開始傳輸實(shí)際數(shù)據(jù)之前TCP的客戶端和服務(wù)器端必須通過三次握手建立連接,會(huì)話結(jié)束之后也要結(jié)束連接。而UDP是無連接的
TCP協(xié)議保證數(shù)據(jù)按序發(fā)送,按序到達(dá),提供超時(shí)重傳來保證可靠性,但是UDP不保證按序到達(dá),甚至不保證到達(dá),只是努力交付,即便是按序發(fā)送的序列,也不保證按序送到。
TCP協(xié)議所需資源多,TCP首部需20個(gè)字節(jié)(不算可選項(xiàng)),UDP首部字段只需8個(gè)字節(jié)。
TCP有流量控制和擁塞控制,UDP沒有,網(wǎng)絡(luò)擁堵不會(huì)影響發(fā)送端的發(fā)送速率
TCP是一對(duì)一的連接,而UDP則可以支持一對(duì)一,多對(duì)多,一對(duì)多的通信。
TCP面向的是字節(jié)流的服務(wù),UDP面向的是報(bào)文的服務(wù)。
TCP介紹和UDP介紹
請(qǐng)?jiān)敿?xì)介紹一下TCP協(xié)議建立連接和終止連接的過程?
助于理解的一段話
兩幅圖(來源):
建立連接:三次握手
關(guān)閉連接:四次揮手
三次握手建立連接時(shí),發(fā)送方再次發(fā)送確認(rèn)的必要性?
主 要是為了防止已失效的連接請(qǐng)求報(bào)文段突然又傳到了B,因而產(chǎn)生錯(cuò)誤。假定出現(xiàn)一種異常情況,即A發(fā)出的第一個(gè)連接請(qǐng)求報(bào)文段并沒有丟失,而是在某些網(wǎng)絡(luò)結(jié) 點(diǎn)長時(shí)間滯留了,一直延遲到連接釋放以后的某個(gè)時(shí)間才到達(dá)B,本來這是一個(gè)早已失效的報(bào)文段。但B收到此失效的連接請(qǐng)求報(bào)文段后,就誤認(rèn)為是A又發(fā)出一次 新的連接請(qǐng)求,于是就向A發(fā)出確認(rèn)報(bào)文段,同意建立連接。假定不采用三次握手,那么只要B發(fā)出確認(rèn),新的連接就建立了,這樣一直等待A發(fā)來數(shù)據(jù),B的許多 資源就這樣白白浪費(fèi)了。
四次揮手釋放連接時(shí),等待2MSL的意義?
第 一,為了保證A發(fā)送的最有一個(gè)ACK報(bào)文段能夠到達(dá)B。這個(gè)ACK報(bào)文段有可能丟失,因而使處在LAST-ACK狀態(tài)的B收不到對(duì)已發(fā)送的FIN和ACK 報(bào)文段的確認(rèn)。B會(huì)超時(shí)重傳這個(gè)FIN和ACK報(bào)文段,而A就能在2MSL時(shí)間內(nèi)收到這個(gè)重傳的ACK+FIN報(bào)文段。接著A重傳一次確認(rèn)。
第二,就是防止上面提到的已失效的連接請(qǐng)求報(bào)文段出現(xiàn)在本連接中,A在發(fā)送完最有一個(gè)ACK報(bào)文段后,再經(jīng)過2MSL,就可以使本連接持續(xù)的時(shí)間內(nèi)所產(chǎn)生的所有報(bào)文段都從網(wǎng)絡(luò)中消失。
常見的應(yīng)用中有哪些是應(yīng)用TCP協(xié)議的,哪些又是應(yīng)用UDP協(xié)議的,為什么它們被如此設(shè)計(jì)?
以下應(yīng)用一般或必須用udp實(shí)現(xiàn)?
多播的信息一定要用udp實(shí)現(xiàn),因?yàn)閠cp只支持一對(duì)一通信。
如果一個(gè)應(yīng)用場景中大多是簡短的信息,適合用udp實(shí)現(xiàn),因?yàn)閡dp是基于報(bào)文段的,它直接對(duì)上層應(yīng)用的數(shù)據(jù)封裝成報(bào)文段,然后丟在網(wǎng)絡(luò)中,如果信息量太大,會(huì)在鏈路層中被分片,影響傳輸效率。
如果一個(gè)應(yīng)用場景重性能甚于重完整性和安全性,那么適合于udp,比如多媒體應(yīng)用,缺一兩幀不影響用戶體驗(yàn),但是需要流媒體到達(dá)的速度快,因此比較適合用udp
如果要求快速響應(yīng),那么udp聽起來比較合適
如果又要利用udp的快速響應(yīng)優(yōu)點(diǎn),又想可靠傳輸,那么只能考上層應(yīng)用自己制定規(guī)則了。
常見的使用udp的例子:ICQ,QQ的聊天模塊。
以qq為例的一個(gè)說明(轉(zhuǎn)載自知乎)
登陸采用TCP協(xié)議和HTTP協(xié)議,你和好友之間發(fā)送消息,主要采用UDP協(xié)議,內(nèi)網(wǎng)傳文件采用了P2P技術(shù)??倎淼恼f:
1.登陸過程,客戶端client 采用TCP協(xié)議向服務(wù)器server發(fā)送信息,HTTP協(xié)議下載信息。登陸之后,會(huì)有一個(gè)TCP連接來保持在線狀態(tài)。
2.和好友發(fā)消息,客戶端client采用UDP協(xié)議,但是需要通過服務(wù)器轉(zhuǎn)發(fā)。騰訊為了確保傳輸消息的可靠,采用上層協(xié)議來保證可靠傳輸。如果消息發(fā)送失敗,客戶端會(huì)提示消息發(fā)送失敗,并可重新發(fā)送。
3.如果是在內(nèi)網(wǎng)里面的兩個(gè)客戶端傳文件,QQ采用的是P2P技術(shù),不需要服務(wù)器中轉(zhuǎn)。






