Setup Java RMI Over the Internet

Many of us are familiar with Java RMI but to get it working over the internet is a bit more involved, at least in my own experience of doing so. Please note that this post assumes a basic understanding of Java RMI. If you would like to start from scratch, see Oracle's official documentation.

The solution

  1. Enable port forwarding for ports 1099 and 1100. Why? 1099 is the defacto-standard port for Java RMI. As for port 1100, we're going to use this for communicating with remote objects. This latter is arbitrary though and can be replaced by a different port if you prefer.
  2. Run rmiregistry in your codebase on any servers. Note that this is not required for clients to receive a response.
  3. In your server-side code do .rebind("//your-local-ip:1099/remoteObjectName"); This will bind the object to your local machine. However, because we are using port forwarding, we are telling our router to direct any packets to 1099 to our local IP address. Please note that you cannot use rebind on an external IP.
  4. IMPORTANT:Any objects which intend to receive any response, rather than extend UnicastRemoteObject, use the following code: UnicastRemoteObject.exportObject(yourObject, 1100) This will allow your object to listen for remote calls on port 1100 (which we have port forwarded). If you do not do this, then Java will use an anonymous port (which will probably get blocked remotely).
  5. In your client-side code do Naming.lookup("//the-remote-ip:1099/remoteObjectName")
  6. Don't forget to compile your remote classes with rmic!
  7. IMPORTANT:Finally, when running your java client/server make sure you pass -Djava.rmi.server.hostname=your-external-IP to Java. Eg: java -Djava.rmi.server.hostname=1.2.3.4 client This JVM argument when passed will set the address to be bound to exported remote objects. It can be set programmatically but doing so is left as an exercise for the reader.

You may get the following error:
java.rmi.ConnectException: Connection refused to host: 127.0.1.1 (or some remote IP); nested exception is:
java.net.ConnectException: Connection refused

If so, this means you may have missed a step from earlier. Unfortunately, there are many causes for this error!

Finally, rather than use string literals for the IP addresses, you will probably want to use automated methods to keep track of them. This has been left out of the code above for simplicity.

Comments

Anonymous - Tue, 24/01/2012 - 04:28

Permalink

I can get RMI working for my local network, however I am bashing my head against the wall trying to get it to work over the internet.

My router port forwards ports 1099, 1100 to 10.10.10.105 (my server's local IP)
I also disable my windows firewall

Here is what my server code looks like:

UserLoginImpl userLog = new UserLoginImpl();
UnicastRemoteObject.unexportObject(userLog, true);
UserLogin stub = (UserLogin) UnicastRemoteObject.exportObject(userLog, 0);
Registry registry = LocateRegistry.createRegistry(1099);
Naming.rebind("//10.10.10.105:1099/UserLogin", stub);

I have -Djava.rmi.server.hostname="server's IP from whatismyip.com" set in the VM Options (I am using Netbeans).

Here is what my client code looks like:

try {
UserLogin stub = (UserLogin)Naming.lookup("//server's IP from whatismyip.com:1099/UserLogin");
}

I get a connection refused error when I attempt to connect with the client. I have been trying to get this to work for the longest time!

Hi there,

First of all, be very careful disabling external firewalls (as tempting as it is) as this may leave you wide open for hacking!

I believe my instructions only work in specific configurations actually as far as I remember... Basically, it has something to do with your security manager for your Java VM. On the client side, I think it may be not allowing external connections. By default, I think it's trying to connect to a loopback address.

One workaround is to have a custom factory for your server and client sockets I believe.

Custom server-socket factory (automatically uses your local IP as specified in my guide and apologies for lack of indentation, the site CSS is a bit quirky with code...)

/** Factory for creating NotificationServerSockets
*
* @author Nicholas Hatter
*/
import java.io.IOException;
import java.net.ServerSocket;
import java.rmi.server.RMIServerSocketFactory;
public class NotificationServerSocketFactory implements RMIServerSocketFactory {
@Override
// Creates a server socket on local IP address on a given port number
public ServerSocket createServerSocket(int port) throws IOException {
return new ServerSocket(port);
}
@Override
public int hashCode() {
return externalIP.hashCode();
}
@Override
public boolean equals(Object obj) {
return (getClass() == obj.getClass() &&
externalIP.equals(((NotificationServerSocketFactory) obj).getExternalIP()));
}
}

Custom socket factory for client connecting to a remote IP:


/** Factory for creating NotificationClientSockets
*
* @author Nicholas Hatter
*/
import java.io.IOException;
import java.io.Serializable;
import java.net.Socket;
import java.rmi.server.RMIClientSocketFactory;
public class NotificationClientSocketFactory implements RMIClientSocketFactory, Serializable {
private final String externalIP;
public NotificationClientSocketFactory(String externalIP) {
this.externalIP = externalIP;
}
public String getExternalIP() {
return externalIP;
}
public Socket createSocket(String host, int port) throws IOException {
return new Socket(externalIP, port);
}
public int hashcode() {
return externalIP.hashCode();
}
@Override
public int hashCode() {
return externalIP.hashCode();
}
@Override
public boolean equals(Object obj) {
return (getClass() == obj.getClass() &&
externalIP.equals(((NotificationClientSocketFactory) obj).getExternalIP()));
}
}

Example call on server-side:

Registry rmiRegistry = LocateRegistry.getRegistry(Registry.REGISTRY_PORT);
stub = (YourObjectInterfaceForYourRemoteObject) UnicastRemoteObject.exportObject(this, port, new NotificationClientSocketFactory(externalIP), new NotificationServerSocketFactory());
rmiRegistry.rebind(name, stub);

Example call client-side:

UnicastRemoteObject.exportObject(this, port, new NotificationClientSocketFactory(externalIP), new NotificationServerSocketFactory());

So basically we create these socket factories to allow us to make external connections. We then have to instantiate these factories when we call .exportObject to let Java RMI that we're using our own custom socket factories.

Try something like this along these lines and don't hesitate to ask for more help :)

Regards,
Nick

First off, thank you so much for taking the time help me in this matter. As I said, this problem has been plaguing me for quite sometime. Since you suggested that the problem may be related to the security manager settings I created a .policy file for the Client that looks like:

grant {
permission java.security.AllPermission "", "";
};

I placed the file in the /src folder of the Client application and added the code: -Djava.security.manager -Djava.security.policy=src/SphereCCGClient.policy (is this correct implementation?). When I do this I still get the same connection refused error.

I created another policy file with the same contents and did the same procedure for the Server application. When I do this and start the Server, I start getting Server an exception: java.security.AccessControlException: access denied ("java.net.SocketPermission"...

So I'm not sure what I'm doing wrong with the policy files, however I'm not sure as they are the issue, since when I had none, I did not receive an access denied exception.

I would like to try the example you provided in creating my own sockets, however I run in to an issue when I try to implement the code as shown. In the NotificationClientSocketFactory class, you make several references to 'externalIP', however I can not find where this variable is instantiated for the class. Also can you elaborate a little bit on the call of the client-side, where does the client receive the stub for the class?

One last thing; I've been testing the server and client on the same machine for this, but since I'm trying to connect to the external IP of my server, would testing the client side from an external machine produce different results?

Thanks so much!

Anonymous - Sun, 24/03/2013 - 18:56

Permalink

-Djava.rmi.server.hostname="ip_to_my_net_where_server_is"

I spent a few hours when was figuring out why my zabbix server had not monitored my tomcat instance. Thanks a lot!

Add new comment

CAPTCHA