【項目】基於TCP/IP的socket編程之心跳機制

什麼是心跳機制?   

  想一下, 當tcp連接被破壞後, 如果是死連接了, 服務端和客戶端怎樣才能知道信息能不能到達對方呢? 很自然的想法是, 不斷地給對方發探測信號, 看有沒有迴應, 這就是心跳機制的直白原理。
  所謂的心跳即是數據包, 發心跳就是一方向另一方發送的數據包, 不斷地發送, 如果收不到迴應, 那麼就有理由認爲是tcp連接出了問題。 那爲什麼要叫心跳呢? 你摸一下你的心, 你看它是不是均勻在跳? 理解了吧, 均勻發出去的數據包就類似於均勻的心跳信號。 所以, 我要說: 心跳就是(探測性的)數據包。
  
  之所以叫心跳包是因爲:它像心跳一樣每隔固定時間發一次,以此來告訴服務器,這個客戶端還活着。事實上這是爲了保持長連接,至於這個包的內容,是沒有什麼特別規定的,不過一般都是很小的包,或者只包含包頭的一個空包。

爲什麼需要心跳機制?

  採用TCP連接的C/S模式軟件,連接的雙方在連接空閒狀態時,如果任意一方意外崩潰、當機、網線斷開或路由器故障,另一方無法得知TCP連接已經失效,除非繼續在此連接上發送數據導致錯誤返回。很多時候,這不是我們需要的。我們希望服務器端和客戶端都能及時有效地檢測到連接失效,然後優雅地完成一些清理工作並把錯誤報告給用戶。
  

怎麼來實現它?

  如何及時有效地檢測到一方的非正常斷開,一直有兩種技術可以運用。一種是由TCP協議層實現的Keepalive,另一種是由應用層自己實現的心跳包。

  在TCP的機制裏面,本身是存在有心跳包的機制的,也就是TCP的選項:SO_KEEPALIVE。系統默認是設置的2小時的心跳頻率。但是它檢查不到機器斷電、網線拔出、防火牆這些斷線。而且邏輯層處理斷線可能也不是那麼好處理。一般,如果只是用於保活還是可以的。
  心跳包一般來說都是在邏輯層發送空的echo包來實現的。下一個定時器,在一定時間間隔下發送一個空包給客戶端,然後客戶端反饋一個同樣的空包回來,服務器如果在一定時間內收不到客戶端發送過來的反饋包,那就只有認定說掉線了。
  其實,要判定掉線,只需要send或者recv一下,如果結果爲零,則爲掉線。但是,在長連接下,有可能很長一段時間都沒有數據往來。理論上說,這個連接是一直保持連接的,但是實際情況中,如果中間節點出現什麼故障是難以知道的。更要命的是,有的節點(防火牆)會自動把一定時間之內沒有數據交互的連接給斷掉。在這個時候,就需要我們的心跳包了,用於維持長連接,保活。
  在獲知了斷線之後,服務器邏輯可能需要做一些事情,比如斷線後的數據清理呀,重新連接呀……當然,這個自然是要由邏輯層根據需求去做了。
  總的來說,心跳包主要也就是用於長連接的保活和斷線處理。一般的應用下,判定時間在30-40秒比較不錯。如果實在要求高,那就在6-9秒。

心跳檢測步驟:

1客戶端每隔一個時間間隔發生一個探測包給服務器
2客戶端發包時啓動一個超時定時器
3服務器端接收到檢測包,應該回應一個包
4如果客戶機收到服務器的應答包,則說明服務器正常,刪除超時定時器
5如果客戶端的超時定時器超時,依然沒有收到應答包,則說明服務器掛了

心跳包的發送

方法1:應用層自己實現的心跳包

  由應用程序自己發送心跳包來檢測連接是否正常,大致的方法是:
  服務器在一個 Timer事件中定時 向客戶端發送一個短小精悍的數據包,然後啓動一個低級別的線程,在該線程中不斷檢測客戶端的迴應, 如果在一定時間內沒有收到客戶端的迴應,即認爲客戶端已經掉線;同樣,如果客戶端在一定時間內沒 有收到服務器的心跳包,則認爲連接不可用。

方法2:TCP的KeepAlive保活機制

  因爲要考慮到一個服務器通常會連接多個客戶端,因此由用戶在應用層自己實現心跳包,代碼較多 且稍顯複雜,而利用TCP/IP協議層爲內置的KeepAlive功能來實現心跳功能則簡單得多。
  不論是服務端還是客戶端,一方開啓KeepAlive功能後,就會自動在規定時間內向對方發送心跳包, 而另一方在收到心跳包後就會自動回覆,以告訴對方我仍然在線。  
  因爲開啓KeepAlive功能需要消耗額外的寬帶和流量,所以TCP協議層默認並不開啓KeepAlive功 能,儘管這微不足道,但在按流量計費的環境下增加了費用,另一方面,KeepAlive設置不合理時可能會 因爲短暫的網絡波動而斷開健康的TCP連接。並且,默認的KeepAlive超時需要7,200,000 MilliSeconds, 即2小時,探測次數爲5次。對於很多服務端應用程序來說,2小時的空閒時間太長。因此,我們需要手工開啓KeepAlive功能並設置合理的KeepAlive參數。

  根據上面的介紹我們可以知道對端以一種非優雅的方式斷開連接的時候,我們可以設置SO_KEEPALIVE屬性使得我們在2小時以後發現對方的TCP連接是否依然存在。
具體操作:

  //設置KeepAlive     
   1BOOL   bKeepAlive   =   TRUE;     
    int nRet=::setsockopt(sockClient,SOL_SOCKET,SO_KEEPALIVE,(char*)&bKeepAlive,sizeof(bKeepAlive));     
    if(nRet!=0)    
    {     
        AfxMessageBox("出錯"); 
        return   ;
    }     

   2、感覺兩小時時間太長可以自行設定方法1 
//設置KeepAlive檢測時間和次數     
    tcp_keepalive    inKeepAlive   =   {0};   //輸入參數     
    unsigned   long   ulInLen   =   sizeof(tcp_keepalive );         

    tcp_keepalive    outKeepAlive   =   {0};   //輸出參數     
    unsigned   long   ulOutLen   =   sizeof(tcp_keepalive );         

    unsigned   long   ulBytesReturn   =   0;     

    //設置socket的keep   alive爲10秒,並且發送次數爲3次     
    inKeepAlive.onoff   =   1;       
    inKeepAlive.keepaliveinterval   =   4000;   //兩次KeepAlive探測間的時間間隔     
    inKeepAlive.keepalivetime   =   1000;   //開始首次KeepAlive探測前的TCP空閉時間     

    nRet=WSAIoctl(sockClient,       
        SIO_KEEPALIVE_VALS,     
        (LPVOID)&inKeepAlive,     
        ulInLen,     
        (LPVOID)&outKeepAlive,     
        ulOutLen,     
        &ulBytesReturn,     
        NULL,     
        NULL);     
    if(SOCKET_ERROR   ==   nRet)     
    {     
        AfxMessageBox("出錯");
        return;    
    }   
3、感覺兩小時時間太長可以自行設定方法2
因此我們可以得到
    int                 keepIdle = 6;
    int                 keepInterval = 5;
    int                 keepCount = 3;
    Setsockopt(listenfd, SOL_TCP, TCP_KEEPIDLE, (void *)&keepIdle, sizeof(keepIdle));
    Setsockopt(listenfd, SOL_TCP,TCP_KEEPINTVL, (void *)&keepInterval, sizeof(keepInterval));
    Setsockopt(listenfd,SOL_TCP, TCP_KEEPCNT, (void *)&keepCount, sizeof(keepCount));

詳見:http://blog.csdn.net/gavin1203/article/details/5290609
對setsockopt的操作,詳見:http://www.cnblogs.com/hateislove214/archive/2010/11/05/1869886.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章