Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > Java: Общие вопросы > Как избежать запуска второй копии программы


Автор: Su_ 12.1.2010, 00:23
Добрый день.. Есть задача,сделать так что бы при втором запуске java-приложение, оно блокировалось и перекидывало фокус на уже открытое .. 

Разделил на два этапа 1)НЕ допустить запуска второй копии 2)Перебросить фокус

1)Почитал в интернете и тут, нашел два способа, это занимать порт и лочить файл.Т.к я и так занимаю порт под нужные цели, первым вариантом пользоваться не хочется . 2 )Нашел в интернете такую реализацию:
Код

try {
    // Get a file channel for the file
    File file = new File("filename");
    FileChannel channel = new RandomAccessFile(file, "rw").getChannel();

    // Use the file channel to create a lock on the file.
    // This method blocks until it can retrieve the lock.
    FileLock lock = channel.lock();

    // Try acquiring the lock without blocking. This method returns
    // null or throws an exception if the file is already locked.
    try {
        lock = channel.tryLock();
    } catch (OverlappingFileLockException e) {
        // File is already locked in this thread or virtual machine
    }

    // Release the lock
    lock.release();

    // Close the file
    channel.close();
} catch (Exception e) {
}



НО что в винде что в линуксе,всё время пишет что файл(ТХТ) залочен,т.е уходит на исключение
Что тут не так?

2)Можно ли вообще сделать фокусировку на приложение программно?


Автор: Su_ 12.1.2010, 00:47
Первый вопрос решил)

Автор: Su_ 12.1.2010, 02:03
По поводу первого пункта, может кому пригодится:
В начало программы:
Код

 accessTheLockedFile();
     if(number_of_copy==0)
     {
            try {
                   lock_file();
        }catch(IOException f)
        {
            System.out.println("error ini");
                 prints("Запущена вторая копия");
        }
     }else if(number_of_copy==1)
     {
         prints("Это ПРОГРАММА УЖЕ ЗАПУЩЕННА!,закройти приложение и откройте 'ALT+TAB' уже запущенную программу");
         System.exit(0);
     }
     if(block)
     {
         System.exit(0);
     }


методы 
Код

  public void lock_file() throws FileNotFoundException, IOException
    {
  RandomAccessFile file = null;
        FileLock fileLock = null;
        try
        {
         //    prints("1");
            file = new RandomAccessFile(PathtoLockFile, "rw");
          //     prints("2");
            FileChannel fileChannel = file.getChannel();
  // prints("3");
            fileLock = fileChannel.tryLock();
           //    prints("4");
            if (fileLock != null){
               prints("Приложение запущенно");
               number_of_copy=1;
              accessTheLockedFile();
            }
        }finally{
            if (fileLock != null){
             //    prints("5");
                 block=false;

            }
        }
    }

     static void accessTheLockedFile(){

        try{
            FileInputStream input = new FileInputStream(PathtoLockFile);
            int data = input.read();
            System.out.println(data);
        //    prints("ФАЙЛ РАЗБЛОКИРОВАН!");
            number_of_copy=0;
        }catch (Exception exception){
            exception.printStackTrace();
          //    prints("ФАЙЛ ЗАБЛОКИРОВАН!");
            number_of_copy=1;
        }
    }


Работает и в win и в lin, при чем: В винде блокирует файлы нормально, в Линуксе файл не блокирует , но повторно войти в 
Код

     if (fileLock != null){
             //    prints("5");
                 block=false;

            }

не может. Для этого еще есть проверка флага block. Почему не блокирует файл я так и не понял, но это работает.

Осталось разворачивать уже запущенное приложение вместо запуска ещё одной копии (lin) ???

Автор: Su_ 12.1.2010, 02:19
увы этот алгоритм дает сбой в Linux.. отсюда вопрос как заблокировать файл в linux?????????7

Автор: dobrolub 12.1.2010, 02:51
Thread t = new Thread() {
  public void run() {
      new FileInputStream("zzz").getChannel().tryLock(); 
   }
};

t.start();
t.join(100);//даём 100 ms на захват лока.

try {
  t.interrupt();
} catch (Exception e) {
 //обработка 
}

Автор: Su_ 12.1.2010, 03:01
если это повесить на кнопку 
Код

    try {
            //chek_alarm();
    new FileInputStream("zzz").getChannel().tryLock();

    }catch(IOException f)
        {
          System.out.println("Не могу заблокировать");
        }
            //tcp_socket_busy=false;        // TODO add your handling code here:
    } 

вылетает на исключение ,при чем не на IOException


А вообще линукс поддерживает блокировку файлов?

Добавлено через 6 минут и 25 секунд
для запуска
Код


import java.io.FileInputStream;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

/**
 *
 * @author Admin
 */
public class NewClass {
Thread t = new Thread() {
  public void run() {
            try {
                new FileInputStream("zzz").getChannel().tryLock();
            } catch (IOException ex) {
                Logger.getLogger(NewClass.class.getName()).log(Level.SEVERE, null, ex);
            }
   }
};
NewClass() throws InterruptedException
{
t.start();
t.join(100);//даём 100 ms на захват лока.

try {
  t.interrupt();
} catch (Exception e) {
 //обработка
}
}

 public static void main(String[] args) throws InterruptedException
  {
     NewClass f=new NewClass();
  }
}



после запуска
Код

run:
Exception in thread "Thread-0" java.nio.channels.NonWritableChannelException
        at sun.nio.ch.FileChannelImpl.tryLock(FileChannelImpl.java:875)
        at java.nio.channels.FileChannel.tryLock(FileChannel.java:962)
        at NewClass$1.run(NewClass.java:20)
BUILD SUCCESSFUL (total time: 0 seconds)


Я не очень понимаю,почему в винде блокирует нормально,а в линуксе нет.?

Автор: dobrolub 12.1.2010, 03:24
Возможно баг в JDK, но вроде с помощью exception всё можно отследить как я понял.

Автор: Su_ 12.1.2010, 03:31
к сожалению нет. суть то основываться на блокировки файла , если залочен ,значит одна копия запущена, а так как в Linux он их не лочит, этого не сделать, походу придется на порт вешать.,хотя писали что это далеко не самое лучше решение! dobrolub  спасибо)

Автор: dobrolub 12.1.2010, 03:47
так получается что не за что :(

слушай а что за JDK / Linux?

Автор: Su_ 12.1.2010, 03:56
java-1.6.0-openJDK уже стояло на мое MandrivaFree2009. Я думаю тут различие идет по возможности lock/unlock по ОС а не по JDK

Автор: dobrolub 12.1.2010, 04:15
на debian 5 с JDK от Sun.
java version "1.6.0_07"
Java™ SE Runtime Environment (build 1.6.0_07-b06)
Java HotSpot™ Client VM (build 10.0-b23, mixed mode, sharing)

код внизу отработал нормально.
Код

import java.io.*;
import java.nio.*;

public class T {
    public static void main(String[] args) throws Exception {
        System.out.println(new FileOutputStream("xxx").getChannel().tryLock());

        synchronized(T.class) {
            T.class.wait();
        }
    }
}


первый запуск:
Код

x@debian:/tmp$ java T
sun.nio.ch.FileLockImpl[0:9223372036854775807 exclusive valid]

второй - параллельный запуск
Код

x@debian:/tmp$ java T
null


Добавлено через 13 минут и 39 секунд
проверил с версией 1.5 - тоже работает. То есть возможно проблема в JDK

java version "1.5.0_16"
Java™ 2 Runtime Environment, Standard Edition (build 1.5.0_16-b02)
Java HotSpot™ Client VM (build 1.5.0_16-b02, mixed mode, sharing)


Автор: Su_ 12.1.2010, 04:38
ого Спасибо, это важно для меня ..поковыряюсь с JDK)

Добавлено через 9 минут и 5 секунд
Слушай у меня такой же вывод выдает теперь.. может я где то напортачил ,надо больше спать) Спасибо большое !

Автор: Su_ 12.1.2010, 05:14
токо не понял почему он в дальнейшем сбрасывает Lock , если в этом же потоке работать с файлами)

Автор: dobrolub 12.1.2010, 05:34
я думаю что lock сбрасывается когда переменные выходят из области видимости и собираются мусорщиком. 
Лучше бы переменные связанные с этим локом положить в новую thread и добавить её kak shutdownHook

Runtime.getRuntime().addShutdownHook(new Thread() {
  public void run() {
    //освободить локи, закрыть выходные потоки.
  }
});



      
      

Автор: dobrolub 12.1.2010, 06:05
>1)Почитал в интернете и тут, нашел два способа, это занимать порт и лочить файл.Т.к я и так занимаю порт под нужные цели, первым вариантом пользоваться не хочется

Я про порт не понял, с ним железней, в общем-то, тем более он так и так должен выделиться системой.

Автор: Zefick 12.1.2010, 07:20
  Su_, я не понимаю, почему Вы отмели идею о занятии порта. Мне она пришла в голову сразу, как только я прочитал название темы (наверное, потому что в универе не так давно была лаба по сетевым приложениям на Java и что будет, если запустить два сервера я отлично помню). К тому же Вы говорите, что порт у вас и так занимается. Вот и замечательно - при второй попытке его занять выбросится исключение, которое надо ловить и передавать фокус в первую программу.

Автор: Su_ 12.1.2010, 12:42
Сейчас я её реализовал правда на другом порте.,т.к на основной не хочеться кидать, иногда в процессе работы (прием сообщений ) вылетает исключение в ожидание порта ,потом пересоздается. С лок-файлом наиболее красивое решение, не надо занимать сетевой порт ,и не должно висеть порт,что если на этот порт начнут спамить ,и он выкинеть исключение)

Автор: Su_ 12.1.2010, 13:03
а как передать фокус в первую программу?

Автор: serger 12.1.2010, 13:30
Грубо.
Занятие порта хорошая идея, до тех пор пока есть сеть.
В последнее время сеть, правда, уже почти везде...

Автор: Su_ 12.1.2010, 17:54
а как передать фокус в первую программу?

Добавлено через 1 минуту и 14 секунд
в смысле ГРубо? там мегафон интернет который иногда отваливается ..

Автор: Su_ 12.1.2010, 20:53
решил вопрос о восстановление фокуса,правда если через порт делать..

т.е. вешаю порт на сервер, если вылетает исключение шлю на этот же порт connect , а если на этом порту срабатывает accept то он делать

Код

setExtendedState(MAXIMIZED_BOTH);


т.е так :
Код

while(true)
      {
s = ss.accept();
prints("Восстановление окна");
setExtendedState(MAXIMIZED_BOTH);
    }
    }
    catch(Exception ioe)
    {
      //    block_multi_sender=false;
      System.out.println(ioe.toString());
      try
    {
      Socket s_block = new Socket();

      s_block.connect(new InetSocketAddress(myadress,Port2),200);
      }catch(Exception f)
    {

      }
      System.exit(0);
     //  prints("Принудительный сброс соединения");
     // prints("Данный порт уже занят!!!Закройте все приожения кроме одного и нажмите кнопку 'перезапустить TCP'.Попытка перезапустить ТСП автоматически...");
         // Закрываем соединение
     }


Автор: dobrolub 12.1.2010, 21:10
хороший способ; другой, кросс-платформный, трудно найти я думаю.

Автор: Su_ 13.1.2010, 02:25
будем тестировать, Важна стабильность)Спасибо всем кто помогал, я думаю топик можно закрыть)

Автор: Viroman 15.6.2010, 08:18
http://forum.vingrad.ru/forum/topic-303234.html

В продолжение этой темы, модер поспешил закрыть даже до конца не поняв сути, не конкретно незапуск второй копии, а именно шаринг списка задач, общий блок памяти, или ещё чего нибудь быстрое.
Порты занимать уже проходили, не то, глупый вариант, и я о нём уже думал, но видимо больше хороших идей нет :(

Автор: Viroman 11.7.2010, 16:09
Код

import java.io.*;
import java.nio.*;
public class T {
    public static void main(String[] args) throws Exception {
        System.out.println(new FileOutputStream("xxx").getChannel().tryLock());
        synchronized(T.class) {
            T.class.wait();
        }
    }
}

Кстати, просто обязан поблагодарить dobrolub за предоставленный код, работает на ура.
Только как я понял, дискуссия была по поводу нерабочести под линуском.
Так вот, если вдруг столкнётся кто то с этой проблемой вновь, есть одно огромное различие в плане использования этой фишки под виндой и линухом.
Дурость заключается в том, что при воспроизведении этого кода
 System.out.println(new FileOutputStream("xxx").getChannel().tryLock());
на винде файл лочится до конца воспроизведения всей джава машины!!! и работает как надо и ожидается, что так и будет работать, ведь никто лок программно не освобождает. Но на линухе это не работает, как ожидается!!! Фишка заключается в том, что если воспроизвести этот код
 System.out.println(new FileOutputStream("xxx").getChannel().tryLock());
то система залочит файл, а потом она его тут же разблокирует, к сожалению, и это и явилось подводным камнем и для меня, и похоже для топикстартера!!!
Дабы избежать этой проблемы в КОРНЕ, учтите на будущее,
запускайте выжеизложенный код как есть с синхронизацией и wait в отдельном потоке и тогда и файл лочится прекрасно и не разлочивается до конца воспроизведения машины, и работает и на винде, линухе, маке итп... Может кому то это поможет, чтобы на те же грабли не наступали..

Автор: koreyko 27.3.2013, 00:58
Здравствуйте, уважаемые форумчане.

Решил не открывать новую тему, т.к. стоит та же задача - сворачивание приложения в трей и его восстановление из трея при попытке запуска второй копии приложения. Задачу блокировки второй копии решил с помощью лок-файла. Осталось передать фокус в запущенную первой копию. 

Я пробую сделать это через instance ID. Получаю и сохраняю ID в залоченный файл, при запуске второй копии - считываю. Теперь остался последний шаг: достучаться к уже запущенному инстансу через его ID и восстановить окно программы. 

Подскажите, пожалуйста, как это сделать?

Спасибо.

Автор: COVD 27.3.2013, 15:04
Цитата

Теперь остался последний шаг: достучаться к уже запущенному инстансу через его ID и восстановить окно программы. 

Надо делать, как уже писали выше, все через порт. Приложение при запуске должно запускать сервер, который принимает сокетные соединения на любом свободном порту из заданного диапазона. Приложение должно провести поиск  других инстансев соединяясь поочередно к портам  из заданного диапазона. Если соединение успешно создано и пароль совпадает, значит два инстанса друг друга нашли и дальше могут обмениваться командами через сокетное соединение.

Автор: koreyko 27.3.2013, 15:36
Спасибо, а где можно почитать об этом способе - через порт? Желательно с примерами. После прочтения ветки ясность не наступила, к сожалению.

Автор: COVD 27.3.2013, 17:31

Примерно так. 
Запустите этот класс два раза. Когда вторая инстанс стартует, первая закрывается командой System.exit(0). Можно любое другое поведение реализовать.

Код

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;

/**
 * On application start ShutdownServer runs one time task.
 * Task searches another application instances on local computer and send shutdown command
 * if discovered instance runs with the same userID value.
 *
 * @author
 */
public class ShutdownServer {
    private static String thisApplicationUserID;
    private static int portDefault = 25000;
    private static int portRange = 5;
    private static int readTimeout  = 2000;// 2 sec
    private static boolean daemon  = true;// false, if started from main()
    
    
    private static final String COMMAND_HELLO = "HELLO";
    private static final String COMMAND_SHUTDOWN = "SHUTDOWN";
    private static Thread serverSocketThread;

    
    public static void start(String userID){
        thisApplicationUserID = userID;                
        Thread t = new Thread(){
            public void run(){                
                discoverInstances(); 
                startServer();
            }
        };       
        t.start();       
    }      
        
    /**
     * Method starts thread listening connections on first found available port from range 25000 .. 25004     
     */
    private static void startServer() {
        int port = portDefault;
        for (int i = 0; i < portRange; i++) {        
            try {                
                final ServerSocket serverSocket = new ServerSocket(port);    
                serverSocketThread = new Thread(){
                    public void run(){
                        runServerSocket(serverSocket);
                    }
                };  
                serverSocketThread.setDaemon(daemon);
                serverSocketThread.start();
                break;
            } catch (IOException ex) {                
            }
            port++;
        }
    } 
    
    /**
     * Method runs socket accept loop.
     * On input command COMMAND_HELLO thread responds with thisApplicationUserID value.
     * On input command COMMAND_SHUTDOWN thread shutdowns this application.
     */    
    private static void runServerSocket(ServerSocket serverSocket) {
        try {
            System.out.println("ShutdownServer starts on port: " + serverSocket.getLocalPort());
            while (!Thread.currentThread().isInterrupted()) {
                Socket socket = null;
                try {
                    
                    socket = serverSocket.accept();                    
                    socket.setSoTimeout(readTimeout);
                   
                    String request = readLine(socket);                                            

                    if (request == null) {
//                        System.out.println("runServerSocket: null input");
                        continue;
                    }


                    int index = request.indexOf(":");
                    if (index == -1) {
//                        System.out.println("runServerSocket: wrong format");
                        continue;
                    }


                    int portSent = -1;
                    try {
                        portSent = Integer.parseInt(request.substring(0, index));
                    } catch (Exception e) {
                    }

                    if (portSent == -1) {
//                        System.out.println("runServerSocket: wrong port: " + request);
                        continue;
                    }

                    if (portSent != serverSocket.getLocalPort()) {
//                        System.out.println("runServerSocket: not equal to current port: " + portSent + " != " + serverSocket.getLocalPort());
                        continue;
                    }

                    String command = request.substring(index + 1);
                    System.out.println("command:"+command);
                    
                    if (command.equals(COMMAND_HELLO)) {//respond this userID to another application 
                        String response = String.valueOf(serverSocket.getLocalPort()) + ":" + thisApplicationUserID;
                        printLine(socket, response);
                    } else if (command.equals(COMMAND_SHUTDOWN)) {//shutdown this application by command from another application    
                        System.out.println("app exits");
                        System.exit(0);
                    } else {
//                        System.out.println("runServerSocket: unsupported command:" + command);
                    }



                } catch (Exception ex) {
                    System.out.println("ShutdownServer:" + ex);                    
                } finally {                    
                    close(socket);
                }
            }
        } finally {            
            close(serverSocket);            
        }        
    }
    
    private static void printLine(Socket socket, String str) throws IOException{
        PrintWriter pw = new PrintWriter(socket.getOutputStream()); 
        pw.println(str);
        pw.flush();        
//        System.out.println("printLine:"+str);
    }
    
    private static String readLine(Socket socket) throws IOException{
        BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        String line = br.readLine();                      
//        System.out.println("readLine:"+line); 
        return line;
        
    }  
    
    private static void close(Object obj) {
        if (obj != null) {
            if (obj instanceof Socket) {
                try {
                    ((Socket) obj).close();
                } catch (IOException ex) {
                }
            }
            else
            if (obj instanceof ServerSocket) {
                try {
                    ((ServerSocket) obj).close();
                } catch (IOException ex) {
                }
            }                
        }
    }
    /**
     * Method perform open ports search on local computer in range 25000 .. 25004
     * If connection to another application instance is established then string like port:COMMAND_HELLO is sent.
     * If response received and it has the same format and parsed userID value is equal this application userID, than COMMAND_SHUTDOWN is send.
     * Another application instance will perform System.exit().     
     */    
    private static void discoverInstances() {
        int port = portDefault;
        for (int i = 0; i < portRange; i++) {
            Socket socket = null;
            try {
                socket = new Socket("127.0.0.1", port);
                socket.setSoTimeout(readTimeout);
                
                String request = String.valueOf(port) + ":" + COMMAND_HELLO;
                
                printLine(socket,request);
               
                String response = readLine(socket);                
                
                if (response != null) {
                    int index = response.indexOf(":");
                    if (index != -1) {
                        String uID = response.substring(index + 1);
                        if(thisApplicationUserID.equals(uID)){
                           close(socket);
                           socket = new Socket("127.0.0.1", port);                             
                           String command = String.valueOf(port) + ":" + COMMAND_SHUTDOWN;
                           printLine(socket,command);
                           System.out.println("ShutdownServer terminates instance on port:" + port); 
                        }                        
                    }
                }

            } catch (UnknownHostException ex) {
//                System.out.println("UnknownHostException:" + ex);                
            } catch(SocketTimeoutException ex){
//                System.out.println("SocketTimeoutException:" + ex);            
            } catch (IOException ex) {
//                System.out.println("IOException:" + ex);
            } finally {
               close(socket);
            }
            port++;
        }
                
    }
    
    public static void main(String[] a){  
        daemon = false;//when started from main
        start("TEST");
    }
}

Автор: koreyko 1.4.2013, 18:35
COVD, спасибо. Вдумчиво изучил Ваш код, но так и не понял -- каким образом я все-таки могу получить из второй запущенной копии доступ к фрейму первой копии? Для того, чтобы развернуть свернутое в трей окно. 

Автор: batigoal 1.4.2013, 22:38
koreyko, тебе не нужно получать доступ к чужому фрейму - это сам первый экземпляр программы слушает события на порту, и, получив сообщение от запущенного второго инстанса, разворачивает себя. Второй же экземпляр после получения сообщения тихо завершается.

Автор: koreyko 2.4.2013, 21:33
batigoal, вы уверены? Потому что COVD писал, цитирую: "Когда вторая инстанс стартует, первая закрывается командой System.exit(0)"

Автор: batigoal 2.4.2013, 22:44
koreyko, да, но выполняет-то этот System.exit сам инстанс, который будет выключен: 

Код

else if (command.equals(COMMAND_SHUTDOWN)) {//shutdown this application by command from another application    
                        System.out.println("app exits");
                        System.exit(0);
                    }


Программа получила сообщение и завершила себя.

Автор: COVD 3.4.2013, 00:12
koreyko
У меня действительно так сделано, что при запуске второго инстанса он шлет команду первому инстансу и первый сам себя закрывает по этой команде.  Я привел код, на основе которого вы можете реализовать свою логику, например, второй закрывается, а первый разворачивается. 

Автор: koreyko 3.4.2013, 11:57
COVD,  еще раз спасибо. Разобрался, всё у меня получилось. Я раньше никогда не работал с сокетами и серверами, поэтому у меня осталась пара вопросов:

- что такое readTimeout, равный 2-м секундам и зачем он нужен? Насколько я понял, это диапазон, через который сервер слушает приходящие команды, или нет? Заметил такую ситуацию: если быстро запустить приложение 2 раза, то остаются запущенными две копии. Как с эти можно бороться?
- каким образом надо выбирать диапазон портов? Если все они оказались до этого заняты другими процессами, то данное решение не сработает? Или же это порты относятся только к запущенному серверу и они всегда свободны?

Автор: COVD 3.4.2013, 16:50
Цитата

- что такое readTimeout, равный 2-м секундам и зачем он нужен?


Поток, читающий из сокета, блокируется, если нет данных (если сокет в блокирующем режиме). Таймаут на чтение определяет максимальное время ожидания данных. Если данных нет большее время, то выбрасывается исключение, и поток разблокируется. Это событие на принимающей стороне (на клиенте) означает, что коммуникация завершилась аварийно. Иначе поток может ожидать данных вечно.

2 сек - предполагается, что в локальной сети этого достаточно. Если обмен данных осуществляется через интернет, то обычно ставят секунд 15.

Цитата

если быстро запустить приложение 2 раза, то остаются запущенными две копии. Как с эти можно бороться?


Очевидно, что второе запущенное приложение начинает поиск, а первое приложение еще не успело запустить свой сервер, так как тоже ищет. Можно попробовать поменять местами операции - сначала стартовать сервер, а потом искать в сети другие инстансы.
И, наверное, надо передавать время старта в миллисек, чтобы инстансы могли понять, кто из них первый. 

Цитата

каким образом надо выбирать диапазон портов? Если все они оказались до этого заняты другими процессами, то данное решение не сработает?


Если все порты из диапазона заняты, то не сработает (сервер не сможет стартовать). Если расширить диапазон, то увеличится время поиска. Я думаю, 5 - 10 портов вполне достаточно для этих целей. Мир не рухнет, если данное решение вдруг не сработает. 

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)