如何在Android中设置keepalive超时?

时间:2011-07-03 21:26:22

标签: android sockets tcp keep-alive

我想降低一个Socket的TCP保持活动时间,从2小时开始到10分钟左右。我可以使用socket.setKeepAlive(true)使用keepalive,但是如何在发送keepalive数据包之前控制时间?

如果我使用NDK,看起来我可以这样做,但我想把这个代码分发为jar,所以这对我来说并不理想。

3 个答案:

答案 0 :(得分:9)

我认为非常重要能够在每个应用级别设置保持活动超时,尤其是在移动设备上,因为它可能是在糟糕的网络条件下(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");
      socketImplField.setAccessible(true);
      if(socketImplField != null) {
        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设备/架构。

至少对于Android版本 4.0.1 / November 2011最新版本 5.1.1 r9似乎都是如此。

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

答案 1 :(得分:4)

Android基于Linux,Linux通过TCP_KEEPIDLE函数支持TCP_KEEPINTVLsetsocketopt()套接字选项,java.net.SocketOptions函数由java.net.SocketImpl接口包装。 SocketOptions实施java.net.SocketSocketImpl包含SocketImpl。我不知道的是,是否可以访问给定Socket对象的Socket.setSocketImplFactory()

您可以尝试执行的操作是使用SocketImplFactory来实现您自己的自定义SocketImpl类,该类负责为Socket个对象创建SocketOptions.setOption()个实例。这样,您的工厂就可以针对TCP_KEEPIDLETCP_KEEPINTVL为您的应用创建的任何套接字调用{{1}}。

答案 2 :(得分:1)

这可能是一个非常明显的答案[即它不是你特定案例的选项]但是你当然可以通过每10分钟发送一个丢弃字节(在任一方向上)来实现你自己的keepalive。