我认为能够在每个应用级别设置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_KEEPIDLE、TCP_KEEPINTVL 和 TCP_KEEPCNT 具有相同的值
(4、5 和 6)在当前 sdk 版本和所有 android 设备/架构。
至少对于 从 4.0.1 / November 2011 到 最新版本 5.1.1 r9 的 android 版本而言,这似乎是正确的。
参见platform/libcore 存储库中的luni/src/main/java/libcore/io/Os.java、luni/src/main/java/java/net/Socket.java 和luni/src/main/java/java/net/SocketImpl.java。
TCP_KEEPIDLE、TCP_KEEPINTVL 和 TCP_KEEPCNT 似乎对于自 2.2.3 r2 和所有架构以来的 android 版本具有相同的值。这可以被验证,例如通过在android platform/ndk 存储库中执行find . -name tcp.h | xargs grep -ho "TCP_KEEP\w\+\s\+\d\+" | sort | uniq -c。