技術(shù)革命
格雷厄姆在《黑客與畫(huà)家》中提到一個(gè)觀點(diǎn),歷史上財(cái)富的積累無(wú)非兩種方式---偷竊和搶奪。新技術(shù)革命的出現(xiàn),財(cái)富的積累的新方式則是技術(shù)進(jìn)步。
技術(shù)進(jìn)步創(chuàng)造了無(wú)數(shù)的財(cái)富,而這個(gè)技術(shù)指的就是網(wǎng)絡(luò)技術(shù)。相對(duì)于人類的文明,網(wǎng)絡(luò)的歷史如同曇花一現(xiàn),恰恰是這么短的時(shí)間內(nèi),所創(chuàng)造的文明和財(cái)富卻是前所未有。相對(duì)于網(wǎng)絡(luò)自身的發(fā)展,其實(shí)已經(jīng)算是歷史悠久了。然而自從網(wǎng)絡(luò)誕生到現(xiàn)在,網(wǎng)絡(luò)的基礎(chǔ)架構(gòu)理論,基本的通信協(xié)議改變也不是很大。
我們所熟知的Internet,把全世界的人連接起來(lái),Inernet構(gòu)建于TCP/IP模型基礎(chǔ)之上。TCP/IP模型有著很多協(xié)議,其中與網(wǎng)絡(luò)(web)應(yīng)用開(kāi)發(fā)者息息相關(guān)的莫過(guò)于TCP協(xié)議。想要了解TCP協(xié)議的本質(zhì),起始于TCP的三次握手和四次揮手。
TCP 握手連接
連接是一個(gè)通信的行為。建立連接,就能使用連接進(jìn)行通信。連接作用于兩個(gè)節(jié)點(diǎn)。TCP是有狀態(tài)的協(xié)議,因此兩個(gè)節(jié)點(diǎn)之間想要通過(guò)tcp發(fā)送數(shù)據(jù),必須先建立可靠有效的連接。tcp是雙通道的通信模式,因此tcp的連接(邏輯連接)其實(shí)是兩條物理連接,即客戶端-->連接
和服務(wù)端--->客戶端
的連接。
上圖即是經(jīng)典的TCP三次握手。握手前,服務(wù)端創(chuàng)建socket對(duì)象,綁定地址,開(kāi)啟監(jiān)聽(tīng);客戶端創(chuàng)建socket,準(zhǔn)備連接。然后進(jìn)行三次握手連接:
- clinet向server端發(fā)送一個(gè)[SYN]包,seq=x。
- server收到[SYN]包之后,向clinet發(fā)送[SYN ACK] seq=y,ack=x+1
- clinet收到server的syn和ack之后,再向server端發(fā)送[ACK] ack=y+1包,至此三次握手完成。
創(chuàng)建一個(gè)tcp連接,通過(guò)三次握手即可。其實(shí)三次握手的clinet和server端是在不同的變化狀態(tài)。下面詳細(xì)討論下三次握手中的兩端的狀態(tài)變化。
TCP 握手c/s端狀態(tài)
針對(duì)三次握手的詳細(xì)過(guò)程,有如下過(guò)程:
發(fā)起握手的時(shí)候,clinet發(fā)送[SYN]包之后,自身馬上變成
SYN_SENT
狀態(tài),server則是進(jìn)行了listen,自身的狀態(tài)則變成LISTEN
。接受到[SYN]包之后,自身變成SYN_RCVD
狀態(tài)。這個(gè)過(guò)程主要含義是clinet向server建立一條發(fā)送數(shù)據(jù)的連接。server端收到了client的SYN之后,馬上會(huì)發(fā)送一個(gè)[SYN ACK]包,這里一共有兩個(gè)作用。ACK用于應(yīng)答client,表示client->server的連接已經(jīng)建立,同時(shí)server也想向client建立一條發(fā)送數(shù)據(jù)的連接,因此也需要發(fā)送一個(gè)[SYN]包。
client收到了server的[SYN ACK]。通過(guò)server發(fā)的ACK確定了client->server這條連接建立。自身狀態(tài)變成了
ESTABLISHED
,表示可以正常的發(fā)送數(shù)據(jù)給server了。同時(shí)為了響應(yīng)server發(fā)送的建立連接的SYN請(qǐng)求,再次給server做一次ACK的應(yīng)答,一旦server收到clinet的ACK應(yīng)答,server的狀態(tài)也會(huì)變成ESTABLISHED
。
上述的過(guò)程中涉及到幾個(gè)狀態(tài),其中SYN_SENT
和SYN_RCVD
非常短暫,使用nc等工具也很難看到這種狀態(tài)的。syn
包表示希望建立一個(gè)連接,ack
包表示應(yīng)答。連接的本質(zhì)是:
client ----- 發(fā)送syn ------> server 希望創(chuàng)建連接
client <----- 發(fā)送ack ------ server 確定創(chuàng)建連接
之所以是三次握手,而不是第四次握手,是因?yàn)榈诙挝帐值臅r(shí)候,server把 [SYN] 和 [ACK]一起發(fā)送了。
客戶端 | 服務(wù)端 | ||||
---|---|---|---|---|---|
開(kāi)始狀態(tài) | 過(guò)程 | 結(jié)束狀態(tài) | 開(kāi)始狀態(tài) | 過(guò)程 | 結(jié)束狀態(tài) |
CLOSED | client創(chuàng)建socket | CLOSED | CLOSED | server創(chuàng)建socket,綁定地址,打開(kāi)監(jiān)聽(tīng) | LISTEN |
CLOSED | [第一次握手]client向server發(fā)送SYN包 | SYS-SENT | LISTEN | 等待client發(fā)送的SYN | LISTEN |
SYS-SENT | 發(fā)送syn之后等待server發(fā)送ack | SYS-SENT | LISTEN | [第二次握手]接受client的syn,同時(shí)向client發(fā)送[SYN ACK] | SYN-RECEIVED |
SYN-SENT | 接受server的SYN+ACK,通過(guò)ack確定client->server連接創(chuàng)立,[第三次握手]同時(shí)針對(duì)SYN發(fā)送ACK | ESTABLISHED | SYN-RECEIVED | 等待clinet發(fā)送的ACK確認(rèn) | SYN-RECEIVED |
ESTABLISHED | 等待server完成對(duì)ack的響應(yīng),等待完成server->clinet的連接 | ESTABLISHED | SYN-RECEIVED | 接受client的ACK確定,完成server->clinet的連接 | ESTABLISHED |
ESTABLISHED | 發(fā)送接受數(shù)據(jù) | ESTABLISHED | ESTABLISHED | 發(fā)送接收數(shù)據(jù) | ESTABLISHED |
TCP 揮手?jǐn)噙B
了解了tcp的握手方式,那么揮手方式就很容易理解啦。與創(chuàng)建連接syn不一樣,想要端口連接需要發(fā)送的是fin包。同樣為了確定斷開(kāi)連接,需要發(fā)送ack應(yīng)答確認(rèn)包。大概方式入下圖:
斷開(kāi)tcp需要四步,斷開(kāi)連接既可以是client主動(dòng),server被動(dòng),也可以server主動(dòng)斷開(kāi)。兩種的狀態(tài)變化也是相對(duì)而言。下面以client主動(dòng)斷開(kāi)為例:
- client發(fā)送[FIN]包,表示要斷開(kāi)clinet->server的連接。
- server收到[FIN]之后,發(fā)送一個(gè)[ACK]包表示確定斷開(kāi)連接啦。
- 同時(shí)server也會(huì)向clinet再發(fā)一個(gè)[FIN]包,表示也想斷開(kāi)server->clinet的連接。
- clinet收到server的[ACK]包,確定了clinet->server的連接的斷開(kāi),該連接將不會(huì)發(fā)送數(shù)據(jù)啦。由于client也會(huì)收到server的[FIN]包,因此也要為斷開(kāi)server->clinet的連接發(fā)送[ACK]給server確定。
至此,四次揮手完成。下面詳細(xì)四次交互過(guò)程兩個(gè)端的狀態(tài)。
client和server端傳送數(shù)據(jù)的時(shí)候,雙方的狀態(tài)都是ESTABLISHED
。一旦clinet發(fā)送了fin之后,自身變成FIN-WAIT-1
的狀態(tài),意思是等待server端的ack確定。此時(shí),該通道不再向server發(fā)送數(shù)據(jù),但是仍然可以接收server數(shù)據(jù)了。
server收到了clinet發(fā)送的ack之后,自身的狀態(tài)由ESTABLISHED
變成CLOSE_WAIT
。client收到ack后,自身由FIN-WAIT-1
變成FIN-WAIT-2
,斷開(kāi)了連接。此時(shí)client在等待server端的fin信號(hào)。
server 發(fā)送 ack之后,自身就由CLOSE_WAIT
變成LAST-ACK
狀態(tài),即等待client的最后的ack確定。client收到server的fin包之后,自身就由 FIN-WAIT-2
變成 TIME_WAIT
狀態(tài),等待一個(gè)2MSL時(shí)間之后,就變成了CLOSED
狀態(tài)。
最后server收到client的ack確定之后,自身也變成CLOSED
狀態(tài)。
客戶端 | 服務(wù)端 | ||||
---|---|---|---|---|---|
開(kāi)始狀態(tài) | 過(guò)程 | 結(jié)束狀態(tài) | 開(kāi)始狀態(tài) | 過(guò)程 | 結(jié)束狀態(tài) |
ESTABLISHED | 向server發(fā)送FIN,表示想關(guān)閉clinet->server的連接 | FIN-WAIT-1 | ESTABLISHED | 正常服務(wù),直到收到FIN | ESTABLISHED |
FIN-WAIT-1 | 發(fā)送完畢FIN,等待server的確認(rèn)應(yīng)答,此時(shí)將不會(huì)再發(fā)送數(shù)據(jù)給server | FIN-WAIT-1 | ESTABLISHED | 接受FIN,發(fā)送ACK確認(rèn)斷開(kāi)連接 | CLOSE-WAIT |
FIN-WAIT-1 | 接收server發(fā)送的ACK,確定client->server連接關(guān)閉 | FIN-WAIT-2 | CLOSE-WAIT | 發(fā)送FIN,表示關(guān)閉server->client的連接 | LAST-ACK |
FIN-WAIT-2 | 收到server的FIN包,發(fā)送ACK作為應(yīng)答 | TIME-WAIT | LAST-ACK | 等待 client發(fā)送的ACK | LAST-ACK |
TIME-WAIT | 等待MSL時(shí)間,保證ACK能被對(duì)方收到,等待fin否則重發(fā) | TIME-WAIT | LAST-ACK | 接受ACK應(yīng)達(dá),關(guān)閉server->clinet的連接 | CLOSED |
TIME-WAIT | MSL 時(shí)間過(guò)期 | CLOSED | CLOSED | 連接關(guān)閉 | CLOSED |
問(wèn)題
三次握手保證了TCP連接的可靠性,假設(shè)沒(méi)有三次握手只有兩次。如果client發(fā)送第一個(gè)syn的時(shí)候延遲很大,導(dǎo)致client發(fā)送了第二個(gè),第二個(gè)syn很快就到達(dá)server端。于是server發(fā)送ack確定連接,并發(fā)送syn。在兩次握手的情況下,服務(wù)器認(rèn)為連接都建立好了。如果此時(shí)第一個(gè)syn又抵達(dá)了服務(wù)器,那么服務(wù)器將會(huì)再次應(yīng)答,向client端發(fā)送連接請(qǐng)求。這樣會(huì)造成無(wú)效的連接,通過(guò)第三次握手,可以在server應(yīng)答無(wú)效連接的時(shí)候提前終止。也就是最后一次握手不再發(fā)送ack,那么server就不會(huì)再創(chuàng)建連接。
在關(guān)閉連接的時(shí)候,雖然兩個(gè)端都統(tǒng)一關(guān)閉連接,并且四次交互也發(fā)送完畢。假設(shè)如果網(wǎng)絡(luò)延遲很大,或者丟包嚴(yán)重,就很難保證client最后一次ack一定能被server收到。如果server收不到,會(huì)重發(fā)fin。為了解決這個(gè)問(wèn)題,通常在client發(fā)送ack之后2MSL時(shí)間,才由TIME_WAIT
變成CLOSED
狀態(tài),在此期間,client可以針對(duì)server補(bǔ)發(fā)的fin重發(fā)ack。
還有一個(gè)CLOSING
狀態(tài),這個(gè)狀態(tài)表示client發(fā)送了FIN之后,并沒(méi)有收到ACK,反而先收到了server的FIN。當(dāng)然這種情況十分罕見(jiàn)的“異常”狀態(tài)。雙方都同時(shí)關(guān)閉連接,有可能在四次交互的時(shí)候,出現(xiàn)某些包延遲。
總結(jié)
TCP 連接和斷開(kāi)的過(guò)程中,都是一問(wèn)一答的方式,創(chuàng)建連接的問(wèn)是syn,回答是ack,同樣斷開(kāi)連接的問(wèn)是fin,回答是ack。有問(wèn)必有答,那么連接就能正常的創(chuàng)建和關(guān)閉。因?yàn)閠cp通信是雙通道的,因此一個(gè)TCP邏輯連接,實(shí)際上是兩條成對(duì)client和server端的物理連接。無(wú)論連接和斷開(kāi),都需要把這兩個(gè)連接都處理完畢才能完成。也因?yàn)檫@兩個(gè)過(guò)程,中間衍生出了很多狀態(tài)。這些狀態(tài)在創(chuàng)建連接的時(shí)候有client和server端的區(qū)分,在斷開(kāi)的連接的時(shí)候就沒(méi)有特別區(qū)別,只有主動(dòng)斷開(kāi)和被動(dòng)斷開(kāi)的差別。
至于tcp三次握手中兩端的“兩個(gè)連接”的通信通道,他們的具體原理和過(guò)程,我們將會(huì)在TCP連接與Python中詳細(xì)討論。