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

25

我正在编写一个端到端测试,以确保我的Java程序释放所有资源——线程、服务器套接字和客户端套接字。由于它是一个库,因此通过退出JVM来释放资源不是一个选项。

检测资源释放的测试很容易,因为您可以询问ThreadGroup中的所有线程,但我还没有找到获取当前JVM正在使用的所有网络套接字列表的好方法。

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

我首选尝试使用纯Java从JVM获取它。我尝试查找MX Bean或类似工具,但没有找到任何线索。

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

如果询问JVM不起作用,则第三个选项将是创建一个跟踪所有套接字创建的设计。这种方法的缺点是有可能会错过某些创建套接字的地方。由于我正在使用Netty,因此似乎可以通过包装ChannelFactory并使用ChannelGroup来实现。


3
我会从Java中运行netstat命令或扫描/proc/self/fd目录以获取套接字信息。 - Peter Lawrey
它需要在Windows和Linux上都能运行,因此netstat不是一个选项。 - Esko Luontola
Linux没有类似于Windows的netstat命令的等效物吗?>_> - Hakanai
我以为Windows没有netstat命令,但它似乎有。无论如何,我能够按照我的答案https://dev59.com/Mmgt5IYBdhLWcg3w-ST1#11696077中描述的方式实现一个仅使用Java的解决方案。 - Esko Luontola
2个回答

18
我能够钩入java.net.Socketjava.net.ServerSocket,并监视这些类的所有新实例。完整代码可以在源代码库中看到。以下是方法概述:
当实例化Socket或ServerSocket时,其构造函数中的第一件事就是调用setImpl(),该函数实例化了真正实现Socket功能的对象。默认实现是java.net.SocksSocketImpl的一个实例,但是可以通过设置自定义java.net.SocketImplFactory来覆盖它,通过java.net.Socket#setSocketImplFactoryjava.net.ServerSocket#setSocketFactory
由于java.net.SocketImpl的所有实现都是包私有的,但是通过一点反射就不太难了。
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<SocketImpl> allSockets = Collections.synchronizedList(new ArrayList<SocketImpl>());
    ServerSocket.setSocketFactory(new SocketImplFactory() {
        public SocketImpl createSocketImpl() {
            SocketImpl socket = newSocketImpl();
            allSockets.add(socket);
            return socket;
        }
    });
请注意,setSocketFactory / setSocketImplFactory只能调用一次,因此您要么需要仅有一个测试执行此操作(就像我所拥有的那样),要么必须创建一个静态单例(令人厌烦!)来保存该spy。
然后问题是如何找出套接字是否关闭?Socket和ServerSocket都有一个isClosed()方法,但是它们使用了一个布尔值来跟踪它们是否被关闭 - SocketImpl实例没有一种简单的方式来检查它是否已关闭。(顺便说一下,Socket和ServerSocket都由SocketImpl支持 - 没有“ServerSocketImpl”)。
幸运的是,SocketImpl引用了它所支持的Socket或ServerSocket。上述的setImpl()方法调用impl.setSocket(this)或impl.setServerSocket(this),可以通过调用java.net.SocketImpl#getSocket或java.net.SocketImpl#getServerSocket来获取该引用。
再次强调,这些方法是包私有的,因此需要进行一些反射:
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));
    }

完整的源代码在这里


这很有趣。我拉了进来,但发现 isClosed() 有时会返回 false,即使可以立即创建一个新的 socket。:/ 所以我的所有四个测试都通过了,除了 socket 检查断言。 :) - Hakanai

5
我自己没有尝试过,但JavaSpecialists新闻通讯提出了类似的问题。在底部,它描述了使用AspectJ的一种方法。您可以将切入点放在创建套接字的构造函数周围,并在那里注册套接字创建的代码。请参考:http://www.javaspecialists.eu/archive/Issue169.html

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接