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.