How to set the keepalive timeout in Android?

14,095

Solution 1

This is probably too obvious an answer [i.e. it's not an option for your specific case] but you can of course implement your own keepalive by sending 1 throw-away byte (in either direction) every 10 minutes.

Solution 2

I think it might be quite important to be able to set the keepalive timeouts on an per app level, especially on a mobile device, because it might be under bad network conditions (wifi/mobile). If the app does not send (m)any data but uses a persistent connection, the socket will not detect whether the connection is lost, unless it sends tcp keepalive probes. Setting this option is usually possible via the setsockopt(2) call, but the android sdk provides only the setKeepAlive(boolean) option. Deeper in the stack, that functions calls libcore.io.ForwardingOs.setsockoptInt(...), which is not available directly, nor the required file descriptor. By using java reflection, setting the keepalive timeouts is possible anyway, e.g like this:

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) {}
}

This works at least until the following requirements are met:

  • libcore.io.ForwardingOs.setsockoptInt/4 exists at current sdk version
  • java.net.Socket has an impl member at the current sdk version
  • java.net.Socket->impl is instance of java.net.SocketImpl at the current sdk version
  • java.net.SocketImpl has a fd member at the current sdk version
  • TCP_KEEPIDLE, TCP_KEEPINTVL and TCP_KEEPCNT have the same values (4, 5 and 6) at the current sdk version and all android devices / architectures.

That seems to be true at least for android versions from 4.0.1 / November 2011 up to recent version 5.1.1 r9.

See luni/src/main/java/libcore/io/Os.java, luni/src/main/java/java/net/Socket.java and luni/src/main/java/java/net/SocketImpl.java from the platform/libcore repository. TCP_KEEPIDLE, TCP_KEEPINTVL and TCP_KEEPCNT seem to have the same values for android versions since 2.2.3 r2 and all architectures. This can be validated e.g. by executing find . -name tcp.h | xargs grep -ho "TCP_KEEP\w\+\s\+\d\+" | sort | uniq -c in the android platform/ndk repository.

Solution 3

Android is based on Linux, and Linux supports the TCP_KEEPIDLE and TCP_KEEPINTVL socket options via the setsocketopt() function, which is wrapped by the java.net.SocketOptions interface. java.net.SocketImpl implements SocketOptions, and java.net.Socket wraps a SocketImpl. What I don't know is whether it is possible to access the SocketImpl of a given Socket object.

What you could try doing is use Socket.setSocketImplFactory() to implement your own custom SocketImplFactory class, which is responsible for creating SocketImpl instances for Socket objects. That way, your factory could call SocketOptions.setOption() for TCP_KEEPIDLE and TCP_KEEPINTVL for any sockets your app creates.

Share:
14,095
lacker
Author by

lacker

Updated on June 11, 2022

Comments

  • lacker
    lacker almost 2 years

    I'd like to lower the TCP keepalive time on a Socket I'm opening from 2 hours to something on the order of ten minutes. I can make it use keepalive with socket.setKeepAlive(true), but how can I control the time before a keepalive packet is sent?

    It looks like I could do this if I was using the NDK, but I want to distribute this code as a jar, so that's not ideal for me.

  • lacker
    lacker almost 13 years
    Yeah, I am probably going to end up doing that. I hear it's more taxing on the battery life to do that, though. I can set the server-side keepalive which hopefully is reasonable.
  • Ajay
    Ajay over 9 years
    +1 for hint for use of TCP_KEEPIDLE and TCP_KEEPINTVL in the case of android. But this is voting for implementation of application level not system level keepalive. What is you opinion?
  • Remy Lebeau
    Remy Lebeau over 9 years
    This question was specifically about system-level keepalive, which every platform supports as it is part of the TCP sec (though not every platform supports setting the interval). Application-level keepalive is dependent on the protocol, and many protocols do not support an application-level keepalive. IF an application-level keepalive is possible, by all means use it. But fall back to system-level keepalive when needed.
  • AdeleGoldberg
    AdeleGoldberg over 4 years
    Excellent! I always thought that linux and android might have same settings but these settings fix the issues I have seen in my keep_alive code on Android :-) Thanks a lot.