socket keepalive理解

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小時。。。結果以下)

驗證無誤。

相關文章
相關標籤/搜索