1. 程式人生 > >tcp短連線TIME_WAIT問題解決方法大全(2)——SO_LINGER

tcp短連線TIME_WAIT問題解決方法大全(2)——SO_LINGER

SO_LINGER是一個socket選項,通過setsockopt API進行設定,使用起來比較簡單,但其實現機制比較複雜,且字面意思上比較難理解。
解釋最清楚的當屬《Unix網路程式設計卷1》中的說明(7.5章節),這裡簡單摘錄:
SO_LINGER的值用如下資料結構表示:
struct linger {
     int l_onoff; /* 0 = off, nozero = on */
     int l_linger; /* linger time */

};

其取值和處理如下:
1、設定 l_onoff為0,則該選項關閉,l_linger的值被忽略,等於核心預設情況,close呼叫會立即返回給呼叫者,如果可能將會傳輸任何未傳送的資料;
2、設定 l_onoff為非0,l_linger為0,則套介面關閉時TCP夭折連線
,TCP將丟棄保留在套介面傳送緩衝區中的任何資料併發送一個RST給對方,
   而不是通常的四分組終止序列,這避免了TIME_WAIT狀態;
3、設定 l_onoff 為非0,l_linger為非0,當套介面關閉時核心將拖延一段時間(由l_linger決定)。
   如果套介面緩衝區中仍殘留資料,程序將處於睡眠狀態,直 到(a)所有資料傳送完且被對方確認,之後進行正常的終止序列(描述字訪問計數為0)
   或(b)延遲時間到。此種情況下,應用程式檢查close的返回值是非常重要的,如果在資料傳送完並被確認前時間到,close將返回EWOULDBLOCK錯誤且套介面傳送緩衝區中的任何資料都丟失。
   close的成功返回僅告訴我們傳送的資料(和FIN)已由對方TCP確認,它並不能告訴我們對方應用程序是否已讀了資料。如果套介面設為非阻塞的,它將不等待close完成。
   
第一種情況其實和不設定沒有區別,第二種情況可以用於避免TIME_WAIT狀態,但在Linux上測試的時候,並未發現傳送了RST選項,而是正常進行了四步關閉流程,
初步推斷是“只有在丟棄資料的時候才傳送RST”,如果沒有丟棄資料,則走正常的關閉流程。
檢視Linux原始碼,確實有這麼一段註釋和原始碼:
=====linux-2.6.37 net/ipv4/tcp.c 1915=====
/* As outlined in RFC 2525, section 2.17, we send a RST here because
* data was lost. To witness the awful effects of the old behavior of
* always doing a FIN, run an older 2.1.x kernel or 2.0.x, start a bulk
* GET in an FTP client, suspend the process, wait for the client to
* advertise a zero window, then kill -9 the FTP client, wheee...
* Note: timeout is always zero in such a case.
*/
if (data_was_unread
) {
/* Unread data was tossed, zap the connection. */
NET_INC_STATS_USER(sock_net(sk), LINUX_MIB_TCPABORTONCLOSE);
tcp_set_state(sk, TCP_CLOSE);
tcp_send_active_reset(sk, sk->sk_allocation);

另外,從原理上來說,這個選項有一定的危險性,可能導致丟資料,使用的時候要小心一些,但我們在實測libmemcached的過程中,沒有發現此類現象,
應該是和libmemcached的通訊協議設定有關,也可能是我們的壓力不夠大,不會出現這種情況。


第三種情況其實就是第一種和第二種的折中處理,且當socket為非阻塞的場景下是沒有作用的。
對於應對短連線導致的大量TIME_WAIT連線問題,個人認為第二種處理是最優的選擇,libmemcached就是採用這種方式,
從實測情況來看,開啟這個選項後,TIME_WAIT連線數為0,且不受網路組網(例如是否虛擬機器等)的影響。