服務器中判斷客戶端socket斷開連接的方法


1, 如果服務端的Socket比客戶端的Socket先關閉,會導致客戶端出現TIME_WAIT狀態,占用系統資源。

所以,必須等客戶端先關閉Socket后,服務器端再關閉Socket才能避免TIME_WAIT狀態的出現。

2, 在linux下寫socket的程序的時候,如果嘗試send到一個disconnected socket上,就會讓底層拋出一個SIGPIPE信號。

client端通過 pipe 發送信息到server端后,就關閉client端, 這時server端,返回信息給 client 端時就產生Broken pipe 信號了。

    當服務器close一個連接時,若client端接着發數據。根據TCP協議的規定,會收到一個RST響應,client再往這個服務器發送數據時,系統會發出一個SIGPIPE信號給進程,告訴進程這個連接已經斷開了,不要再寫了。
    根據信號的默認處理規則SIGPIPE信號的默認執行動作是terminate(終止、退出),所以client會退出。若不想客戶端退出可以把SIGPIPE設為SIG_IGN

    如:    signal(SIGPIPE,SIG_IGN);
    這時SIGPIPE交給了系統處理。

 

 

    這個信號的缺省處理方法是退出進程,大多數時候這都不是我們期望的。因此我們需要重載這個信號的處理方法。調用以  下代碼,即可安全的屏蔽SIGPIPE:
    struct sigaction sa;
    sa.sa_handler = SIG_IGN;
    sigaction( SIGPIPE, &sa, 0 );

 


  服務器采用了fork的話,要收集垃圾進程,防止僵屍進程的產生,可以這樣處理:
  signal(SIGCHLD,SIG_IGN); 交給系統init去回收。
   這里子進程就不會產生僵屍進程了。

 

判斷連接斷開的方法

法一:

當recv()返回值小於等於0時,socket連接斷開。但是還需要判斷 errno是否等於 EINTR,如果errno == EINTR 則說明recv函數是由於程序接收到信號后返回的,socket連接還是正常的,不應close掉socket連接。

 

法二:

  struct tcp_info info; 
  int len=sizeof(info); 
  getsockopt(sock, IPPROTO_TCP, TCP_INFO, &info, (socklen_t *)&len); 
  if((info.tcpi_state==TCP_ESTABLISHED))  則說明未斷開  else 斷開

 

法三:

若使用了select等系統函數,若遠端斷開,則select返回1,recv返回0則斷開。其他注意事項同法一。

 

法四:

int keepAlive = 1; // 開啟keepalive屬性
int keepIdle = 60; // 如該連接在60秒內沒有任何數據往來,則進行探測 
int keepInterval = 5; // 探測時發包的時間間隔為5 秒
int keepCount = 3; // 探測嘗試的次數.如果第1次探測包就收到響應了,則后2次的不再發.

setsockopt(rs, SOL_SOCKET, SO_KEEPALIVE, (void *)&keepAlive, sizeof(keepAlive));
setsockopt(rs, SOL_TCP, TCP_KEEPIDLE, (void*)&keepIdle, sizeof(keepIdle));
setsockopt(rs, SOL_TCP, TCP_KEEPINTVL, (void *)&keepInterval, sizeof(keepInterval));
setsockopt(rs, SOL_TCP, TCP_KEEPCNT, (void *)&keepCount, sizeof(keepCount));

設置后,若斷開,則在使用該socket讀寫時立即失敗,並返回ETIMEDOUT錯誤

 

法五:

自己實現一個心跳檢測,一定時間內未收到自定義的心跳包則標記為已斷開。

另外一網摘,方法如下:

判斷客戶端Socket的關閉

最近試驗發現,當客戶端Socket關閉時,服務端的Socket會接收到0字節的通知。

private int Receive(StringBuilder sb)

{

    int read = 0, total = 0;

    if (_Client != null)

    {

        try

        {

            byte[] bytes = new byte[SIZE];

            int available = _Client.Available;

            do

            {

                read = _Client.Receive(bytes);//如果客戶端Socket關閉,_Client會接受到read=0

                total += read;

                if (read > 0)

                    sb.Append(_Server.DefaultEncoding.GetString(bytes, 0, read));

 

            } while (read > 0 && total < available);

        }

        catch (SocketException)

        {

            CloseSocket();

        }

    }

    if (_Server.TraceInConsole && total > 0)

    {

        Console.WriteLine("Receive:" + total + "======================================");

        Console.WriteLine(sb.ToString());

    }

    return total;

}

利用0字節接收條件判斷客戶端Socket的關閉,開始執行服務端Socket關閉代碼。

private void ThreadHandler()

{

    if (_Server.TraceInConsole)

        Console.WriteLine("Begin HttpRequest...");

    try

    {

        while (true)

        {

            StringBuilder sb = new StringBuilder();

            int receive = Receive(sb);

            if (receive > 0)

            {

                _Server.ReadRequest(this, sb.ToString());

                _Server.Response(this);

                _Server.ResponseFinished(this);

            }

            else

            {

                TryCloseSocket();

            }

            if (_Client == null)

                break;

        }

    }

    catch (Exception ex)

    {

        if (_Server.TraceInConsole)

            Console.WriteLine(ex.Message);

    }

    if (_Server.TraceInConsole)

        Console.WriteLine("End HttpRequest.");

}

服務端Socket的關閉

如果直接調用Socket的Close方法會關閉得太快,可能導致客戶端TIME_WAIT現象;而Thead.Sleep延時再調用Socket的Close方法也不理想。應該采用嘗試向客戶端發送數據,然后利用異常來關閉Socket,方法如下。

private void TryCloseSocket()

{

    try

    {

        while (true)

        {

            Thread.Sleep(1500);

            Send(HttpServer.BYTES_CRLF); //發送自定義的字節,如果客戶端關閉出現SocketException,然后關閉服務端Socket

            if (_Client == null)

                break;

        }

    }

    catch (SocketException)

    {

        CloseSocket();

    }

}

 

private void CloseSocket()

{

    if (_Client != null)

    {

        _Client.Shutdown(SocketShutdown.Both);

        _Client.Close();

        _Client = null;

        if (_Server.TraceInConsole)

        {

            Console.WriteLine("Close socket.");

        }

    }

}

 


免責聲明!

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



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