zoukankan      html  css  js  c++  java
  • 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小时。。。结果如下)

    验证无误。

  • 相关阅读:
    word批量打印工具,c#写的
    word添加页眉脚和设置各页不同的页眉页脚.
    打印机双面打印
    ORACLE OCP认证
    基于.net程序,使用cefsharp开发的打开网页工具,如何不加载图片
    在iis上运行的服务器端程序,运行一段时间后,访问都只出现一行乱码,回收进程池后又好了,求大神回复
    ArcGis API for JavaScript 开发笔记一 加载地图
    修改现有消息类让.net core项目支持Protobuf
    结合现有分布式系统的数据一致性思考
    让现有vue前端项目快速支持多语言
  • 原文地址:https://www.cnblogs.com/xiao-tao/p/9718017.html
Copyright © 2011-2022 走看看