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