1. 程式人生 > >TCP異常連接的檢測方法

TCP異常連接的檢測方法

處理 資源 分發 spa 還要 重要 使用 延遲 非阻塞

背景

  在平時的開發中,經常會碰到一些需要檢測tcp連接是否正常的場景。比如一個分布式的應用,一個調度任務的節點管理一堆用來跑業務的節點。當調度節點進行調度的時候,需要把任務分發給它認為正常的業務節點去執行。業務節點是否正常,一個重要的參考依據就是調度節點和業務節點之間的tcp連接是否正常。這時候就需要調度節點主動地去檢測tcp連接。常見的檢測方法有以下幾種

方案一、通過TCP協議的返回值進行判斷

    <1> 利用select,把socket設置為非阻塞。然後使用select等待該socket的可讀事件。如果socket可讀,但是recv的返回值是0,則說明socket已經被對端斷開,這時候就可以調用close關閉socket。這裏還要註意一點,recv還可能返回負數,這個代表socket操作出錯。但是仍然應該判斷一下errno是否為EINTR。如果errno是EINTR,則說明recv函數是被信號中斷返回的,這時候不能判斷socket的連接是否正常,也不應該調用close關閉socket。

    <2> 利用poll的事件。poll本身提供了POLLHUP,POLLERR, POLLNVAL三個事件。如果文件描述符是socket,則POLLHUP代表socket已經斷開了連接,在TCP底層就是已經收到了FIN報文。POLLERR表示socket出現了錯誤,一般情況下是收到了rst報文,或者已經發送了rst報文。這兩種情況都應該調用close關閉socket。POLLNVAL代表socket沒有打開,這時不能使用close關閉它,而應該根據自己的業務做一些其他的操作。因為關閉一個未打開的socket會出錯。

    這兩種方法都可以很精確地判斷tcp連接是否正常,但是仍然有很明顯的缺陷。就是它只可以根據TCP操作的返回值來進行判斷。如果TCP四次握手沒有正常被執行呢?比如連接對端機器直接掛了,那麽就不會發送FIN報文給這一端,select不會返回socket可讀,poll不會返回socket異常。那麽這個死鏈接將會永遠檢測不到。直到寫這個socket的時候,對端直接返回一個ret報文,這時才知道這個連接已經斷掉了。這就意味著tcp連接異常可能永遠檢測不到,或者檢測到的延遲非常大。這對於一些資源寶貴而且要求高性能的服務器是不能接受的,比如遊戲服務器,比如搜索服務器。

  

方案二、在第一種方案的基礎上設置socket的 keep alive 機制

    方案一的主要缺陷在於檢測不及時,或者根本檢測不到。TCP協議提供了keep alive機制。如果開啟了這個特性(暫時稱開啟了keep alive的一端為開啟端),在默認情況下,開啟的著一端的socket相關結構中會維護一個定時器,默認是2小時。如果在2小時內兩端沒有數據往來,那麽開啟端就會給另一端發送一個ack空報文。這時候分幾種情況:

    <1> 對端機器可達,而且TCP相關組件運行正常。那麽對端就會給開啟端發送一個ack空報文。這時開啟端就知道對端是正常的,意味著tcp連接也沒有問題。開啟端會重新初始化定時器,等待下一個超時的到來。需要註意的是,如果兩端之間有數據往來,定時器也會被重新初始化為2個小時。  

    <2> 對端掛了,或者正在重啟,還沒有完全起來。或者對端服務器不可達。 這種狀態的對端是不會響應這個ack的。開啟端的 keep alive 機制會把這種情況當探測超時來處理,並且重新發送ack到對端。當超時次數超過一定限制,keep alive 就認為這個tcp連接有問題。典型值是每次75秒,超時9次。

    <3> 對端掛過,但是已經重啟完成。這時候發送這個ack和寫已經關閉的socket是一種情況,對端會返回一個rst報文,這樣開啟端就知道tcp連接出問題了。

    可以看出 keep alive 機制彌補了方案一種不能判斷沒有進行正常四次揮手連接出現問題的缺陷。默認的發送超時和發送間隔都是可以調整的。    

    tcp_keepalive_time: KeepAlive的空閑時長,默認是2小時

    tcp_keepalive_intvl: KeepAlive探測包的發送間隔,默認是75s 

    tcp_keepalive_probes: 在tcp_keepalive_time之後,沒有接收到對方確認,繼續發送保活探測包次數,默認是9次

    這3個參數使用 setsockopt函數都是可以配置的。

    方案二看似已經完美了,能夠比較精確而且及時地發現有問題的連接。但是還有2個缺點。第一個是 keep alive 機制看似牛逼,但是很多人不建議使用。因為上面說的3個參數很難根據業務場景給出合適的值,設置不好很容易對tcp連接狀態發生誤判,關閉了一個本來正常的連接。而且沒有一個主動通知應用層的方式。比如socket連接出錯了,TCP協議接到了rst,fin,或者keep alive判斷出socket有問題了,但是並不會主動去通知應用層,必須我們自己 recv socket或者等待錯誤事件才能得到這個錯誤。第二個是很多場景下,keep alive 檢測仍然不夠及時,比如對端掛了,最長需要等待 tcp_keepalive_intvl * tcp_keepalive_probes時間才可以檢測出來,而且這兩個值還不能設置得太小,太小了容易誤判。

方案三、應用層的心跳

    這種形式的心跳設計就比較多樣化了,而且靈活,可以很好地適應業務場景。唯一的缺點就是要自己寫代碼。我目前接觸到的就是定期進行RPC調用。看RPC調用是否正常,如果返回錯誤或者拋出異常,就說明連接有問題。

TCP異常連接的檢測方法