Verhindern das Programm mehrmals geöffnet wird

Status
Nicht offen für weitere Antworten.

Spezi

Aktives Mitglied
Hi,

ich habe ein kleines programm, allerdings lässt es sich mehrmals starten und das möchte ich gerne verhindern,
dass das programm mehrmals geöffnet werden kann.

wie stelle ich das an?


gruß
sebastian
 
eine Datenbank oder einen Netzwerkport oder eine Datei im Dateisystem oder vielleicht bei Windows irgendwas in der Registry blockieren,

bei jedem Programmstart dann prüfen ob die Ressource schon vergeben ist
 
Am allerbesten geht das vielleicht, in dem du via Sockets einen Port belegst. Wenn dieser Port belegt ist, läuft eben schon eine andere Instanz.
 
Das Prinzip mal quick and dirty:
Java:
import javax.swing.*;
import java.net.*;
import java.io.*;

public class OnlyOneInstance {
   private ServerSocket ss;

   public OnlyOneInstance() {
      try {
         ss = new ServerSocket(65432);
         System.out.println("Port wurde gebunden");
      }
      catch(IOException e) {
         System.out.println(e.getMessage());
      }

      if(ss != null) {
         JFrame f = new JFrame("Test-Frame");
         f.setSize(600, 400);
         f.setLocationRelativeTo(null);
         f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
         f.setVisible(true);
      }
   }

   public static void main(String[] args) {
      new OnlyOneInstance();
   }
}

Damit kann man aber z.B. ein unsichtbares Fenster nicht in den Vordergrund holen, weil man keinen Zugriff darauf hat.
Das ginge aber bspw. mit RMI.
 
Hi,

und wie kann man das bereits geöffnete Programm in den Vordergrund holen und ihm vielleicht ein paar Parameter übergeben? Man müsste ja irgendeine Verbindung zum bestehenden Programm aufbauen, wie Remoting, oder sowas.

Es ginge natürlich, in dem das Programm in eine Datei seinen Port schreibt und man an den Port einige Informationen sendet. Aber geht das auch irgendwie anders mit Java?

Ansonsten finde ich das mit dem Port öffnen etwas ekelig 🙂 Kann ja nicht jedes Programm ständig irgendwelche Ports öffnen. Da ist das mit der Datei vielleicht besser. Dann müsste ich über die Datei mit einem bereits geöffneten Programm kommunizieren.

Gruß

Mike
 
Hi,

ich möchte das Thema gerne noch einmal aufgreifen, weil ich immer noch keine optimale Lösung für mein angesprochenes Problem habe.

Folgendes soll passieren: Wenn mein Programm gestartet wird und schon eine Instanz läuft, soll an das Programm eine Nachricht geschickt werden. Es gibt ja zahlreiche Anwendungsfälle dafür.

Wie löse ich das am geschicktesten? Kann ich irgendwie vielleicht eine Queue initialisieren, wenn das Programm zum ersten mal gestartet wird? Und jeder weitere Programmaufruf schreibt auf die Queue? Oder muss ich auf klassische Client-Server-Architektur zurückgreifen und der Anwendung an einen Port Daten schicken?

Und in eine Datei schreiben, die bereits lesend geöffnet ist wird nicht wirklich funktionieren, vermute ich.

Hat jemand eine Idee?

Gruß

Mike
 
Die einfachste Möglichkeit besteht darin,
L-ectron-X's Vorgabe so umzuwandeln, daß
bei einem Fehlschlag beim Aufbau des ServerSockets,
wenn also bereits eine Instanz am Laufen ist, diese
Instanz über den definierten Port die Parameter
zu übergeben.

Das Programm muß dann selbstverständlich
in einem separaten Thread auf Eingänge auf
diesen Port lauschen; quasi also auch wie
ein Server arbeiten.
 
Ah fein, merci. Gibts vielleicht auch sowas, wie eine Queue? JMS oder so? Oder ist das zuviel des Guten? Unter Windows kann man mit .NET für solche Sachen die Message Queue unter Windows verwenden, oder wie die heißt.

Gruß

Mike
 
Das war halt so meine Lösung (sollte auch funktionieren, ist aber jetzt nicht großartig getestet). Inwiefern Queue?
 
Hi,

naja ich dachte an eine Message Queue. Die bereits laufende Anwendung liest von der Queue und die neue schreibt die Daten auf die Queue. Ist der Lösung mit dem offnen Port ähnlich, aber es muss halt kein Port geöffnet werden.

Finde Deine Lösung aber sehr schön, wie sie nach freien Ports sucht und diesen in die Datei schreibt. Was mir hier nicht ganz klar ist: Du legst ja diese Datei an, in der der Port steht. Wenn die existiert, dann läuft die Anwendung schon. Aber diese Datei wird nie gelöscht, oder?

Gruß

Mike
 
Also, die Datei wird nie gelöscht, das ist soweit korrekt. Weil ich denke mal, wenn einmal ein freier Port gefunden wurde, muss ich nicht jedesmal neu Suchen 😉 .

Ich schau also zuerst ob eine Datei existiert. Existiert sie nicht, ist klar, dass das Programm nicht gestartet ist. Existiert sie, wird überprüft ob ein ServerSocket an dem ausgelesenen Port lauscht. Lauscht dort einer, wird eine Message geschickt. Bekommt man dann vom Server einen bestimmten Wert zurückgeliefert ist klar, dass die Anwendung bereits gestartet wurde.

Lauscht kein Server am Port, wird ein neuer Server auf diesen Port gestartet. Lauscht ein Server an dem Port, aber die Anwendung bekommt keine Antwort wird ein neuer Port gesucht, dieser in die Datei gespeichert und ein Server auf diesen Port gestartet.

So wird sichergestellt, dass die Anwendung auch gestartet wird, wenn eine andere Anwendung an diesem Port lauscht.
 
Statt per Sockets oder JMS könnte man RMI verwenden, um Daten an die Hauptinstanz der Anwendung weiterzureichen.
 
L-ectron-X hat gesagt.:
Am allerbesten geht das vielleicht, in dem du via Sockets einen Port belegst. Wenn dieser Port belegt ist, läuft eben schon eine andere Instanz.

Funktioniert bei einer lokal gestarteten Applikation wunderbar, aber offenbar nicht bei Applikationen, die per Webstart installiert wurden. Selbst, wenn diese signiert sind und mit
Code:
  <security>
    <all-permissions/>
  </security>
im JNLP stehen. Gibt's da eine alternative Methode, um mehrfache Programminstanzen zu verhindern?
 
Kann ich nicht bestätigen. Mein RSS-Reader Blogbridge ist auch eine Webstart-Anwendung und die meckert sehrwohl, wenn ich sie versehentlich versuche doppelt zu starten und benutzt ebenfalls den Port-Kniff. Ich sehe auch technisch keinen Grund warum eine Webstart-Anwendung mit Netzwerkzugriff anders funktionieren sollte.
 
Bei einer Win32-Anwendung kann man ja einen von allen Instanzen der Anwendung gemeinsam benutzen Variablenbereich für sowas benutzen, der dann allerdings in einer Dll liegen muß. Das geht ja nun leider nicht in Java.
Als simpelste Lösung würde sich eine Datei anbieten, auf die man z.B. den Schreibschutz setzt, wenn eine Instanz der Anwendung gestartet wurde und wieder löscht, wenn sie beendet wird. Somit kann eine zweite Instanz beim Start den Schreibschutz erkennen und sich wieder beendet.
Falls allerdings Parameter übergeben werden sollen (z.B. Dokument mit der laufenden Instanz öffnen statt mit der neu gestarteten), wird es ein wenig komplizierter. Dann wäre die Portlösung in abgewandelter Form wohl tatsächlich erste Wahl, weil sie eine direkte Benachrichtigung der laufenden Instanz ohne Polling ermöglicht.
 
Wenn wir mal annehmen, dass der Rechner abstürzt oder die Stadtwerke den nicht bezahlten Stromanschluss abschalten, während gerade dieses Programm läuft, ist es nicht mehr ohne weiteres möglich das Programm später zu starten, weil der Schreibschutz noch bestehen bleibt. Ganz anders da die Lösung mit Öffnung eines Ports...

Ich persönlich finde auch die Lösung mit RMI ziemlich elegant.
 
Wenn du ein bischen Geduld hast, kann ich in einigen Tagen ein Beispiel posten. Im Moment bin ich voll im Weihnachts-/Jahresendstress, da hab ich nicht viel Zeit. Mal sehen, vielleicht mach ich auch einen FAQ-Beitrag draus, wird ja öfter mal gefragt.
 
Ich weiß nicht, ob man sich gleich "outet" wenn man Begriffe verwendet, die einem Zwischen-den-Zeilen-Leser die Herkunft verraten.
Ich bin auch nicht beim Teufel groß geworden...
 
Nicht böse sein! Deine Wortkonstruktion ist mir nur ins Auge gestoßen
und hat ein paar meiner Gehirnzellenverbindungen reanimiert! 😎
 
Bin nicht böse. 🙂

Ich hab mal zwei Klassen gekritzelt, die in kleinem Stil demonstrieren, wie man mit Hilfe von RMI ein Programm so schreiben kann, dass davon wirklich nur noch eine Instanz gestartet werden kann. Man kann das Beispiel noch verfeinern, wenn man noch die Namen der Anwendung mit dem RMI-Port koppelt.

Ein stinknormaler JFrame mit einer JTextArea drin.
Java:
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;

public class RMIFrame extends JFrame {
   private JMenu file, properties;
   private JMenuItem exit;
   private JTextArea textArea;
   
   public RMIFrame(String title) {
      super(title);
      setSize(500, 400);
      setLocationRelativeTo(null);
      setDefaultCloseOperation(WindowConstants.HIDE_ON_CLOSE);
      
      JMenuBar bar = new JMenuBar();
      
      file = new JMenu("Datei");
      
      exit = new JMenuItem("Beenden");
      exit.addActionListener(new ActionListener() {
         public void actionPerformed(ActionEvent e) {
            exit();
         }
      });
      file.add(exit);
      
      bar.add(file);
      
      setJMenuBar(bar);

      textArea = new JTextArea();
      add(new JScrollPane(textArea));
      
      setVisible(true);
   }
   
   private void exit() {
      int result = JOptionPane.showConfirmDialog(this,
         "Möchten Sie die Anwendung wirklich beenden?",
         "Programm Beenden",
         JOptionPane.YES_NO_OPTION);

      switch(result) {
         case JOptionPane.YES_OPTION:
            System.exit(0);
      }
   }
}

RMI-Server und -Client in einem. Diese Klasse sorgt dafür, dass von RMIFrame nur noch eine Instanz gestartet werden kann.
Java:
import java.rmi.*;
import java.rmi.server.*;
import java.rmi.registry.*;

public class Loader extends UnicastRemoteObject implements Loadable {
   private RMIFrame frame;
   
   public Loader() throws RemoteException {
      this(Registry.REGISTRY_PORT);
   }

   public Loader(int port) throws RemoteException {
      try {
         //1. RMIRegistry starten
         LocateRegistry.createRegistry(port);

         //2. Registrieren des Servers bei RMIRegistry
         Naming.rebind("rmi:///Server", this);

         //3. Erzeugen und starten der Anwendung
         frame = new RMIFrame("RMIFrame");
      }
      catch(java.net.MalformedURLException e) {
         e.printStackTrace();
      }
      catch(ExportException e) {
         try {
            //Die RMIRegistry nach der Remote-Referenz des RMI-Servers durchsuchen
            Loadable loader = (Loadable)Naming.lookup("rmi://localhost/Server");
            loader.load();
         }
         catch(NotBoundException nbe) {
            nbe.printStackTrace();
         }
         catch(java.net.MalformedURLException murle) {
            murle.printStackTrace();
         }
         catch(RemoteException re) {
            System.out.println(re.getMessage());
         }
      }
   }
   
   public void load() throws RemoteException {
      frame.setVisible(true);
   }
   
   public static void main(String[] args) {
      try {
         new Loader(); //ein benutzerdefinierter Port als Argument möglich
      }
      catch(RemoteException e) {
         e.printStackTrace();
      }
   }
}

Das Remote-Interface:
Java:
import java.rmi.*;

public interface Loadable extends Remote {
   public void load() throws RemoteException;
}

Erklärung zum Test:
Wenn man nun das Fenster über den Schließen-Knopf am Fenster schließen will, wird das Programm zu Demonstrationszwecken nur unsichtbar gemacht, es läuft im Hintergrund weiter. Das kann auch im TaskManager beobachtet werden.
Wenn man nun das Programm ein weiteres Mal starten will, wird immer nur das bereits laufende Programm wieder sichtbar gemacht.
"Richtig" beenden kann man dieses Programm über den Menüeintrag "Beenden" unter "Datei".
 
Moin L-ectron-X,
ich war mal so frei und habe deinen Code etwas überarbeitet, um eine allgemeine und (hfftl) korrekte Klasse für das Problem zu erstellen. Deine Lösung basiert ja auf RMIFrame, d.h. auf einer speziellen Klasse und somit auf einem speziellen Programm.

Java:
public class SingleInstanceController extends UnicastRemoteObject {

	public SingleInstanceController(String guid, SingleInstanceListener listener)
			throws RemoteException {
		super();

		try {
			// 1. RMIRegistry starten
			LocateRegistry.createRegistry(Registry.REGISTRY_PORT);
		} catch (RemoteException e) {
			System.out.println("RemoteException");
		}
		
		try {
			// 2. Registrieren des Servers bei RMIRegistry
			Naming.bind("rmi:///"+guid, this);

			// 3a. Signalisiere "Erste Instanz"
			listener.singleInstanceSignaled(true);

		} catch (MalformedURLException e) {
			System.out.println("MalformedURLException");
		} catch (AlreadyBoundException e) {
			System.out.println("AlreadyBoundException");
			// 3b. Signalisiere "Zusätzliche Instanz"
			listener.singleInstanceSignaled(false);
		}
	}
}
Java:
public interface SingleInstanceListener {

	void singleInstanceSignaled(boolean isSingleInstance);
}

Hierbei ist jetzt zu sehen, dass das Auslösen einer Remote-/ExportException durch
Java:
LocateRegistry.createRegistry(port);
noch kein Indikator für eine zweite Instanz eines bestimmten Programmes ist! Die Exception wird ausgelöst, wenn ein Port bereits belegt ist. Dies kann auch geschehen, wenn zwei unterschiedliche Programme, die die Klasse SingleInstanceController verwenden, jeweils einmal gestartet werden.

Zur Unterscheidung verschiedener Programme muss daher dem Konstruktor ein eindeutiger Programmname per Parameter String guid übergeben werden.

Ich muss gestehen, ich habe nur feststellen können, DASS die o.g. Lösung soweit funktioniert. Ich wusste vorher nichts vom RMI und weiß jetzt auch nicht viel mehr. Damit will ich sagen, dass ich keine Ahnung habe,
  • - wie viele verschiedene Server per Naming.bind registriert werden können
  • - ob die registrierten Server bei einem System-/Programmabsturz gelöscht werden (Ich hoffe es einfach mal)
  • - ob die Firewall iwas mit den RemoteObjekten zu tun hat.
Kurz gesagt: Ich weiß nicht, ob die Nachteile, die hier erläutert werden, in dieser Lösung alle entfallen. Zudem weiß ich nichts über ggf. weitere Nachteile durch RMI.
Daher würde ich gern mal einige Experten aus diesem Forum zu Wort kommen lassen. Ich wäre an jeglicher Kritik erfreut.

Gruß Nogge
 
Schön, da hast du einen interessanten und wichtigen Aspekt umgesetzt und meine Gedanken fortgeführt. :toll:
 
Status
Nicht offen für weitere Antworten.

Zurück
Oben