socket keepalive理解


java socket編程中有個keepalive選項,看到這個選項經常會誤解為長連接,不設置則為短連接,實則不然。

socket連接建立之后,只要雙方均未主動關閉連接,那這個連接就是會一直保持的,就是持久的連接。keepalive只是為了防止連接的雙方發生意外而通知不到對方,導致一方還持有連接,占用資源。

其實這個選項的意思是TCP連接空閑時是否需要向對方發送探測包,實際上是依賴於底層的TCP模塊實現的,java中只能設置是否開啟,不能設置其詳細參數,只能依賴於系統配置。

首先看看源碼里面是怎么說的

源碼的意思是,如果這個連接上雙方任意方向在2小時之內沒有發送過數據,那么tcp會自動發送一個探測探測包給對方,這種探測包對方是必須回應的,回應結果有三種:

1.正常ack,繼續保持連接;

2.對方響應rst信號,雙方重新連接。

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 (空閑多長時間,則發送探測包)

為了能驗證所說的,我們來進行測試一下,本人測試環境是客戶端在本地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小時。。。結果如下)

驗證無誤。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM