Java single instance software with socket. issue in closing socket under windows
Asked Answered
M

2

3

I need to force my Java application to run with a single instance. I found on this link this very nice piece of code that solve the problem using socket instead of using the file system.

here the as i adjusted:

package cern.ieplc.controller; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.ServerSocket; import java.net.Socket; import java.net.UnknownHostException;

import org.apache.log4j.Logger;

import java.io.BufferedReader; import 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");
            }
        }
    }
}

I tryed the code and it works really well under Linux. if i close the application (even trying to kill it) the socket is immediatly released and i can launch a new application! Unfortunatelly under windows thinks are not so easy. once the resource is allocated is never released. if i close the software i will not be able to launch it again till i close my section.

Any idea about how fix nicelly the code to make it works under windows. I tought i could use a shut down hook to catch at least the normal shutting down. Do not really know instead wat to do in case he process terminates in an unexpected way.

Here i attach a print screen done over the SW TCPView that shoes how the port is kept open by java: enter image description here

I tryed implementing a much simpler version. still the same problem. under windows the resources are not released.

Here is the second code:

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);
        }
    }
}

There is somethink that does not clse properly. It does not work if i put in the shutdown hook the followin code as well:

// shut down server

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

Thanks in advance

Mireille answered 13/9, 2011 at 6:38 Comment(3)
A word of warning: above code contains a bug. Luckily one that is easy to fix. You should use InetAddress.getByAddress(new byte[]{127, 0, 0, 1}); instead of .getLocalHost(). Reason is simply that that the getLocalHost() methods return value can change if machine gets new IP address from DHCP-server. See .getLocalHost():s documentation for more details.Banks
What is the issue with using a lock file?Install
well. beside the fact that i do not really like the fact that in case of crashing the file stays there while socket will be closed, i cannot use it becase i have no write priviledges in my sw. (does not depend on me) . using a socket in the worst case will work after a reboot while the file will have to be somehow removedMireille
L
0

Try

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

When a TCP connection is closed the connection may remain in a timeout state for a period of time after the connection is closed (typically known as the TIME_WAIT state or 2MSL wait state). For applications using a well known socket address or port it may not be possible to bind a socket to the required SocketAddress if there is a connection in the timeout state involving the socket address or port.

Enabling SO_REUSEADDR prior to binding the socket using bind(SocketAddress) allows the socket to be bound even though a previous connection is in a timeout state.

Lovesick answered 13/9, 2011 at 6:44 Comment(9)
i tryed: socket = new ServerSocket(SINGLE_INSTANCE_NETWORK_SOCKET, 10, InetAddress.getByAddress(new byte[]{127,0,0,1})); socket.setReuseAddress(true); socket.bind(new InetSocketAddress(SINGLE_INSTANCE_NETWORK_SOCKET));Mireille
in this way it does not wok under linux as well.Mireille
i added a method closeInstance which i cal from the shutdown procedure. but nothing as well! I aded te method in the code on top.Mireille
Ok, Did you try setting the flag before binding to a port, as it says in the documentation?Lovesick
what do yo mean? if you mean the flag socket.setReuseAddress(true); yes! otherwhise i did not understand. (sorry i'm a newby in sockets)Mireille
You create the socket without binding. you set the flag. Only after that do you bind to a port.Lovesick
should it be done then in the runnable? could you show me how to do so on my code. i know i am asking a lot... but i am a bit lost....Mireille
i tryed implementing a much simpler soultion. the previous one was too tediousand an overshut for what i needed. would you mind give a look to it? i basically just neet to understand why under windows resources are not releasedMireille
The resource is not release immediately in Windows or Linux. There is a timeout period as the documentation explains. In stead, that this does is allow you to reuse a socket which is timing out but not yet completely free. BTW: You are still binding on the first line which I keep telling not to do.Lovesick
D
0

Unfortunatelly under windows thinks are not so easy. once the resource is allocated is never released. if i close the software i will not be able to launch it again till i close my section.

What exactly do you mean by these statements? If a process is terminated all its resources are released, on any operating system short of Novell Netware 3.x. What evidence do you have that the listening socket isn't being closed, and what happens when you try to relaunch the application?

Dud answered 13/9, 2011 at 8:16 Comment(1)
at the beginning of my main i call: if(!ApplicationInstanceManager.registerInstance()) and this is true. in linux this is true just if i did not close my previos instance. under windows unfortunaty once i open one even if i close it this will be true till the system reboot. :(Mireille

© 2022 - 2024 — McMap. All rights reserved.