带套接字的 Java 单实例软件。 windows下关闭socket的问题

发布于 2024-12-04 07:02:56 字数 6730 浏览 2 评论 0原文

我需要强制我的 Java 应用程序以单个实例运行。我在这个链接上发现了这段非常好的代码使用socket而不是使用文件系统来解决问题。

这是我调整的:

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

导入 org.apache.log4j.Logger;

导入 java.io.BufferedReader; 导入java.io.IOException; 导入 java.io.InputStreamReader; 导入 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下想起来并不那么容易。资源一旦分配就永远不会释放。如果我关闭该软件,我将无法再次启动它,直到我关闭我的部分。

关于如何很好地修复代码以使其在 Windows 下运行的任何想法。 我以为我可以使用关闭钩子来捕获至少正常的关闭。 不知道该怎么办,以防进程以意外方式终止。

这里我附上了通过 SW TCPView 完成的打印屏幕,该屏幕显示了 java 保持端口打开的方式: 在此处输入图像描述

我尝试实现一个更简单的版本。还是同样的问题。 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();
}

提前致谢

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

箜明 2024-12-11 07:02:56

尝试

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。

在使用bind(SocketAddress)绑定套接字之前启用SO_REUSEADDR,即使先前的连接处于超时状态,也可以绑定套接字。

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.

删除→记忆 2024-12-11 07:02:56

不幸的是在windows下想起来并不那么容易。一旦资源
被分配永远不会被释放。如果我关闭软件我就不会
能够再次启动它,直到我关闭我的部分。

这些说法到底是什么意思?在任何低于 Novell Netware 3.x 的操作系统上,如果进程被终止,其所有资源都会被释放。您有什么证据表明侦听套接字未关闭,以及当您尝试重新启动应用程序时会发生什么?

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?

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文