Socket心跳包機制總結【轉】


轉自:https://blog.csdn.net/qq_23167527/article/details/54290726

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


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

 
 
很多人會用boolean socketFlag = socket.isConnected() && socket.isClosed()來判斷就行了,但事實上這些方法都是訪問socket在內存駐留的狀態,當socket和服務器端建立鏈接后,即使socket鏈接斷掉了,調用上面的方法返回的仍然是鏈接時的狀態,而不是socket的實時鏈接狀態,所以這樣心跳用這個不靠譜,下面給出例子證明這一點。
 

服務器端:

[java]  view plain  copy
 
  1. package com.csc.server;  
  2. import java.net.*;  
  3. /** 
  4.  * @description 從這里啟動一個服務端監聽某個端口 
  5.  * @author csc 
  6.  */  
  7. public class DstService {  
  8.     public static void main(String[] args) {  
  9.         try {             
  10.             // 啟動監聽端口 30000  
  11.             ServerSocket ss = new ServerSocket(30000);  
  12.             // 沒有連接這個方法就一直堵塞  
  13.             Socket s = ss.accept();  
  14.             // 將請求指定一個線程去執行  
  15.             new Thread(new DstServiceImpl(s)).start();  
  16.         } catch (Exception e) {  
  17.             e.printStackTrace();  
  18.         }  
  19.     }  
  20. }  

    這里我設置了啟動新線程來管理建立的每一個socket鏈接,此處我們設置收到鏈接后10秒端來鏈接,代碼如下:

[java]  view plain  copy
 
  1. package com.csc.server;  
  2. import java.net.Socket;  
  3. /** 
  4.  * @description 服務的啟動的線程類 
  5.  * @author csc 
  6.  */  
  7. public class DstServiceImpl implements Runnable {  
  8.     Socket socket = null;  
  9.     public DstServiceImpl(Socket s) {  
  10.         this.socket = s;  
  11.     }  
  12.     public void run() {  
  13.         try {  
  14.             int index = 1;  
  15.             while (true) {  
  16.                 // 5秒后中斷連接  
  17.                 if (index > 10) {  
  18.                     socket.close();  
  19.                     System.out.println("服務端已經關閉鏈接!");  
  20.                     break;  
  21.                 }  
  22.                 index++;  
  23.                 Thread.sleep(1 * 1000);//程序睡眠1秒鍾  
  24.             }  
  25.         } catch (Exception e) {  
  26.             e.printStackTrace();  
  27.         }  
  28.     }  
  29. }  

   以上是服務端代碼,下面寫一個客戶端代碼來測試:

[java]  view plain  copy
 
  1. package com.csc.client;  
  2. import java.net.*;  
  3. /** 
  4.  * @description 客戶端打印鏈接狀態 
  5.  * @author csc 
  6.  */  
  7. public class DstClient {  
  8.     public static void main(String[] args) {  
  9.         try {  
  10.             Socket socket = new Socket("127.0.0.1"30000);  
  11.             socket.setKeepAlive(true);  
  12.             socket.setSoTimeout(10);  
  13.             while (true) {  
  14.                 System.out.println(socket.isBound());  
  15.                 System.out.println(socket.isClosed());  
  16.                 System.out.println(socket.isConnected());  
  17.                 System.out.println(socket.isInputShutdown());  
  18.                 System.out.println(socket.isOutputShutdown());  
  19.                 System.out.println("------------我是分割線------------");  
  20.                 Thread.sleep(3 * 1000);  
  21.             }  
  22.         } catch (Exception e) {  
  23.             e.printStackTrace();  
  24.         }  
  25.     }  
  26. }  

   先運行服務端代碼,再運行客戶端代碼,我們會在客戶端代碼的控制台看到如下信息:

[java]  view plain  copy
 
  1. true  
  2. false  
  3. true  
  4. false  
  5. false  
  6. ------------我是分割線------------  

   從連接對象的屬性信息來看,連接是沒有中斷,但實際鏈接已經在服務端建立鏈接10秒后斷開了。這說明了上述幾個方法是不能實時判斷出socket的鏈接狀態,只是socket駐留在內存的狀態。其實,此時如果調用流去讀取信息的話,就會出現異常。

 

    其實,想要判斷socket是否仍是鏈接狀態,只要發一個心跳包就行了,如下一句代碼:

[java]  view plain  copy
 
  1. socket.sendUrgentData(0xFF); // 發送心跳包  

    關於心跳包的理論可以去google一下,我給出點參考:心跳包就是在客戶端和服務器間定時通知對方自己狀態的一個自己定義的命令字,按照一定的時間間隔發送,類似於心跳,所以叫做心跳包。 用來判斷對方(設備,進程或其它網元)是否正常運行,采用定時發送簡單的通訊包,如果在指定時間段內未收到對方響應,則判斷對方已經離線。用於檢測TCP的異常斷開。基本原因是服務器端不能有效的判斷客戶端是否在線,也就是說,服務器無法區分客戶端是長時間在空閑,還是已經掉線的情況。所謂的心跳包就是客戶端定時發送簡單的信息給服務器端告訴它我還在而已。代碼就是每隔幾分鍾發送一個固定信息給服務端,服務端收到后回復一個固定信息如果服務端幾分鍾內沒有收到客戶端信息則視客戶端斷開。 比如有些通信軟件長時間不使用,要想知道它的狀態是在線還是離線就需要心跳包,定時發包收包。發包方:可以是客戶也可以是服務端,看哪邊實現方便合理,一般是客戶端。服務器也可以定時發心跳下去。一般來說,出於效率的考慮,是由客戶端主動向服務器端發包,而不是服務器向客戶端發。客戶端每隔一段時間發一個包,使用TCP的,用send發,使用UDP的,用sendto發,服務器收到后,就知道當前客戶端還處於“活着”的狀態,否則,如果隔一定時間未收到這樣的包,則服務器認為客戶端已經斷開,進行相應的客戶端斷開邏輯處理!

 

    既然找到了方法,我們就在測試一下,服務端代碼無需改動,客戶端代碼如下:

[java]  view plain  copy
 
  1. package com.csc.client;  
  2. import java.net.*;  
  3. /** 
  4.  * @description 客戶端打印鏈接狀態 
  5.  * @author csc 
  6.  */  
  7. public class DstClient {  
  8.     public static void main(String[] args) {  
  9.         try {  
  10.             Socket socket = new Socket("127.0.0.1"30000);  
  11.             socket.setKeepAlive(true);  
  12.             socket.setSoTimeout(10);  
  13.             while (true) {  
  14.                 socket.sendUrgentData(0xFF); // 發送心跳包  
  15.                 System.out.println("目前處於鏈接狀態!");  
  16.                 Thread.sleep(3 * 1000);//線程睡眠3秒  
  17.             }  
  18.         } catch (Exception e) {  
  19.             e.printStackTrace();  
  20.         }  
  21.     }  
  22. }  

    重新運行客戶端程序,看到控制台打印如下信息:

    服務端程序運行10秒后再當執行“socket.sendUrgentData(0xFF);”語句時,socket鏈接斷開了,所以會拋出異常。
    另外注意,心跳包只是用來檢測socket的鏈接狀態,並不會作為socket鏈接的通信內容,這點應當注意。
    轉載請注明出處:http://blog.csdn.net/caoshichao520326/article/details/8900446


免責聲明!

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



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