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_KEEPw+s+d+" | sort | uniq -c
in the android platform/ndk repository.
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…