I have been working on polishing Knute's code. Mostly I have been
adding informal comments and renaming to help myself understand how it
works.
I have not run it yet, but it is getting close to a test.
see
https://wush.net/svn/mindprod/com/mindprod/singleinstance/SingleInstance.java
I have added UUIDs to break the tie for equal start times.
I have added app ids so different apps can share the same port.
I have added the ability avoid several different apps.
I have added the ability to permit two apps to run, so long as they
run on different files.
Below is what I ended up with. I'm curious about the UUID. How do you
create a time based UUID? And does it have less granularity that
currentTimeMillis?
package com.knutejohnson.classes;
import java.awt.*;
import java.io.*;
import java.lang.reflect.*;
import java.net.*;
import java.nio.charset.*;
import java.util.*;
import javax.swing.*;
public class Exclusive implements Runnable {
private static final int port = 35798;
private static final String addr = "228.237.246.255";
private final long myTime;
private final String name;
private final InetAddress group;
private final MulticastSocket socket;
private final String token;
private volatile boolean runFlag;
private volatile Thread thread;
public Exclusive(String name) throws IOException {
myTime = System.currentTimeMillis();
this.name = name;
if (name.indexOf(",") >= 0)
throw new IllegalArgumentException(
"Comma character not allowed in name");
group = InetAddress.getByName(addr);
socket = new MulticastSocket(port);
socket.joinGroup(group);
token = String.format("%s,%d",name,myTime);
}
public void start() throws IOException {
if (thread == null || !thread.isAlive()) {
runFlag = true;
thread = new Thread(this);
thread.start();
sendToken();
}
}
public void sendToken() throws IOException {
byte[] buf = token.getBytes(StandardCharsets.US_ASCII);
DatagramPacket dp = new DatagramPacket(buf,buf.length,group,port);
socket.send(dp);
}
@Override public void run() {
while (runFlag) {
try {
// receive their time
byte[] buf = new byte[64];
DatagramPacket dp = new DatagramPacket(buf,buf.length);
socket.receive(dp);
String recStr = new String(dp.getData(),dp.getOffset(),
dp.getLength(),StandardCharsets.US_ASCII);
String[] arr = recStr.split(",");
// if names don't match there is nothing to do
if (!name.equals(arr[0]))
continue;
long theirTime = Long.parseLong(arr[1]);
// if we are seeing our own packet, do nothing
if (theirTime == myTime) {
// if their time is before my time, we need to shut down
} else if (theirTime < myTime) {
stop();
// can't use invokeLater()
try {
EventQueue.invokeAndWait(new Runnable() {
public void run() {
JOptionPane.showMessageDialog(null,
"Another Copy of this Program is Already
Running",
"Start
Inhibited",JOptionPane.ERROR_MESSAGE);
}
});
} catch (InterruptedException|
InvocationTargetException ex) {
ex.printStackTrace();
}
shutdown();
// if their time is after my time, send out my time
} else if (theirTime > myTime) {
sendToken();
}
} catch (IOException|NumberFormatException ex) {
ex.printStackTrace();
stop();
}
}
}
private void stop() {
if (thread != null && thread.isAlive()) {
runFlag = false;
if (socket != null)
socket.close();
}
// signal the waitFor() method to stop waiting
synchronized (this) {
notify();
}
}
// wait for up to two seconds to see if any other copy responds
// returns true if no other copy is running.
public synchronized boolean waitFor() throws InterruptedException {
wait(2000);
return runFlag;
}
public void shutdown() {
System.exit(0);
}
public static void main(String[] args) {
try {
Exclusive e = new Exclusive("Test");
e.start();
if (e.waitFor())
System.out.println("no other copy running!");
else
System.out.println("another copy is running");
} catch (IOException|InterruptedException ex) {
// probably don't want to start if you get an exception either
ex.printStackTrace();
}
}
}