我想将我正在打开的Socket上的TCP keepalive时间从2小时降低到大约10分钟。我可以通过socket.setKeepAlive(true)使其使用keepalive,但我如何控制keepalive数据包发送前的时间?如果我使用NDK的话,看起来我可以做到这一点,但是我想将这些代码作为一个jar分发,所以这对我来说并不理想。
qxgroojn1#
我认为能够在每个应用级别设置keepalive超时可能非常重要,尤其是在移动的设备上,因为它可能处于恶劣的网络条件下(wifi/移动的)。如果应用程序不发送(m)任何数据但使用持久连接**,则套接字将不检测连接是否丢失,除非它发送tcp keepalive探测。通常可以通过setsockopt(2)调用设置此选项,但android sdk仅提供setKeepAlive(boolean)选项。在堆栈的更深处,该函数调用libcore.io.ForwardingOs.setsockoptInt(...),这是不可直接使用的,也不需要所需的文件描述符。通过使用java反射,无论如何都可以设置keepalive超时,例如:
setKeepAlive(boolean)
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
java.net.Socket
impl
java.net.Socket->impl
java.net.SocketImpl
fd
TCP_KEEPIDLE
TCP_KEEPINTVL
TCP_KEEPCNT
4
5
6
至少对于安卓版本从4.0.1 / November 2011到最新版本5.1.1 r9来说,这似乎是正确的。参见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来验证。
luni/src/main/java/libcore/io/Os.java
luni/src/main/java/java/net/Socket.java
luni/src/main/java/java/net/SocketImpl.java
find . -name tcp.h | xargs grep -ho "TCP_KEEP\w\+\s\+\d\+" | sort | uniq -c
svdrlsy42#
Android是基于Linux的,Linux通过setsocketopt()函数支持TCP_KEEPIDLE和TCP_KEEPINTVL套接字选项,java.net.SocketOptions接口 Package 了setsocketopt()函数,java.net.SocketImpl实现了SocketOptions,java.net.Socket Package 了一个SocketImpl,我不知道的是是否可以访问给定Socket对象的SocketImpl。您可以尝试使用Socket.setSocketImplFactory()实现您自己的自定义SocketImplFactory类,该类负责为Socket对象创建SocketImpl示例。这样,您的工厂就可以为TCP_KEEPIDLE调用SocketOptions.setOption(),为您的应用创建的任何套接字调用TCP_KEEPINTVL。
setsocketopt()
java.net.SocketOptions
SocketOptions
SocketImpl
Socket
Socket.setSocketImplFactory()
SocketImplFactory
SocketOptions.setOption()
xe55xuns3#
这可能是一个太明显的答案[即它不是您的特定情况下的选项],但您当然可以通过每10分钟发送1个丢弃字节(在任一方向)来实现自己的keepalive。
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。因此,运行良好的代码如下:
setsockoptInt
android.system.Os
Os.setsockoptInt
SocketInputStream
FileInputStream
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(); } }
4条答案
按热度按时间qxgroojn1#
我认为能够在每个应用级别设置keepalive超时可能非常重要,尤其是在移动的设备上,因为它可能处于恶劣的网络条件下(wifi/移动的)。如果应用程序不发送(m)任何数据但使用持久连接**,则套接字将不检测连接是否丢失,除非它发送tcp keepalive探测。通常可以通过setsockopt(2)调用设置此选项,但android sdk仅提供
setKeepAlive(boolean)
选项。在堆栈的更深处,该函数调用libcore.io.ForwardingOs.setsockoptInt(...),这是不可直接使用的,也不需要所需的文件描述符。通过使用java反射,无论如何都可以设置keepalive超时,例如:至少在满足以下要求之前这样做:
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
在当前sdk版本和所有android设备/架构中具有相同的值(4
、5
和6
)。至少对于安卓版本从4.0.1 / November 2011到最新版本5.1.1 r9来说,这似乎是正确的。
参见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
来验证。svdrlsy42#
Android是基于Linux的,Linux通过
setsocketopt()
函数支持TCP_KEEPIDLE
和TCP_KEEPINTVL
套接字选项,java.net.SocketOptions
接口 Package 了setsocketopt()
函数,java.net.SocketImpl
实现了SocketOptions
,java.net.Socket
Package 了一个SocketImpl
,我不知道的是是否可以访问给定Socket
对象的SocketImpl
。您可以尝试使用
Socket.setSocketImplFactory()
实现您自己的自定义SocketImplFactory
类,该类负责为Socket
对象创建SocketImpl
示例。这样,您的工厂就可以为TCP_KEEPIDLE
调用SocketOptions.setOption()
,为您的应用创建的任何套接字调用TCP_KEEPINTVL
。xe55xuns3#
这可能是一个太明显的答案[即它不是您的特定情况下的选项],但您当然可以通过每10分钟发送1个丢弃字节(在任一方向)来实现自己的keepalive。
h79rfbju4#
深入研究我的Android 10设备上的高投票率answer和反射限制,我发现了一个现代Android API上的工作解决方案。首先,检查java的sysem方法的blocklists,以了解不同Android版本上的
setsockoptInt
限制,我发现4个包使用此方法,唯一没有阻止的包是核心Androidandroid.system.Os
公共包。令人惊讶的是,该包允许在不直接使用Java反射的情况下访问低级系统功能。从Android 8(API 26)开始,这个软件包使用
Os.setsockoptInt
方法进行了丰富。第二,通过将SocketInputStream
转换为FileInputStream
(如that answer),可以更优雅地获取套接字的FileDescriptor
。因此,运行良好的代码如下: