【VS開發】TCP服務端如何判斷客戶端斷開鏈接

原文出自:http://www.cnblogs.com/youxin/p/4056041.html

一篇文章:css

 
最近在作一個服務器端程序,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介紹html

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

這就能夠認爲無論是客戶端的仍是服務器端的應用程序都沒有應用程序級(application-level)的定時器來探測鏈接的不活動狀態(inactivity),從而引發任何一個應用程序的終止。回憶在10.7結束,BGP每隔30秒就向對方發送一個應用程序探測。這是一個應用程序定時器(application timer),與TCP存活定時器不一樣。web

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

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

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

若是兩個終端系統之間的某個中間網絡上有鏈接的暫時中斷,那麼存活選項(option)就可以引發兩個進程間一個良好鏈接的終止。例如,若是正好在某個中間路由器崩潰、重啓的時候發送存活探測,TCP就將會認爲客戶端主機已經崩潰,但事實並不是如此。網絡

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

我的計算機用戶使用TCP/IP協議經過Telnet登陸一臺主機,這是可以說明須要使用存活定時器的一個經常使用例子。若是某個用戶在使用結束時只是關掉了電源,而沒有註銷(log off),那麼他就留下了一個半打開(half-open)的鏈接。在圖18.16,咱們看到如何在一個半打開鏈接上經過發送數據,獲得一個復位(reset)返回,但那是在客戶端,是由客戶端發送的數據。若是客戶端消失,留給了服務器端半打開的鏈接,而且服務器又在等待客戶端的數據,那麼等待將永遠持續下去。存活特徵的目的就是在服務器端檢測這種半打開鏈接。socket

更多: 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 
#include 
#include 
#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來專門傳輸「心跳」呢?我我的認爲二者皆可。若是擔憂的是端到端的鏈接狀態,那麼就直接在該條鏈接中實現「心跳」。但不少時候,關注的是網絡情況和兩臺主機間的鏈接狀態,這種狀況下, 建立專門的「心跳」鏈接也何嘗不可。

相關文章
相關標籤/搜索