Java RMI Problem

TobyNick

Grünschnabel
Hallo Leute,

ich hoffe ihr könnt mir helfen. Ich verzweifel schon ein wenig.
Und zwar wollte ich mich ein bisschen in RMI einarbeiten und hab gedacht ich fang klein an um zu schauen wie das alles genau funktioniert und programmiere ein ganz simples Chat-Programm. Tja, nur funktioniert das leider nicht so, wie ich will. Ich poste erstmal den Code und schreibe dann, was nicht funktioniert. Beachtet erstmal die Kommentare nicht, die brauche ich später für weitere Erklärungen.

Das Server-Interface IServer:

Code:
public interface IServer extends Remote {

	public void addClient(IListener l) throws RemoteException;
	
	public void removeClient(IListener l) throws RemoteException;
	
	public void sendMessage(String s) throws RemoteException;
}

Die implementierende Server-Klasse Server:

Code:
public class Server implements IServer {

	private ArrayList<IListener> clients;
	
	public Server() {
		try {
			LocateRegistry.createRegistry(Registry.REGISTRY_PORT);
			
			IServer server = (IServer) UnicastRemoteObject.exportObject(this, 0);
			Registry reg = LocateRegistry.getRegistry();
			reg.bind("Chat", server);
			
			clients = new ArrayList<IListener>();
		} catch (RemoteException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (AlreadyBoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	@Override
	public void addClient(IListener l) throws RemoteException {
		clients.add(l);
	}

	@Override
	public void removeClient(IListener l) throws RemoteException {
		clients.remove(l);
	}

	@Override
	public void sendMessage(String s) throws RemoteException {
		for (IListener l : clients) {
			l.receiveMessage(s);
		}
	}

	public static void main(String[] args) {
		new Server();
	}
}

Ein Listener-Interface IListener:

Code:
public interface IListener extends Remote, Serializable {

	public void receiveMessage(String s) throws RemoteException;
}

Und die implementierende Klasse Listener:

Code:
public class Listener implements IListener {

	Client client;
	
	public Listener(Client client) {
		this.client = client;

		Registry reg;
		try {
			reg = LocateRegistry.getRegistry();
			reg.bind("Listener_" + client.getName(), this);
		} catch (RemoteException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (AlreadyBoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	@Override
	public void receiveMessage(String s) throws RemoteException {
		client.print(s);
	}

}

Und zu guter letzt die Client-Klasse Client:

Code:
public class Client extends JFrame {

	private String name;
	IServer server;
	IListener listener;
	
	JTextArea ta;
	
	public Client() {
		super("Chat");
		name = JOptionPane.showInputDialog("Bitte Name eingeben: ");		
		 
                //----------------Teil 1
		this.setVisible(true);
		this.setSize(500, 500);
		this.setLocationRelativeTo(null);
		this.addWindowListener(new WindowAdapter() {
			@Override
			public void windowClosing(WindowEvent e) {
				try {
					server.removeClient(listener);
				} catch (RemoteException e1) {
					// TODO Auto-generated catch block
					e1.printStackTrace();
				}
				
				System.exit(0);
			}
		});
		
		addComps();
                //Teil 1-----------------------------
		
		listener = new Listener(this);
		Registry reg;
		try {
			reg = LocateRegistry.getRegistry();
			server = (IServer) reg.lookup("Chat");
			
			server.addClient(listener);
		} catch (RemoteException e2) {
			// TODO Auto-generated catch block
			e2.printStackTrace();
		} catch (NotBoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

                //Platz 1
	}
	
	private void addComps() {
		ta = new JTextArea();
		ta.setLineWrap(true);
		ta.setWrapStyleWord(true);
		ta.setEditable(false);
		
		JPanel inputPanel = new JPanel(new FlowLayout());
		final JTextField tf = new JTextField(25);
		JButton send = new JButton("Send");
		send.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				try {
					server.sendMessage(name + ": " + tf.getText());
				} catch (RemoteException e1) {
					// TODO Auto-generated catch block
					e1.printStackTrace();
				}
			}
		});
		
		inputPanel.add(tf);
		inputPanel.add(send);
		
		this.getContentPane().add(ta, BorderLayout.CENTER);
		this.getContentPane().add(inputPanel, BorderLayout.SOUTH);
	}
	
	public void print(String s) {
		ta.append(s + "\n");
	}
	
	public static void main(String[] args) {
		new Client();
	}
}

So, wenn ich jetzt alles starte - keine Fehlermeldung kommt - und ich nun etwas ins Textfeld eingebe und senden drücke, passiert gar nichts. Wieso?
Interessant ist, dass wenn ich das, was als "Teil 1" kommentiert ist an "Platz 1" packe, also im Prinzip hinter das addClient, dann kommt nach dem senden eine NullPointerException in der Methode print. Also so, dass ta ein NullPointer ist. Wieso das denn?
Aber die print-Methode selbst funktioniert. Wenn ich in die print-Methode anstatt "ta.append(...)" nämlich "System.out.println(...)" hineinschreibe, wird es in der Konsole ausgegeben. Es muss also irgendetwas mit der JTextArea zu tun haben, aber ich verstehe nicht was. Vielleicht habe ich die Funktionsweise von RMI nicht richtig verstanden oder so.

Ich wäre für jede Hilfe/Erklärung wirklich dankbar!! :)
Falls etwas nicht verstanden wurde, weil ich es nicht vernünftig beschrieben habe, bitte einfach nachfragen!
 
Hey,

also ich würde mal behaupten, dass in deinem Listenerkonzept der Fehler liegt. Du registrierst zwar die Clients beim Listener aber der Server ruft die Methode dann bei sich auf und nicht beim Client bzw der Aufruf geht nicht übers Netz, da RMI keine bidirektionale Kommunikation unterstützt, außer du machst aus deinem Client auch einen Server.
Um ein wenig mit RMI rumzuspielen würde ich dir daher raten ein anderes Beispiel zu suchen oder wenns wirklich ein Chat sein muss dann doch auf Sockets zurückzugreifen.

ansonsten einfach mal folgendes einbauen:

Java:
@Override
    public void sendMessage(String s) throws RemoteException {
       System.out.println("Message received :"+s); // TEST AUSGABE
       for (IListener l : clients) {
            l.receiveMessage(s);
        }
    }

Dann siehste zumindest, ob die Kommuniktion funktioniert.

Gruß

Sebastian
 
Hmm...aber 1. habe ich doch den Listener dem Server übergeben (mit addClient(IListener l)) und 2. funktioniert ja die Kommunikation an sich. Ich habe ja mal in der print-Methode ein System.out.println(..) eingebaut anstatt das ta.append(..). Und die Ausgabe macht er. Das heißt, das Programm schafft es bis zur print-Methode aber komischerweise scheint die JTextArea ta nicht zu existieren oder für den Server ist eine andere als für den Client oder was weiß ich. Auf jeden Fall zeigt er es bei ta.append(..) nicht an.
 
Das ist so nicht korrekt. Wenn du in der print-Methode im Client ein System.out einbaust wird das zwar vom Server aufgerufen aber wenn du genau hinschaust, wird das auf der Konsole vom Server ausgegeben und nicht beim Client. Demzufolge kommt bei deinem Client nie ein Wert an, wo wir wieder bei meinem ersten Post wären.

Gruß Sebastian
 
Ok, gut, danke für die Erklärung.
Aber trotzdem muss es doch gehen, auf Referenzen in der Client-Klasse zuzugreifen (so wie die JTextArea). Das ist ja jetzt keine Eigenart des Chats, sondern das braucht man doch in jedem Programm.
Wenn man zum Beispiel ein Spiel programmieren will, dass 2 Spieler über Netzwerk spielen können. Dann wird doch der Server den aktuellen Spielzustand speichern und die Clients auf dem laufenden halten und die Clients ändern lokal, anhand der Informationen vom Server, die Spielumgebung.
Als Beispiel: Mensch ärger dich nicht. Ein Spieler macht einen Zug, setzt einen Spielstein auf Feld x. Dann muss der Spieler das doch dem Server melden und der Server kümmert sich darum, dass alle Spieler erfahren: dieser Spielstein ist jetzt auf Feld x. Und jetzt verändert sich doch auf dem Computer jedes einzelnen Spielers lokal die GUI.
Verstehst du was ich meine, was ich also nicht verstehe?
 
Also bassemoluff ;) bei RMI ruft der Client eine Methode im Server auf und bekommt ein Ergebnis geliefert und nicht andersherum - gleiches Prinzipp wie bei HTTP.
Du übergibst zwar die Referenz aber der Server läuft ja in einer anderen VM also kann das gar nicht funktionieren, was du vorhast.

So wie kommt nun das Objekt zum Server?

Sobald der Client eine Methode im Server aufruft und du ein Objekt mitgibts, wird das Objekt beispielsweise Serialisiert und übers Netz zum Server gejagt, welcher den Datenstrom entgegennimmt und eines Neues Objekt daraus baut (equals), und auf diesem Objekt dann arbeit. (Völlig transparent und wird durch RMI abstrahiert).Somit haben wir zwei idententische Objekte (equals) - einmal im Client und einmal im Server. Dies ist nämlich auch der Grund warum die Ausgabe in deinem Fall im Server stattfindet.

Was du aber willst ist eine Bidirektionale Verbindung und das ist eben mit RMI nicht möglich und deshalb programmiert auch kein Mensch ein Spiel, Chat o.Ä. mit RMI. Für diesen Fall brauchst du entweder Sockets oder du musst im Client ein Polling-Mechanismus einbauen, der den Server zyklisch nach Änderungen anfragt (Unschön).

hier findest du ein total simples Beispiel eines Chats mit Sockets:

http://peheko.php0h.com/cliserv/wn7/udp.htm

Cheers

Sebastian
 
Zuletzt bearbeitet:
Hey, ich bin total dankbar für diese geduldigen Erklärungen. :)
Irgendwie muss mans ja mal lernen und herausfinden.

Ok, gut, aber über Sockets kann man in Java doch keine Objekte übergeben oder?
Aber das würde hier zu weit führen, wenn du noch ansatzweise erklären würdest, wie man ein Spiel dann mit Sockets programmiert! :D

Du hast mir sehr geholfen oder bzw. mich gut aufgeklärt ;-)
Herzlichen Dank! :)
 
Zurück