close wait是什麼
close wait的意思到底是什麼?下面是本站小編給大家整理的close wait是什麼,供大家參閱!
close wait是什麼等待結束
淺談CLOSE WAITTCP 有很多連接狀態,每一個都夠聊十塊錢兒的,比如我們以前討論過TIME_WAIT 和FIN_WAIT1,最近時不時聽人提起 CLOSE_WAIT,感覺有必要梳理一下。
所謂 CLOSE_WAIT,借用某位大牛的話來說應該倒過來叫做 WAIT_CLOSE,也就是說「等待關閉」,如果你還不理解其含義,可以看看 TCP 關閉連接時的圖例:
TCP Close
不要被圖中的 client 和 server 所迷惑,你只要記住:主動關閉的一方發出 FIN 包,被動關閉的一方響應 ACK 包,此時,被動關閉的一方就進入了 CLOSE_WAIT 狀態。如果一切正常,稍後被動關閉的一方也會發出 FIN 包,然後遷移到 LAST_ACK 狀態。
通常,CLOSE_WAIT 狀態在服務器停留時間很短,如果你發現大量的 CLOSE_WAIT 狀態,那麼就意味着被動關閉的一方沒有及時發出 FIN 包,一般有如下幾種可能:
程序問題:代碼層面遺漏或者死循環之類的,沒有 close 相應的 socket 連接。
響應太慢:對方已經 timeout 了,本方還忙於耗時邏輯,導致 close 被延後。
BACKLOG 太大:隊列堆積嚴重,導致多餘的請求來不及消費就被關閉了。
如果遭遇了 CLOSE_WAIT 故障,那麼需要立刻通過「netstat」或者「ss」命令來判斷 CLOSE_WAIT 連接是哪些進程引起的,如果是我們自己寫的一些程序,比如用 HttpClient 自定義的蜘蛛,那麼八九不離十是忘記了 close 相應的 socket,如果是一些使用廣泛的程序,比如 Tomcat 之類的,那麼不太可能是它們自身的 BUG,更可能是響應速度太慢或者 BACKLOG 設置過大導致的故障。
此外還有一點需要說明:按照前面圖例所示,當被動關閉的一方處於 CLOSE_WAIT 狀態時,主動關閉的一方處於 FIN_WAIT2 狀態。 那麼爲什麼我們總聽說 CLOSE_WAIT 狀態過多的故障,但是卻相對少聽說 FIN_WAIT2 狀態過多的故障呢?這是因爲 Linux 有一個「tcp_fin_timeout」設置,控制了 FIN_WAIT2 的最大生命週期。壞消息是 CLOSE_WAIT 沒有類似的設置,如果不重啓進程,那麼 CLOSE_WAIT 狀態很可能會永遠持續下去;好消息是如果連接開啓了 keepalive機制,那麼可以通過對應的設置來清理無效連接,不過 keepalive 是治標不治本的方法,還是應該對照前面的解釋找到問題的癥結纔對。
本來想多寫點的,但是着急下班回家,就寫到這吧,結尾推薦兩個案例:
PHP升級導致系統負載過高問題分析
又見CLOSE_WAIT
CLOSE WAIT狀態的生成原因首先我們知道,如果我們的Client程序處於CLOSE_WAIT狀態的話,說明套接字是被動關閉的!
因爲如果是Server端主動斷掉當前連接的話,那麼雙方關閉這個TCP連接共需要四個packet:
Server ---> FIN ---> Client
Server <--- ACK <--- Client
這時候Server端處於FIN_WAIT_2狀態;而我們的程序處於CLOSE_WAIT狀態。
Server <--- FIN <--- Client
這時Client發送FIN給Server,Client就置爲LAST_ACK狀態。
Server ---> ACK ---> Client
Server迴應了ACK,那麼Client的套接字纔會真正置爲CLOSED狀態。
我們的程序處於CLOSE_WAIT狀態,而不是LAST_ACK狀態,說明還沒有發FIN給Server,那麼可能是在關閉連接之前還有許多數據要發送或者其他事要做,導致沒有發這個FIN packet。
原因知道了,那麼爲什麼不發FIN包呢,難道會在關閉己方連接前有那麼多事情要做嗎?
elssann舉例說,當對方調用closesocket的時候,我的程序正在調用recv中,這時候有可能對方發送的FIN包我沒有收到,而是由TCP代回了一個ACK包,所以我這邊套接字進入CLOSE_WAIT狀態。
所以他建議在這裏判斷recv函數的返回值是否已出錯,是的話就主動closesocket,這樣防止沒有接收到FIN包。
因爲前面我們已經設置了recv超時時間爲30秒,那麼如果真的是超時了,這裏收到的錯誤應該是WSAETIMEDOUT,這種情況下也可以主動關閉連接的。
還有一個問題,爲什麼有數千個連接都處於這個狀態呢?難道那段時間內,服務器端總是主動拆除我們的連接嗎?
不管怎麼樣,我們必須防止類似情況再度發生!
首先,我們要保證原來的端口可以被重用,這可以通過設置SO_REUSEADDR套接字選項做到:
重用本地地址和端口
以前我總是一個端口不行,就換一個新的使用,所以導致讓數千個端口進入CLOSE_WAIT狀態。如果下次還發生這種尷尬狀況,我希望加一個限定,只是當前這個端口處於CLOSE_WAIT狀態!
在調用
sockConnected = socket(AF_INET, SOCK_STREAM, 0);
之後,我們要設置該套接字的選項來重用:
/// 允許重用本地地址和端口:
/// 這樣的好處是,即使socket斷了,調用前面的socket函數也不會佔用另一個,而是始終就是一個端口
/// 這樣防止socket始終連接不上,那麼按照原來的做法,會不斷地換端口。
int nREUSEADDR = 1;
setsockopt(sockConnected,
SOL_SOCKET,
SO_REUSEADDR,
(const char*)&nREUSEADDR,
sizeof(int));
教科書上是這麼說的:這樣,假如服務器關閉或者退出,造成本地地址和端口都處於TIME_WAIT狀態,那麼SO_REUSEADDR就顯得非常有用。
也許我們無法避免被凍結在CLOSE_WAIT狀態永遠不出現,但起碼可以保證不會佔用新的端口。
其次,我們要設置SO_LINGER套接字選項:
從容關閉還是強行關閉?
LINGER是“拖延”的意思。
默認情況下(Win2k),SO_DONTLINGER套接字選項的是1;SO_LINGER選項是,linger爲{l_onoff:0,l_linger:0}。
如果在發送數據的過程中(send()沒有完成,還有數據沒發送)而調用了closesocket(),以前我們一般採取的措施是“從容關閉”:
因爲在退出服務或者每次重新建立socket之前,我都會先調用
/// 先將雙向的通訊關閉
shutdown(sockConnected, SD_BOTH);
/// 安全起見,每次建立Socket連接前,先把這個舊連接關閉
closesocket(sockConnected);
我們這次要這麼做:
設置SO_LINGER爲零(亦即linger結構中的l_onoff域設爲非零,但l_linger爲0),便不用擔心closesocket調用進入“鎖定”狀態(等待完成),不論是否有排隊數據未發送或未被確認。這種關閉方式稱爲“強行關閉”,因爲套接字的虛電路立即被複位,尚未發出的所有數據都會丟失。在遠端的recv()調用都會失敗,並返回WSAECONNRESET錯誤。
在connect成功建立連接之後設置該選項:
linger m_sLinger;
m_sLinger.l_onoff = 1; // (在closesocket()調用,但是還有數據沒發送完畢的時候容許逗留)
m_sLinger.l_linger = 0; // (容許逗留的時間爲0秒)
setsockopt(sockConnected,
SOL_SOCKET,
SO_LINGER,
(const char*)&m_sLinger,
sizeof(linger));
總結
也許我們避免不了CLOSE_WAIT狀態凍結的再次出現,但我們會使影響降到最小,希望那個重用套接字選項能夠使得下一次重新建立連接時可以把CLOSE_WAIT狀態踢掉。
我的意思是:當一方關閉連接後,另外一方沒有檢測到,就導致了CLOSE_WAIT的出現,上次我的一個朋友也是這樣,他寫了一個客戶端和 APACHE連接,當APACHE把連接斷掉後,他沒檢測到,出現了CLOSE_WAIT,後來我叫他檢測了這個地方,他添加了調用 closesocket的代碼後,這個問題就消除了。
如果你在關閉連接前還是出現CLOSE_WAIT,建議你取消shutdown的調用,直接兩邊closesocket試試。
另外一個問題:
比如這樣的一個例子:
當客戶端登錄上服務器後,發送身份驗證的請求,服務器收到了數據,對客戶端身份進行驗證,發現密碼錯誤,這時候服務器的一般做法應該是先發送一個密碼錯誤的信息給客戶端,然後把連接斷掉。
如果把
m_sLinger.l_onoff = 1;
m_sLinger.l_linger = 0;
這樣設置後,很多情況下,客戶端根本就收不到密碼錯誤的消息,連接就被斷了。
出現CLOSE_WAIT的原因很簡單,就是某一方在網絡連接斷開後,沒有檢測到這個錯誤,沒有執行closesocket,導致了這個狀態的實現,這在TCP/IP協議的狀態變遷圖上可以清楚看到。同時和這個相對應的還有一種叫TIME_WAIT的。
另外,把SOCKET的SO_LINGER設置爲0秒拖延(也就是立即關閉)在很多時候是有害處的。
還有,把端口設置爲可複用是一種不安全的網絡編程方法。
能不能解釋請看這裏
再看這個圖:
斷開連接的時候,
當發起主動關閉的左邊這方發送一個FIN過去後,右邊被動關閉的這方要回應一個ACK,這個ACK是TCP迴應的,而不是應用程序發送的,此時,被動關閉的一方就處於CLOSE_WAIT狀態了。如果此時被動關閉的這一方不再繼續調用closesocket,那麼他就不會發送接下來的FIN,導致自己老是處於CLOSE_WAIT。只有被動關閉的這一方調用了closesocket,纔會發送一個FIN給主動關閉的這一方,同時也使得自己的狀態變遷爲LAST_ACK。
比如被動關閉的是客戶端。。。
當對方調用closesocket的時候,你的程序正在
int nRet = recv(s,....);
if (nRet == SOCKET_ERROR)
{// closesocket(s);return FALSE;}
很多人就是忘記了那句closesocket,這種代碼太常見了。
我的理解,當主動關閉的一方發送FIN到被動關閉這邊後,被動關閉這邊的TCP馬上回應一個ACK過去,同時向上面應用程序提交一個ERROR,導致上面的SOCKET的send或者recv返回SOCKET_ERROR,正常情況下,如果上面在返回SOCKET_ERROR後調用了 closesocket,那麼被動關閉的者一方的TCP就會發送一個FIN過去,自己的狀態就變遷到LAST_ACK.