摘要
- TCP斷開連接
- TIME_WAIT
- TIME_WAIT優(yōu)化
- TCP保活
- Sokcet編程
TCP斷開連接
TCP斷開連接,需要經(jīng)歷四次揮手,通信的雙方都可主動斷開連接,斷開連接通信的雙方占用的資源將會被釋放。
- 客戶端會發(fā)送一個FIN報文給服務(wù)端,然后進(jìn)入FIN_WAIT_1狀態(tài)
- 服務(wù)端在收到FIN報文后,會回復(fù)客戶端段一個ACK報文,然后進(jìn)入CLOSED_WAIT狀態(tài)
- 客戶端在收到服務(wù)端的ACK報文以后會進(jìn)入FIN_WAIT_2狀態(tài)
- 服務(wù)端在處理完歷史數(shù)據(jù)以后會發(fā)送FIN報文給客戶端,然后進(jìn)入LAST_ACK狀態(tài)
- 客戶端在收到服務(wù)端的FIN報文以后,會發(fā)送一個ACK報文給服務(wù)端,然后進(jìn)入TIME_WAIT狀態(tài)
- 服務(wù)器在收到ACK報文以后,就會真正的關(guān)閉連接,進(jìn)入CLOSED狀態(tài)
- 客戶端在經(jīng)過2MSL時間后,也會自動關(guān)閉連接進(jìn)入CLOSED狀態(tài)
為什么回收需要四次
原因是客戶端在主動發(fā)起FIN報文以后僅表示客戶端不再主動發(fā)送數(shù)據(jù)了但是還可以接收數(shù)據(jù)。服務(wù)器在響應(yīng)ACK報文以后,還有可能有數(shù)據(jù)還在處理且需要發(fā)送給客戶端,因此當(dāng)服務(wù)器處理完這些數(shù)據(jù)以后才能發(fā)送FIN報文表示同意關(guān)閉連接。
因此服務(wù)端的ACK和FIN報文需要分開發(fā)送,揮手也就變成了4次。
什么是MSL和TTL
TTL是IP頭部中的一個字段,是指IP數(shù)據(jù)報可以經(jīng)過的最大路由數(shù),每經(jīng)過一個路由器都需要減1,當(dāng)TTL值為0時數(shù)據(jù)報就會被丟棄,同時發(fā)送ICMP報文給源主機(jī)。TTL的單位是路由跳數(shù)。
MSL是報文在網(wǎng)絡(luò)中存在的最長時間,超過該時間就會被丟棄。
為什么TIME_WAIT需要經(jīng)歷2MSL后才可以變?yōu)镃LOSED
網(wǎng)絡(luò)中存在的發(fā)送方數(shù)據(jù)包,首先需要發(fā)送給服務(wù)端,服務(wù)端在處理完以后又會將相應(yīng)發(fā)送給客戶端,所以總共需要2個倍的時間。
2MSL的時間是從客戶端接收到FIN報文并且發(fā)送ACK報文時開始的。如果此時ACK報文沒有被服務(wù)端接收到觸發(fā)了服務(wù)端的超時重傳,客戶端又再次收到了FIN報文,那么2MSL將重新開始計時。
linux中默認(rèn)一個MSL是30s,也就是說TIME_WAIT的時間是60s。
TIME_WAIT
為什么需要TIME_WAIT狀態(tài)
主動發(fā)起連接中斷的一方需要有TIME_WAIT狀態(tài),主要是以下原因:
- 防止具有相同四元組的舊數(shù)據(jù)包被收到
- 保證最后一次ACK報文能被被動關(guān)閉連接的一方收到,也就是保證被動關(guān)閉連接的一方能被正確關(guān)閉。
防止舊連接的數(shù)據(jù)包被收到
假設(shè)沒有TIME_WAIT狀態(tài),如果有相同的端口的TCP連接被服用后,上圖中被延遲SEQ=301的數(shù)據(jù)包抵達(dá)了客戶端,客戶端是有可能正常接收該報文的,此時就會產(chǎn)生數(shù)據(jù)錯亂現(xiàn)象。
但通過2MSL的等待時間,通信雙方的數(shù)據(jù)包都可以在網(wǎng)絡(luò)中消失,新的數(shù)據(jù)包一定是新連接的。
保證連接正確關(guān)閉
通過等待2MSL的時間確保最后一次ACK報文被被動斷開連接的一方收到,從而正常關(guān)閉。
上圖如果服務(wù)端沒有收到最后一個ACK報文會處于LAST_ACK狀態(tài),如果此時客戶端發(fā)起了一個新的SYN報文請求建立連接,服務(wù)端會發(fā)送RST報文給客戶端,連接建立失敗。
但是通過等待2MSL的時間會解決上述問題,因為假設(shè)服務(wù)端沒有收到最后一次ACK報文,會觸發(fā)超時重傳重新發(fā)送FIN報文并等待新的ACK報文。客戶端在收到新的FIN報文時會重新發(fā)送ACK報文并刷新2MSL的計時,最終能夠保證服務(wù)端的連接能夠正常關(guān)閉。
TIME_WAIT過多的弊端
服務(wù)器如果有TIME_WAIT狀態(tài)的連接,說明TCP連接的斷開是由服務(wù)端發(fā)起的,此時如果TIME_WAIT的連接過多,將會出現(xiàn)以下問題:
- 內(nèi)存資源占用
- 端口資源占用,假設(shè)端口被占滿,將無法建立新的連接
# 該參數(shù)用于指定開放的端口資源,默認(rèn)是32768-61000
net.ipv4.ip_local_port_range
TIME_WAIT優(yōu)化
TIME_WAIT的優(yōu)化主要有以下幾種方式,每種方式都有利有弊:
- 打開net.ipv4.tcp_tw_reuse和net.ipv4.tcp_timestamps選項
- net.ipv4.tcp_max_tw_buckets
- 應(yīng)用程序使用SO_LINGER,應(yīng)用強(qiáng)制使用RST關(guān)閉
打開net.ipv4.tcp_tw_reuse和net.ipv4.tcp_timestamps選項
參數(shù)開啟以后,可以復(fù)用處于TIME_WAIT的Socket給新的連接使用。
tcp_tw_reuse的功能只能用于連接發(fā)起方,開啟該參數(shù)以后,在調(diào)用connect函數(shù)時,內(nèi)核會隨機(jī)找一個time_wait超過1s的連接給新的連接復(fù)用。
net.ipv4.tcp_timestamp默認(rèn)開啟,表示打開對TCP時間戳的支持。時間戳字段存儲在TCP頭部的選項字段中,用于記錄TCP發(fā)送方的時間戳和從對端接收到的最新時間戳。
net.ipv4.tcp_max_tw_buckets
當(dāng)系統(tǒng)中的TIME_WAIT的連接數(shù)超過該項的值時,系統(tǒng)那個會將后面TIME_WAIT的連接重置,不推薦使用。
程序使用SO_LINGER
通過設(shè)置Sokcet的一些選項,來影響close方法的一些行為。
如果SO_LINGER中的onoff為非0,并且linger為0,調(diào)用close方法以后會立即發(fā)送一個RST報文給對方,TCP連接會直接跳過四次握手關(guān)閉。也過于暴力不推薦。
TCP保活機(jī)制
在某個時間段內(nèi),如果TCP連接上無任何活動,TCP保活機(jī)制開始生效,每隔一段時間就會發(fā)送一個探測報文,如果連續(xù)幾個探測報文都沒有收到響應(yīng),則認(rèn)為TCP連接已死,系統(tǒng)內(nèi)核會將錯誤信息通知給應(yīng)用程序。
# 用于控制保活時間,如果7200s內(nèi)沒有活動,則會啟動保活機(jī)制
net.ipv4.tcp_keepalive_time=7200
# 保活機(jī)制每次檢測間隔為75s
net.ipv4.tcp_keepalive_intvl=75
# 如果9次探測無響應(yīng),則認(rèn)為對端不可答,中斷本次連接
net.ipv4.tcp_keepalive_probes=9
上述三個都是Linux中的默認(rèn)值,也就是說Linux操作系統(tǒng)中至少經(jīng)過2小時11分15秒才可以發(fā)現(xiàn)一個死亡連接。
Socket編程
public ServerSocket(int port, int backlog) throws IOException {
this(port, backlog, null);
}
JAVA中的ServerSokcet的初始化方法中有一個backlog參數(shù),該參數(shù)在Linux2.2以前代表SYN隊列大小,但是在Linux 2.2以后就是全連接隊列的大小(accept隊列的大小)。
- 半連接隊列(SYN隊列):接收SYN請求,處于SYN_RCVD狀態(tài)的連接
- 全連接隊列(Accept隊列):完成三次握手處于ESTABLISHED狀態(tài)的連接
Socket的一些連接操作對應(yīng)的tcp連接步驟
- Socket在調(diào)用connect方法時,會發(fā)送SYN包給服務(wù)端,服務(wù)端會接收到到SYN報文,并且服務(wù)端會半連接隊列里初始化一個連接。
- 服務(wù)端在處理完以后會發(fā)送ACK+SYN報文給客戶端,客戶端收到以后切實是就是connect方法的返回,同時客戶端也需要對服務(wù)端的SYN報文進(jìn)行應(yīng)答。
- 服務(wù)端收到ACK報文以后,半連接隊里的連接會被轉(zhuǎn)移到全連接隊列中,此時accept方法會成功拿到連接并生成一個Socket(這個就是傳輸時的Socket,不是監(jiān)聽Socket)。
close方法對應(yīng)的TCP四次揮手
- 客戶端調(diào)用close方法,會發(fā)送一個FIN報文給服務(wù)端
- 服務(wù)端收到FIN報文時,TCP協(xié)議棧會為該包插入一個文件結(jié)束符EOF到接收緩沖區(qū),應(yīng)用程序可以通過read方法獲取到該文件結(jié)束符。**EOF會被放在所有的數(shù)據(jù)之后。**服務(wù)端會進(jìn)入CLOSED_WAIT狀態(tài)。
- 服務(wù)端處理完所有的數(shù)據(jù)以后,會讀取到EOF,此時會調(diào)用close方法關(guān)閉Socket,然后發(fā)送一個FIN包進(jìn)入LAST_ACK狀態(tài)。
- 后面的其實就是TCP最終斷開連接。






