Search code examples
javarmi

RMI: create server on certain net address and port


I want to make rmi server on port and network interfaces I choose programmatically (without jvm settings). For example I want the rmi server listen interface 127.0.0.1 and port 2525. I've read information in internet and this is the solution I finally came to.

class ServerSocketFactory implements RMIServerSocketFactory, Serializable {

    public ServerSocket createServerSocket(int port) throws IOException
    {
        ServerSocket server = new ServerSocket(2525, 0, InetAddress.getByName("127.0.0.1"));
        return server;
    }
}

And this is how I create my registy

registry = LocateRegistry.createRegistry(2525,null,new ServerSocketFactory());

However, I get exception:

java.rmi.server.ExportException: Port already in use: 2525; nested exception is: 
    java.net.BindException: Address already in use
    at sun.rmi.transport.tcp.TCPTransport.listen(TCPTransport.java:341)

I can't understand why if I use ServerSocketFactory in createRegistry there is also a port argument. What is my mistake?

P.S. This is not duplicate of Remote method invocation port in use because it is about selecting network interface but not port.


Solution

  • That BindException means the port is already in use. So you're running this code twice, or something else is listening at the port.

    You don't have to specify the port number twice. Just use the port number that is provided as a parameter to createServerSocket(). It will be the port number you specified when exporting, or zero.

    If you are using multiple instances of this socket factory, you need to implement equals() in your socket factory class, such that it returns truefor all instances of this class.

    NB RMIServerSocketFactory implementations do not need to be Serializable. As a matter of fact you don't really need your RMIServerSocketFactory implementation class at all other than for binding to 127.0.0.1. Unclear why you would want to do that, as it would mean you are doing all your RMI within the same host, which is pretty futile.

    So in summary you need:

    registry = LocateRegistry.createRegistry(2525);
    

    and

    myObject = new MyRemoteObject(2525);
    

    and

    // constructor
    public MyRemoteObject(int port) throws RemoteException
    {
        super(port);
    }
    

    If you need the remote object(s) (including the Registry) to listen at only one local IP address instead of all of them, that's when you need an RMIServerSocketFactory:

    public class MyRMIServerSocketFactory implements RMIServerSocketFactory
    {
        private InetAddress address;
    
        public MyRMIServerSocketFactory(InetAddress address)
        {
            this.address = address;
        }
    
        @Override
        public ServerSocket createServerSocket(int port) throws IOException
        {
            return new ServerSocket(port, 0, address);
        }
    
        @Override
        public boolean equals(Object that)
        {
            return that != null && this.getClass() == that.getClass() && this.address.equals((MyServerSocketFactory)that).address);
        }
    }
    

    To use it:

    Registry registry = LocateRegistry.createRegistry(2525, null, new MyServerSocketFactory(address));
    

    and

    public MyRemoteObject(int port, InetAddress address) throws RemoteException
    {
        super(port, null, new MyServerSocketFactory(address);
    }
    

    The rules of this game are that ports are shared between remote objects exported from the same JVM (which can include the Registry) if and only if:

    1. none of them use server socket factories, or all of them use server socket factories that are equal under equals()
    2. they all use the same port number, or zero, or no port number, or all after the first object exported use zero or no port number (when they will share the first one's port). Note that this means that port sharing happens by default, provided (1) is met.