# RMI hat langsame verbindung



## Tokka (29. Nov 2005)

Hallo zusammen!

Ich musste als Semesteraufgabe BlackJack Client/Server fähig programmieren (Eclipse mit Java 1.5).
Für die Kommunikation habe ich mich für RMI entschieden. 

An meinem Server können sich bis zu 7 Clients anmelden. Das funktioniert auch alles wunder bar.
Aber es dauert doch immer recht lange, bis ein Client seinen "Zug" ausführen darf. 
Ich habe das gefühl, das beim wechsel der Clients die verbindung abreist und immer wieder neu aufgebaut
werden muss. 

Habt ihr da eine Idee wie ich dieses geschwindigkeits problem lösen kann??


Gruß
Tokka


----------



## Guest (29. Nov 2005)

Hallo, ich habe auch ein ähnliches Problem. 
Ich habe zwei Clients und einen Server programiert. Die einzige Aufgabe der Server ist die von einem Client empfangenen Nachrichten über den anderen weiterzuleiten. Es dauert aber manchmal bis zu 5 Sekunden, bis ein Client eine Nachricht empfingt. Das ist meine erste Erfahrungen mit RMI, deshal weiß ich nicht genau ob das normal ist. Ist die Kommunikation über RMI so langsam ? Würde die Kommunikation etwas schneller geschehen, wenn man einen http server benutzt?


----------



## Guest (29. Nov 2005)

Wie werden die Clients verwaltet?
Passiert es immer, oder erst wenn z.B. ein Client beendet wurde?


----------



## Guest (29. Nov 2005)

Versucht auch folgendes (Angaben in Millisekunden)

-Dsun.rmi.transport.connectionTimeout=60000

auf beiden Seiten, also client- und serverseitig beim Start


----------



## Guest (29. Nov 2005)

Hallo,



			
				Anonymous hat gesagt.:
			
		

> Wie werden die Clients verwaltet?
> Passiert es immer, oder erst wenn z.B. ein Client beendet wurde?



ich bin mir nicht sicher, ob du mich fragst oder Tokka. Wenn die Frage an mich war, hier ist meine Antwort.
Die  clients benutzen eine einfache Callback-Methode. Sie registrieren sich erst beim Server. Der server hat nur 2 Referenzen zu diesen Remote-Objects.  Nach der Registrierung senden die Clients periodisch Strings an Server und der Server leitet diese String weiter an den anderen Client.



			
				Anonymous hat gesagt.:
			
		

> Versucht auch folgendes (Angaben in Millisekunden)
> 
> -Dsun.rmi.transport.connectionTimeout=60000
> 
> auf beiden Seiten, also client- und serverseitig beim Start



das hat leider nichts geändert.


----------



## Guest (30. Nov 2005)

Hast du getestet, wo die meiste Zeit drauf geht?

Wenn du ein synchrones Callback hast, dann summiert sich die Zeit der Einzelaufrufe
auf dem Server. Wurde ein Client beendet (Server hat Referenz auf einen nicht mehr
existierenden Client), dauert es noch länger, bis serverseitig ein SocketTimeout kommt.


----------



## Guest (30. Nov 2005)

Der Server erzeugt einen Thread um den String weiter zu leiten. Die Kommunikation ist eher asynchron.
Ich denke, dass die Verzögerung wegen der Kontextwechsel der Threads entstehen könnte.  
Aber auf jeden Fall 3 bis 4 Sekunden sind viel zu viel für 4 Threads, denke ich.


----------



## Guest (30. Nov 2005)

Anonymous hat gesagt.:
			
		

> Der Server erzeugt einen Thread um den String weiter zu leiten. Die Kommunikation ist eher asynchron.


Jeder Client in einem Thread, nehme ich an. Das "eher asynchron" verwirrt etwas. 


			
				Anonymous hat gesagt.:
			
		

> Ich denke, dass die Verzögerung wegen der Kontextwechsel der Threads entstehen könnte.
> Aber auf jeden Fall 3 bis 4 Sekunden sind viel zu viel für 4 Threads, denke ich.


Was auch immer es ist, an den Threads wird es vermutlich nicht liegen, wenn sie unabhängig 
voneinander sind.
Versuche den Engpass mit einem Profiler zu lokalisieren. Man kann keine Aussagen über die
Performance einer Anwendung treffen, wenn man keine Messdaten hat. Aus der Ferne, ohne 
den Code zu sehen, schon gar nicht.


----------



## Bleiglanz (30. Nov 2005)

zeig mal Code, die 4 Sekunden müssen woanders herkommen

hast du sowas wie eine Remote Methode der Form

sendMessage(String s, OtherClient other) 
// Nachricht vom Client an den Server 
//soll an other weitergeleitet werden

und in der serverseitigen implementierung schlägt der Server nach, wer other ist und ruft dessen Callbackmethode mit dem String auf?

die Clients müssen natürlich selbst "serverähnlich" d.h. multithreaded funktionieren, d.h. auf Ereignisse vom Server geeignet reagieren....


----------



## Tokka (30. Nov 2005)

Hier mal mein Code:

Interface vom Server:

```
public interface ServerInterface extends java.rmi.Remote {
    public void connect(ClientInterface client) throws java.rmi.RemoteException;;
    public void disconnect(ClientInterface client) throws java.rmi.RemoteException;
    public Card getCard(ClientInterface client) throws java.rmi.RemoteException;
    public void hold(ClientInterface client) throws java.rmi.RemoteException;
}
```

Server implementierung:


```
public class Server extends UnicastRemoteObject implements ServerInterface, Runnable {
	private DiscardTray	discardTray;
	private Gambler		dealer = new Gambler("Dealer");
	private volatile ClientInterface currentClient;

	private HashMap<ClientInterface, Gambler>localGambler = new HashMap<ClientInterface, Gambler>();

	private List<ClientInterface>	clientList= Collections	.synchronizedList(new ArrayList<ClientInterface>());

	protected Server() throws RemoteException {
		super();
	}

	public void startServer() {
		int port = 4711;

		if (System.getSecurityManager() == null)
                {
			System.setProperty("java.security.policy", "rmi.policy");
			System.setSecurityManager(new RMISecurityManager());
		}

		try{
			LocateRegistry.createRegistry(port);
			Naming.rebind("rmi://localhost:4711/Server", this);
			System.out.println("Server gestartet");

			new Thread(this).start();

		}catch (RemoteException e){
			e.printStackTrace();

		}catch (MalformedURLException e){
			e.printStackTrace();
		}
	}

	
	public void connect(ClientInterface client) throws RemoteException{
		this.clientList.add(client);
		this.localGambler.put(client, new Gambler(client.getName()));
		System.out.println("Client wird hinzugefügt");
	}

	public void disconnect(ClientInterface client) throws RemoteException{
		if (this.currentClient != null  && this.currentClient.equals(client))
		{
			this.currentClient = null;
		}
		this.clientList.remove(client);
		this.localGambler.remove(client);
		System.out.println("Client wird entfernt");
	}

	public Card getCard(ClientInterface client) throws RemoteException
	{
		if (!client.equals(this.currentClient))
		{
			throw new RemoteException("Spieler nicht am zug");
		}
		Gambler gambler = this.localGambler.get(client);
		Card card = this.discardTray.getCard();
		gambler.getHand().addCard(card);

		if (gambler.getHand().hasBusted())
		{
			gambler.setActive(false);
		}

		this.currentClient = null;
		return card;
	}

	public void hold(ClientInterface client) throws RemoteException
	{
		if (!client.equals(this.currentClient))
		{
			throw new RemoteException("Spieler nicht am zug");
		}
		Gambler gambler = this.localGambler.get(client);
		gambler.setActive(false);
		this.currentClient = null;
	}
}
```


Client interface

```
public interface ClientInterface extends Remote
{
	public String getName() throws RemoteException;
	public void onNewGame(Card dealerCard) throws RemoteException;
	public void onTurn() throws RemoteException;
	public void onDealerCard(Card dealerCard) throws RemoteException;
	public void onEndGame(String msg, Resources.GameState gs) throws RemoteException;
}
```


Client Implementierung

```
public class Client extends UnicastRemoteObject implements ClientInterface {

	protected Client(String name) throws RemoteException
	{
		super();
		this.gambler = new Gambler(name);
		// TODO Auto-generated constructor stub
	}

	Gambler gambler;
	Gambler dealer = new Gambler("dealer");
	ServerInterface server;
	
	public static void main(String args[])
	{
		Client client;
		try
		{
			int playerId = (int) Math.round(Math.random() * 10000);
			client = new Client("" + playerId);
			client.run();
		}
		catch (RemoteException e)
		{
			e.printStackTrace();
		}
		
	}
	
	public void run()
	{
	try
		{
			this.server = (ServerInterface) Naming.lookup("rmi://localhost:4711/Server");
		}
		catch (Exception e)
		{
			e.printStackTrace();
			return;
		}
		
		try
		{
			this.server.connect(this);
			System.out.println("Spielername: " + this.gambler.getName());
		}
		catch (RemoteException e)
		{
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}

	public String getName() throws RemoteException
	{
		return this.gambler.getName();
	}

	public void onNewGame(Card dealerCard) throws RemoteException
	{
		this.gambler.dropCards();
		this.dealer.dropCards();
		this.dealer.getHand().addCard(dealerCard);
	
		System.out.println("Neues Spiel, Dealer hat: " + dealerCard);
	}

	public void onTurn() throws RemoteException
	{
		System.out.println("1 = Karte Ziehen");
		System.out.println("2 = Karten halten");
		
		int zahl = Integer.parseInt(getInput());
		
		switch (zahl)
		{
			case 1:
				Card card = this.server.getCard(this);
				this.gambler.getHand().addCard(card);	
				System.out.println(card);
				System.out.println("Spieler zieht " + this.gambler.getHand().getSumOfHand());
				break;

			case 2:
				this.server.hold(this);
				System.out.println("Spieler hat " + this.gambler.getHand().getSumOfHand()+ " Punkte");
			default:
				break;
		}
	}

	public void onDealerCard(Card dealerCard) throws RemoteException
	{
		this.dealer.getHand().addCard(dealerCard);
		System.out.println("Dealer hat: " + dealerCard);
		
	}

	public void onEndGame(String msg, GameState gs) throws RemoteException
	{
		System.out.println(msg + gs);
		
	}
	
	private String getInput()
	{
		BufferedReader din = new BufferedReader( new InputStreamReader(System.in));
		try
		{
			return din.readLine();
		}
		catch (IOException e)
		{
			return "";
		}
	}


}
```


----------



## Guest (30. Nov 2005)

Hallo,

sorry, es war mein Fehler. Ich hätte schon von anfang an meinen code posten sollen.



			
				Bleiglanz hat gesagt.:
			
		

> hast du sowas wie eine Remote Methode der Form
> 
> sendMessage(String s, OtherClient other)
> // Nachricht vom Client an den Server
> //soll an other weitergeleitet werden



da es mehrere Clients geben könnte, habe ich als argument eigene Referenz übergeben:

```
sendMessage(String str, ClientAdapter client)
```

Ich habe versucht den code soweit wie möglich vereinachen. Hier ist mein Code:

Interface vom Client:

```
public interface ClientAdapter extends Remote {
	public void processMessage(String str) throws RemoteException;
}
```

Client Implementierung:

```
public class ClientAdapterImpl 
				extends UnicastRemoteObject 
				implements ClientAdapter {
	
	private static final long serialVersionUID = -566357368009904972L;
	private int counter = 1;

	public ClientAdapterImpl() throws RemoteException {
		super();
	}

	public void processMessage(String str) throws RemoteException {
		System.out.println(counter + ". Message: " + str);
	}
}
```

Eine Thread-Klasse, die die Nachrichten sendet:

```
public class SenderThread extends Thread {
	private String str = null;
	private ClientAdapter client = null;
	public SenderThread(ClientAdapter client, String str){
		this.client = client;
		this.str = str;
	}
	
	public void run(){
		try {
			client.processMessage(str);
		} catch (RemoteException re) {
			re.printStackTrace();
		}
	}
}
```

Interface vom Server:

```
public interface ServerAdapter extends Remote {
	public void registerClient(ClientAdapter client) throws RemoteException;
	public void removeClient(ClientAdapter client) throws RemoteException;
	public void sendMessage(String str, ClientAdapter client) throws RemoteException;
}
```

Implementierung vom Server:

```
public class ServerAdapterImpl 
				extends UnicastRemoteObject
				implements ServerAdapter {
	private static final long serialVersionUID = 2840727540913742816L;
	private List<ClientAdapter> clientList = Collections.synchronizedList(new ArrayList<ClientAdapter>());

	public ServerAdapterImpl() throws RemoteException {
		super();
		
	}

	public void registerClient(ClientAdapter client) throws RemoteException {
		clientList.add(client);
	}

	public void removeClient(ClientAdapter client) throws RemoteException {
		clientList.remove(client);
	}

	public void sendMessage(String str, ClientAdapter client) throws RemoteException {
		 for(Iterator<ClientAdapter> iterator = clientList.iterator(); iterator.hasNext(); ) {
			 ClientAdapter destination = iterator.next();
			 if(destination != client)
				 new SenderThread(destination, str).start();
		      
		 } 
	}
}
```


Server Klasse, die die main methode enthält:

```
public class Server {
	public static void main(String[] args) {
		String host = "rmi://localhost";
		int port = 1099;
		if(args.length > 0)
			host = args[0];
		if(args.length > 1)
			port = Integer.parseInt(args[1]);
		
		String addr = host + ":" + Integer.toString(port) + "/MessageServer";

		if ( System.getSecurityManager() == null ) {
            System.setSecurityManager(new RMISecurityManager());
        }				   
        try {
        	LocateRegistry.createRegistry(port);
        } catch (RemoteException re) {
        }
        
        ServerAdapterImpl messageServer;
		try {
			messageServer = new ServerAdapterImpl();
			Naming.rebind(addr, (ServerAdapter) messageServer);
			
		} catch (RemoteException re) {
			re.printStackTrace();
		} catch (MalformedURLException murle) {
			murle.printStackTrace();
		}
	}
}
```

und die Client-main klasse, führe diesen code mehrmal um die Clients zu simulieren.

```
public class Client {
	public static void main(String[] args) {
		String host = "rmi://localhost";
		ClientAdapterImpl client = null;
		ServerAdapter server = null;
		int port = 1099;
		if(args.length > 0)
			host = args[0];
		if(args.length > 1)
			port = Integer.parseInt(args[1]);
		
		String addr = host + ":" + Integer.toString(port) + "/MessageServer";
		
		if ( System.getSecurityManager() == null ) {
            System.setSecurityManager(new RMISecurityManager());
        }				   
        try {
        	LocateRegistry.createRegistry(port);
        } catch (RemoteException re) {
        }
		
        try {
			client = new ClientAdapterImpl();
			server = (ServerAdapter) Naming.lookup(addr);
			server.registerClient(client);
		} catch (MalformedURLException murle) {
			murle.printStackTrace();
		} catch (RemoteException re) {
			re.printStackTrace();
		} catch (NotBoundException nbe) {
			nbe.printStackTrace();
		}
        
		BufferedReader in = new BufferedReader( new InputStreamReader(System.in));
        String str= "";
        int option = 0;
        try {
			while(true){
				System.out.println("0   Exit");
				System.out.println("1   Send Message");
				System.out.println("option?:");
				str = in.readLine();
				option = Integer.parseInt(str);
				switch(option) {
					case 0: 
						server.removeClient(client);
						return;
					case 1:
						System.out.println("Enter the message to be sent: ");
						str = in.readLine();
						server.sendMessage(str, client);
						break;
					default:
						System.out.println("try again! ");
				}
			}
        }catch(Exception e){
			e.printStackTrace();
        }
}
}
```


----------



## Guest (1. Dez 2005)

@Gast
Dein Code schaut, bis auf paar Kleinigkeiten, OK aus. Ich kann keinen Grund für eine langsame Verbindung 
erkennen.

Mach vielleicht den "counter" in ClientAdapterImpl und "clientList" in ServerAdapterImpl *transient*
und ändere den SenderThread so, dass nicht mehr existierende Clients aus der Liste der registierten Clients
entfernt werden.


```
public class SenderThread extends Thread {
   private String str = null;
   private ClientAdapter client = null;
   private ServerAdapterImpl server = null;
   
  public SenderThread(ServerAdapterImpl server, ClientAdapter client, String str){
      this.server = server;
      this.client = client;
      this.str = str;
   }

   public void run(){
      try {
        client.processMessage(str);
      }
      catch (ConnectException ce) {
        try {
          server.removeClient(client);  // Client entfernen, wenn nicht mehr erreichbar
        }
        catch (Exception e) {
          e.printStackTrace();
        }
      }
      catch (RemoteException re) {
        re.printStackTrace();
      }
   }
}
```
und in ServerAdapterImpl

```
private transient ArrayList<SenderThread> threadList = new ArrayList<SenderThread>(clientList.size());
...
   public void sendMessage(String str, ClientAdapter client) throws RemoteException {
      synchronized(threadList) {
         threadList.ensureCapacity(clientList.size());
         for(ClientAdapter adapter : clientList)
           threadList.add(new SenderThread(this, adapter, str));

         for(SenderThread thread : threadList)
           thread.start();
         threadList.clear();
      }
   }
```
Aufgeteilt in zwei Schleifen, damit die SenderThreads den Iterator nicht aus dem Schritt bringen,
wenn ein Client nicht mehr erreichbar ist (also wegen server.removeClient(...) bzw. clientList.remove(...)).

@Tokka
Bei dir ist auch das meiste nicht transient, obwohl es sein sollte. Dadurch geht immer deine halbe Anwendung 
durch die Leitung. Du verwendest vorwiegend synchrone Kommunikation, so dass evtl. nicht mehr existierende 
Clients das ganze ausbremsen.


----------



## Guest (1. Dez 2005)

:shock: ServerAdapter statt ServerAdapterImpl
	
	
	
	





```
public class SenderThread extends Thread {
   private String str = null;
   private ClientAdapter client = null;
   private ServerAdapter server = null;
   public SenderThread(ServerAdapter server, ClientAdapter client, String str){
...
```


----------



## Tokka (1. Dez 2005)

Habe das mal mit dem Transistent probiert, trotzdem bleibt mein geschwindigkeits problem!

Es scheint so, als wenn Client 2 die Verbindung von Client 1 löscht und seine eigene aufbaut. Es müssen sich also die Clients scheinbar immer neu connecten


----------



## Guest (1. Dez 2005)

Eine letzte Frage:
kann die Übertragung schneller geschehen, wenn ich die Messages statt in String als char-Array übertrage?


----------



## Tokka (1. Dez 2005)

es wird nichtmal schneller wenn ich nur ein Integer übergebe!

Ich habe das Gefühl das die Clients sich gegenseitig kicken


----------



## Guest (1. Dez 2005)

Ein Integer ist auch ein ganzes Objekt die serialisiert werden muss. Ich meint, ob sich irgendetwas ändern könnte wenn man als Argument nur primitiven typen  benutzt


----------



## Tokka (1. Dez 2005)

ich denke das sich nix ändern wird.

Darüber hinaus sind die Interfaces so festgelegt worden 

muss owhl mit der mangelnden Geschwindigkeit leben


----------

