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

时间:2021-07-08 03:41:50

I'm writing an end-to-end test that my Java program releases all of its resources - threads, server sockets, client sockets. It's a library, so releasing resources by exiting the JVM is not an option. Testing the releasing of threads was easy, because you can ask a ThreadGroup for all threads in it, but I haven't yet found a good way to get a list of all network sockets that the current JVM is using.

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

Is there some way to get from a JVM the list of all client and server sockets, similar to netstat? I'm using Netty with OIO (i.e. java.net.ServerSocket and java.net.Socket) on Java 7. The solution needs to work on both Windows and Linux.

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

My first preference would be to ask it from the JVM using pure Java. I tried to look for an MX Bean or similar, but did not find any.

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

Another option might be to connect to the JVM's profiling/debugging APIs and ask for all instances of Socket and ServerSocket, but I don't know how to do that and whether it can be done without native code (AFAIK, JVMTI is native-only). Also, it shouldn't make the tests slow (even my slowest end-to-end test is just 0.5 seconds, which includes starting another JVM process).

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

If interrogating the JVM doesn't work, a third option would be to create a design which tracks all sockets as they are created. This has the disadvantage of having a possibility of missing some place where sockets are created. Since I'm using Netty, it seems implementable by wrapping ChannelFactory and using a ChannelGroup.

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

2 个解决方案

#1


15  

I was able to hook into java.net.Socket and java.net.ServerSocket and spy all new instances of those classes. The complete code can be seen in the source repository. Here is an overview of the approach:

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

When a Socket or ServerSocket is instantiated, the first thing in its constructor is a call to setImpl() which instantiates the object which really implements the socket functionality. The default implementation is an instance of java.net.SocksSocketImpl, but it's possible to override that by setting a custom java.net.SocketImplFactory through java.net.Socket#setSocketImplFactory and java.net.ServerSocket#setSocketFactory.

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

This is complicated a bit by all implementations of java.net.SocketImpl being package-private, but with a little bit of reflection that's not too hard:

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);
    }
}

The SocketImplFactory implementation for spying on all sockets as they are created looks something like this:

在创建时监视所有套接字的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;
        }
    });

Note that setSocketFactory/setSocketImplFactory can be called only once, so you either need to have only one test which does that (like I have it), or you must create a static singleton (yuck!) for holding that spy.

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

Then the question is that that how to find out whether the socket is closed? Both Socket and ServerSocket have a method isClosed(), but that uses a boolean internal to those classes for keeping track of whether it was closed - the SocketImpl instance does not have an easy way of checking whether it was closed. (BTW, both Socket and ServerSocket are backed by a SocketImpl - there is no "ServerSocketImpl".)

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

Thankfully the SocketImpl has a reference to the Socket or ServerSocket which it is backing. The aforementioned setImpl() method calls impl.setSocket(this) or impl.setServerSocket(this), and it's possible to get that reference back by calling java.net.SocketImpl#getSocket or java.net.SocketImpl#getServerSocket.

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

Once again those methods are package-private, so a little bit of reflection is needed:

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

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);
    }
}

Note that getSocket/getServerSocket may not be called inside the SocketImplFactory, because Socket/ServerSocket sets them only after the SocketImpl is returned from there.

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

Now there is all the infrastructure necessary for checking in our tests whatever we want about the Socket/ServerSocket:

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

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

The full source code is here.

完整的源代码在这里。

#2


5  

I haven't tried it myself, but the JavaSpecialists newsletter presents a similar problem:

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

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

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

At the bottom, he describes an approach using AspectJ. You could probably put the pointcut around the constructor that creates the socket, and have code that registers socket creation there.

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

#1


15  

I was able to hook into java.net.Socket and java.net.ServerSocket and spy all new instances of those classes. The complete code can be seen in the source repository. Here is an overview of the approach:

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

When a Socket or ServerSocket is instantiated, the first thing in its constructor is a call to setImpl() which instantiates the object which really implements the socket functionality. The default implementation is an instance of java.net.SocksSocketImpl, but it's possible to override that by setting a custom java.net.SocketImplFactory through java.net.Socket#setSocketImplFactory and java.net.ServerSocket#setSocketFactory.

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

This is complicated a bit by all implementations of java.net.SocketImpl being package-private, but with a little bit of reflection that's not too hard:

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);
    }
}

The SocketImplFactory implementation for spying on all sockets as they are created looks something like this:

在创建时监视所有套接字的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;
        }
    });

Note that setSocketFactory/setSocketImplFactory can be called only once, so you either need to have only one test which does that (like I have it), or you must create a static singleton (yuck!) for holding that spy.

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

Then the question is that that how to find out whether the socket is closed? Both Socket and ServerSocket have a method isClosed(), but that uses a boolean internal to those classes for keeping track of whether it was closed - the SocketImpl instance does not have an easy way of checking whether it was closed. (BTW, both Socket and ServerSocket are backed by a SocketImpl - there is no "ServerSocketImpl".)

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

Thankfully the SocketImpl has a reference to the Socket or ServerSocket which it is backing. The aforementioned setImpl() method calls impl.setSocket(this) or impl.setServerSocket(this), and it's possible to get that reference back by calling java.net.SocketImpl#getSocket or java.net.SocketImpl#getServerSocket.

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

Once again those methods are package-private, so a little bit of reflection is needed:

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

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);
    }
}

Note that getSocket/getServerSocket may not be called inside the SocketImplFactory, because Socket/ServerSocket sets them only after the SocketImpl is returned from there.

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

Now there is all the infrastructure necessary for checking in our tests whatever we want about the Socket/ServerSocket:

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

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

The full source code is here.

完整的源代码在这里。

#2


5  

I haven't tried it myself, but the JavaSpecialists newsletter presents a similar problem:

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

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

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

At the bottom, he describes an approach using AspectJ. You could probably put the pointcut around the constructor that creates the socket, and have code that registers socket creation there.

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