# Erhalten und Senden von Nachrichten anhand des Indexes des Clients



## Fenixx (4. Nov 2009)

Hi zusammen,

ich habe die Aufgabe einen Server und einen Client zu programmieren. Ich frage mich nur, was folgende Anforderungen bedeuten sollen:


```
public abstract class AServer {
/**
     * Sendet die Nachricht message an den Client mit Index index.
     * 
     * @param index
     *            Index des Clients
     * @param message
     *            Zu sendende Nachricht
     * @throws IOException
     */
    public abstract void sendMessage(int index, String message)
            throws IOException;

    /**
     * Wartet auf und empfaengt eine Nachricht vom Client mit Index index.
     * 
     * @param index
     *            Index des Clients
     * @return erhaltene Nachricht
     * @throws IOException
     */
    public abstract String receiveMessage(int index) throws IOException;
}
```

Irgendwie muss sich der Client beim Server ja registrieren, denn ansonsten kann der Server ja keine Nachrichten erhalten. Nur meine Frage: Wie soll das passieren?

Vielleicht habe ich auch einfach einen Denkfehler in der Implementierung der anderen Methoden gemacht. Die Anforderungen:

```
abstract void acceptConnection() throws IOException;
abstract void openPort(int port) throws IOException;
abstract void disconnect();
abstract void disconnect(int index);
```

Meine bisherige Implementierung dazu:

```
public final class Server extends AServer {
	private ServerSocket serverSocket;
	private Socket server;
	private boolean isConnected=false;
	private int connectionCount=0;

    @Override
    void openPort(int port) throws IOException {
    	serverSocket = new ServerSocket(port);
    }

    @Override
    void acceptConnection() throws IOException {
    	server = serverSocket.accept();
    }
}
```

Fehlt da eventuell in meiner bisherigen Implementierung etwas? Ich versteh nur nicht, wie der Client sich mit einem Index beim Server verbinden soll.

Gruß
Fenixx


----------



## SlaterB (4. Nov 2009)

der Client braucht zur Verbindung keinen Index, er kommt an beim accept, und wenn er da ist bekommt er einen Index zugeordnet, wird in eine Liste eingefügt usw.,
sendMessage und receiveMessage sind Methoden, die das restliche Server-Programm an der Server-Klasse aufruft,

für sich und mit dem restlichen Interface macht das aber alles noch wenig Sinn,
es sollte zumindest noch Methoden geben, um zu erfahren, wieviele Clients vorhanden sind, mit welchen Indexen usw.,
sonst müsste man die Methoden blind aufrufen,

wer wo wann acceptConnection() usw. aufruft ist auch zu klären,
komplizierte Sache


----------



## Fenixx (4. Nov 2009)

Ich wollte den Code das auf das Wesentliche reduzieren, aber hier die beiden Vorgaben:

Server:

```
public abstract class AServer {

    /**
     * Konstuktor. Erstellt einen neuen Server und baut eine lauschende
     * Verbindung am Port port auf.
     * 
     * @param port
     * @throws IOException
     */
    public AServer(int port) throws IOException {
        openPort(port);
        Runnable r = new Runnable() {
            public void run() {
                while (isConnected()) {
                    try {
                        acceptConnection();
                    } catch (SocketException se) {
                        if (!se.getMessage().equals("socket closed")) {
                            se.printStackTrace();
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        new Thread(r).start();
    }

    /**
     * Sendet die Nachricht message an den Client mit Index index.
     * 
     * @param index
     *            Index des Clients
     * @param message
     *            Zu sendende Nachricht
     * @throws IOException
     */
    public abstract void sendMessage(int index, String message)
            throws IOException;

    /**
     * Wartet auf und empfaengt eine Nachricht vom Client mit Index index.
     * 
     * @param index
     *            Index des Clients
     * @return erhaltene Nachricht
     * @throws IOException
     */
    public abstract String receiveMessage(int index) throws IOException;

    /**
     * Liefert die Anzahl der verbundenen Clients.
     * 
     * @return Anzahl der verbundenen Clients
     */
    public abstract int connectionCount();

    /**
     * Wartet darauf, dass sich ein neuer Client verbindet. Die Verbindung kommt
     * nur zu Stande, wenn der Client direkt nach der Verbindung seine ID
     * übermittelt. Die Verbindung muss festgehalten werden. Wir keine ID
     * uebermittelt, wird die Verbindung abgebrochen.
     * 
     * @throws IOException
     */
    abstract void acceptConnection() throws IOException;

    /**
     * Oeffnet einen Port auf dem Server, um Clientverbindungen zu zulassen.
     * 
     * @param port
     * @throws IOException
     */
    abstract void openPort(int port) throws IOException;

    /**
     * Beendet alle offenen Verbindungen zu den Clients und schliesst den
     * lauschenden Port.
     */
    abstract void disconnect();

    /**
     * Beendet die Verbindung mit Client index.
     * 
     * @param index
     *            Index des Clients
     */
    abstract void disconnect(int index);

    /**
     * Gibt an, ob der lauschende Port geoeffnet ist.
     * @return true, wenn der lauschende Port offen ist. false sonst.
     */
    abstract boolean isConnected();

    /**
     * Gibt an, ob eine Verbindung zu dem Client mit Index index besteht.
     * @param index Index des Clients 
     * @return true, wenn eine Verbindung besteht. false sonst.
     */
    abstract boolean isConnected(int index);
}
```

Client:

```
public interface IClient {

    /**
     * Verbindet sich mit einem Server.
     * 
     * @param server
     *            Adresse des Servers
     * @param port
     *            Port des Servers
     * @throws IOException
     */
    public void connect(String server, int port) throws IOException;

    /**
     * Wartet auf und empfaengt eine Nachricht.
     * 
     * @return Empfangene Nachricht
     * @throws IOException
     */
    public String receiveMessage() throws IOException;

    /**
     * Sendet eine Nachricht message an Server.
     * 
     * @param message
     *            Zu sendende Nachricht
     * @throws IOException
     */
    public void sendMessage(String message) throws IOException;

    /**
     * Beendet die Verbindung zum Server, sofern eine besteht.
     * @throws IOException
     */
    public void disconnect() throws IOException;

    /**
     * Gibt den Verbindungsstatus wieder. Liefert true nachdem connect
     * aufgerufen wurde und false wenn disconnect aufgerufen wurde.
     * 
     * @return Verbindungsstatus
     */
    public boolean isConnected();

    /**
     * Liefert die ID des Clients.
     * 
     * @return ID des Clients
     */
    public String getID();
}
```

Ich denke, dass dann auch irgendwo ein Fehler in meiner bisherigen Implementierung ist.

Gruß
Fenixx


----------



## SlaterB (4. Nov 2009)

ist das eine Frage?
deine bisherige (gepostete) Implementierung besteht ja gerademal aus 2 Zeilen Code + 4 Zeilen Deklaration,
da würde ich noch nicht von Fehlern sprechen


----------



## Fenixx (4. Nov 2009)

Die Frage ist: Was hat der Index auf sich  . 

Vielleicht muss folgende Methode so aussehen:


```
List<Socket> clients = new ArrayList<Socket>();
 @Override
    void acceptConnection() throws IOException {
        Socket client = serverSocket.accept();
        clients.add(client);
        //Hier kann ich dann von der Liste mithilfe des Indexes zugreifen
        connectionCount++;
    }
```


----------



## SlaterB (4. Nov 2009)

jo, so in der Art stell ich mir das auch vor


----------



## Fenixx (4. Nov 2009)

Siehst du den Emfang und das Senden von Daten auch so wie ich, oder ist da noch Verbesserungspotenzial:


```
@Override
	public String receiveMessage(int x) throws IOException {
		String incoming = null;
		if (clients.get(x).getInputStream().available() == 0) {
			BufferedReader receivedReader = new BufferedReader(
					new InputStreamReader(clients.get(x).getInputStream()));

			String fromReadLine;
			while ((fromReadLine = receivedReader.readLine()) != null) {
				incoming += fromReadLine;
			}
			receivedReader.close();
		}
		return incoming;
	}

	@Override
	public void sendMessage(int x, String message) throws IOException {
		StringWriter buffer = new StringWriter();
		PrintWriter out = new PrintWriter(buffer);
		out.println(message);
		clients.get(x).getInputStream().read(buffer.toString().getBytes());
		out.close();
		buffer.close();
	}
```

Gruß
Fenixx


----------



## SlaterB (4. Nov 2009)

ein close() beendet einen Stream für immer,

beim senden liest du im Grunde auch nur vom InputStream statt etwas in den OutputStream zu schreiben

so gesehen ist da jede Menge zu machen, aber teste doch erstmal,
und fange besser ganz klein an, versuche einen Server mit einem Client zu verbinden und Daten auszutauschen, ganz unabhängig von der Aufgabe und all den Indexen..


----------



## Fenixx (5. Nov 2009)

SlaterB hat gesagt.:


> ein close() beendet einen Stream für immer,
> 
> beim senden liest du im Grunde auch nur vom InputStream statt etwas in den OutputStream zu schreiben



Das verstehe ich nicht so ganz. Sei A der Client und B der Server. B möchte etwas an A senden (so verstehe ich das). B erzeugt die Nachricht und packt diese hinterher in den InputStream von A, damit A diese lesen kann.

Oder verwechsel ich was?


----------



## SlaterB (5. Nov 2009)

> B erzeugt die Nachricht und packt diese hinterher in den InputStream von A

so ist es es korrekt, nur um das zu erreichen, muss B den Text bei sich im Server in den OutputStream der Verbindung schreiben,
das geht dann 'raus', übers Internet und landet im Client-Programm im InputStream,

ich empfehle dringend, Grundlagen zu Streams + Sockets zu lesen, z.B.
Galileo Computing :: Java ist auch eine Insel (8. Auflage) – 18 Netzwerkprogrammierung
besonders ab
Galileo Computing :: Java ist auch eine Insel (8. Auflage) – 18.7 Mit dem Socket zum Server

vorher vielleicht
Galileo Computing :: Java ist auch eine Insel (8. Auflage) – 14 Dateien und Datenströme


----------



## Gast2 (5. Nov 2009)

Moin,

mal etwas pseudo Code


```
class Server

  Socket server

  begin ServerStart
    server = new Socket(port)
    while true
      new Client(server.accept())
    end
  end  

end
```


```
class Client

  Socket socket

  begin Konstruktor(Socket s)
    socket = s
    new Thread(ThreadProcess)
  end

  begin ThreadProcess
    while true
      Array daten = socket.read()
      print daten.tostring()
    end
  end

  begin send(Array daten)
    socket.send(daten)
  end

end
```

der Client lässt sich übrigens auf beiden Seiten verwenden

hand, mogel


----------



## Fenixx (6. Nov 2009)

Hi zusammen,

ich hab mich nun mehr eingearbeitet und habe eigentlich zwei Probleme:
1.) An der Stelle 

```
server.isConnected(x) //Liefert true zurück, wenn der Client mit dem Index x verbunden ist
```

läuft es im Debugger so wie erwartet (server.isConnected(x) gibt true zurück). Mit Run As -> Java Application anscheinend false, denn folgender Code wird nicht ausgeführt:


```
if (server.isConnected(x)) {
                    System.out.println("  to client " + x + ": " + number);
                    try {
                        server.sendMessage(x, "" + number);
                    } catch (SocketException se) {
                        server.disconnect(x);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    number++;
                }
```

Hier der Aufruf bis zur Stelle:

```
public static void main(String[] args) {
        int servermessages = 100;
        int initdelay = 2;
        
        // 1. Server starten
        try {
            new DataProvider(servermessages).start();
        } catch (IOException e1) {
            e1.printStackTrace();
            System.exit(0);
        }

        // 2. Einen Moment warten
        Util.sleep(initdelay);

        // 3. Clients starten
        for (int x = 0; x < 3; x++) {
            try {
                new Thread(new DataReceiver("" + x, "localhost", 2096))
                        .start();
                Util.sleep(initdelay);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
```

In der DataProvider-Klasse:

```
public void run() {
        int counter = 0;
        while (counter < amount) {
            System.out.println("Sleeping");
            Util.sleep(1);
            System.out.println("Sending");
            for (int x = 0; x < server.connectionCount(); x++) {
                if (server.isConnected(x)) {
                    System.out.println("  to client " + x + ": " + number);
                    try {
                        server.sendMessage(x, "" + number);
                    } catch (SocketException se) {
                        server.disconnect(x);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    number++;
                }
            }
            counter++;
        }
        System.out.println("Disconnecting");
        server.disconnect();
        System.out.println("Finished");
    }
```

Die Methode isConnected() des Servers:

```
boolean isConnected(int x) {
		Socket indexSocket = null;
		for (int i = 0; i < clientSockets.size(); i++) {
			if (i == x) {
				indexSocket = clientSockets.get(x);
				break;
			}
		}
		return indexSocket.isConnected();
	}
```

Habe ich da einen Fehler eingebaut? Ich kann mir das halt nicht erklären.


----------



## SlaterB (6. Nov 2009)

wann und wo finden denn die Connections statt, im Konstruktor von DataProvider?

wenn du beim Server sowas wie accept() hast, dann gib doch dort aus
System.out.println("neue Verbindung eingetroffen");

und statt
 System.out.println("Sending");
schreibst du 
 System.out.println("Sending, Current Connections: "+server.connectionCount());

dann kann man aus den Ausgaben vielleicht etwas erkennen,
ansonsten mehr Code, vollständige Programme lassen sich viel besser testen (ich mach das heute allerdings nicht mehr  )


----------



## Fenixx (6. Nov 2009)

Ich habe deinen Rat befolgt. Nun werden Verbindungen zwischen Clients und Server aufgebaut.
Hier die Debugausgabe:

```
>>Verbindung wird aufgebaut
Sleeping
Sending
Sleeping
Sending
Sleeping
<<Verbindung wurde aufgebaut
>>Verbindung wird aufgebaut
Der Client erhält die Nachricht...
Sending
  to client 0: 0
>> Nachricht wird gesendet
<< Nachricht wurde gesendet
```

Man sieht, dass die receiveMessage()-Methode des Clients noch vor dem Server aufgerufen wird. Hier liegt das Problem: Der Client kann aus dem bufferedReader nichts lesen. Das Programm kommt über folgende Zeile nicht hinaus:

Client:

```
while ((line = bufferedReader.readLine()) != null)
```

Die komplette Methode des Clients:

```
public String receiveMessage() throws IOException {
    	System.out.println("Der Client erhält die Nachricht...");
    	String s = null;
    	String line = null;
        while ((line = bufferedReader.readLine()) != null) {
            System.out.println(line);
            s+=line;
        }
        return s;
    }
```

Die sende-Methode des Servers:


```
public void sendMessage(int x, String message) throws IOException {
		PrintWriter currentPrintWriterToClient = null;
		for (int i = 0; i < printWriter.size(); i++) {
			if (i == x) {
				currentPrintWriterToClient = printWriter.get(x);
				break;
			}
		}
		currentPrintWriterToClient.print(message);
	}
```

Hier der Kontext des Aufrufs:
Test.java:

```
public final class Test {

    /**
     * Testmethode
     * 
     * @param args
     */
    public static void main(String[] args) {
        int servermessages = 100;
        int initdelay = 2;
        
        // 1. Server starten
        try {
            new DataProvider(servermessages).start();
        } catch (IOException e1) {
            e1.printStackTrace();
            System.exit(0);
        }

        // 2. Einen Moment warten
        Util.sleep(initdelay);

        // 3. Clients starten
        for (int x = 0; x < 3; x++) {
            try {
                new Thread(new DataReceiver("" + x, "localhost", 2096))
                        .start();
                Util.sleep(initdelay);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
```

DataProvider.java:

```
public final class DataProvider extends Thread {

    private final AServer server;
    private int           number;
    private final int     amount;

    public DataProvider(int amount) throws IOException {
        server = new Server(2096);
        number = 0;
        this.amount = amount;
    }

    public void run() {
        int counter = 0;
        while (counter < amount) {
            System.out.println("Sleeping");
            Util.sleep(1);
            System.out.println("Sending");
            for (int x = 0; x < server.connectionCount(); x++) {
                if (server.isConnected(x)) {
                    System.out.println("  to client " + x + ": " + number);
                    try {
                    	System.out.println(">> Nachricht wird gesendet");
                        server.sendMessage(x, "" + number);
                        System.out.println("<< Nachricht wurde gesendet");
                    } catch (SocketException se) {
                        server.disconnect(x);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    number++;
                }
            }
            counter++;
        }
        System.out.println("Disconnecting");
        server.disconnect();
        System.out.println("Finished");
    }
}
```

DataReceiver.java:

```
public final class DataReceiver implements Runnable {

    private IClient c;

    public DataReceiver(String id, String server, int port) throws IOException {
        c = new Client(id);
        c.connect(server, port);
    }

    /**
     * Wartet auf und liest die Nachrichten vom Server. Bei Beendigung der
     * Verbindung durch den Server wird die Methode ebenfalls beendet.
     */
    public void run() {
        int x = 0;
        while (c.isConnected()) {
            try {
                String message = c.receiveMessage();
                if (message == null) {
                    c.disconnect();
                } else {
                    System.out.println("Message received at " + c.getID()
                            + ": " + message);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            x++;
        }
        System.out.println("Disconnected");
    }
}
```

Hinweis: Der Client implementiert das Interface IClient.

Ist vielleicht etwas beim Lesen falsch?


----------



## SlaterB (6. Nov 2009)

bufferedReader.readLine()
liefert erst was zurück wenn ein \n gelesen wurde, 
ob das der Sender derzeit in der message hat?


----------



## Gast2 (6. Nov 2009)

Moin,

minimaler Server


```
public static void main(String[] args)
{
	try {
		if (args.length > 0 && args[0].equals("-server"))
		{
			System.out.println("starte Server");
			new Server();
		} else
		{
			System.out.println("starte Client");
			new Client(new Socket("127.0.0.1", 54321));
		}
	} catch (IOException e) {
		e.printStackTrace();
	}
}
```


```
public class Server implements Runnable {	
	
	ServerSocket socket;
	boolean ende = false;
	
	public Server() throws IOException
	{
		socket = new ServerSocket(54321);
		(new Thread(this)).start();
	}

	public void run() {
		while(!ende)
		{
			try {
				Client c = new Client(socket.accept());
				c.send("hello");
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
}
```


```
import java.io.IOException;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;

public class Client implements Runnable {

	Socket socket;
	boolean ende = false;
	Scanner in;
	PrintWriter out;
	
	public Client(Socket s)
	{
		socket = s;

		try {
			in  = new Scanner( socket.getInputStream() );
			out = new PrintWriter(socket.getOutputStream(), true);
		} catch (IOException e) {
			e.printStackTrace();
		} 

		(new Thread(this)).start();
	}
	
	public void run() {	
		while(!ende)
		{
			String msg = in.nextLine();
			System.out.println("<<< " + msg);
			if (msg.equals("hello")) send("server");
			if (msg.equals("server")) send("127.0.0.1");
			if (msg.equals("127.0.0.1")) { send("bye"); ende = true; }
			if (msg.equals("bye")) { ende = true; }
		}
	}
	
	public void send(String msg)
	{
		System.out.println(">>> " + msg);
		out.println(msg);
	}
}
```

Ausgabe des Client


```
starte Client
<<< hello
>>> server
<<< 127.0.0.1
>>> bye
```

und der Server


```
starte Server
>>> hello
<<< server
>>> 127.0.0.1
<<< bye
```

versuch erstmal die Kommunikation zwischen Client und Server hinzubekommen ... dann kannst Du den Client mit einer ID versehen und in eine Liste packen

hand, mogel

PS: <<< ist Nachricht erhalten ...  >>> Nachricht wird gesendet


----------



## Fenixx (7. Nov 2009)

Moin,

bei dir hat der Server auch einen Client in sich. Ich möchte erstmal die Kommunikation zwischen einem Server und einem Client realisieren. Hast du in dem Server den Client implementiert um Redundanz zu vermeiden, oder hätte man auch stattdessen einen Socket in dem Server implementieren können? Ich hatte nämlich bisher auf die Einbindung von Clients in dem Server vermieden. Ist das vielleicht der falsche Weg?


----------



## SlaterB (7. Nov 2009)

Client ist nicht gleich Client,

da gibt es zum einen die Organisation beim Verbindungaufbau, der ein wartet, der andere fragt aktiv an, 
diese Rolle von Server/ Client kann man nicht umgehen

wenn aber erstmal die Verbindung da ist, sind die beiden Kommunikationsparter kaum noch voneinander zu unterscheiden,
jeder hat einen Socket mit In/ Out-Stream, jeder darf beliebig senden und empfangen,
es wäre natürlich immer noch besser, wenn getrennte Rollen vorliegen, z.B. immer einer der beiden die Kommunikation steuert und der andere nur mit korrekten Antworten reagiert,

aber technisch ist die Klasse Client für das Server- wie für das Client-Programm gleichermaßen geeignet,
man hätte die Klasse auch SocketTalker oder so nennen können


----------



## Gast2 (7. Nov 2009)

Moin,



Fenixx hat gesagt.:


> bei dir hat der Server auch einen Client in sich. Ich möchte erstmal die Kommunikation zwischen einem Server und einem Client realisieren.


wenn Du jetzt bis auf Betriebssystemebene runter gehst diskutiert anschließend nur Socket mit Socket ... das macht keinen unterschied ... vgl.



SlaterB hat gesagt.:


> wenn aber erstmal die Verbindung da ist, sind die beiden Kommunikationsparter kaum noch voneinander zu unterscheiden,





> Hast du in dem Server den Client implementiert um Redundanz zu vermeiden, oder hätte man auch stattdessen einen Socket in dem Server implementieren können? Ich hatte nämlich bisher auf die Einbindung von Clients in dem Server vermieden.


in meiner DLL (Arbeit) habe ich eine Client-Klasse die bei der Initialisierung unterschiedlich gehandhabt wird ... entweder übernimmt der Konstruktor IP und Port (Client-Seite) oder einen Socket (Server-Seite -> socket.accept()) ... innerhalb passiert nichts anders als Senden und Empfangen

versuch mal das Prinzip Server/Client wie folgt zu betrachten

Server - nimmt nur Verbindungen entgegen (socket.accept()), mehr nicht!
ServerClient - eine Socket-Verbindung auf Server-Seite
ClientClient - eine Socket-Verbindung auf Client-Seite

zum Schluss reden nur noch ServerClient und ClientClient miteinander

der von mir gepostete Client kann erstmal für die reine Kommunikation verwendet werden ... dann musst Du nur noch die einzelnen Seite und ihr Verhalten implementieren


```
public class Client { /* ... */ }
public class ServerClient extends Client { /* ... */ }
public class ClientClient extends Client { /* ... */ }
```



SlaterB hat gesagt.:


> Client ist nicht gleich Client,
> da gibt es zum einen die Organisation beim Verbindungaufbau, der ein wartet, der andere fragt aktiv an, diese Rolle von Server/ Client kann man nicht umgehen


etwas verwirrend das ich beide Rollen gleich in die gleiche Klasse gepackt habe 

hand, mogel


----------



## Fenixx (7. Nov 2009)

Hi zusammen,

ich habe mogels Umsetzung auch so in der Art implementiert und es funktioniert jetzt. Wo der Fehler jetzt genau war kann ich nicht sagen, da ich nochmal ganz von vorne angefangen hab.
Vielen Dank dafür.

Gruß
Fenixx


----------

