# Client Server Problem



## Angel4585 (29. Sep 2007)

Hallo,

ich wusste jetz nich ob das in die Performanceecke soll, aber ich dachte es gehört eher hierher.

Ich programmiere gerade an einer Client-Server Software, die einen Chat, Buddyliste IM usw enthalten soll.

Jetzt habe ich folgendes kleines Problem:

Ich habe einen Server der einen Acceptor(nimmt eingehende Verbindungen an) und mehrere Dispatcher(regelt den Nachrichtenverkehr sämtlicher Dienste zwischen Client und Server)

Ich habe mir eine Testanwendung geschrieben die mir 1000 Clientverbindungen simuliert.
Die Testanwendung, bzw jeder Client davon schickt jetzt alle 500ms einen String an den Server, der diesen dann einfach ausgibt, also nichtmal groß verarbeitet oder so...
Das Problem: manchmal schafft es der Server nicht rechtzeitig einen String von nem Client "abzuarbeiten" und es wird direkt der nächste angefügt und dann auch ausgegeben.

Wenn ich jetzt am Server pro Dispatcher weniger Clients zuordne und dafür mehr Dispatcher mache wird das noch schlimmer, da sinds dann teilweise 3 Messages die der Server auf einmal ausgibt.

Die Fragen:

Gibt es für solch eine Anwendung eine Regel? 
Mit wieviel sollte der Server denn klarkommen?
Gibt es eine bessere Lösung für diese Art von Anwendung?


----------



## jPat (1. Okt 2007)

Meiner Meinung nach brauchst du für jeden Client einen ServerThread. Dann sollte das problem behoben sein.

Beispiel:
eine Klasse zum annehmen der Verbindung.
Diese Klasse erzeugt bei jeder Verbindung einen Thread, der mit dem Client verbunden ist über den Socket. 
Hier wartest du auf nachrichten von den Clients. Dann wird eine Senderklasse Implementiert, die eine Methode "sendeAnClienten" bereitstellt. Diese Methode kann von jedem Thread dann aufgerufen werden. Sie sollte static und synchronized sein. In der SenderKlasse kannst du zb. Alle Sockets in einem Vector speichern, um an alle beteiligten die Nachricht zu senden.


----------



## tuxedo (1. Okt 2007)

@Angel

Du nutzt doch Java NIO... Und in dem Artikel den ich dir gezeigt hatte war doch die Rede davon, dass im Server ein Eventhandling betrieben wird, das auf eingehenden Daten basiert. 

So wie ich das jetzt verstanden habe gehen keine eingehenden Strings verloren, nur werden sie nicht einzeln ausgegeben, sondern eher "gebündelt" x-Stück auf einmal.

Naja, das ist doch vollkommen "normal" bei dieser Architektur:

* Hast du mehr Dispatcher (annahme: tendiert gegen unendlich), hast du wohl auch mehr Threads. Hast du mehr Threads läuft der Server schneller an sein Limit. Da die Dispatcher-Threads erstmal alle gleichberechtigt sind, haben wohl auch _alle_ das Problem dass sie nicht mehr bedient werden. Und das ist seeehr schlecht...

* Hast du weniger Threads (annahme: tendiert gegen 1) können noch alle Clients bedient werden, jedoch nicht mehr "sofort": Die eingehenden Daten stauen sich eben solange bis der Server dazu komm sich drum zu kümmern. Deshalb auch die Bündelung der Ausgaben. Das hat im vergleich zu vorigem Beispiel den Vorteil, dass es immer Clients gibt die sofort bedient werden (die am Anfang der Schlange). 

Du musst jetzt nur noch das Feintuning machen. Was für eine Maschine nutzt du denn als Server? Netzwerkanbindung? CPU? RAM? Java-Version?

@jPat: Wenn sie pro Client einen festen Thread nimmt, dann kann sie auch gleich wieder zum alten Java IO wechseln. 

gruß
Alex


----------



## Angel4585 (2. Okt 2007)

Ich bin übrigens ein ER 

Ich arbeite grad auf meinem Notebook, 

Inte Core2 Duo T5500
1024MB RAM
Das ganze läuft lokal, aber ich gehe natürlich über die Sockets usw raus
JDK 1.6

Nun hab ich aber wowieso ein anderes Problem:

Wenn ich socketchannel.write ausführe dann kommen die Daten mehrfach am Server an, obwohl ich ja nur einmal sende..


----------



## Angel4585 (3. Okt 2007)

ok das write mit den SocketChannels hat sich erledigt.
Ich hatte beim Server vergessen den ByteBuffer zu clearen, dewswegen hats mir ettliche Male den gleichen String angezeigt..


----------



## tuxedo (3. Okt 2007)

Sorry wegen der "Verwechslung"... Aber es ist nunmal nicht unbedingt "gewöhnlich" dass sich ein "er"  Angel nennt ... ;-)

Aber ich werd's mir jetzt merken ...


----------



## Angel4585 (3. Okt 2007)

naja in der Bibel hab ich bisher aber auch keinen weiblichen Engel gefunden


----------



## tuxedo (4. Okt 2007)

[yoda]Recht du hast[/yoda] verwirrend war's trotzdem.

Wie hast du die Sache mit den Threads jetzt gelöst?

- Alex


----------



## Angel4585 (4. Okt 2007)

garnicht, ich lass es auf mich zukommen.
Das waren ja 800 Clients die jeder für sich im 500 ms Takt ne Nachricht schicken. Ist das ein realer Wert? Kann ich mir eigentlich nicht vorstellen.


----------



## tuxedo (5. Okt 2007)

Naja, kommt drauf an welche Zielanwendergruppe das Ding benutten soll. Verglichen mit einem MMORPG-Server wie Lineage2 (L2J) auf dem mal bis zu 2500 User rumspringen ist das vielleicht noch ein geringer Wert. Aber auf der anderen Seite verwenden die XEAN-CPU's mit einigen GB Ram ...

Ob das ein reeller Wert ist musst also du wissen .. 

- Alex


----------



## Angel4585 (5. Okt 2007)

Ich bin Azubi im zweiten Lehrjahr, da mangelt es mir noch an Erfahrung würd ich sagen.

Also das ganze soll eine Anwendung für Firmen werden die mit einer SQL-Datenbank am Server ihre Mitarbeiter verwalten wollen, übers Netzwerk chatten können sollen(was oft sehr nützlich ist) und noch vieles mehr verwalten sollen.

Also kein Netzwerkspiel wo ständig irgendwelche Koordinaten verschickt werden müssen.


----------



## tuxedo (5. Okt 2007)

Naja, also eher was für den "Mittelstand". Dann sollte es, so denke ich, keine Probleme geben.

Aber wäre RMI da nicht einfacher gewesen?

- Alex


----------



## Angel4585 (5. Okt 2007)

was ist RMI? (Ich mach erst seit ein paar Monaten was mit Java und habe mich erst an Sockets angenähert und dann die ocketChannels entdeckt.. kommt jetzt RMI?)


----------



## tuxedo (5. Okt 2007)

RMI = Remote Method Invocation
siehe auch: http://de.wikipedia.org/wiki/Remote_Method_Invocation

Da sparst du dir das lästige Transformieren der Daten vor dem senden und das auswerten nach dem Empfangen.

Mit RMI bekommt der Client ein Server-Objekt. Mit diesem Obnjekt kann der Client direkt Methoden auf dem Server aufrufen etc. Ergo: Keine Quälerei mit Sockets und einem selbst ausgedachten Protokoll.

Ein schickes und einfaches Tutorial findest du hier:
http://www.mm.informatik.tu-darmstadt.de/courses/helpdesk/rmi_tutorial.html

- Alex


----------



## Angel4585 (5. Okt 2007)

Das klingt ja schonmal endsgeil 
Werd ich mir anschauen, Danke Alex!


----------



## tuxedo (5. Okt 2007)

RMI ist nicht due einzige und alleinige ultimative Lösung. RMI ist etwas langsamer als normale Socketverbindungen bzw. auch als NIO. Aber für die Datenübertragung war's ja auch nie gedacht. Für deine Zwecke wäre RMI vermutlich schon das non-plus-ultra weil es einfach zu bewerkstelligen und wenig fehleranfällig ist.

Für Chat's etc. ist es ebenfalls prima. 

Aber solltest du irgendwann auf die Idee kommen, große Files von A nach B transportieren zu wollen, würd ich das mit IO oder NIO machen. Zur steuerung und initiierung des Transports kann man jedoch weiter prima RMI nutzen.

Im Studium hab ich RMI gehasst wie die pest. Anfangs war es noch recht umständlich und aufwendig, und/oder die Profs habens   "falsch" vermittelt.

Aber seit kurzem nutze ich es mit Begeisterung für diverse Projekte. 

- Alex


----------



## Angel4585 (5. Okt 2007)

hast du mir mal ein kleines funktionierendes beispiel?
Ich bekomm das in dem Tutorial da nicht hin..


----------



## tuxedo (5. Okt 2007)

Hast Glück dass ich mir Anfangs selbst ein einfaches Gerüst zusammengeschustert hab:

Die Hauptklasse für den Client

```
package client;

import java.rmi.Naming;
import java.rmi.RemoteException;

import server.ServerRmi;

public class Client {
	
	private ServerRmi server = null; 
	
	public Client() throws RemoteException {
		// Erstelle neues Objekt mit dem uns der Server antworten kann
		ClientCallbackImpl c = new ClientCallbackImpl();
		
		// hole das Serverobjekt
		server=getServer();
		
		// rufe eine Methode "login" am Server auf und 
		// übergebe die Callback-Klasse dem Server für die 
		// Rückantwort
		System.out.println(server.login(c));
	}
	
	/**
	 * Holt den Server von der Registry
	 * @return
	 */
	private ServerRmi getServer(){
		try {
			// Die Registry ist via 127.0.0.1 erreichbar, und das dort
			// verzeichnete Serverobjekt hat die Bezeichnung "Server"
			// gecastet wird gegen das Interface des Servers, welches 
			// von "Remote" erbt 
			server = (ServerRmi) Naming.lookup("//127.0.0.1/Server");
		} catch (Exception ex) {
			ex.printStackTrace();
		}
		return server;		
	}
	
	// Starte den Client ...
	public static void main(String[] args) throws RemoteException {
		Client c = new Client();
	}


}
```

Das Callbackinterface

```
package client;

import java.rmi.Remote;
import java.rmi.RemoteException;

/**
 * Dem Server wurde über die Servermethode "login()" eine Referenz einer Klasse, die
 * dieses Interface implementiert mitgegeben. Da hier wieder das "Remote"-Interface
 * mit im Spiel ist, kann der Server über die Objektreferenz die er erhalten hat quasi antworten.
 * @author ACHR
 *
 */
public interface ClientCallback extends Remote {

	public void messageFromServer(String msg) throws RemoteException;
	
}
```

Die Implementierungsklasse des Callback-Interfaces

```
package client;

import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;

/**
 * Das Remote-Objekt des Clients, welches dem Server für Antworten an den Client
 * in der ServerRmi#login() Methode übergeben wird.
 * Der Server hat nur Zugriff auf Methoden im Interface "ClientCallback"
 * @author ACHR
 *
 */
public class ClientCallbackImpl extends UnicastRemoteObject implements ClientCallback {

	/**
	 * Ist nötig für's erben von UnicastRemoteObject
	 * @throws RemoteException
	 */
	protected ClientCallbackImpl() throws RemoteException {
		super();
	}

	/**
	 * 
	 */
	private static final long serialVersionUID = -7516228656348476224L;

	public void messageFromServer(String msg) throws RemoteException {
		System.out.println("Server says: "+msg);
	}

}
```

Die Serverhauptklasse

```
package server;

import java.net.MalformedURLException;
import java.rmi.Naming;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

/**
 * Seit Java 6 braucht man kein RMI-Plugin mehr
 * Java kümmert sich selbst um die Stubs und Proxies.
 * 
 *  Auch muss man die Registry nicht extern starten (siehe unten).
 * @author ACHR
 *
 */
public class Server {

	public static void main(String[] args) {
		
		try {
			
			// Startet die RMI-Registry!
			LocateRegistry.createRegistry(Registry.REGISTRY_PORT);
			
		} catch (RemoteException ex) {
			System.out.println(ex.getMessage());
		}
		
		try {
			
			// Meldet ein neues ServerRmiImpl Objekt 
			// unter dem Namen "Server" bei der Registry an
			Naming.rebind("Server", new ServerRmiImpl());
			
		} catch (MalformedURLException ex) {
			System.out.println(ex.getMessage());
		} catch (RemoteException ex) {
			System.out.println(ex.getMessage());
		}
	}

}
```

Das Server-Interface

```
package server;

import java.rmi.Remote;
import java.rmi.RemoteException;

import client.ClientCallback;

/**
 * Das hier ist das Interface über das am Client das Serverobjekt gecastet wird
 * Die Methoden die hier drin stehen, sind für den Client erreichbar. 
 * @author ACHR
 *
 */
public interface ServerRmi extends Remote {
	
	public String login(ClientCallback c) throws RemoteException;
	
}
```

Die Server-Interface Implementierungsklasse

```
package server;

import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;

import client.ClientCallback;

/**
 * Der Client benutzt via RMI diese Klasse. Dabei hat er nur Zugriff auf die 
 * Klassen im Interface "ServerRmi" (da dieses Interface das "Remote"-Interface erweitert)
 * @author ACHR
 *
 */
public class ServerRmiImpl extends UnicastRemoteObject implements ServerRmi {
	
	
	
	/**
	 * will Eclipse so haben
	 */
	private static final long serialVersionUID = -642163661869382287L;


	/**
	 * Ist notwendig für die vererbung von UnicastRemoteObject
	 * @throws RemoteException
	 */
	protected ServerRmiImpl() throws RemoteException {
		super();
	}

	
	/**
	 * Die ausformulierte Implementierung einer Methode die ein Client nutzen kann
	 */
	public String login(ClientCallback c) throws RemoteException {
		c.messageFromServer("huhu");
		return "eingeloggt";
	}

}
```

Siehe auch unbedingt folgenden Thread:

http://www.java-forum.org/de/viewtopic.php?t=56811

Gruß
Alex


----------



## Angel4585 (7. Okt 2007)

OK, das funzt perfekt, danke schonmal 

Aber eine Frage hab ich, da kommt immer ein Fehler:

Wenn ich jetzt bei der login Methode zB kein String zurückgeben möchte sondern eine Eigene Klasse "User" in welcher alle möglichen Userdaten drin sind, wie stelle ich das an?


----------



## Angel4585 (7. Okt 2007)

wenn ich die Methode login mit "User" aufrufe geht das genausowenig


----------



## HoaX (7. Okt 2007)

und wenn man rmi in ganz trivial haben will nimmt man cajo


----------



## Angel4585 (7. Okt 2007)

ah habs gefunden: die "User"-Klasse muss einfach "implements java.io.Serializable" beinhalten


----------



## tuxedo (7. Okt 2007)

@Hoax

Naja, auf den ersen Blick ist's mit Cajo nicht wirklich viel einfacher. 
Finde es ehrlich gesagt auch etwas verwirrender:



			
				http://de.wikipedia.org/wiki/Cajo hat gesagt.:
			
		

> Wie am Beispiel erkennbar ist, hat der Verzicht auf eine explizite Schnittstelle den Nachteil, dass der Code des Clients etwas schwerer lesbar ist. Statt wie bei Verwendung einer RMI-Schnittstelle zu schreiben „server.hallo("Wiki")“ wird geschrieben „Remote.invoke(server, "hallo", "Wiki")“.



Nen RMI Compiler braucht man seit Java 5 auch nicht mehr. Der "Vorteil" von Cajo gegenüber RMI ist mir nicht mehr ganz ersichtlich. Aber vor Java 5 war Cajo sicher ne feine Sache.

- Alex


----------



## HoaX (7. Okt 2007)

dann sollte man man die doku lesen, und nicht nur den wikipedia-müll. z.B. http://wiki.java.net/bin/view/Communications/TransparentProxy


----------



## tuxedo (7. Okt 2007)

Naja, auch das: Sooo viel einfacher find ich's nicht.

- Alex


----------



## Angel4585 (7. Okt 2007)

ok, die User sieht jetz so aus:

public class User implements Serializable{..

warum kann ich auf dem Server das ClientCallback ausführen und da eine Methode :

public void setUser(User aUser);

aufrufen, aber nicht vom Client die Methode:

public User getUser(String aUserName);

die aufm Server liegt?

bei der Zweiten bekomm ich ne Fehlermeldung, die erste geht wunderbar


----------



## tuxedo (8. Okt 2007)

Öhm, wie sieht denn die Serverklasse aus? Wie holst du beim Client das Serverobjekt? Gibts ne Fehlermeldung oder warum kannst du die Methode server#getUser() Methode beim Client ausführen?

- Alex


----------



## Angel4585 (8. Okt 2007)

Im Prinzip ist es noch genau das von dir da oben.

Nur anstatt der server#login() eben eine server#getUser()

Die Methopde setUser(User aUser) ist im ClientCallback und kann ohne Probleme aufgerufen werden.

die Methode getUser() ist im ServerRmi und verursacht einen Fehler.
Die Aufrufe sind relativ simpel:


```
public void getUser(ClientCallback c)throws RemoteException{
c.setUser(new User());
}

//bzw.. beim andern Versuch

public User getUser()throws RemoteException{
return new User();
}
```

Das erste geht, das Zweite geht nicht.


----------



## tuxedo (8. Okt 2007)

Interessant wäre noch der Stacktrace gewesen ...

Ich vermute aber mal deine User-Klasse hat kein "extends UnicastRemoteObject" drin, oder?

- Alex


----------



## Angel4585 (8. Okt 2007)

ne hat es nicht.. aber warum geht es dann einmal und das andere mal nicht? Wo genau ist der Unterschied?


----------



## tuxedo (8. Okt 2007)

Hmm, so wie ich das sehe ist im ersten Fall das User-Objekt nicht das Transport-Objekt. Über die Leitung geht nur ein "ClientCallback Objekt" ...

Im zweiten Fall *antwortet* der Server mit einem User-Objekt. D.h. der Server veschickt direkt ein User-Objekt an den Client. Und da das User-Objekt nicht für RMI-Transaktionen vorbereitet ist (extends UnicastRemoteObject), wird's fehl schlagen. Zeig doch endlich mal den Stacktrace ...

- Alex


----------



## Angel4585 (8. Okt 2007)

Bin grad auf Arbeit und hier hab ich diesen Code nicht, deswegen kann ich den StackTrace nicht posten 
Aber das klingt zumindest alles schonmal logisch was du da schreibst, werde ich heute Abend direkt ausprobieren.


----------



## tuxedo (8. Okt 2007)

Ach so... das alte "Code liegt daheim" Problem ;-)

Naja, bin gespannt was du dann heute Abend berichtest.

- Alex


----------



## Angel4585 (8. Okt 2007)

OK, ich hab in der Mittagspause gemerkt das ich den Code bzw den Stick wo der drauf ist doch dabei hab.. und hier gehts auf einmal.. :shock: 

wenn ich dieses UnicastRemoteObject rein mache kommt aber folgendes:


```
Exception occurred during event dispatching:
java.lang.ClassCastException: $Proxy2 cannot be cast to classes.User
        at $Proxy1.login(Unknown Source)
        at client.ClientDesk.doLogin(ClientDesk.java:78)
        at client.ClientLogin.btnLoginActionPerformed(ClientLogin.java:116)
        at client.ClientLogin.access$200(ClientLogin.java:16)
        at client.ClientLogin$3.actionPerformed(ClientLogin.java:68)
        at javax.swing.AbstractButton.fireActionPerformed(AbstractButton.java:1995)
        at javax.swing.AbstractButton$Handler.actionPerformed(AbstractButton.java:2318)
        at javax.swing.DefaultButtonModel.fireActionPerformed(DefaultButtonModel.java:387)
        at javax.swing.DefaultButtonModel.setPressed(DefaultButtonModel.java:242)
        at javax.swing.plaf.basic.BasicButtonListener.mouseReleased(BasicButtonListener.java:236)
        at java.awt.Component.processMouseEvent(Component.java:6038)
        at javax.swing.JComponent.processMouseEvent(JComponent.java:3265)
        at java.awt.Component.processEvent(Component.java:5803)
        at java.awt.Container.processEvent(Container.java:2058)
        at java.awt.Component.dispatchEventImpl(Component.java:4410)
        at java.awt.Container.dispatchEventImpl(Container.java:2116)
        at java.awt.Component.dispatchEvent(Component.java:4240)
        at java.awt.LightweightDispatcher.retargetMouseEvent(Container.java:4322)
        at java.awt.LightweightDispatcher.processMouseEvent(Container.java:3986)
        at java.awt.LightweightDispatcher.dispatchEvent(Container.java:3916)
        at java.awt.Container.dispatchEventImpl(Container.java:2102)
        at java.awt.Window.dispatchEventImpl(Window.java:2429)
        at java.awt.Component.dispatchEvent(Component.java:4240)
        at java.awt.EventQueue.dispatchEvent(EventQueue.java:599)
        at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:273)
        at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:183)
        at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:177)
        at java.awt.Dialog$1.run(Dialog.java:1039)
        at java.awt.Dialog$3.run(Dialog.java:1091)
        at java.security.AccessController.doPrivileged(Native Method)
        at java.awt.Dialog.show(Dialog.java:1089)
        at java.awt.Component.show(Component.java:1419)
        at java.awt.Component.setVisible(Component.java:1372)
        at java.awt.Window.setVisible(Window.java:801)
        at java.awt.Dialog.setVisible(Dialog.java:979)
        at client.ClientDesk.formComponentShown(ClientDesk.java:169)
        at client.ClientDesk.access$000(ClientDesk.java:22)
        at client.ClientDesk$1.componentShown(ClientDesk.java:106)
        at java.awt.AWTEventMulticaster.componentShown(AWTEventMulticaster.java:162)
        at java.awt.Component.processComponentEvent(Component.java:5870)
        at java.awt.Component.processEvent(Component.java:5818)
        at java.awt.Container.processEvent(Container.java:2058)
        at java.awt.Window.processEvent(Window.java:1787)
        at java.awt.Component.dispatchEventImpl(Component.java:4410)
        at java.awt.Container.dispatchEventImpl(Container.java:2116)
        at java.awt.Window.dispatchEventImpl(Window.java:2429)
        at java.awt.Component.dispatchEvent(Component.java:4240)
        at java.awt.EventQueue.dispatchEvent(EventQueue.java:599)
        at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:273)
        at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:183)
        at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:173)
        at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:168)
        at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:160)
        at java.awt.EventDispatchThread.run(EventDispatchThread.java:121)
```

wobei ich dabei den User direkt von der login zurückgeben lasse


----------



## tuxedo (8. Okt 2007)

und wie sah der fehler ohne "extends unicastremoteobject" aus?

- Alex


----------



## Angel4585 (8. Okt 2007)

ja das iss ja der schmu.. ohne Unicastremoteobject kommt hier auf der Arbeit garkein Fehler.. ich schau nochmal heut Abend ob der daheim wieder auftaucht oder ob sich der in Luft aufgelöst hat


----------



## tuxedo (8. Okt 2007)

hmm, weil wenn ich mir's genau überlege, müsste im zweiten Fall User das UnicastRemoteObject gar nicht erweitern.. weiß nicht wie ich da drauf gekommen bin. hmm...

- Alex


----------



## Angel4585 (9. Okt 2007)

also zuhause gehts auch plötzlich.. kA warum vorher nich ging :bahnhof:


----------

