socketkeepalive理解

编程

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):

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

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();

        

}

    

}

}

客户端代码如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

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小时。。。结果如下)

验证无误。

以上是 socketkeepalive理解 的全部内容, 来源链接: utcz.com/z/518061.html

回到顶部