Как установить время ожидания активности в Android? - PullRequest
7 голосов
/ 04 июля 2011

Я бы хотел уменьшить время поддержки TCP для сокета, который я открываю, с 2 часов до чего-то порядка десяти минут. Я могу заставить его использовать keepalive с socket.setKeepAlive (true), но как я могу контролировать время перед отправкой пакета keepalive?

Похоже, я мог бы сделать это, если бы использовал NDK, но я хочу распространять этот код в виде jar, так что это не идеально для меня.

Ответы [ 3 ]

9 голосов
/ 07 августа 2015

Я думаю, что весьма важно иметь возможность установить время ожидания активности на уровне для приложения , особенно на мобильном устройстве, потому что ономожет быть в плохих условиях сети (Wi-Fi / мобильный).Если приложение не отправляет (m) никаких данных , но использует постоянное соединение , сокет не будет определять, потеряно ли соединение , если только он не отправляет tcpkeepalive зонды.Установка этой опции обычно возможна с помощью вызова 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 имеет член impl в текущей версии SDK
  • java.net.Socket->impl является экземпляром java.net.SocketImpl в текущей версии SDK
  • java.net.SocketImpl имеет член fd в текущей версии SDK
  • TCP_KEEPIDLE, TCP_KEEPINTVL и TCP_KEEPCNT имеют одинаковые значения (4, 5 и 6) в текущей версии SDK и для всех устройств / архитектур Android.

Топохоже на правду, по крайней мере, для версий Android от 4.0.1 / ноябрь 2011 до последняя версия 5.1.1 r9 .

См. luni/src/main/java/libcore/io/Os.java, luni/src/main/java/java/net/Socket.java и luni/src/main/java/java/net/SocketImpl.java из репозитория platform / libcore .TCP_KEEPIDLE, TCP_KEEPINTVL и TCP_KEEPCNT имеют одинаковые значения для версий Android, начиная с 2.2.3 r2 и всех архитектур.Это можно проверить, например, выполнив find . -name tcp.h | xargs grep -ho "TCP_KEEP\w\+\s\+\d\+" | sort | uniq -c в android platform / ndk репозитории.

4 голосов
/ 05 сентября 2013

Android основан на Linux, а Linux поддерживает опции сокетов TCP_KEEPIDLE и TCP_KEEPINTVL через функцию setsocketopt(), которая обернута интерфейсом java.net.SocketOptions. java.net.SocketImpl реализует SocketOptions, а java.net.Socket упаковывает SocketImpl. Чего я не знаю, так это того, возможно ли получить доступ к SocketImpl данного Socket объекта.

То, что вы можете попробовать сделать, это использовать Socket.setSocketImplFactory() для реализации собственного пользовательского класса SocketImplFactory, который отвечает за создание SocketImpl экземпляров для Socket объектов. Таким образом, ваша фабрика может вызывать SocketOptions.setOption() для TCP_KEEPIDLE и TCP_KEEPINTVL для любых сокетов, которые создает ваше приложение.

1 голос
/ 04 июля 2011

Это, вероятно, слишком очевидный ответ [т.е. это не вариант для вашего конкретного случая], но вы, конечно, можете реализовать свой собственный keepalive, отправляя 1 одноразовый байт (в любом направлении) каждые 10 минут.

...