# Threads mit Client/Server und GUI (laufend bis button-click)



## Yonnig (22. Jan 2022)

Guten Tag zusammen!

Ich beiße mir gerade irgendwie die Zähne an einer Aufgabe aus... Irgendwie will nicht in meinen Kopf, wie das funktioniert.
Ich soll eine App schreiben, welche gleichzeitig einen Server Socket für eingehende Clients bereitstellt sowie einen eigenen Socket, um sich mit einem anderen Server zu verbinden.
Dazu habe ich eine sporadische GUI gebaut und mit einem "disconnect"-Button versehen. Mein Ziel wäre es, dass sobald man auf diesen Button klickt, die Verbindungen zu den anderen Parteien aufgelöst wird:
-> Bei der anderen Client-Partei soll diese quasi mit beenden "mitzerstört" werden.

Aber das ist noch nicht mal das eigentliche Problem. Es scheitert momentan schon daran, dass ich es einfach nicht ganz verstehe, wie ich es schaffe, dass beim Starten der App ein Thread anspringt und folgende Dinge erledigt:
1. Anmelden beim anderen Server über ein Text-Protokoll
2. Zeichnen der GUI für die App
3. Der Thread in eine Endlosschleife geht und die vom User in der GUI getätigten Operationen erledigt (das ist für mich noch am unverständlichsten wie das gehen soll)
4. Beim disconnect-Buttonklick wie oben beschrieben Thread auflösen und abmelden.

Ich habe heute den ganzen Tag versucht zu verstehen, wie ich das hinbekomme... Bin irgendwie zu blöd gerade.

Zudem verstehe ich nicht ganz, wieso folgende Zeilen dazu führen, dass sich die App aufhängt...


```
else if(ae.getSource() == harbourInfo) {
                try(OutputStreamWriter osw = new OutputStreamWriter(socket.getOutputStream());
                        PrintWriter pw = new PrintWriter(osw, true);
                        InputStreamReader isr = new InputStreamReader(socket.getInputStream());
                        BufferedReader br = new BufferedReader(isr);){
                    String msg = "getinfo:harbour";
                    pw.write(msg + "\r\n");
                    pw.flush();
                    String response = "";
                    String setInfoText = "";
                    while((response = br.readLine()) != null) {
                        System.out.println(response);
                        setInfoText = new String(setInfoText + response + "\n");
                    }
                    buttonInfos.setText(setInfoText);
                    } catch (IOException e) {
                      System.err.println(e.toString());
                    }
```

Dabei handelt es sich um durch einen Buttonklick angefordertes ActionEvent. Dabei sollen Daten vom Server angefordert werden.
In die Kommandozeile werden diese noch geschrieben, aber das Programm hängt sich dann auf. 
Außerdem scheinen die Zeilen in der Kommandozeile auch "Macken" zu haben... immer die ersten Buchstaben sind nicht richtig.

Liebe Grüße
Yonnig


----------



## mihe7 (23. Jan 2022)

Beim Programmstart existiert bereits ein Thread, der main-Thread. Für das UI wird vom Framework ebenfalls ein Thread gestartet (UI-Thread aka Event-Dispatch-Thread in Swing). Wenn vom Framework eine Methode aufgerufen wird, z. B. als Folge eines Button-Klicks, dann läuft diese Methode im UI-Thread.

Dein Client soll nun unabhängig vom UI laufen. In Deinem Button-Listener hat der Socket also schon mal gar nichts verloren. Vielmehr muss dein UI mit dem Client kommunizieren. 

Das Senden wird vom UI ausgelöst, insofern genügt es, im Button-Listener das Senden zu initiieren. Etwas schwieriger wird es beim Empfangen. Der Zeitpunkt des Empfangs ist nicht alleine vom UI oder dem Programm abhängig, sondern stellt ein Ereignis von außen dar. Wenn der Server etwas sendet, wird es empfangen. Das UI muss dann über den Empfang informiert werden. 

Der Empfänger-Thread liest also blockierend vom InputStream und sobald er etwas gelesen hat, informiert er einen Beobachter (s. Callback bzw. Observer-Pattern). Zusätzlich kann auch ein Puffer helfen, aus dem das UI dann letztlich liest. Beim Senden könnte man synchron arbeiten (wenn man in Kauf nimmt, dass das UI für die kurze Zeit blockiert), alternativ kann ein zusätzlicher Sender-Thread helfen, das UI nicht zu blockieren. Das UI könte z. B. in eine BlockingQueue schreiben, während der Sender-Thread die Daten daraus (blockierend) liest und dann sendet.


----------



## Mart (23. Jan 2022)

mit was für einem Framework hast du es geschrieben ?

wenn du es mit javafx geschrieben hast wird jeder Versuch einen Thread zu öffnen in eine Zufalls basierten absturz führen
deswegen gibts da Task und Service ... das sind ansich threads die aber innerhalb von javafx funktionieren


----------



## mihe7 (23. Jan 2022)

Mart hat gesagt.:


> wenn du es mit javafx geschrieben hast wird jeder Versuch einen Thread zu öffnen in eine Zufalls basierten absturz führen


?!?


----------



## Yonnig (23. Jan 2022)

mihe7 hat gesagt.:


> Beim Programmstart existiert bereits ein Thread, der main-Thread. Für das UI wird vom Framework ebenfalls ein Thread gestartet (UI-Thread aka Event-Dispatch-Thread in Swing). Wenn vom Framework eine Methode aufgerufen wird, z. B. als Folge eines Button-Klicks, dann läuft diese Methode im UI-Thread.
> 
> Dein Client soll nun unabhängig vom UI laufen. In Deinem Button-Listener hat der Socket also schon mal gar nichts verloren. Vielmehr muss dein UI mit dem Client kommunizieren.
> 
> ...


Danke für die ausführliche Beschreibung. Ich muss mich nochmal näher mit alldem beschäftigen... Eventuell versuche ich erstmal alles ohne UI umzusetzen und schaue dann, wie ich es mit UI mache.



Mart hat gesagt.:


> mit was für einem Framework hast du es geschrieben ?


ich habe das UI mit Swing geschrieben.


----------



## Yonnig (23. Jan 2022)

Hallo nochmal,

vielleicht passt das jetzt nicht mehr so ganz hier rein, aber ich hätte noch eine Frage zu den Input/Output Streams.
Wenn ich eine Klasse habe, und diese sich mit 2 Servern verbinden können soll, muss ich dann für jeden Socket quasi 
einen kompletten Satz von reader/writern erstellen?

also z.B. für Server connection 1:
zum Lesen:
1x InputStreamReader
1x BufferedReader
zum Schreiben:
1x OutputStreamWriter
1x PrintWriter

also z.B. so:

```
try(InputStreamReader isr = new InputStreamReader(socket1.getInputStream());
                    BufferedReader br = new BufferedReader(isr);
                    OutputStreamWriter osw = new OutputStreamWriter(socket1.getOutputStream());
                    PrintWriter pw = new PrintWriter(osw, true);) {
```

und das ganze dann nochmal für die andere Verbindung oder wie löst man das geschickter?

Gruß
Yonnig


----------



## mihe7 (23. Jan 2022)

Yonnig hat gesagt.:


> muss ich dann für jeden Socket quasi
> einen kompletten Satz von reader/writern erstellen?


Jein. Der Socket liefert einen InputStream zum Lesen und einen OutputStream zum Schreiben. Wenn Dir die Streams nicht gefallen und Du lieber einen Reader bzw. Writer verwenden willst, dann musst Du natürlich entsprechende Objekte erzeugen.

Du kannst das auch schachteln, wird aber dann leicht unübersichtlich:

```
try(BufferedReader rd = new BufferedReader(new InputStreamReader(socket1.getInputStream()));
         PrintWriter pw = new PrintWriter(new OutputStreamWriter(socket1.getOutputStream()), true)) {
}
```
Natürlich kannst Du das in Methoden auslagern und die grundsätzliche Frage wäre auch, ob Du die Streams tatsächlich ständig neu aufbauen willst. Das Schließen der Streams schließt auch den Socket. (Verbindung).


----------



## Yonnig (23. Jan 2022)

Man könnte dann ja die Streams erzeugen und innerhalb eines Threads in Dauerschleife Ein/Ausgaben verarbeiten und mit Beendigung des Threads auch die Streams dann schließen, korrekt? Wie genau das funktioniert weiß ich zwar noch nicht aber so würde ich das denke ich versuchen umzusetzen...

Also ich starte das Programm, verbinde mich mit den Servern, baue Streams auf, lasse auf Dauerschleife laufen und wenn der Thread interrupted wird Streams schließen und fertig?

Edit: Für jede Verbindung (jeden Socket) einen eigene Thread natürlich.


----------



## mihe7 (23. Jan 2022)

Ja, wobei sich das blockierende Lesen nicht mit einem Thread.interrupt() unterbrechen lässt, wenn ich es richtig im Kopf habe. Du musst dann einfach den Stream schließen.

Dürfte in etwa so aussehen:


```
class ReaderThread implements Runnable {
    private final BufferedReader input;

    public ReaderThread(BufferedReader input) {
        this.input = input;
    }

    public void close() {
        try {
            input.close();
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }

    @Override
    public void run() {
        String line;
        try {
            while ((line = input.readLine()) != null) {
                received(line);
            }
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }

    private void received(String line) {
        // mach was mit line, z. B. irgendwelche Beobachter informieren
    }
}
```


----------



## Yonnig (23. Jan 2022)

Ok, vielen Dank nochmal für die schnellen Antworten!


----------

