Socket心跳包機制總結
阿新 • • 發佈:2019-02-05
跳包之所以叫心跳包是因為:它像心跳一樣每隔固定時間發一次,以此來告訴伺服器,這個客戶端還活著。事實上這是為了保持長連線,至於這個包的內容,是沒有什麼特別規定的,不過一般都是很小的包,或者只包含包頭的一個空包。
在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的實時連結狀態,所以這樣心跳用這個不靠譜,下面給出例子證明這一點。
在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的實時連結狀態,所以這樣心跳用這個不靠譜,下面給出例子證明這一點。
伺服器端:
- package com.csc.server;
- import java.net.*;
- /**
-
* @description 從這裡啟動一個服務端監聽某個埠
- * @author csc
- */
- publicclass DstService {
- publicstaticvoid main(String[] args) {
- try {
- // 啟動監聽埠 30000
- ServerSocket ss = new ServerSocket(30000);
- // 沒有連線這個方法就一直堵塞
- Socket s = ss.accept();
- // 將請求指定一個執行緒去執行
-
new
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
- package com.csc.server;
- import java.net.Socket;
- /**
- * @description 服務的啟動的執行緒類
- * @author csc
- */
- publicclass DstServiceImpl implements Runnable {
- Socket socket = null;
- public DstServiceImpl(Socket s) {
- this.socket = s;
- }
- publicvoid run() {
- try {
- int index = 1;
- while (true) {
- // 5秒後中斷連線
- if (index > 10) {
- socket.close();
- System.out.println("服務端已經關閉連結!");
- break;
- }
- index++;
- Thread.sleep(1 * 1000);//程式睡眠1秒鐘
- }
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
- package com.csc.client;
- import java.net.*;
- /**
- * @description 客戶端列印連結狀態
- * @author csc
- */
- publicclass DstClient {
- publicstaticvoid main(String[] args) {
- try {
- Socket socket = new Socket("127.0.0.1", 30000);
- socket.setKeepAlive(true);
- socket.setSoTimeout(10);
- while (true) {
- System.out.println(socket.isBound());
- System.out.println(socket.isClosed());
- System.out.println(socket.isConnected());
- System.out.println(socket.isInputShutdown());
- System.out.println(socket.isOutputShutdown());
- System.out.println("------------我是分割線------------");
- Thread.sleep(3 * 1000);
- }
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
- true
- false
- true
- false
- false
- ------------我是分割線------------
其實,想要判斷socket是否仍是連結狀態,只要發一個心跳包就行了,如下一句程式碼:
- socket.sendUrgentData(0xFF); // 傳送心跳包
既然找到了方法,我們就在測試一下,服務端程式碼無需改動,客戶端程式碼如下:
- package com.csc.client;
- import java.net.*;
- /**
- * @description 客戶端列印連結狀態
- * @author csc
- */
- publicclass DstClient {
- publicstaticvoid main(String[] args) {
- try {
- Socket socket = new Socket("127.0.0.1", 30000);
- socket.setKeepAlive(true);
- socket.setSoTimeout(10);
- while (true) {
- socket.sendUrgentData(0xFF); // 傳送心跳包
- System.out.println("目前處於連結狀態!");
- Thread.sleep(3 * 1000);//執行緒睡眠3秒
- }
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
重新執行客戶端程式,看到控制檯列印如下資訊:
服務端程式執行10秒後再當執行“socket.sendUrgentData(0xFF);”語句時,socket連結斷開了,所以會丟擲異常。
另外注意,心跳包只是用來檢測socket的連結狀態,並不會作為socket連結的通訊內容,這點應當注意。
轉載請註明出處:http://blog.csdn.net/caoshichao520326/article/details/8900446