一、問題引入
socket.isConnected()
或者socket.isClosed()
等方法都是訪問socket在內存駐留的狀態,當socket 和服務器端建立鏈接後,即使 socket 鏈接斷掉了,調用上面的方法返回的仍然是鏈接時的狀態,而不是socket的實時鏈接狀態
例如如下實例:
客戶端:
public class Client{
public static void main(String[] args) {
try {
Socket socket = new Socket("127.0.0.1", 66666);
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(1000);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
服務端:
class DstService implements Runnable {
Socket socket = null;
public DstService(Socket s) {
this.socket = s;
}
public void run() {
try {
int index = 1;
while (true) {
if (index > 2) {
socket.close();
System.out.println("服務端已經關閉鏈接!");
break;
}
index++;
Thread.sleep(1000);//程序睡眠1秒鐘
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
public class Server{
public static void main(String[] args) {
try {
// 啓動監聽端口 66666
ServerSocket server = new ServerSocket(66666);
// 沒有連接這個方法就一直堵塞
Socket socket = server.accept();
// 將請求指定一個線程去執行
new Thread(new DstService(socket)).start();
} catch (Exception e) {
e.printStackTrace();
}
}
}
客戶端運行結果:
結果可以看出,儘管服務端建立連接 3 秒後就斷開了,但是客戶端並沒有接收到服務端斷開的信息,顯示的依然是 Socket 駐留內存的狀態
二、心跳包
心跳包就是在客戶端和服務器間定時通知對方自己狀態的一個自己定義的命令字,按照一定的時間間隔發送,類似於心跳,所以叫做心跳包
(1)用來判斷對方(設備,進程或其它網元)是否正常運行,採用定時發送簡單的通訊包,如果在指定時間段內未收到對方響應,則判斷對方已經離線。
(2)用於檢測TCP的異常斷開。基本原因是服務器端不能有效的判斷客戶端是否在線,也就是說,服務器無法區分客戶端是長時間在空閒,還是已經掉線的情況。
所謂的心跳包就是客戶端定時發送簡單的信息給服務器端告訴它我還在而已,代碼就是每隔幾分鐘發送一個固定信息給服務端,服務端收到後回覆一個固定信息如果服務端幾分鐘內沒有收到客戶端信息則視客戶端斷開
比如有些通信軟件長時間不使用,要想知道它的狀態是在線還是離線就需要心跳包,定時發包收包。
發包方:可以是客戶也可以是服務端,看哪邊實現方便合理,一般是客戶端。服務器也可以定時發心跳下去。一般來說,出於效率的考慮,是由客戶端主動向服務器端發包,而不是服務器向客戶端發。
客戶端每隔一段時間發一個包,使用TCP的,用send發,使用UDP的,用sendto發,服務器收到後,就知道當前客戶端還處於“活着”的狀態,否則,如果隔一定時間未收到這樣的包,則服務器認爲客戶端已經斷開,進行相應的客戶端斷開邏輯處理。
使用心跳包改進客戶端:
import java.net.Socket;
public class Test{
public static void main(String[] args) {
try {
Socket socket = new Socket("127.0.0.1", 6666);
socket.setKeepAlive(true);
//設置最大連接超時時間,超過此時間,無響應則斷開連接
socket.setSoTimeout(10);
while (true) {
socket.sendUrgentData(0xFF);//心跳包的值可以任意指定
System.out.println(socket.isBound());//判斷當前是否綁定本地的地址和端口
System.out.println(socket.isClosed());
System.out.println(socket.isConnected());
System.out.println("------------ ***** ------------");
Thread.sleep( 1000);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
這裏心跳包只是用來檢測 Socket 的鏈接狀態,並不會作爲 Socket 鏈接的通信內容