【问题标题】:How to set the keepalive timeout in Android?如何在Android中设置keepalive超时?
【发布时间】:2011-09-27 18:48:30
【问题描述】:

我想将我打开的 Socket 上的 TCP keepalive 时间从 2 小时降低到大约 10 分钟。我可以通过 socket.setKeepAlive(true) 让它使用 keepalive,但是如何控制发送 keepalive 数据包之前的时间?

如果我使用 NDK,我似乎可以做到这一点,但我想将此代码作为 jar 分发,所以这对我来说并不理想。

【问题讨论】:

    标签: android sockets tcp keep-alive


    【解决方案1】:

    我认为能够在每个应用级别设置keepalive超时可能是非常重要,尤其是在移动设备上,因为它可能在不良网络条件下(wifi/移动)。如果应用不发送(m)任何数据但使用持久连接,则套接字不会检测连接是否丢失,除非它发送 tcp keepalive 探测。通常可以通过setsockopt(2) 调用设置此选项,但android sdk 仅提供setKeepAlive(boolean) 选项。在堆栈的更深处,该函数调用libcore.io.ForwardingOs.setsockoptInt(...),它不能直接使用,也不是所需的文件描述符。 通过使用 java 反射,仍然可以设置 keepalive 超时,例如:

    private final static int SOL_TCP = 6;
    
    private final static int TCP_KEEPIDLE = 4;
    private final static int TCP_KEEPINTVL = 5;
    private final static int TCP_KEEPCNT = 6;
    
    protected void setKeepaliveSocketOptions(Socket socket, int idleTimeout, int interval, int count) {
      try {
        socket.setKeepAlive(true);
        try {
          Field socketImplField = Class.forName("java.net.Socket").getDeclaredField("impl");
          if(socketImplField != null) {
            socketImplField.setAccessible(true);
            Object plainSocketImpl = socketImplField.get(socket);
            Field fileDescriptorField = Class.forName("java.net.SocketImpl").getDeclaredField("fd");
            if(fileDescriptorField != null) {
              fileDescriptorField.setAccessible(true);
              FileDescriptor fileDescriptor = (FileDescriptor)fileDescriptorField.get(plainSocketImpl);
              Class libCoreClass = Class.forName("libcore.io.Libcore");
              Field osField = libCoreClass.getDeclaredField("os");
              osField.setAccessible(true);
              Object libcoreOs = osField.get(libCoreClass);
              Method setSocketOptsMethod = Class.forName("libcore.io.ForwardingOs").getDeclaredMethod("setsockoptInt", FileDescriptor.class, int.class, int.class, int.class);
              if(setSocketOptsMethod != null) {
                setSocketOptsMethod.invoke(libcoreOs, fileDescriptor, SOL_TCP, TCP_KEEPIDLE, idleTimeout);
                setSocketOptsMethod.invoke(libcoreOs, fileDescriptor, SOL_TCP, TCP_KEEPINTVL, interval);
                setSocketOptsMethod.invoke(libcoreOs, fileDescriptor, SOL_TCP, TCP_KEEPCNT, count);
              }
            }
          }
        }
        catch (Exception reflectionException) {}
      } catch (SocketException e) {}
    }
    

    这可行至少直到满足以下要求

    • libcore.io.ForwardingOs.setsockoptInt/4 存在于当前 sdk 版本中
    • java.net.Socket 在当前 sdk 版本中有一个 impl 成员
    • java.net.Socket->impl 是当前 sdk 版本中 java.net.SocketImpl 的实例
    • java.net.SocketImpl 在当前 sdk 版本中有一个 fd 成员
    • TCP_KEEPIDLETCP_KEEPINTVLTCP_KEEPCNT 具有相同的值 (456)在当前 sdk 版本所有 android 设备/架构。

    至少对于 4.0.1 / November 2011最新版本 5.1.1 r9 的 android 版本而言,这似乎是正确的。

    参见platform/libcore 存储库中的luni/src/main/java/libcore/io/Os.javaluni/src/main/java/java/net/Socket.javaluni/src/main/java/java/net/SocketImpl.javaTCP_KEEPIDLETCP_KEEPINTVLTCP_KEEPCNT 似乎对于自 2.2.3 r2 和所有架构以来的 android 版本具有相同的值。这可以被验证,例如通过在android platform/ndk 存储库中执行find . -name tcp.h | xargs grep -ho "TCP_KEEP\w\+\s\+\d\+" | sort | uniq -c

    【讨论】:

    • 太棒了!我一直认为 linux 和 android 可能具有相同的设置,但这些设置解决了我在 Android 上的 keep_alive 代码中看到的问题 :-) 非常感谢。
    【解决方案2】:

    Android 基于 Linux,Linux 通过 setsocketopt() 函数支持 TCP_KEEPIDLETCP_KEEPINTVL 套接字选项,该函数由 java.net.SocketOptions 接口包装。 java.net.SocketImpl 实现了SocketOptions,而java.net.Socket 包装了SocketImpl。我不知道是否可以访问给定Socket 对象的SocketImpl

    您可以尝试使用Socket.setSocketImplFactory() 来实现您自己的自定义SocketImplFactory 类,该类负责为Socket 对象创建SocketImpl 实例。这样一来,您的工厂就可以调用SocketOptions.setOption() 来获取TCP_KEEPIDLETCP_KEEPINTVL 来获取您的应用创建的任何套接字。

    【讨论】:

    • +1 用于提示在 android 的情况下使用 TCP_KEEPIDLE 和 TCP_KEEPINTVL。但是this 正在投票支持应用程序级别的实现,而不是系统级别的保活。你怎么看?
    • 这个问题专门关于系统级的keepalive,每个平台都支持它,因为它是TCP sec的一部分(尽管不是每个平台都支持设置间隔)。应用层保活依赖于协议,很多协议不支持应用层保活。 IF 应用程序级别的保活是可能的,无论如何都要使用它。但在需要时回退到系统级别的保活。
    【解决方案3】:

    这可能是一个太明显的答案 [i.e.这不是您特定情况的选项],但您当然可以通过每 10 分钟发送 1 个丢弃字节(在任一方向)来实现自己的 keepalive。

    【讨论】:

    • 是的,我可能最终会这样做。不过,我听说这样做会增加电池寿命。我可以设置服务器端keepalive,希望是合理的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-11-13
    • 2012-05-20
    • 2016-09-06
    • 2010-10-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多