Java Multi Client Server Applikation

PEASANT KING

Commander
Registriert
Okt. 2008
Beiträge
2.397
Hallo Leute,

ich bin dabei mir meine Multi Client Server Anwendung zu programmieren, klappt auch ganz gut.
Nun bin ich dennoch auf ein Problem gestoßen, das ich nicht wirklich weiß zu beheben.

Hier mal meine Run Methode aus der Klasse AnyConnectServer
Code:
public class AnyConnectServer implements Runnable {

    public void run() {

        synchronized (this) {
            this.runningThread = Thread.currentThread();
        }
        System.out.println(runningThread);
        while (!isStopped()) {
            try {
                addThreadArray(serverSocket.accept());
                console.printMessage("Eingehende Verbindung!");
            } catch (IOException e) {
                if (isStopped()) {
                    console.printMessage("Service wurde gestoppt " + e);
                }
                console.printError("Fehler Clientverbindung!");
            }
        }
    }
}

Was ich möchte ist, das jedes mal wenn ein Client versucht sich zu verbinden die Run Methode automatisch gestartet wird.
Zur Zeit muss ich die Run Methode immer manuell aufrufen, was natürlich totaler Blödsinn ist.
So kann sich beim ersten Start der Run Methode 1 Client verbinden beim zweiten Start 2 usw. das ist aber nicht so gewollt. Es sollen sich so viele anmelden können wie mein Array begrenzt und zwar sofort und nicht nach jedes Mal starten der Methode.

Ich habe mir beholfen indem ich ne For Schleife benutzt habe im Aufruf
Code:
    private class StartServer implements ActionListener {

        @Override
        public void actionPerformed(ActionEvent e) {
            for (int i = 0; i < 1000; i++) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        server.run();
                    }
                }).start();
            }
        }
    }

Das ist allerdings sehr unperformant und denke ich absolut Quark.
Kann mir Jemand helfen ? Ich kann auch gerne Sources rausgeben, es sei angemerkt das es nur ein Hobby Projekt ist.
 
Ja, ich glaube ein paar mehr Codezeilen wären schon ganz gut, um das ganze einschätzen zu können.
Die For-Schleife, wie du schon selbst geschrieben hast, ist natürlich Quark aber ohne weitere Codezeilen/Erkenntnisse ist es etwas schwer, da jetzt was konkretes zu sagen. Welche Actions triggert denn z.B. StartServer? Hast du direkt eine Action, wenn eine Anfrage von 'nem Client kommt?
 
Ich glaube du hast hier einen grundlegenden Denkfehler. Was du tun musst ist folgendes (gekürzt/Pseudocode mit 'unendlichen' Threads):

Code:
[..]
ServerSocket serverSocket = new ServerSocket(portNumber);
while (true) {
    Socket clientSocket = serverSocket.accept();
    (new CommunicationThread(clientSocket)).start();
}
[..]
Code:
public class CommunicationThread extends Thread {
    Socket clientSocket;
    public CommunicationThread(Socket socket) {
      this.clientSocket = socket;
    }
    public void run() {
        // communicate with client using clientSocket
    }
}

Ehrlich gesagt wundert mich dass dein Beispiel überhaupt etwas gemacht hat (dass die 1000 serverSocket.accept() angenommen wurden).
 
Moin moin sry das ich mich jetzt erst melde, gestern war bisschen stressig.

Ich kann ja hier mal meine Server Klasse posten und die Thread Klasse:
Code:
/*
 *  File AnyConnectServer.java created on 10.07.2014 by LL
 *  Copyright © 2007 - 2014 LL. All rights reserved.
 */
package com.sks.server;

import com.sks.console.AnyConnectConsole;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

/**
 *
 * @author *****
 */
public class AnyConnectServer implements Runnable {

    private final ServerThread client[] = new ServerThread[1000];
    private ServerSocket serverSocket;
    private final int port;
    private Thread runningThread = null;
    private boolean isStopped;
    private int clientCount;
    private final AnyConnectConsole console;

    public AnyConnectServer(int port, AnyConnectConsole console) {
        this.port = port;
        this.console = console;
        createSocket(port);
    }

    /**
     * Erstellt einen neuen Socket auf Basis des vorgegebenen Ports
     */
    private void createSocket(int port) {
        this.isStopped = false;
        try {
            serverSocket = new ServerSocket(port);
            console.printMessage("Server erfolgreich gestartet!");
            console.printMessage(serverSocket.toString());
        } catch (IOException e) {
            console.printError(e.getMessage());
        }



    }

    /**
     * Schließt den geöffneten Socket
     */
    public synchronized void closeSocket() {
        this.isStopped = true;
        try {
            this.serverSocket.close();
            console.printMessage("Server erfolgreich getoppt!");
        } catch (IOException e) {
            System.out.println(e);
        }
    }

    /**
     * Gibt den Status von isStopped zurück
     *
     * @return boolean
     */
    private synchronized boolean isStopped() {
        return this.isStopped;
    }

    /**
     * Fügt den neuen Thread einem Array hinzu
     *
     * @param socket
     */
    private void addThreadArray(Socket socket) {

        if (clientCount < client.length) {
            client[clientCount] = new ServerThread(socket);
            console.printMessage("client count: " + client[clientCount]);
            try {
                client[clientCount].open();
                client[clientCount].start();
                clientCount++;
            } catch (IOException e) {
                System.out.println(e);
            }
        } else {
            console.printWarning("Fehler maximale Anzahl an Clients erreicht!");
        }
    }

    @Override
    public void run() {

        synchronized (this) {
            this.runningThread = Thread.currentThread();
        }
        System.out.println(runningThread);
        while (!isStopped()) {
            try {
                addThreadArray(serverSocket.accept());
                console.printMessage("Eingehende Verbindung!");
            } catch (IOException e) {
                if (isStopped()) {
                    console.printMessage("Service wurde gestoppt " + e);
                }
                console.printError("Error accepting client connection!");
            }
        }
    }

}

und die Thread Klasse:

Code:
/*
 *  File ServerThread.java created on 10.07.2014 by LL
 *  Copyright © 2007 - 2014 LL. All rights reserved.
 */
package com.sks.server;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.UUID;

/**
 *
 * @author ***
 */
public class ServerThread extends Thread {

    private final Socket socket;
    private DataInputStream dIn;
    private DataOutputStream dOut;

    /**
     *
     * @param socket
     */
    public ServerThread(Socket socket) {
        this.socket = socket;
    }

    /**
     * Opens an InputStream/OutputStream and reads incomming messages from
     * clients
     *
     * @throws IOException
     */
    public void open() throws IOException {

        UUID uuid = UUID.randomUUID();

        InputStream in = socket.getInputStream();
        OutputStream out = socket.getOutputStream();

        dIn = new DataInputStream(in);
        dOut = new DataOutputStream(out);
        dOut.writeUTF("Erfolgreich mit dem AnyConnect Server verbunden!");
        String line;

        while (true) {
            line = dIn.readUTF();

            dOut.writeUTF("[MSG]: " + line
                    + " [ID]: " + uuid.toString()
                    + " [IP]: " + socket.getInetAddress().toString());

            dOut.flush();

            System.out.println("[MSG]: " + line
                    + " [ID]: " + uuid.toString()
                    + " [IP]: " + socket.getInetAddress().toString());
        }
    }

    /**
     * Schließt alle Verbindungen
     *
     * @throws IOException
     */
    public void close() throws IOException {
        if (socket != null) {
            socket.close();
        }
        if (dIn != null || dOut != null) {
            dIn.close();
            dOut.close();
        }
    }
}
 
Ich gehe mal davon aus, dass die Variable "server" aus "StartServer" vom Typ "AnyConnectServer" ist, oder?

Evtl. hilft dann schon folgende Änderung:
Code:
private class StartServer implements ActionListener {

        @Override
        public void actionPerformed(ActionEvent e) {
                new Thread(server).start();
        }
    }:

server.run() führt die run Methode ja eben nicht nebenläufig, sondern quasi ganz normal aus.

Sonstige (davon unabhängige) Anmerkungen:

- Wieso speicherst du dir denn alle Clients in einem Array der Größe 1000 (z.B. auch im AnyConnectServer)?
Arrays benutzt man egtl. nur, wenn man vorher die "Größe" möglichst genau weiß. Im Normalfall sollte die Zahl also eher unbegrenzt sein. In jedem Fall würde ich, wenn du es schon so löst eine Liste benutzen und kein Array. Aus dieser könntest du u.a. auch Clients die wieder disconnecten ganz einfach wieder entfernen.

- Hast du bedacht, dass die accept() Methode blockiert?

- Generell gehe ich auch in die Richtung von Henni und würde empfehlen den Code umzuschreiben. (Musst dir auch nichts größeres dabei denken; das ist so weit eine gängige Praxis^^)

Speziell zu den letzten beiden Punkten kann ich dir noch folgendes Tutorial/Video - für einen gelungenen und einfachen Start - empfehlen: https://www.youtube.com/watch?v=bjt7NJMr624&list=PLJGP1apZeqS0EK3NbJ9knYQeXO7yj6TMI&index=20


Edit: Wenn man "AnyConnectServer" nicht das Interface Runnable implementieren lässt, sondern direkt von Thread erben lässt, sollte auch
Code:
private class StartServer implements ActionListener {

        @Override
        public void actionPerformed(ActionEvent e) {
                        server.start();
        }
    }:
funktionieren.

Wann genau wird egtl. der "ActionListener" von "StartServer" ausgelöst??

Es wäre glaub ich auch mal ganz nett deine main Methoden (Client+Server) respektive Haupt- bzw. Starterklassen zu sehen.
 
Zuletzt bearbeitet:
Hallo SilentBob_ danke für deine ausführliche Antwort.
Ich weiß das ein Array nicht passt und das eine Liste hier der richtige Ansatz ist, es gab auch mal eine Methode addThreadList.
Aufgrund von logischen Denkfehlern und Unverständlichkeit habe ich die aber erstmal bei Seite geschoben.

Dein Beispiel sollte denk ich funktionieren, ich habe eine Controller Klasse in der das Objekt server als Instanz gestartet wird sodass ich in einer inneren ActionListener Klasse auf die Methode run zugegriffen habe.

Der ActionListener sollte mittels Button in einer GUI ausgelöst werden, klappt auch alles mein Problem war das beim Buttondruck nur ein Thread gestartet wurde, was ja nicht mein Ziel war. Mein Ziel war eigentlich mittels Startbutton den kompletten Server zu starten der dann auf Port xyz horcht und dann Verbindungen akzeptiert.

Ich werde deine Lösung ausprobieren und natürlich weiter daran arbeiten und verbessern.
 
Ok, das mit der Controller- samt innerer ActionListener-Klasse klingt ein bisschen "abenteuerlich", ist aber ohne Code schwer zu beurteilen.^^

Per Druck auf den Button der GUI sollte es jetzt dann aber hoffentlich mit den vorgeschlagenen Änderungen funktionieren (sofern ich grad keinen Denkfehler hab ;)).

Ansonsten - wie gesagt - schreib den Code ruhig großzügig nochmal geeignet um.
So etwas kommt schon häufiger vor und im Endeffekt hat man dann meistens später weniger Arbeit und einen deutlich saubereren Code.

Ich geh mal davon aus du wirst uns hier ohnehin von deinen Ergebnissen berichten, nachdem du den neuen Code getestet hast.

Edit: Was mir grad noch einfällt...Da du ja mit einer GUI arbeitest (auch wenn's u.U. nur rudimentär ist), achte am besten von vornherein darauf größere Berechnungen etc. aus dem EDT auszulagern. Sonst handelst du dir evtl. mit einem UI-freeze o.ä. gleich die nächsten Probleme ein.
 
Zuletzt bearbeitet:
Ja ich schreibe mir gerade eine neue Server Runnable Klasse.

Es funktioniert mit deinem Codebespiel leider nicht, da .start(); immer angemeckert wird.
Es wird nach einer Methode start() in der AnyConnectServer Klasse verlangt, aber die AnyConnectServer Klasse implementiert doch Runnable sodass ich start() aufrufen kann, oder versteh ich was falsch ?

Wie dem auch sei ich versuch das ganze ja zu verstehen und schreibe mir deswegen gerade eine neue Klasse.

Achso mit dem ActionListener in meiner GUI Swing wohl bemerkt, gibt es eine Methode
Code:
    public void startServer(ActionListener l) {
        jButton1.addActionListener(l);
    }

die ich in meiner Controller Klasse dann aufrufe mittels der ActionListener Klasse von Oben.
Ihr dürft mich aber gerne steinigen, ich habe nie programmieren studiert und habe von daher keine wirkliche Ahnung von Herangehensweisen und Methoden sowas professionell um zu setzen.
 
Zuletzt bearbeitet:
DJ_We$t schrieb:
Es funktioniert mit deinem Codebespiel leider nicht, da .start(); immer angemeckert wird.
Es wird nach einer Methode start() in der AnyConnectServer Klasse verlangt, aber die AnyConnectServer Klasse implementiert doch Runnable sodass ich start() aufrufen kann, oder versteh ich was falsch ?

Ups, sorry mein Fehler. Dein AnyConnectServer implementiert ja das Runnable Interface und ist kein Thread.
Habe meinen ersten Beitrag noch einmal dahingehend korrigiert/editiert. Versuch's mal bitte damit.
 
Damit funktioniert es so wie mit meinem alten unangepassten auch.

[THEORIE]
Ich glaube ich habe ein allgemeines Verständnisproblem. Also was ich will ist, ich starte die Serveranwendung und die fängt an auf einem Port zu horchen. Sobald ich die Client Anwendung starte soll diese sich mit dem Server verbinden und eine ID zugeteilt bekommen und diese mit der IP in einer Datenbank abgelegt werden.

[PRAXIS]
Soweit so easy an meiner Umsetzung hapert es, denn ich kann mich immernur mit einem Client am Server anmelden, sobald der erste Client verbunden ist funktioniert auch der Datenaustausch zwischen Server und Client, starte ich nun einen zweiten Client wird dessen Verbindung solange geblockt bis ich manuell einen neuen Thread starte, was ich ja nicht will, denn der Thread soll automatisch gestartet werden, das quasi beliebig viele Clients sich anmelden können.
Es können sich ja auch beliebig viele Clients anmelden bzw. im Moment so viele wie ich mit dem Array vorgebe, das Thema hatten wa ja schon eben. Wenn ich in meiner ActionListener Klasse eine Forschleife einbaue die 1000 mal = Größe des Arrays Threads öffnet bei einem Klick das ist natürlich total unperformant und unprofessionell. Denn 1000 Threads gleichzeitig laufen lassen obwohl villt grad ma zwei gebraucht werden ist absoluter Unsinn.
 
Hm ok, dann bringt das so natürlich auch nichts.

Zwecks den Verständnisproblemen: Halte dich doch dann an den Pseudocode von Henni oder das verlinkte Video und überarbeite deine Architektur damit Schritt für Schritt von Grund auf am besten. Im Endeffekt sparst du dir damit vermutlich sogar insgesamt Zeit.

Alternativ könntest du natürlich a) mal den gesamten Source-Code hier posten, damit man den auf Fehler überprüfen kann, oder b) die Sache mit deiner for-Schleife ein wenig "eleganter" (aber immernoch wahnsinnig unsauber) lösen, indem du halt nicht einfach so 1000 Threads erzeugst, sondern eben jeweils nur die Erzeugung eines neuen Threads anstößt, sobald sich ein neuer Client verbindet.
 
So ich habe nun meinen Server etwas ungeschrieben und mein Server basiert jetzt minimal auf einem Threadpool Server.

Ich poste hier mal meinen Server Thread, denn ich habe Probleme die Streams zu schließen.
Ich weiß aber nicht wieso, angeblich wären die Statements unreachable.

Code:
public class ServerThread implements Runnable {

    protected Socket clientSocket;

    /**
     *
     * @param clientSocket
     */
    public ServerThread(Socket clientSocket) {
        this.clientSocket = clientSocket;
    }

    @Override
    public void run() {
        UUID uuid = UUID.randomUUID();

        try {
            // Input und Output Streams
            InputStream in = clientSocket.getInputStream();
            OutputStream out = clientSocket.getOutputStream();

            // DataInput und DataOutput Streams
            DataInputStream dIn = new DataInputStream(in);
            DataOutputStream dOut = new DataOutputStream(out);
            
            String line = null;

            while (true) {

                // Nachricht vom Client empfangen
                line = dIn.readUTF(); 
                // Nachricht an Client übermitteln
                dOut.writeUTF("[MSG]: " + line 
                        + " [ID]: " + uuid.toString()
                        + " [IP]: " + clientSocket.getInetAddress().toString());

                dOut.flush();
            }
            dIn.close();
            in.close();
            
            dOut.close();
            out.close();
            
        } catch (IOException e) {
            System.out.println(e);
        }
    }
}

Und dann ist da noch das Problem mit dem blockierten SwingWorker Thread aber dazu komme ich später.

EDIT:
Problem gelöst liegt natürlich an der While Schleife die unendlich durchläuft wegen TRUE
 
Zuletzt bearbeitet:
Zurück
Oben