Socket心跳包機制總結(jié)

轉(zhuǎn)載自:https://blog.csdn.net/qq_23167527/article/details/54290726

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

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

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

package com.csc.server;  
import java.net.*;  
/** 
 * @description 從這里啟動一個服務(wù)端監(jiān)聽某個端口 
 * @author csc 
 */  
public class DstService {  
    public static void main(String[] args) {  
        try {             
            // 啟動監(jiān)聽端口 30000  
            ServerSocket ss = new ServerSocket(30000);  
            // 沒有連接這個方法就一直堵塞  
            Socket s = ss.accept();  
            // 將請求指定一個線程去執(zhí)行  
            new Thread(new DstServiceImpl(s)).start();  
        } catch (Exception e) {  
            e.printStackTrace();  
        }  
    }  
}  

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

package com.csc.server;  
import java.net.Socket;  
/** 
 * @description 服務(wù)的啟動的線程類 
 * @author csc 
 */  
public class DstServiceImpl implements Runnable {  
    Socket socket = null;  
    public DstServiceImpl(Socket s) {  
        this.socket = s;  
    }  
    public void run() {  
        try {  
            int index = 1;  
            while (true) {  
                // 5秒后中斷連接  
                if (index > 10) {  
                    socket.close();  
                    System.out.println("服務(wù)端已經(jīng)關(guān)閉鏈接!");  
                    break;  
                }  
                index++;  
                Thread.sleep(1 * 1000);//程序睡眠1秒鐘  
            }  
        } catch (Exception e) {  
            e.printStackTrace();  
        }  
    }  
}  

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

package com.csc.client;  
import java.net.*;  
/** 
 * @description 客戶端打印鏈接狀態(tài) 
 * @author csc 
 */  
public class DstClient {  
    public static void 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();  
        }  
    }  
}  

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

true  
false  
true  
false  
false  
------------我是分割線------------  

從連接對象的屬性信息來看,連接是沒有中斷,但實際鏈接已經(jīng)在服務(wù)端建立鏈接10秒后斷開了。這說明了上述幾個方法是不能實時判斷出socket的鏈接狀態(tài),只是socket駐留在內(nèi)存的狀態(tài)。其實,此時如果調(diào)用流去讀取信息的話,就會出現(xiàn)異常。
其實,想要判斷socket是否仍是鏈接狀態(tài),只要發(fā)一個心跳包就行了,如下一句代碼:

socket.sendUrgentData(0xFF); // 發(fā)送心跳包 

關(guān)于心跳包的理論可以去google一下,我給出點參考:心跳包就是在客戶端和服務(wù)器間定時通知對方自己狀態(tài)的一個自己定義的命令字,按照一定的時間間隔發(fā)送,類似于心跳,所以叫做心跳包。 用來判斷對方(設(shè)備,進程或其它網(wǎng)元)是否正常運行,采用定時發(fā)送簡單的通訊包,如果在指定時間段內(nèi)未收到對方響應(yīng),則判斷對方已經(jīng)離線。用于檢測TCP的異常斷開?;驹蚴欠?wù)器端不能有效的判斷客戶端是否在線,也就是說,服務(wù)器無法區(qū)分客戶端是長時間在空閑,還是已經(jīng)掉線的情況。所謂的心跳包就是客戶端定時發(fā)送簡單的信息給服務(wù)器端告訴它我還在而已。代碼就是每隔幾分鐘發(fā)送一個固定信息給服務(wù)端,服務(wù)端收到后回復(fù)一個固定信息如果服務(wù)端幾分鐘內(nèi)沒有收到客戶端信息則視客戶端斷開。 比如有些通信軟件長時間不使用,要想知道它的狀態(tài)是在線還是離線就需要心跳包,定時發(fā)包收包。發(fā)包方:可以是客戶也可以是服務(wù)端,看哪邊實現(xiàn)方便合理,一般是客戶端。服務(wù)器也可以定時發(fā)心跳下去。一般來說,出于效率的考慮,是由客戶端主動向服務(wù)器端發(fā)包,而不是服務(wù)器向客戶端發(fā)??蛻舳嗣扛粢欢螘r間發(fā)一個包,使用TCP的,用send發(fā),使用UDP的,用sendto發(fā),服務(wù)器收到后,就知道當前客戶端還處于“活著”的狀態(tài),否則,如果隔一定時間未收到這樣的包,則服務(wù)器認為客戶端已經(jīng)斷開,進行相應(yīng)的客戶端斷開邏輯處理!
既然找到了方法,我們就在測試一下,服務(wù)端代碼無需改動,客戶端代碼如下:

package com.csc.client;  
import java.net.*;  
/** 
 * @description 客戶端打印鏈接狀態(tài) 
 * @author csc 
 */  
public class DstClient {  
    public static void main(String[] args) {  
        try {  
            Socket socket = new Socket("127.0.0.1", 30000);  
            socket.setKeepAlive(true);  
            socket.setSoTimeout(10);  
            while (true) {  
                socket.sendUrgentData(0xFF); // 發(fā)送心跳包  
                System.out.println("目前處于鏈接狀態(tài)!");  
                Thread.sleep(3 * 1000);//線程睡眠3秒  
            }  
        } catch (Exception e) {  
            e.printStackTrace();  
        }  
    }  
}  
20130508162015307.jpeg

服務(wù)端程序運行10秒后再當執(zhí)行“socket.sendUrgentData(0xFF);”語句時,socket鏈接斷開了,所以會拋出異常。
另外注意,心跳包只是用來檢測socket的鏈接狀態(tài),并不會作為socket鏈接的通信內(nèi)容,這點應(yīng)當注意。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 孔子的學生子貢曾問孔子:“老師,有沒有一個字,可以作為終身奉行的原則呢?”孔子說:“那大概就是‘恕’吧?!彼^“恕...
    王虎林閱讀 248評論 0 0
  • AngularJS 1.x是Angular的上一代框架,Angular團隊做了規(guī)范,老框架為AngularJS 1...
    何幻閱讀 527評論 0 1
  • 清河上翼著官家的酒樓,高大人常常在此會客,飲酒作樂通宵達旦。 與別的大人不同,高大人多是與南北往來九州內(nèi)外大客商來...
    夢蕪閣閱讀 453評論 0 1

友情鏈接更多精彩內(nèi)容