Java единое программное обеспечение с сокетом.проблема в закрытии сокета под окнами - PullRequest
3 голосов
/ 13 сентября 2011

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

здесь как я настроил:

пакет cern.ieplc.controller; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.ServerSocket; импорт java.net.Socket; import java.net.UnknownHostException;

import org.apache.log4j.Logger;

import java.io.BufferedReader; импорт java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStream;

public class ApplicationInstanceManager {

    public interface ApplicationInstanceListener {
        public void newInstanceCreated();
    }

    private static final Logger log = Logger.getLogger(CustomLock.class);

    private static ApplicationInstanceListener subListener;

    /** Randomly chosen, but static, high socket number */
    public static final int SINGLE_INSTANCE_NETWORK_SOCKET = 44331;

    /** Must end with newline */
    public static final String SINGLE_INSTANCE_SHARED_KEY = "$$NewInstance$$\n";

    private static ServerSocket socket;
    /**
     * Registers this instance of the application.
     *
     * @return true if first instance, false if not.
     */
    public static boolean registerInstance() {
        // returnValueOnError should be true if lenient (allows app to run on network error) or false if strict.
        boolean returnValueOnError = true;
        // try to open network socket
        // if success, listen to socket for new instance message, return true
        // if unable to open, connect to existing and send new instance message, return false
        try {
            socket = new ServerSocket(SINGLE_INSTANCE_NETWORK_SOCKET, 10, InetAddress.getByAddress(new byte[]{127,0,0,1}));
            socket.setReuseAddress(true);//allows the socket to be bound even though a previous connection is in a timeout state.
            socket.bind(new InetSocketAddress(SINGLE_INSTANCE_NETWORK_SOCKET));
            log.debug("Listening for application instances on socket " + SINGLE_INSTANCE_NETWORK_SOCKET);
            Thread instanceListenerThread = new Thread(new Runnable() {
                public void run() {
                    boolean socketClosed = false;
                    while (!socketClosed) {
                        if (socket.isClosed()) {
                            socketClosed = true;
                        } else {
                            try {
                                Socket client = socket.accept();
                                BufferedReader in = new BufferedReader(new InputStreamReader(client.getInputStream()));
                                String message = in.readLine();
                                if (SINGLE_INSTANCE_SHARED_KEY.trim().equals(message.trim())) {
                                    log.debug("Shared key matched - new application instance found");
                                    fireNewInstance();
                                }
                                in.close();
                                client.close();
                            } catch (IOException e) {
                                socketClosed = true;
                            }
                        }
                    }
                }
            });
            instanceListenerThread.start();
            // listen
        } catch (UnknownHostException e) {
            log.error(e.getMessage(), e);
            return returnValueOnError;
        } catch (IOException e) {
            log.debug("Port is already taken.  Notifying first instance.");
            try {
                Socket clientSocket = new Socket(InetAddress.getByAddress(new byte[]{127,0,0,1}), SINGLE_INSTANCE_NETWORK_SOCKET);
                OutputStream out = clientSocket.getOutputStream();
                out.write(SINGLE_INSTANCE_SHARED_KEY.getBytes());
                out.close();
                clientSocket.close();
                log.debug("Successfully notified first instance.");
                return false;
            } catch (UnknownHostException e1) {
                log.error(e.getMessage(), e);
                return returnValueOnError;
            } catch (IOException e1) {
                log.error("Error connecting to local port for single instance notification");
                log.error(e1.getMessage(), e1);
                return returnValueOnError;
            }

        }
        return true;
    }

    public static void setApplicationInstanceListener(ApplicationInstanceListener listener) {
        subListener = listener;
    }

    private static void fireNewInstance() {
        if (subListener != null) {
            subListener.newInstanceCreated();
        }
    }

    public static void closeInstance() {
        if (socket != null) {
            try {
                socket.close();
            } catch (IOException e) {
                log.error("Error while closing the socket");
            }
        }
    }
}

Я попробовал код, и он отлично работает под Linux. если я закрою приложение (даже пытаясь убить его), сокет немедленно освобождается, и я могу запустить новое приложение! К сожалению, под окнами думает, что это не так просто. если ресурс выделен, он никогда не освобождается. если я закрою программное обеспечение, я не смогу запустить его снова, пока не закрою свой раздел.

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

Здесь я прилагаю экран печати, выполненный поверх SW TCPView, который показывает, как порт остается открытым с помощью Java: enter image description here

Я попытался реализовать гораздо более простую версию. все та же проблема. под windows ресурсы не освобождаются.

Вот второй код:

import java.net.ServerSocket;
import javax.swing.JOptionPane;
import javax.swing.JFrame;
import java.io.IOException;
import java.net.BindException;

class MyApplication{
    public static ServerSocket serverSocket;
    public static void main(String as[])
    {
        try
        {
            //creating object of server socket and bind to some port number
            serverSocket = new ServerSocket(15486);
            ////do not put common port number like 80 etc.
            ////Because they are already used by system
            JFrame jf = new JFrame();
            jf.setVisible(true);
            jf.setSize(200, 200);
        }
        catch (BindException exc)
        {
            JOptionPane.showMessageDialog(null, "Another instance of this application is already running.", "Error", JOptionPane.ERROR_MESSAGE);
            System.exit(0);
        }
        catch (IOException exc)
        {
            JOptionPane.showMessageDialog(null, "Another instance of this application is already running.", "Error", JOptionPane.ERROR_MESSAGE);
            System.exit(0);
        }
    }
}

Есть что-то, что не подходит правильно. Это не работает, если я вставлю в ловушку отключения следующий код:

// выключить сервер

try{
    serverSocket.close();
}catch (IOException e) {
    e.printStackTrace();
}

Заранее спасибо

Ответы [ 2 ]

0 голосов
/ 13 сентября 2011

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

Что именно вы подразумеваете под этими утверждениями?Если процесс завершается, все его ресурсы освобождаются в любой операционной системе, кроме Novell Netware 3.x.Какие у вас есть доказательства того, что сокет прослушивания не закрыт, и что происходит, когда вы пытаетесь перезапустить приложение?

0 голосов
/ 13 сентября 2011

Попробуйте

ServerSocket socket = new ServerSocket();
socket.setReuseAddress(true);
socket.bind(new InetSocketAddress(port));

http://download.oracle.com/javase/6/docs/api/java/net/ServerSocket.html#setReuseAddress%28boolean%29

Когда TCP-соединение закрыто, оно может оставаться в состоянии тайм-аута в течение некоторого времени после закрытия соединения (обычно это состояние TIME_WAIT или состояние ожидания 2MSL). Для приложений, использующих общеизвестный адрес сокета или порт, может быть невозможно связать сокет с требуемым SocketAddress, если в состоянии тайм-аута имеется соединение с адресом или портом сокета.

Включение SO_REUSEADDR перед привязкой сокета с помощью bind (SocketAddress) позволяет связать сокет, даже если предыдущее соединение находится в состоянии тайм-аута.

...