في بعض الأحيان قد نجبر المستخدم على عدم فتح أكثر من نسخه من البرنامج ، وخاصه في البرامج التي تتصل مع قاعده البيانات والتي لا تقوم بعمل Commit الا مثلا عند الخروج من البرنامج أو أي برنامج أخر مثلا مثل برامج المنبهات والتي تعمل كل مده معينه .. أو لأي سبب أخر يريده المبرمج.
ولأن أي برنامج نقوم بتشغليه فسوف يعمل على VM خاصه به ، لذلك لا توجد طريقه مباشره لهذا الأمر وسوف نستعرض اشهر طريقتين ، أحدهما عن طريق socket ، والأخرى عن طريق FileLocking .
– قم بالأتصال بالسيرفر بالمنفذ 2000 ، في حال أتصل فمعناه أنه هناك نسخه تعمل ، فقم الأن باغلاق النسخه الجديده .
– في حال لم يتصل البرنامج بالسيرفر (معناه أنه لا يوجد سيرفر يعمل ) سوف ينفذ جزء Exception وهنا سوف نقوم بتشغيل السيرفر من داخل الException .كود بسيط للسيرفر :
// This MonitorServer will start when you open myApp // Wajdy Essam - 2008 import java.net.ServerSocket ; import java.net.Socket ; import java.io.IOException ; public class MonitorServer extends Thread { private ServerSocket serverSocket ; private Socket socket ; private static final int PORT = 5000 ; public void run () { try { serverSocket = new ServerSocket(PORT); while ( true ) { socket = serverSocket.accept(); // get client socket.close(); } } catch ( IOException e) { e.printStackTrace(); } } }
وهذا كود البرنامج :
// Wajdy Essam - 2008 import java.net.Socket ; import java.io.IOException ; import javax.swing.JOptionPane ; public class MyApp { public static void main (String args[]) { try { Socket socket = new Socket("localhost",5000); // if get connection in mean that server is open , it mean that is client is opening JOptionPane.showMessageDialog(null,"Their is Opening Vesrion of this Program", "Cannot Open More Than One Version",JOptionPane.ERROR_MESSAGE); System.exit(1); } catch (IOException e) { // here the server is not opening , so we open it now MonitorServer server = new MonitorServer(); server.start(); } // here application code // good Luck } }
نأخذ مثال لتطبيق الفكره ، وهو لبرنامج مفكره بسيط يقوم بحفظ وقرائه ملفات txt وبه خاصيه نسخ ولصق … المهم أنظر الصوره التاليه وستجد أننا نستطيع تشغيل أكثر من نسخه .. نريد الأن اضافه الطريقه التي تعلمناه قبل قليل عليه
ننظر الى الداله الرئيسيه :
public class JNotePad { public static void main (String args[]) { JNotePadFrame app = new JNotePadFrame(); app.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); app.setVisible(true); } }
نقوم الأن باضافه كود الأتصال في البدايه -أول سطر في البرنامج- :
public static void main (String args[]) { try { Socket socket = new Socket("localhost",5000); // if get connection in mean that server is open , it mean that is client is opening JOptionPane.showMessageDialog(null,"Their is Opening Vesrion of this Program", "Cannot Open More Than One Version",JOptionPane.ERROR_MESSAGE); System.exit(1); } catch (IOException e) { // here the server is not opening , so we open it now MonitorServer server = new MonitorServer(); server.start(); } JNotePadFrame app = new JNotePadFrame(); app.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); app.setVisible(true); }
وها هو برنامجنا الأن يصيح عندما تقوم بتشغيل أكثر من نسخه :
على العموم ، الطريقه ليست سريعه كما في الطريقه التاليه ، ولكنها تفي بالغرض !!
الطريقه الثانيه The file lock technique :
تعتمد هذه الطريقه على java.nio.channels وطريقتها هي انشاء ملف في مجلد user.home وعمل lock عليه channel.tryLock() ، وفي حاله لم يستطع البرنامج عمل lock وذلك بسبب وجود نسخه تعمل سوف يحصل Exception من نوع OverlappingFileLockException . وعند اغلاق البرنامج يجب أن نزيح هذا المقبض على الملف وذلك باستخدام shutdown hook . هذا الكود ، قم بتشغيله وستجد أنه أسرع من الطريقه الأولى ..
الملف الأول JustOneLock وهو الخاص بوضع الLock . ونستطيع استخدامه مع أي برنامج أخر بلا تغيير .
import java.io.*; import java.nio.channels.*; public class JustOneLock { private String appName; private File file; private FileChannel channel; private FileLock lock; public JustOneLock(String appName) { this.appName = appName; } public boolean isAppActive() { try { file = new File(System.getProperty("user.home"), appName + ".tmp"); channel = new RandomAccessFile(file, "rw").getChannel(); try { lock = channel.tryLock(); } catch (OverlappingFileLockException e) { // already locked closeLock(); return true; } if (lock == null) { closeLock(); return true; } Runtime.getRuntime().addShutdownHook ( new Thread() { // destroy the lock when the JVM is closing public void run() { closeLock(); deleteFile(); } } ); return false; } catch (Exception e) { closeLock(); return true; } } private void closeLock() { try { lock.release(); } catch (Exception e) { } try { channel.close(); } catch (Exception e) { } } private void deleteFile() { try { file.delete(); } catch (Exception e) { } } }
برنامجنا الذي نريده أن يعمل مره واحده :
public class JustOneTest { public static void main(String[] args) { new JustOneTest().test(); } void test() { JustOneLock ua = new JustOneLock("JustOneId"); if (ua.isAppActive()) { System.out.println("Already active."); System.exit(1); } else { System.out.println("NOT already active."); try { while(true) { try { System.out.print("."); Thread.sleep(5 * 60); } catch(Exception e) { e.printStackTrace(); } } } catch (Exception e) { } } } }
نقوم بالتعديل الأن في برنامج المفكره لكي نستخدم هذ الطريقه ، وسوف تكون الداله الرئيسيه بهذا الشكل :
public static void main (String args[]) { JustOneLock ua = new JustOneLock("JustOneId"); if (ua.isAppActive()) { JOptionPane.showMessageDialog(null,"Their is Opening Vesrion of this Program","Cannot Open More Than One Version", JOptionPane.ERROR_MESSAGE); System.exit(1); } JNotePadFrame app = new JNotePadFrame(); app.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); app.setVisible(true); }
أعتقد أن تقنية file lock أسهل و أسرع و هذا ما أتبعة منذ زمن.
أعجبنى جدا موضوع ال ShutdownHooks
لكن لي سؤال … في POSIX systems فإن نظام التشغيل يسمح لأي شخص بالتعديل (حتى المسح) للملفات و إن كانت مستخدمه بعمليات أخري.
فما الوضع إذن إن قمنا بتشغيل البرنامج و من ثم حذفنا ملف ال Lock و من ثم أردنا تشغيل نسخه اخرى من البرنامج؟ هل ستعمل النسخه الأخري؟
موضوع جميل كالعادة، لكن أظن أن هذه الطرق قد لا تنجح بعض الأحيان وخصوصا طريقة ال Socket لأنك لا تعلم هل البورت الذي اخترته مستعمل أم لا في جهاز المستخدم كما أن طريقة ال FileLock لا تعمل في بعض أنظمة التشغيل مثل Solaris.
في رأيي يمكن برمجة Luncher يقوم بتشغيل البرنامج ويحتفظ بعدد ال instances المشغلة أو يمكن البحث في ال TaskList عن اسم البرنامج فأذا كان موجود تظهر الرسالة و اذا لم يكن موجود يتم تشغيله.