連接正常結(jié)束:四次揮手,好好告別
1)序號(hào)(sequence number):Seq序號(hào),占32位,用來標(biāo)識(shí)從TCP源端向目的端發(fā)送的字節(jié)流,發(fā)起方發(fā)送數(shù)據(jù)時(shí)對(duì)此進(jìn)行標(biāo)記。
2)確認(rèn)號(hào)(acknowledgement number):Ack序號(hào),占32位,只有ACK標(biāo)志位為1時(shí),確認(rèn)序號(hào)字段才有效,Ack=Seq+1。
3)標(biāo)志位(Flags):共6個(gè),即URG、ACK、PSH、RST、SYN、FIN。具體含義如下:
ACK:確認(rèn)序號(hào)有效。
RST:重置連接。
SYN:發(fā)起一個(gè)新連接。
FIN:釋放一個(gè)連接。
為何建立連接時(shí)一起傳輸,釋放連接時(shí)卻要分開傳輸?
建立連接時(shí),被動(dòng)方服務(wù)器端結(jié)束CLOSED階段進(jìn)入“握手”階段并不需要任何準(zhǔn)備,可以直接返回SYN和ACK報(bào)文,開始建立連接。釋放連接時(shí),被動(dòng)方服務(wù)器,突然收到主動(dòng)方客戶端釋放連接的請(qǐng)求時(shí)并不能立即釋放連接,因?yàn)檫€有必要的數(shù)據(jù)需要處理,所以服務(wù)器先返回ACK確認(rèn)收到報(bào)文,經(jīng)過CLOSE-WAIT階段準(zhǔn)備好釋放連接之后,才能返回FIN釋放連接報(bào)文。
為什么客戶端在TIME-WAIT階段要等2MSL?
為的是確認(rèn)服務(wù)器端是否收到客戶端發(fā)出的ACK確認(rèn)報(bào)文
當(dāng)客戶端發(fā)出最后的ACK確認(rèn)報(bào)文時(shí),并不能確定服務(wù)器端能夠收到該段報(bào)文。所以客戶端在發(fā)送完ACK確認(rèn)報(bào)文之后,會(huì)設(shè)置一個(gè)時(shí)長為2MSL的計(jì)時(shí)器。MSL指的是Maximum Segment Lifetime:一段TCP報(bào)文在傳輸過程中的最大生命周期。2MSL即是服務(wù)器端發(fā)出為FIN報(bào)文和客戶端發(fā)出的ACK確認(rèn)報(bào)文所能保持有效的最大時(shí)長。
服務(wù)器端在1MSL內(nèi)沒有收到客戶端發(fā)出的ACK確認(rèn)報(bào)文,就會(huì)再次向客戶端發(fā)出FIN報(bào)文;
如果客戶端在2MSL內(nèi),再次收到了來自服務(wù)器端的FIN報(bào)文,說明服務(wù)器端由于各種原因沒有接收到客戶端發(fā)出的ACK確認(rèn)報(bào)文??蛻舳嗽俅蜗蚍?wù)器端發(fā)出ACK確認(rèn)報(bào)文,計(jì)時(shí)器重置,重新開始2MSL的計(jì)時(shí);否則客戶端在2MSL內(nèi)沒有再次收到來自服務(wù)器端的FIN報(bào)文,說明服務(wù)器端正常接收了ACK確認(rèn)報(bào)文,客戶端可以進(jìn)入CLOSED階段,完成“四次揮手”。
所以,客戶端要經(jīng)歷時(shí)長為2SML的TIME-WAIT階段;這也是為什么客戶端比服務(wù)器端晚進(jìn)入CLOSED階段的原因。
這些東西畢竟都是停留在理論層面的,實(shí)際的場景可比這要錯(cuò)綜復(fù)雜的多了。
故障模式
網(wǎng)絡(luò)中斷
如果網(wǎng)絡(luò)發(fā)生了中斷,那就不用提什么“主動(dòng)關(guān)閉”,什么“FIN”包了。TCP程序也并不能感應(yīng)到連接異常,除非路由器發(fā)出一條ICMP報(bào)文,說明目的網(wǎng)絡(luò)或主機(jī)不可達(dá);或者說通過read或write調(diào)用才會(huì)返回UNreachable的錯(cuò)誤。
可惜大多數(shù)時(shí)候并不是如此,在沒有 ICMP 報(bào)文的情況下,TCP 程序并不能理解感應(yīng)到連接異常。如果程序是阻塞在 read 調(diào)用上,那么很不幸,程序無法從異常中恢復(fù)。
如果程序先調(diào)用了 write 操作發(fā)送了一段數(shù)據(jù)流,接下來阻塞在 read 調(diào)用上,結(jié)果會(huì)非常不同。Linux 系統(tǒng)的 TCP 協(xié)議棧會(huì)不斷嘗試將發(fā)送緩沖區(qū)的數(shù)據(jù)發(fā)送出去,大概在重傳 12 次、合計(jì)時(shí)間約為 9 分鐘之后,協(xié)議棧會(huì)標(biāo)識(shí)該連接異常,這時(shí),阻塞的 read 調(diào)用會(huì)返回一條 TIMEOUT 的錯(cuò)誤信息。如果此時(shí)程序還執(zhí)著地往這條連接寫數(shù)據(jù),寫操作會(huì)立即失敗,返回一個(gè) SIGPIPE 信號(hào)給應(yīng)用程序。
而一旦返回了這種信號(hào),進(jìn)程就會(huì)被終止掉了。也就是我們常說的,程序崩了。
對(duì)端有 FIN 包發(fā)出
這種情況呢,是比較常見的了,至少在我這里是比較常見的,一般不會(huì)造成太惡劣的影響,除非在同一時(shí)間內(nèi)有大批量的連接斷開,那會(huì)占用很多的資源的。
對(duì)端如果有 FIN 包發(fā)出,可能的場景是對(duì)端調(diào)用了 close 或 shutdown 顯式地關(guān)閉了連接,也可能是對(duì)端應(yīng)用程序崩潰,操作系統(tǒng)內(nèi)核代為清理所發(fā)出的。從應(yīng)用程序角度上看,無法區(qū)分是哪種情形。
阻塞的 read 操作在完成正常接收的數(shù)據(jù)讀取之后,F(xiàn)IN 包會(huì)通過返回一個(gè) EOF 來完成通知,此時(shí),read 調(diào)用返回值為 0。這里強(qiáng)調(diào)一點(diǎn),收到 FIN 包之后 read 操作不會(huì)立即返回。你可以這樣理解,收到 FIN 包相當(dāng)于往接收緩沖區(qū)里放置了一個(gè) EOF 符號(hào),之前已經(jīng)在接收緩沖區(qū)的有效數(shù)據(jù)不會(huì)受到影響。
服務(wù)器斷開
注意如果我們的速度不夠快,導(dǎo)致服務(wù)器端從睡眠中蘇醒,并成功將報(bào)文發(fā)送出來后,客戶端會(huì)正常顯示,此時(shí)我們停留,等待標(biāo)準(zhǔn)輸入。如果不繼續(xù)通過 read 或 write 操作對(duì)套接字進(jìn)行讀寫,是無法感知服務(wù)器端已經(jīng)關(guān)閉套接字這個(gè)事實(shí)的。