如何在Android中设置keepalive超时?

uttx8gqw  于 2022-12-16  发布在  Android
关注(0)|答案(4)|浏览(148)

我想将我正在打开的Socket上的TCP keepalive时间从2小时降低到大约10分钟。我可以通过socket.setKeepAlive(true)使其使用keepalive,但我如何控制keepalive数据包发送前的时间?
如果我使用NDK的话,看起来我可以做到这一点,但是我想将这些代码作为一个jar分发,所以这对我来说并不理想。

qxgroojn

qxgroojn1#

我认为能够在每个应用级别设置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在当前sdk版本所有android设备/架构中具有相同的值(456)。

至少对于安卓版本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.javaTCP_KEEPIDLETCP_KEEPINTVLTCP_KEEPCNT2.2.3 r2之后的android版本和所有架构中似乎都有相同的值。这可以通过在android platform/ndk仓库中执行find . -name tcp.h | xargs grep -ho "TCP_KEEP\w\+\s\+\d\+" | sort | uniq -c来验证。

svdrlsy4

svdrlsy42#

Android是基于Linux的,Linux通过setsocketopt()函数支持TCP_KEEPIDLETCP_KEEPINTVL套接字选项,java.net.SocketOptions接口 Package 了setsocketopt()函数,java.net.SocketImpl实现了SocketOptionsjava.net.Socket Package 了一个SocketImpl,我不知道的是是否可以访问给定Socket对象的SocketImpl
您可以尝试使用Socket.setSocketImplFactory()实现您自己的自定义SocketImplFactory类,该类负责为Socket对象创建SocketImpl示例。这样,您的工厂就可以为TCP_KEEPIDLE调用SocketOptions.setOption(),为您的应用创建的任何套接字调用TCP_KEEPINTVL

xe55xuns

xe55xuns3#

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

h79rfbju

h79rfbju4#

深入研究我的Android 10设备上的高投票率answer和反射限制,我发现了一个现代Android API上的工作解决方案。首先,检查java的sysem方法的blocklists,以了解不同Android版本上的setsockoptInt限制,我发现4个包使用此方法,唯一没有阻止的包是核心Android android.system.Os公共包。令人惊讶的是,该包允许在不直接使用Java反射的情况下访问低级系统功能。
从Android 8(API 26)开始,这个软件包使用Os.setsockoptInt方法进行了丰富。第二,通过将SocketInputStream转换为FileInputStream(如that answer),可以更优雅地获取套接字的FileDescriptor。因此,运行良好的代码如下:

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);
        FileDescriptor socketFileDescriptor = ((FileInputStream)socket.getInputStream()).getFD();
        if (socketFileDescriptor != null) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                Os.setsockoptInt(socketFileDescriptor, SOL_TCP, TCP_KEEPIDLE, idleTimeout);
                Os.setsockoptInt(socketFileDescriptor, SOL_TCP, TCP_KEEPINTVL, interval);
                Os.setsockoptInt(socketFileDescriptor, SOL_TCP, TCP_KEEPCNT, count);
            } else {
                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);
                setSocketOptsMethod.invoke(libcoreOs, socketFileDescriptor, SOL_TCP, TCP_KEEPIDLE, idleTimeout);
                setSocketOptsMethod.invoke(libcoreOs, socketFileDescriptor, SOL_TCP, TCP_KEEPINTVL, interval);
                setSocketOptsMethod.invoke(libcoreOs, socketFileDescriptor, SOL_TCP, TCP_KEEPCNT, count);
            }
        }
    } catch (Exception e) {
        if (BuildConfig.DEBUG) e.printStackTrace();
    }
}

相关问题