Die grundlegende Logik wurde bereits erläutert, weshalb ich an dieser Stelle nicht noch einmal alles wiederholen werde. Stattdessen sollten Sie anhand der vorhergehenden Abschnitte und der Kommentare im Code die Funktionen der Klasse ohne Probleme nachvollziehen können.
package de.jbb.sic;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
/**
* SingleInstanceController ist eine Klasse, mit der ueberprueft werden kann, ob
* eine andere, mit dem SingleInstanceController registrierte, Klasse bereits auf
* dem System laeuft (Port- und Klassenabhaengig). Sollte eine solche Klasse
* bereits ausgefuehrt worden sein, besteht die Moeglichkeit mit dieser zu
* kommunizieren.
*/
public class SingleInstanceController {
private boolean result = false;
private File file = null;
private ObjectOutputStream oos = null;
private ObjectInputStream ois = null;
private ServerSocket server = null;
private Socket client = null;
private ArrayList<ApplicationStartedListener> listener = null;
private String appname = null;
/**
* Konstruktor für ein neues SingleInstanceController Objekt. Uebergeben
* wird ein File-Objekt, dass auf den Ort verweist, wo der aktuelle Port
* gespeichert werden soll. Zusaetzlich wird eine eindeutige
* Programmbezeichnung uebergeben (z. B. package + classenname), anhand der
* identische Applikationen identifiziert werden koennen.
*
* @param file Speicherort
* @param masterclass eindeutige Programmbezeichnung
* @see SingleInstanceController(String)
*/
public SingleInstanceController(File file, String appname) {
this.file = file;
this.appname = appname;
this.listener = new ArrayList<ApplicationStartedListener>();
}
/**
* Konstruktor für ein neues SingleInstanceController Objekt. Es wird ein
* default Pfad für den Port erstellt. Es wird eine eindeutige
* Programmbezeichnung uebergeben (z. B. package + classenname), anhand der
* identische Applikationen identifiziert werden koennen. Dieser Konstruktor
* ist nicht zu empfehlen da auch andere Applikationen, die den
* SingleInstanceController verwenden, auf dieses File zugreifen koennten.
*
* @param masterclass aufrufende Klasse
* @see SingleInstanceController(File, String)
*/
public SingleInstanceController(String appname) {
this(new File(System.getProperty("java.io.tmpdir") + "/923jhakE53Kk9235b43.6m7"), appname);
}
/**
* Fuegt einen ApplicationStartedListener hinzu
*
* @param asl
*/
public void addApplicationStartedListener(ApplicationStartedListener asl) {
this.listener.add(asl);
}
/**
* Entfernt einen ApplicationStartedListener
*
* @param asl
*/
public void removeApplicationStartedListener(ApplicationStartedListener asl) {
this.listener.remove(asl);
}
/**
* Ueberprueft, ob die Applikation schon registriert wurde und somit bereits
* ausgefuert wird
*
* @return true falls sichergestellt werden kann, dass die Applikation
* bereits laeuft
*/
public boolean isOtherInstanceRunning() {
if (!this.file.exists()) {
return false;
}
return sendMessageToRunningApplication(new ClassCheck(this.appname));
}
/**
* Schickt der registrierten Applikation eine Nachricht als Object.
*
* @param obj das zu sendende Objekt
* @return true falls die Nachricht erfolgreich uebermittelt wurde
*/
public boolean sendMessageToRunningApplication(final Object obj) {
this.result = false;
try {
this.client = new Socket("localhost", getPortNumber());
// In einem neuen Thread kommunizieren, um einen Deadlock zu verhindern
new Thread(new Runnable() {
public void run() {
try {
SingleInstanceController.this.oos = new ObjectOutputStream(SingleInstanceController.this.client.getOutputStream());
SingleInstanceController.this.ois = new ObjectInputStream(SingleInstanceController.this.client.getInputStream());
SingleInstanceController.this.oos.writeObject(obj);
SingleInstanceController.this.oos.flush();
SingleInstanceController.this.result = SingleInstanceController.this.ois.readBoolean();
}
catch (IOException e) {
SingleInstanceController.this.result = false;
}
}
}).start();
// Falls nach 1 Sekunde keine Antwort = Server nicht erreichbar
for (int i = 0; i < 10; i++) {
if (this.result == true) {
break;
}
try {
Thread.sleep(100);
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
this.client.close();
return this.result;
}
catch (IOException e) {
return false;
}
}
/**
* Registriert diese Applikation als gestartet. Sollte die Applikation
* nochmal gestartet werden, so kann diese registrierte Applikation darueber
* informiert werden
*
* @return true wenn die Applikation erfolgreich registriert wurde
*/
public boolean registerApplication() {
try {
if (!this.file.exists()) {
if (!this.file.getParentFile().mkdirs() && !this.file.getParentFile().exists()) {
return false;
}
if (!this.file.createNewFile()) {
return false;
}
}
BufferedWriter wuffy = new BufferedWriter(new FileWriter(this.file));
int port = getFreeServerSocket();
if (port != -1) {
startServer();
}
wuffy.write(String.valueOf(port));
wuffy.close();
return true;
}
catch (IOException e) {
return false;
}
}
/**
* Listener informieren, dass Nachricht angekommen ist
*
* @param obj Objekt fuer die Listener
*/
protected void messageArrived(Object obj) {
for (ApplicationStartedListener asl : this.listener) {
asl.messageArrived(obj);
}
}
/**
* Listener informieren, dass selbe Applikation gestartet wurde und eine
* Verbindung aufgebaut hat
*/
protected void applicationStartet() {
for (ApplicationStartedListener asl : this.listener) {
asl.applicationStarted();
}
}
/**
* Listener informieren, dass eine andere Applikation versucht hat sich
* anzumelden
*/
protected void foreignApplicationStarted(String name) {
for (ApplicationStartedListener asl : this.listener) {
asl.foreignApplicationStarted(name);
}
}
/**
* Ermittelt die zuletzt eingetragene Port-Nummer
*
* @return die zuletzt eingetragene Port-Nummer
*/
private int getPortNumber() {
try {
BufferedReader buffy = new BufferedReader(new FileReader(this.file));
int port = Integer.parseInt(buffy.readLine().trim());
buffy.close();
return port;
}
catch (Exception e) {
return -1;
}
}
/**
* Startet den Server, um mit spaeter gestarteten Applikationen zu
* kommunizieren
*/
private void startServer() {
new Thread(new Runnable() {
public void run() {
while (true) {
try {
SingleInstanceController.this.client = SingleInstanceController.this.server.accept();
if (SingleInstanceController.this.client.getInetAddress().isLoopbackAddress()) {
new Thread(new Runnable() {
public void run() {
try {
SingleInstanceController.this.oos = new ObjectOutputStream(SingleInstanceController.this.client.getOutputStream());
SingleInstanceController.this.ois = new ObjectInputStream(SingleInstanceController.this.client.getInputStream());
Object obj = SingleInstanceController.this.ois.readObject();
if (obj instanceof ClassCheck) {
if (obj.toString().equals(SingleInstanceController.this.appname)) {
SingleInstanceController.this.oos.writeBoolean(true);
applicationStartet();
}
else {
SingleInstanceController.this.oos.writeBoolean(false);
foreignApplicationStarted(obj.toString());
}
}
else {
messageArrived(obj);
SingleInstanceController.this.oos.writeBoolean(true);
}
SingleInstanceController.this.oos.flush();
SingleInstanceController.this.client.close();
}
catch (IOException e) {
e.printStackTrace();
}
catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}).start();
}
}
catch (IOException e) {
e.printStackTrace();
}
}
}
}).start();
}
/**
* Ermittelt einen freien Port zwischen 2000 und 10000
*
* @return den 1. freien Port oder falls kein freier Port gefunden wurde -1
*/
private int getFreeServerSocket() {
for (int i = 2000; i < 10000; i++) {
try {
this.server = new ServerSocket(i);
return i;
}
catch (IOException ignore) {}
}
return -1;
}
}
Ein Beispiel zur Implementierung des SingleInstanceControllers finden Sie auf der nächsten Seite.
