java socket編程中有個keepalive選項,看到這個選項常常會誤解爲長鏈接,不設置則爲短鏈接,實則否則。java
socket鏈接創建以後,只要雙方均未主動關閉鏈接,那這個鏈接就是會一直保持的,就是持久的鏈接。keepalive只是爲了防止鏈接的雙方發生意外而通知不到對方,致使一方還持有鏈接,佔用資源。linux
其實這個選項的意思是TCP鏈接空閒時是否須要向對方發送探測包,其實是依賴於底層的TCP模塊實現的,java中只能設置是否開啓,不能設置其詳細參數,只能依賴於系統配置。編程
首先看看源碼裏面是怎麼說的windows
源碼的意思是,若是這個鏈接上雙方任意方向在2小時以內沒有發送過數據,那麼tcp會自動發送一個探測探測包給對方,這種探測包對方是必須迴應的,迴應結果有三種:服務器
1.正常ack,繼續保持鏈接;socket
2.對方響應rst信號,雙方從新鏈接。tcp
3.對方無響應。工具
這裏說的兩小時,實際上是依賴於系統配置,在linux系統中(windows在註冊表中,能夠自行查詢資料),tcp的keepalive參數測試
net.ipv4.tcp_keepalive_intvl = 75 (發送探測包的週期,前提是當前鏈接一直沒有數據交互,纔會以該頻率進行發送探測包,若是中途有數據交互,則會從新計時tcp_keepalive_time,到達規定時間沒有數據交互,纔會從新以該頻率發送探測包)
net.ipv4.tcp_keepalive_probes = 9 (探測失敗的重試次數,發送探測包達次數限制對方依舊沒有迴應,則關閉本身這端的鏈接)
net.ipv4.tcp_keepalive_time = 7200 (空閒多長時間,則發送探測包)spa
爲了能驗證所說的,咱們來進行測試一下,本人測試環境是客戶端在本地windows上,服務端是在遠程linux上,主要測試服務器端向客戶端發送探測包(客戶端向服務端發送是同樣的原理)。
首先須要裝一個抓包工具,本人用的wireshark;
而後修改一下tcp_keepalive_time系統配置,改爲1分鐘,2小時太長了,難等,其他配置不變。修改方法:執行sysctl -w net.ipv4.tcp_keepalive_time=60進行修改,執行sysctl -p刷新配置生效;
最後寫一個服務器端和一個客戶端,分別啓動。
服務器端代碼以下(java8):
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class Server {
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(12345);
while (true) {
Socket socket = ss.accept();
new Thread(() -> {
try {
socket.setKeepAlive(true);
socket.setReceiveBufferSize(8 * 1024);
socket.setSendBufferSize(8 * 1024);
InputStream is = socket.getInputStream();
OutputStream os = socket.getOutputStream();
try {
byte[] bytes = new byte[1024];
while (is.read(bytes) > -1) {
System.out.println(System.currentTimeMillis() + " received message: " + new String(bytes, "UTF-8").trim());
os.write("ok".getBytes("UTF-8"));
os.flush();
bytes = new byte[1024];
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (!socket.isInputShutdown()) {
socket.shutdownInput();
}
if (!socket.isOutputShutdown()) {
socket.shutdownOutput();
}
if (!socket.isClosed()) {
socket.close();
}
}
} catch (IOException e) {
e.printStackTrace();
}
}).start();
}
}
}
客戶端代碼以下:
public class Client {
public static void main(String[] args) throws IOException, InterruptedException {
Socket socket = new Socket("192.168.16.84", 12345);
socket.setKeepAlive(true);
socket.setSendBufferSize(8192);
socket.setReceiveBufferSize(8192);
InputStream is = socket.getInputStream();
OutputStream os = socket.getOutputStream();
os.write("get test-key".getBytes("UTF-8"));
os.flush();
Thread.sleep(155 * 1000L);
os.write("get test-key".getBytes("UTF-8"));
os.flush();
byte[] bytes = new byte[1024];
while (is.read(bytes) > -1) {
System.out.println(System.currentTimeMillis() + " received message: " + new String(bytes, "UTF-8").trim());
bytes = new byte[1024];
}
if (!socket.isOutputShutdown()) {
socket.shutdownOutput();
}
if (!socket.isInputShutdown()) {
socket.shutdownInput();
}
if (!socket.isClosed()) {
socket.close();
}
}
}
分別啓動服務端和客戶端以後,抓包工具抓到的數據:
能夠看到,60秒時服務器發送了探測包,探測客戶端是否正常,客戶端正常響應了,以後以tcp_keepalive_intvl(75秒)的週期進行發送,能夠看到135秒又進行發送了探測包。
可是由於咱們客戶端的代碼是在155秒從新發送了數據,因此須要繼續空閒60秒,直到215秒才繼續發送探測包,後續沒有數據交互,因此仍是以75秒間隔頻率進行發送探測包。從抓包的數據上很容易看出來。
keepalive默認是關閉的,下面咱們把服務器端的socket.setKeepAlive(true)一行註釋掉的抓包結果:
能夠看到服務器端沒有向客戶端發送探測包,其實客戶端設置了socket.setKeepAlive(true),客戶端在7355(7200+155)秒時應該會向服務器發送探測包(我把程序掛了2小時。。。結果以下)
驗證無誤。