如何在Android中设置keepalive超时?

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

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

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

我认为能够在每个应用程序级别设置keepalive超时可能非常重要 ,尤其是在移动设备上,因为它可能处于恶劣的网络条件(wifi /移动)。 如果应用程序不发送(m)任何数据但使用持久连接 ,则套接字将不会检测连接是否丢失 ,除非它发送tcp keepalive探测器。 通常可以通过setsockopt(2)调用设置此选项,但android sdk仅提供setKeepAlive(boolean)选项。 在堆栈中,该函数调用libcore.io.ForwardingOs.setsockoptInt(…) ,它不是直接可用的 ,也不是所需的文件描述符。 通过使用javareflection,无论如何都可以设置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在当前sdk版本所有Android设备/体系结构中具有相同的值( 46 )。

至少对于 2011年11月的4.0.1 / 最新版本 5.1.1 r9的 Android版本来说这似乎是真的。

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

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

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