We're trying to make a simple chat program using RMI with a push configuration. The program works on an internal network, but when were trying to run the program with a server on an external network, we get an error:
java.rmi.ConnectException: Connection refused to host: 192.168.2.24;
Caused by: java.net.ConnectException: Connection timed out: connect
The error occurs when the client calls a method 'Broadcast(String s)' on the interface 'IChatServer' This method is located on the server and calls other clients subscribed as listener on the server.
Our client can connect to the server. It can get bindings from the registry and call methods from the server.
But when the server tries to call a method from the client, we get this error.
On the server, port 1099 is forwarded, and port 1099 is allowed in the firewall.
Is there a way to make this possible (using RMI)? Or do ports on the client side need to be forwarded?
Server:
try {
String theIp = serverHostExternalIp;
System.setProperty("java.rmi.server.hostname", theIp);
//Implemented this so no random ports will be used
RMISocketFactory.setSocketFactory(new FixedPortRMISocketFactory());
registry = LocateRegistry.createRegistry(PORT_NUMBER);
publisher = new RemotePublisher();
publisher.registerProperty(BINDING_NAME);
UnicastRemoteObject.unexportObject(server, true);
UnicastRemoteObject.exportObject(server, PORT_NUMBER);
UnicastRemoteObject.unexportObject(publisher, true);
UnicastRemoteObject.exportObject(publisher, PORT_NUMBER);
registry.rebind(BINDING_NAME, server);
registry.rebind(PUBLISH_NAME, publisher);
} catch (RemoteException ex) {
System.err.println("[Server] Cannot bind student administration");
System.err.println("[Server] RemoteException: " + ex.getMessage());
}
IChatServer:
public synchronized void tryConnect(String s, IChatClient client) throws RemoteException {
System.out.println("[Server] User connected: " + s);
}
public synchronized void broadcast(String s) throws RemoteException {
// When this line is called, no errors occur, and the string is printed correctly.
System.out.println("[Message] " + s);
//on this line, the server tries to reach to all the clients (all listeners)
//This line of code will generate an error.
publisher.inform(BINDING_NAME, null, s);
}
Client:
try {
registry = LocateRegistry.getRegistry(ipAddress, PORT_NUMBER);
mycs = (IChatServer) registry.lookup(BINDING_NAME);
//This method is located on the server and is called without errors.
mycs.tryConnect(userid, this);
publisher = (IRemotePublisherForListener) registry.lookup(PUBLISH_NAME);
publisher.subscribeRemoteListener(this, BINDING_NAME);
} catch (RemoteException ex) {
System.err.println("[Client] Cannot lookup or subscribe publisher");
System.err.println("[Client] RemoteException: " + ex.getMessage());
registry = null;
} catch (NotBoundException e) {
System.err.println("[Client] Cannot lookup or subscribe publisher");
System.err.println("[Client] NotBoundException: " + e.getMessage());
registry = null;
}
For the RMI manner, dont think of sides as client-server but rather remote service
and caller
.
So the caller
needs to be able to connect via TCP to remote service
in order to perform Remote Method Invocation (RMI). So in your case, port forwarding must be set propely on both sides. As this can be troublesome (eg client can be behind NAT that is not managed - like in your case) it is better to threat RMI more like REST service - so only one side is calling remote service
Besides forwarding 1099
whitch is RMI registry port, you have also to forward ports used by exported objects. RMI registry only holds informations about how to connect to actual exported object handler.