質問

I'm trying to start an embedded JMX server in my java app. I want to use the same port for the RMI Registry and for the actual RMI traffic (or JMX traffic if you like). Apparently this is possible since the RMI Registry is merely a Remote Object itself.

The added difficulty is that I need to use Socket Factories because I need to bind to a specific NIC. I start off by:

int registryPort = 3012;
int jmxPort = 3012;    // use the same port

and here's my server socket factory. Pretty straight-forward stuff:

public class MyRMIServerSocketFactory implements RMIServerSocketFactory {

    private final InetAddress inetAddress;

    public MyRMIServerSocketFactory(InetAddress inetAddress) {
        this.inetAddress = inetAddress;
    }

    @Override
    public ServerSocket createServerSocket(int port) throws IOException {
        return new ServerSocket(port, 0, inetAddress);
    }

    @Override
    public int hashCode() {
        int hash = 5;
        hash = 97 * hash + (this.inetAddress != null ? this.inetAddress.hashCode() : 0);
        return hash;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        final FlexibleRMIServerSocketFactory other = (FlexibleRMIServerSocketFactory) obj;
        if (this.inetAddress != other.inetAddress && (this.inetAddress == null || !this.inetAddress.equals(other.inetAddress))) {
            return false;
        }
        return true;
    }    
}

(the equals() and hashCode() are auto-generated by my IDE, don't get stuck on them)

I create the RMI Registry like this:

serverSocketFactory = new MyRMIServerSocketFactory(inetAddressBind);
LocateRegistry.createRegistry(
        registryPort,
        RMISocketFactory.getDefaultSocketFactory(),  // client socket factory
        serverSocketFactory // server socket factory
        );       

and then on to creating the JMXConnectorServer:

JMXServiceURL url = new JMXServiceURL(
     "service:jmx:rmi://localhost:" + jmxPort + 
      "/jndi/rmi://:" + registryPort + "/jmxrmi");

Map env = new HashMap();
env.put(RMIConnectorServer.RMI_SERVER_SOCKET_FACTORY_ATTRIBUTE, serverSocketFactory);

connector = JMXConnectorServerFactory.newJMXConnectorServer(
                url, 
                env,
                ManagementFactory.getPlatformMBeanServer());

connector.start();

This results in a bind error on the connector.start() saying that the address is already in use.

If I skip using Socket Factories altogether:

LocateRegistry.createRegistry(registryPort);

and

JMXServiceURL url = new JMXServiceURL(
     "service:jmx:rmi://localhost:" + jmxPort + 
      "/jndi/rmi://:" + registryPort + "/jmxrmi");


connector = JMXConnectorServerFactory.newJMXConnectorServer(
                url, 
                null,
                ManagementFactory.getPlatformMBeanServer());

connector.start();

it works as expected, i.e. only a single port will be opened and no errors.

Question: How to make the 'single-listening-port-scenario' work with Socket Factories ?

UPDATE - FINAL SOLUTION

It works if you create the Registry with a null client socket factory, i.e.

LocateRegistry.createRegistry(
        registryPort,
        null,  // client socket factory (let it default to whatever RMI lib wants)
        serverSocketFactory // server socket factory
        );       

I also had to set the java.rmi.server.hostname System Property which I guess will often be the case in a scenario like mine.

役に立ちましたか?

解決

This should work: you have a correct-looking equals() method in your ServerSocketFactory, which is the important bit. RMI does call that. However the same doesn't apply currently to your client socket factory. You need to pass null as the client socket factory, not RMISocketFactory.getDefaultSocketFactory(), as that gives you a sun.rmi.transport.proxy.RMIMasterSocketFactory, which doesn't implement equals() for some reason. Or else your own implementation of RMIClientSocketFactory with a plausible equals() method.

So what is happening here is that RMI is comparing the CSFs first, and they are coming out unequal, so it doesn't even bother comparing the SSFs:

csf1.equals(csf2) && ssf1.equals(ssf2)

so it tries to create a new ServerSocket on the port you specifed, which is the same as the first port, so it fails.

You could add a shortcut at the beginning of equals that returns true if this == that.

他のヒント

You should search for the JMXMP protocol, and for the jmxremote_optional.jar that contains it. This is a more controllable, more efficient protocol for JMX.

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top