找出当前Java VM中打开的网络套接字

我正在编写一个端到端测试,我的Java程序发布了它的所有资源 – 线程,服务器套接字,客户端套接字。 它是一个库,因此通过退出JVM释放资源不是一种选择。 测试线程的释放很容易,因为你可以向ThreadGroup询问其中的所有线程,但是我还没有找到一个很好的方法来获取当前JVM正在使用的所有网络套接字的列表。

有没有办法从JVM获取所有客户端和服务器套接字的列表,类似于netstat? 我在Java 7上使用Netty和OIO (即java.net.ServerSocket和java.net.Socket )。该解决方案需要在Windows和Linux上运行。

我的第一个偏好是使用纯Java从JVM中询问它。 我试图寻找一个MX Bean或类似的,但没有找到任何。

另一个选择可能是连接到JVM的分析/调试API并询问Socket和ServerSocket的所有实例,但我不知道如何做到这一点以及是否可以在没有本机代码的情况下完成(AFAIK, JVMTI仅限本机) )。 此外,它不应该使测试变慢(即使我最慢的端到端测试只有0.5秒,其中包括启动另一个JVM进程)。

如果询问JVM不起作用,第三个选项是创建一个设计,在设置时跟踪所有套接字。 这样做的缺点是可能会遗漏一些创建套接字的地方。 由于我使用Netty,它似乎可以通过包装ChannelFactory和使用ChannelGroup来实现 。

我能够挂钩到java.net.Socketjava.net.ServerSocket并窥探这些类的所有新实例。 可以在源存储库中看到完整的代码。 以下是该方法的概述:

当实例化Socket或ServerSocket时,其构造函数中的第一件事是调用setImpl() ,它实例化实际实现套接字function的对象。 默认实现是java.net.SocksSocketImpl一个实例,但是可以通过java.net.Socket#setSocketImplFactoryjava.net.ServerSocket#setSocketFactory设置自定义java.net.SocketImplFactory来覆盖它。

java.net.SocketImpl的所有实现都是package-private,这有点复杂,但有一些反思并不太难:

 private static SocketImpl newSocketImpl() { try { Class defaultSocketImpl = Class.forName("java.net.SocksSocketImpl"); Constructor constructor = defaultSocketImpl.getDeclaredConstructor(); constructor.setAccessible(true); return (SocketImpl) constructor.newInstance(); } catch (Exception e) { throw new RuntimeException(e); } } 

在创建时监视所有套接字的SocketImplFactory实现看起来像这样:

  final List allSockets = Collections.synchronizedList(new ArrayList()); ServerSocket.setSocketFactory(new SocketImplFactory() { public SocketImpl createSocketImpl() { SocketImpl socket = newSocketImpl(); allSockets.add(socket); return socket; } }); 

请注意,setSocketFactory / setSocketImplFactory只能被调用一次,因此您需要只有一个测试(就像我拥有它),或者您必须创建一个静态单例(yuck!)来保存该间谍。

那么问题是如何找出套接字是否关闭? Socket和ServerSocket都有一个方法isClosed() ,但它使用这些类的布尔内部来跟踪它是否被关闭 – SocketImpl实例没有一种简单的方法来检查它是否被关闭。 (顺便说一句,Socket和ServerSocket都有SocketImpl支持 – 没有“ServerSocketImpl”。)

值得庆幸的是,SocketImpl引用了它支持的Socket或ServerSocket。 前面提到的setImpl()方法调用impl.setSocket(this)impl.setServerSocket(this) ,并且可以通过调用java.net.SocketImpl#getSocketjava.net.SocketImpl#getServerSocket impl.setServerSocket(this)来获取该引用。

这些方法再次是包私有的,因此需要进行一些反思:

 private static Socket getSocket(SocketImpl impl) { try { Method getSocket = SocketImpl.class.getDeclaredMethod("getSocket"); getSocket.setAccessible(true); return (Socket) getSocket.invoke(impl); } catch (Exception e) { throw new RuntimeException(e); } } private static ServerSocket getServerSocket(SocketImpl impl) { try { Method getServerSocket = SocketImpl.class.getDeclaredMethod("getServerSocket"); getServerSocket.setAccessible(true); return (ServerSocket) getServerSocket.invoke(impl); } catch (Exception e) { throw new RuntimeException(e); } } 

请注意,可能不会在SocketImplFactory中调用getSocket / getServerSocket,因为Socket / ServerSocket仅在从那里返回SocketImpl之后才设置它们。

现在有了检查我们的测试所需的所有基础结构,无论我们想要什么关于Socket / ServerSocket:

  for (SocketImpl impl : allSockets) { assertIsClosed(getSocket(impl)); } 

完整的源代码在这里 。

我自己没有尝试过,但JavaSpecialists时事通讯提出了类似的问题:

http://www.javaspecialists.eu/archive/Issue169.html

在底部,他描述了使用AspectJ的方法。 您可以将切入点放在创建套接字的构造函数周围,并且具有在那里注册套接字创建的代码。