I am trying to store some client specific variable in a RMI implementation.
Interface:
public interface RMIInterface extends Remote{
public void setName(String name) throws RemoteException;
public String getMessage() throws RemoteException;
}
Implementation:
public class TSImp implements RMIInterface {
private String name;
#Override
public String getMessage() throws RemoteException {
return "Hello "+this.name;
}
#Override
public void setName(String name) throws RemoteException {
this.name=name;
}
}
Server:
try {
RMIInterface i = new TSImp();
RMIInterface stub = (RMIInterface) UnicastRemoteObject.exportObject(i, 1099);
Registry registry = LocateRegistry.createRegistry(1099);
registry.rebind("TrafficServer", i);
System.out.println("Server started");
} catch (RemoteException ex) {
Logger.getLogger(Server.class.getName()).log(Level.SEVERE, null, ex);
}
Client:
try {
Registry registry=LocateRegistry.getRegistry("localhost", 1099);
RMIInterface rmi = (RMIInterface) registry.lookup("TrafficServer");
rmi.setName("Alice");
System.out.println(rmi.getMessage());
} catch (RemoteException | NotBoundException ex) {
System.out.println(ex.getLocalizedMessage());
}
Now if I try to run multiple clients by passing different parameters in the setName function, it seems to replace the variable in my server.
Example:
Client1:
setName("Alice")
Client2:
setName("Bob")
Client1:
getMessage() //returns "Bob"
Is there a way to have client/connection specific variables?
You need the Remote Session pattern. The remote object bound in the Registry is a singleton, which should have something like a login() method, which returns a unique per-client instance of a second remote interface, called something like RemoteSession, which contains the real API used by clients, and whose implementation can contain per-client information, as there is an instance per client. This second interface can contain a logout() method, which unexports the remote object,and/or its implementation can implement Unreferenced, which also unexports itself.
Related
I have an object to be injected that is defined as:
public class EnvironmentModule extends AbstractModule {
#Override
protected void configure() {
}
#Provides
#Singleton
private String getObject(final Client client) {
...
}
}
Client is an enum defined as :
#NoArgsConstructor(force = true)
public enum Client {
private String name;
Client(final String name) {
this.name = name;
}
public static Client identifyClient(final String clientName) {
}
}
This gives me an error-
Could not find a suitable constructor in Client. Classes must have either one (and only one) constructor annotated with #Inject or a zero-argument constructor that is not private
at Client.class(Client.java:5)
at EnvironmentModule.getObject(EnvironmentModule.java:35)
Please help. What has to be done.
The reason this is happening is because in your module you do not declare an instance of Client to be injected in the scope of the module, so it tries to create one with an empty constructor. This does not work because your enum has two constructors, and guice requires a single empty constructor. The solution to this to create a singleton of your client. I assume the code you omitted in Client looks like
public enum Client {
//I assume it works like this
NORMAL_CLIENT("whatever");
private String name;
Client(final String name) {
this.name = name;
}
public static Client identifyClient(final String clientName) {
return Arrays.stream(Client.values())
.filter(client -> clientName.equals(client.name))
.findAny()
//This is dangerous, throw an error if its not found
.get();
}
}
So we need to create a singleton in the environment module for the client. this would look like
public class EnvironmentModule extends AbstractModule {
#Override
protected void configure() {
super.configure();
}
#Provides
#Singleton
private Client getClient() {
return Client.identifyClient("whatever");
}
#Provides
#Singleton
private String doWhatever(final Client client) {
System.out.println("found client " + client);
return "cool it works";
}
}
invoking the module through
public class Main {
public static void main(String[] args) {
final var envInjector = Guice.createInjector(new EnvironmentModule());
final var client = envInjector.getInstance(Client.class);
final var doWhateverString = envInjector.getInstance(String.class);
System.out.println(doWhateverString);
System.out.println("found instance " + client);
}
}
we can see
found client NORMAL_CLIENT
cool it works
found instance NORMAL_CLIENT
My aim is to make a simple chat program. I'm new at RMI. What I've got so far is that the server works. I start it. Then I start the client, it transfers the strings to the server through RMI. But then it doesn't appear on the GUI I made. That's where my problem lies.
My project structure
My StartClient class. I created a chatClient, and put the chatServer stub as parameter.
public StartClient() throws RemoteException, NotBoundException, MalformedURLException {
chatServer = (ChatServer) Naming.lookup("rmi://localhost:1099/chatServer");
}
private void run() throws RemoteException, MalformedURLException, NotBoundException {
ChatClientImpl chatClient1 = new ChatClientImpl(chatServer, "ikke");
new ChatFrame(chatClient1);
ChatClientImpl chatClient2 = new ChatClientImpl(chatServer, "bla");
new ChatFrame(chatClient2);
}
public static void main(String[] args) throws RemoteException, NotBoundException, MalformedURLException {
StartClient start = new StartClient();
start.run();
}
In the ChatClientImpl constructor I use the remote method register.
public ChatClientImpl(ChatServer chatServer, String name) throws MalformedURLException, NotBoundException, RemoteException {
this.chatServer = chatServer;
this.name = name;
chatServer.register(this);
}
Now we're in the ChatServerImpl class, in the REGISTER method. I add the client to an ArrayList of clients. Then I use the method SENT to display the text. It calls the RECEIVE method that each client object has.
public class ChatServerImpl extends UnicastRemoteObject implements ChatServer {
private List<ChatClient> clients;
public ChatServerImpl() throws RemoteException {
this.clients = new ArrayList<ChatClient>();
}
public void register(ChatClientImpl client) throws RemoteException {
clients.add(client);
send("server", client.getName() + " has entered the room");
}
public void unregister(ChatClientImpl client) throws RemoteException {
clients.remove(client);
send("server", client.getName() + " has left the room");
}
public void send(String name, String message) throws RemoteException {
for(ChatClient client : clients) {
client.receive(name + ": " + message);
}
}
}
This is where things go wrong. The textReceiver is ALWAYS null. (textReceiver is attribute/field of the client object.)
public void receive(String message) {
if (textReceiver == null) return;
textReceiver.receive(message);
}
The ArrayList of clients are server-side and all the clients in there all have their textReceivers set on null. If you look back at StartClient there's an important line. The new ChatFrame(chatClient). In the ChatFrame's constructor is where I set the textReceiver.
public ChatFrame(ChatClientImpl chatClient) {
this.chatClient = chatClient;
chatClient.setTextReceiver(this);
String name = chatClient.getName();
setTitle("Chat: " + name);
createComponents(name);
layoutComponents();
addListeners();
setSize(300, 300);
setVisible(true);
}
This project works when I don't use RMI and they're in one package but once I separate them into client-server this problem arose. How do I communicate between them? Server-side I have an (irrelevant?) list of ChatClients that don't influence anything even though the text arrives.
Do I use RMI for every separate ChatClient and make the ChatServer connect with it and send the text like that? Seems very complicated to me. How do I go about this?
EDIT:
ChatClientImpl class
public class ChatClientImpl implements ChatClient, Serializable {
private ChatServer chatServer;
private TextReceiver textReceiver;
private String name;
public ChatClientImpl(ChatServer chatServer, String name) throws MalformedURLException, NotBoundException, RemoteException {
this.chatServer = chatServer;
this.name = name;
chatServer.register(this);
}
public String getName() {
return name;
}
public void send(String message) throws RemoteException {
chatServer.send(name, message);
}
public void receive(String message) {
if (textReceiver == null) return;
textReceiver.receive(message);
}
public void setTextReceiver(TextReceiver textReceiver) {
this.textReceiver = textReceiver;
}
public void unregister() throws RemoteException {
chatServer.unregister(this);
}
}
Your ChatClientImpl class isn't an exported remote object, so it will be serialized to the server, and execute there. And because register() happens during construction, it will be serialized before the setReceiverTextReceiver() method is called. So, the corresponding field will be null. At the server. This is not what you want and it is also not where you want it.
So, make it extend UnicastRemoteObject and implement your ChatClient (presumed) remote interface. If you have problems with doing that, solve them. Don't just mess around with things arbitrarily. And it should not implement Serializable.
NB The signature of register() should be register(ChatClient client). Nothing to do with the ChatClientImpl class. Ditto for unregister().
Edit: The Java™ Tutorials say that
the server and the client communicate and pass information back and
forth
and that RMI
provides mechanisms for loading an object's class definitions as well
as for transmitting an object's data.
I was hoping that "an object's data" would include a server object's variables (such as Test.value in my code, below) - but the first comments I got indicate that perhaps I was wrong. My original question follows.
I am trying to access a remote object that I am sending over RMI to a client. I am only able to access its methods, but not its instance variables - I get the interface's fields instead. My question is, once I implement and instantiate a class on a server, how do I access its [public] fields, without using getters? I am able to send a stub without any errors or exceptions, but like I said, I am not able to access the server's object's fields, only the interface's. Following is an abbreviated version of my interface, implementation, server, and client.
package test;
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface TESTint extends Remote {
double value = -22;
String shortName = "TESTint";
double getValue() throws RemoteException;
}
package test;
import java.rmi.RemoteException;
public class Test implements TESTint {
public double value = -33;
public String shortName = "TestAccount";
public int whole = 1;
public Test(String shortName) {
this.shortName = shortName;
print(shortName);
}
public double getValue() throws RemoteException {
return value;
}
public void print(Object o) {
System.out.println(shortName + ": " + o);
}
}
package test;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
public class RemoteTestMain {
Test test;
public static void main(String[] args) throws InterruptedException {
if (System.getSecurityManager() == null) { System.setSecurityManager(new SecurityManager()); }
new RemoteTestMain();
} // main
public RemoteTestMain() {
test = new Test("Charlie");
Registry registry;
try {
registry = LocateRegistry.createRegistry(1234);
registry.list( ); // will throw an exception if the registry does not already exist
print(test.shortName); // it gets it right here
print(test.value); // it gets it right here
TESTint r = (TESTint) UnicastRemoteObject.exportObject(test, 0);
registry.rebind("DCregistry", r);
print("test bound");
} catch (java.rmi.RemoteException ex) {
print("Remote Exception at Server");
ex.printStackTrace();;
}
}
public static void print(Object o) {
System.out.println("Server: " + o);
}
}
package test;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class Client {
TESTint test;
public static void main(String[] args) {
try {
new Client();
} catch (RemoteException e) {
e.printStackTrace();
}
} // main
private void init(int account) {
print("INITiating Account " + account);
try {
Registry registry = LocateRegistry.getRegistry(1234);
test = (TESTint) registry.lookup("DCregistry");
} catch (Exception e) {
System.err.println("RMI exception:");
e.printStackTrace();
}
print("Short name : " + test.shortName);
print("value: " + test.value);
try {
print("Value through getter is " + test.getValue());
} catch (RemoteException e) {
print("Could not get equity");
e.printStackTrace();
}
} // init(int account)
public Client() throws RemoteException {
if (System.getSecurityManager() == null) { System.setSecurityManager(new SecurityManager()); }
init(2);
}
private static void print(Object o) {
System.out.println("GUI: " + o);
}
}
P.S. In the Client code above, test.shortName is wiggly-underlined and Eclipse suggests that The static field TESTint.shortName should be accessed in a static way. I understand that the Client does not recognize the implementation, only the interface - but is there a way to access test's fields, not only its methods? I have many fields in my original code and I do not want to write getters for each and every one, if possible.
RMI stands for Remote Method Invocation which means that you can remotely execute a method of an object. The implementation of the method resides in the remote system. You can never access the instance variables of the implementation class which exists in the remote system even if they are public. You can only execute public methods which are exposed by the Inteface. So if you want to access the variables, you need add public gettter methods both in the Inteface and implementation class.
I have 2 projects. One works fine in ever department. I downloaded and modified it to better understand it. The 2nd one is a project in development phase.
Now, both these projects have almost exactly the same RMI package, which works fine in the first project, but not in the 2nd.
My test classes in each package are essentially identical as well.
The main difference is what objects there are attempting to access, which are both interfaces in a database package.
Now, the database package in the 2nd project otherwise works absolutely fine, it just wont work with the RMI.
In short:
database package works fine
RMI package works fine
RMI package and database together does not work fine.
Here is my DBInterface
public interface DB extends Remote {
public String[] read(int recNo) throws RecordNotFoundException;
public void update(int recNo, String[] data, long lockCookie)
throws RecordNotFoundException, SecurityException, IOException;
public void delete(int recNo, long lockCookie)
throws RecordNotFoundException, SecurityException, IOException;
public int[] find(String[] criteria);
public int create(String[] data) throws DuplicateKeyException, IOException;
public long lock(int recNo) throws RecordNotFoundException;
public void unlock(int recNo, long cookie)
throws RecordNotFoundException, SecurityException;
}
and here is my RMIInterface
public interface RMIInterface extends Remote{
public DB getClient() throws RemoteException;
}
My RMIImplementation
public class RMIImplementation extends UnicastRemoteObject
implements RMIInterface {
private static String dbLocation = null;
private DB a;
public RMIImplementation() throws RemoteException{
}
public RMIImplementation(String dbLocation) throws RemoteException{
System.out.println(dbLocation);
this.dbLocation = dbLocation;
}
public static DB getRemote(String hostname, String port)
throws RemoteException {
String url = "rmi://" + hostname + ":" + port + "/DvdMediator";
try {
RMIInterface factory
= (RMIInterface) Naming.lookup(url);
// at this point factory equals Proxy[RMIInterface,................etc
// i want the return to equal Proxy[DB,..............etc
return (DB) factory.getClient();
} catch (NotBoundException e) {
throw new RemoteException("Dvd Mediator not registered: ", e);
}
catch (java.net.MalformedURLException e) {
throw new RemoteException("cannot connect to " + hostname, e);
}
}
public DB getClient() throws RemoteException {
try {
a = new ContractorDatabase(dbLocation);
}
catch(Exception e){
System.out.println("NewClass exception: " + e.toString());
}
return a;
}
And my the RMI registry
public class RegDvdDatabase {
private RegDvdDatabase() {
}
public static void register()
throws RemoteException {
register(".", java.rmi.registry.Registry.REGISTRY_PORT);
}
public static void register(String dbLocation, int rmiPort)
throws RemoteException {
Registry r = java.rmi.registry.LocateRegistry.createRegistry(rmiPort);
r.rebind("DvdMediator", new RMIImplementation(dbLocation));
}
}
Getting these two to work together throws a
Exception in thread "main" java.lang.ClassCastException: com.sun.proxy.$Proxy0 cannot be cast to sjdproject.remote.RMIImplementation
Can u please help me find the database issue that prevents it from working.
You must cast it to the remote interface.
EDIT The Registry reference r in your server code must be static. I can't see any good reason for locating the client lookup code inside the implementation class. That class should only exist at the server, not the client.
If you dont have a debugger, I would suggest using reflection on the provided object and see which interfaces it implements. It appears to be a proxy object, so must implement some interfaces.
for(Class clazz : factory.getClass().getInterfaces()) {
System.out.println(clazz.getSimpleName());
}
My suspicion with multiple deployments is of course the jvm version and the classpath. Can you verify that they match?
I'm learning Java RMI so that i'm writing and testing this code, the problem here is that the client (notificationSink class) send a message and register itself with the server but the server (notificationSource class) doesn't do anything.
This is RemoteThingInterface that extends Remote class:
public interface RemoteThingInterface extends Remote{
public void register(NotificationSink sink) throws RemoteException;
public void notifySink(String text) throws RemoteException;
}
This is NotificationSink class:
public class NotificationSink{
private String name;
private static String hostName = "Shine";
private static int serverPort = 2712;
private static String text = (new Date()).toString();
public NotificationSink(String name){
name = this.name;
}
public static void main(String [] args){
RemoteThingInterface rmiserver;
Registry reg;
System.out.println("Sending "+text+" to "+hostName+" through port "+serverPort);
try{
reg = LocateRegistry.getRegistry(hostName, serverPort);
rmiserver = (RemoteThingInterface) reg.lookup("server");
NotificationSink s = new NotificationSink("Eagle 1");
rmiserver.register(s);
rmiserver.notifySink(text);
}catch(RemoteException ex){} catch (NotBoundException ex) {
ex.printStackTrace();
}
}
}
This is NotificationSource class:
public class NotificationSource extends UnicastRemoteObject implements RemoteThingInterface{
private ArrayList sinks = new ArrayList<>();
int port;
Registry registry;
public NotificationSource() throws RemoteException{
try{
port = 2712;
registry = LocateRegistry.createRegistry(port);
registry.rebind("server", this);
}catch(RemoteException ex){
ex.printStackTrace();
}
}
#Override
public void register(NotificationSink sink) {
sinks.add(sink);
}
public ArrayList getSinks(){
return sinks;
}
#Override
public void notifySink(String text){
System.out.println("new sink registered, he is "+getSinks().get(0));
System.out.println(text);
}
public static void main(String [] args){
try{
NotificationSource s = new NotificationSource();
}catch(Exception e){
e.printStackTrace();
}
}
}
Please help to explain where i'm wrong and how to fix this. I tried to add some code to find the size of arraylist in server, it find out successfully, but other methods don't work .... codes are below:
adding this line to remotethinginterface: ArrayList getArray() throws RemoteException;
adding this line to notiSource:
#Override
public ArrayList getArray() throws RemoteException {
return sinks;
}
adding this line to notiSink: System.out.println(rmiserver.getArray().size()); (before rmiserver.register()
the client (notificationSink class) send a message
No it doesn't.
and register itself with the server
No it doesn't.
but the server (notificationSource class) doesn't do anything.
Why should it? There is no client request to do anything with. There can't be. It's impossible.
catch(RemoteException ex){}
The first major problem is here. You are ignoring RemoteException. Don't do that. Log it, print it, never ignore an exception unless you really know what you're doing. In this case you will therefore have ignored the nested NotSerializableException that was thrown when you called register().
The second major problem is that NotificationSink needs to either:
Implement Serializable, if you want it to execute at the server, or
Implement a remote interface and extend UnicastRemoteObject, if you want it to execute at the client.