tcp 服務端如何判斷客戶端斷開連接


一篇文章:

 
最近在做一個服務器端程序,C/S結構。功能方面比較簡單就是client端與server端建立連接,然后發送消息給server。 我在server端會使用專門的線程處理一條socket連接。這就涉及到一個問題,如果socket連接斷開(異常,正常)后,我如何才能感知到?server端這邊是絕對被動的,sever端不能主動斷開連接。也沒有連接鏈路維持包之類的。client端發送數據的時間也是不定的。在socket連接斷開后, server要能夠感知到並釋放資源。
這個問題在思考測試,詢問同事之后,找到了一個方法,可以做到這一點。
當使用 select()函數測試一個socket是否可讀時,如果select()函數返回值為1,且使用recv()函數讀取的數據長度為0 時,就說明該socket已經斷開。
為了更好的判定socket是否斷開,我判斷當recv()返回值小於等於0時,socket連接斷開。但是還需要判斷 errno是否等於 EINTR 。如果errno == EINTR 則說明recv函數是由於程序接收到信號后返回的,socket連接還是正常的,不應close掉socket連接。

PS:對於堵塞socket的recv函數會在以下三種情況下返回:
(1)recv到數據時,會返回。
(2)在整個程序接收到信號時,返回-1。 errno = EINTR。//在程序的起始階段,屏蔽掉信號的除外。部分信號還是屏蔽不掉的。
(3)socket出現問題時,返回-1.具體錯誤碼看 man recv()
(4)一定要看 man 說明,很詳細,很有幫助。
這種方法經過長時間測試后,是有效的。所以寫出來讓大家參考一下,請大家發表意見。
 
參考: http://www.cppblog.com/prayer/archive/2009/04/14/79900.aspx
 
tcp會自動斷開連接嗎?
已經建立了TCP連接,並可能互通信息。但是如果長時間不進行信息的傳遞。這個TCP連接會自動斷開嗎?
如果能自動斷開的話,這個時間大約是多少呢?
回答: TCP的保活定時器能夠保證TCP連接一直保持,但是TCP的保活定時器不是每個TCP/IP協議棧就實現了,因為RFC並不要求TCP保活定時器一定要實現。

摘自《TCP/IP詳解》卷1第23章:保活並不是T C P規范中的一部分。Host Requirements RFC提供了3個不使用保活定
時器的理由: (1) 在出現短暫差錯的情況下,這可能會使一個非常好的連接釋放掉;
(2)它們耗費不必要的帶寬;(3)在按分組計費的情況下會在互聯網上花掉更多的錢。
然而,許多實現提供了保活定時器。

更具體的資料,請參閱RFC。
tcp/ip詳解更全面的描述:
tcp保活定時器

23.1介紹

在一個空閑的(idle)TCP連接上,沒有任何的數據流,許多TCP/IP的初學者都對此感到驚奇。也就是說,如果TCP連接兩端沒有任何一個進程在向對方發送數據,那么在這兩個TCP模塊之間沒有任何的數據交換。你可能在其它的網絡協議中發現有輪詢(polling),但在TCP中它不存在。言外之意就是我們只要啟動一個客戶端進程,同服務器建立了TCP連接,不管你離開幾小時,幾天,幾星期或是幾個月,連接依舊存在。中間的路由器可能崩潰或者重啟,電話線可能go down或者back up,只要連接兩端的主機沒有重啟,連接依舊保持建立

這就可以認為不管是客戶端的還是服務器端的應用程序都沒有應用程序級(application-level)的定時器來探測連接的不活動狀態(inactivity),從而引起任何一個應用程序的終止。回憶在10.7結束,BGP每隔30秒就向對方發送一個應用程序探測。這是一個應用程序定時器(application timer),與TCP存活定時器不同。

然而有的時候,服務器需要知道客戶端主機是否已崩潰並且關閉,或者崩潰但重啟。許多實現提供了存活定時器來完成這個任務。

存活(keepalive)並不是TCP規范的一部分。在Host Requirements RFC羅列有不使用它的三個理由:(1)在短暫的故障期間,它們可能引起一個良好連接(good connection)被釋放(dropped),(2)它們消費了不必要的寬帶,(3)在以數據包計費的互聯網上它們(額外)花費金錢。然而,在許多的實現中提供了存活定時器。

存活定時器是一個包含爭議的特征。許多人認為,即使需要這個特征,這種對對方的輪詢也應該由應用程序來完成,而不是由TCP中實現。一些人對這個話題表現了極大的熱情,甚至達到宗教般的狂熱。

如果兩個終端系統之間的某個中間網絡上有連接的暫時中斷,那么存活選項(option)就能夠引起兩個進程間一個良好連接的終止。例如,如果正好在某個中間路由器崩潰、重啟的時候發送存活探測,TCP就將會認為客戶端主機已經崩潰,但事實並非如此。

一些服務器應用程序可能代表客戶端占用資源,它們需要知道客戶端主機是否崩潰。存活定時器可以為這些應用程序提供探測服務。Telnet服務器和Rlogin服務器的許多版本都默認提供存活選項。

個人計算機用戶使用TCP/IP協議通過Telnet登錄一台主機,這是能夠說明需要使用存活定時器的一個常用例子。如果某個用戶在使用結束時只是關掉了電源,而沒有注銷(log off),那么他就留下了一個半打開(half-open)的連接。在圖18.16,我們看到如何在一個半打開連接上通過發送數據,得到一個復位(reset)返回,但那是在客戶端,是由客戶端發送的數據。如果客戶端消失,留給了服務器端半打開的連接,並且服務器又在等待客戶端的數據,那么等待將永遠持續下去。存活特征的目的就是在服務器端檢測這種半打開連接。

更多: http://blog.csdn.net/zhoujunyi/article/details/1920871

我的方法不一樣,我用getsockopt來判斷,還是蠻准確的 
  1. int SocketConnected(int sock) 
  2. if(sock<=0) 
  3. return 0; 
  4. struct tcp_info info; 
  5. int len=sizeof(info); 
  6. getsockopt(sock, IPPROTO_TCP, TCP_INFO, &info, (socklen_t *)&len); 
  7. if((info.tcpi_state==TCP_ESTABLISHED)) 
  8. //myprintf("socket connected\n"); 
  9. return 1; 
  10. else 
  11. //myprintf("socket disconnected\n"); 
  12. return 0; 
  13. }
tcp_info和TCP_ESTABLISHED在 linux/tcp.h
包含
#include <linux/types.h>
#include <asm/byteorder.h>
#include <linux/config.h>
#include < linux/skbuff.h>
#include < linux/ip.h>
#include < net/sock.h>
 
http://www.cse.scu.edu/~dclark/am_256_graph_theory/linux_2_6_stack/globals.html#index_t
int SocketConnected(int sock) 

if(sock<=0) 
return 0; 
struct tcp_info info; 
int len=sizeof(info); 
getsockopt(sock, IPPROTO_TCP, TCP_INFO, &info, (socklen_t *)&len); 
if((info.tcpi_state==TCP_ESTABLISHED)) 

//myprintf("socket connected\n"); 
return 1; 

else 

//myprintf("socket disconnected\n"); 
return 0; 

}
 
 
 
 
目前主要有三種方法來實現用戶掉線檢測:SO_KEEPALIVE ,SIO_KEEPALIVE_VALS 和Heart-Beat線程。
下面我就上面的三種方法來做一下介紹。
(1)SO_KEEPALIVE 機制
        這是socket庫提供的功能,設置接口是setsockopt API:
   BOOL bSet=TRUE;
   setsockopt(hSocket,SOL_SOCKET,SO_KEEPALIVE,(const char*)&bSet,sizeof(BOOL));
       根據MSDN的文檔,如果為socket設置了KEEPALIVE選項,TCP/IP棧在檢測到對方掉線后,
   任何在該socket上進行的調用(發送/接受調用)就會立刻返回,錯誤號是WSAENETRESET ;
   同時,此后的任何在該socket句柄的調用會立刻失敗,並返回WSAENOTCONN錯誤。
 
   該機制的缺點也很明顯:
         默認設置是空閑2小時才發送一個“保持存活探測分節”,不能保證實時檢測!
   當然也可以修改時間間隔參數,但是會影響到所有打開此選項的套接口!
         關聯了完成端口的socket可能會忽略掉該套接字選項。
 
 
(2)SIO_KEEPALIVE_VALS 機制
         設置接口是WSAIoctl API:
     DWORD dwError = 0L ;
     tcp_keepalive sKA_Settings = {0}, sReturned = {0} ;
     sKA_Settings.onoff = 1 ;
     sKA_Settings.keepalivetime = 5500 ; // Keep Alive in 5.5 sec.
     sKA_Settings.keepaliveinterval = 3000 ; // Resend if No-Reply
     if (WSAIoctl(skNewConnection, SIO_KEEPALIVE_VALS, &sKA_Settings,
          sizeof(sKA_Settings), &sReturned, sizeof(sReturned), &dwBytes,
          NULL, NULL) != 0)
     {
           dwError = WSAGetLastError() ;
     }
     實現時需要添加tcp_keepalive and SIO_KEEPALIVE_VALS的定義文件MSTCPiP.h
     該選項不同於SO_KEEPALIVE 機制的就是它是針對單個連接的,對系統其他的套接
     口並不影響。
        針對完成端口的socket,設置了SIO_KEEPALIVE_VALS后,激活包由TCP STACK來負責。
     當網絡連接斷開后,TCP STACK並不主動告訴上層的應用程序,但是當下一次RECV或者SEND操作
     進行后,馬上就會返回錯誤告訴上層這個連接已經斷開了.如果檢測到斷開的時候,在這個連接
     上有正在PENDING的IO操作,則馬上會失敗返回.
 
 
     該機制的缺點:
             不通用啦。MS的API只能用於Windows拉。不過,優雅一些^_^.
    
(3)Heart-Beat線程
        沒說的。自己寫一個后台線程,實現Heart-Beat包,客戶端受到該包后,立刻返回相應的反饋 包。
 
    該方法的好處是通用,但缺點就是會改變現有的通訊協議!
/* Net check Make sure you have not used OUT OF BAND DATA AND YOU CAN use OOB */
int netcheck(int fd) 
{
        int buf_size = 1024;
        char buf[buf_size];
        //clear OOB DATA 
        recv(fd, buf, buf_size);
        if(send(fd, (void *)"\0", 1, MSG_OOB) < 0 )
        {
                fprintf(stderr, "Connection[%d] send OOB failed, %s", fd, strerror(errno));
                return -1;
        }
        return 0;
}
/* Setting SO_TCP KEEPALIVE */
//int keep_alive = 1;//設定KeepAlive
//int keep_idle = 1;//開始首次KeepAlive探測前的TCP空閉時間
//int keep_interval = 1;//兩次KeepAlive探測間的時間間隔
//int keep_count = 3;//判定斷開前的KeepAlive探測次數
void set_keepalive(int fd, int keep_alive, int keep_idle, int keep_interval, int keep_count)
{
        int opt = 1;
        if(keep_alive)
        {
                if(setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE,
                                        (void*)&keep_alive, sizeof(keep_alive)) == -1)
                {
                        fprintf(stderr, 
                                "setsockopt SOL_SOCKET::SO_KEEPALIVE failed, %s\n",strerror(errno));
                }
                if(setsockopt(fd, SOL_TCP, TCP_KEEPIDLE,
                                        (void *)&keep_idle,sizeof(keep_idle)) == -1)
                {
                        fprintf(stderr,
                                "setsockopt SOL_TCP::TCP_KEEPIDLE failed, %s\n", strerror(errno));
                }
                if(setsockopt(fd,SOL_TCP,TCP_KEEPINTVL,
                                        (void *)&keep_interval, sizeof(keep_interval)) == -1)
                {
                        fprintf(stderr,
                                 "setsockopt SOL_tcp::TCP_KEEPINTVL failed, %s\n", strerror(errno));
                }
                if(setsockopt(fd,SOL_TCP,TCP_KEEPCNT,
                                        (void *)&keep_count,sizeof(keep_count)) == -1)
                {
                        fprintf(stderr, 
                                "setsockopt SOL_TCP::TCP_KEEPCNT failed, %s\n", strerror(errno));
                }
        }
}

 

一篇文章:
keep alive VS heart beart:

這周在上班的路上看了本書《Effective TCP/IP Programming》,以下是一些讀書筆記。順帶推薦一下這本書,寫的很棒,適用於像我這樣經常要寫一些有一定質量的網絡編程,但又沒時間啃那些講解TCPIP協議大部頭書的人。

很多人都知道TCP並不會去主動檢測連接的丟失,這意味着,如果雙方不產生交互,那么如果網絡斷了或者有一方機器崩潰,另外一方將永遠不知道連接已經不可用了。檢測連接是否丟失的方法大致有兩種:keepalive和heart-beat。

Keepalive是很多的TCP實現提供的一種機制,它允許連接在空閑的時候雙方會發送一些特殊的數據段,並通過響應與否來判斷連接是否還存活着(所謂keep~~alive)。我曾經寫過一篇關於keepalive的blog ,但后來我也發現,其實keepalive在實際的應用中並不常見。為何如此?這得歸結於keepalive設計的初衷。Keepalive適用於清除死亡時間比較長的連接。 
比如這樣的場景:一個用戶創建tcp連接訪問了一個web服務器,當用戶完成他執行的操作后,很粗暴的直接撥了網線。這種情況下,這個tcp連接已經斷開了,但是web服務器並不知道,它會依然守護着這個連接。如果web server設置了keepalive,那么它就能夠在用戶斷開網線的大概幾個小時以后,確認這個連接已經中斷,然后丟棄此連接,回收資源。
采用keepalive,它會先要求此連接一定時間沒有活動(一般是幾個小時),然后發出數據段,經過多次嘗試后(每次嘗試之間也有時間間隔),如果仍沒有響應,則判斷連接中斷。可想而知,整個周期需要很長的時間。
所以,如前面的場景那樣,需要一種方法能夠清除和回收那些在系統不知情的情況下死去了很久的連接,keepalive是非常好的選擇。 
但是,在大部分情況下,特別是分布式環境中,我們需要的是一個能夠快速或者實時監控連接狀態的機制,這里,heart-beat才是更加合適的方案。 
Heart-beat(心跳),按我的理解,它的原理和keepalive非常類似,都是發送一個信號給對方,如果多次發送都沒有響應的話,則判斷連接中斷。它們的不同點在於,keepalive是tcp實現中內建的機制,是在創建tcp連接時通過設置參數啟動keepalive機制;而heart-beat則需要在tcp之上的應用層實現。一個簡單的heart-beat實現一般測試連接是否中斷采用的時間間隔都比較短,可以很快的決定連接是否中斷。並且,由於是在應用層實現,因為可以自行決定當判斷連接中斷后應該采取的行為,而keepalive在判斷連接失敗后只會將連接丟棄。
關於heart-beat,一個非常有趣的問題是,應該在傳輸真正數據的連接中發送“心跳”信號,還是可以專門創建一個發送“心跳”信號的連接。比如說,A,B兩台機器之間通過連接m來傳輸數據,現在為了能夠檢測A,B之間的連接狀態,我們是應該在連接m中傳輸“心跳”信號,還是創建新的連接n來專門傳輸“心跳”呢?我個人認為兩者皆可。如果擔心的是端到端的連接狀態,那么就直接在該條連接中實現“心跳”。但很多時候,關注的是網絡狀況和兩台主機間的連接狀態,這種情況下, 創建專門的“心跳”連接也未嘗不可。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM