servidor - Software de instancia única de Java con socket. problema al cerrar el socket debajo de Windows
ejercicios de sockets en java (2)
Necesito forzar a mi aplicación Java para que se ejecute con una sola instancia. Encontré en este enlace esta muy buena pieza de código que resuelve el problema usando socket en lugar de usar el sistema de archivos.
aquí el como me ajusté:
paquete cern.ieplc.controller; import java.net.InetAddress; import java.net.InetSocketAddress; importar java.net.ServerSocket; import java.net.Socket; import java.net.UnknownHostException;
import org.apache.log4j.Logger;
importar java.io.BufferedReader; importar 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");
}
}
}
}
Probé el código y funciona muy bien en Linux. si cierro la aplicación (incluso si intento matarla) el socket se libera inmediatamente y puedo iniciar una nueva aplicación. Desafortunadamente, en Windows, los pensamientos no son tan fáciles. una vez que se asigna el recurso, nunca se libera. si cierro el software no podré volver a abrirlo hasta que cierre mi sección.
Cualquier idea sobre cómo arreglar el código para que funcione bajo Windows. Pensé que podría usar un gancho de cierre para atrapar al menos el apagado normal. En realidad, no sé qué hacer en caso de que el proceso termine de una manera inesperada.
Aquí adjunto una pantalla impresa sobre el SW TCPView que calza cómo java mantiene el puerto abierto:
Intenté implementar una versión mucho más simple. sigue siendo el mismo problema en Windows, los recursos no se liberan.
Aquí está el segundo código:
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);
}
}
}
Hay algo de lo que no se trata correctamente. No funciona si pongo el cronómetro engancha el código followin también:
// cierre el servidor
try{
serverSocket.close();
}catch (IOException e) {
e.printStackTrace();
}
Gracias por adelantado
Desafortunadamente, en Windows, los pensamientos no son tan fáciles. una vez que se asigna el recurso, nunca se libera. si cierro el software no podré volver a abrirlo hasta que cierre mi sección.
¿Qué quieres decir con estas declaraciones? Si se finaliza un proceso, se liberan todos sus recursos, en cualquier sistema operativo que no sea Novell Netware 3.x. ¿Qué evidencia tiene de que el socket de escucha no se está cerrando, y qué sucede cuando intenta reiniciar la aplicación?
Tratar
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
Cuando se cierra una conexión TCP, la conexión puede permanecer en un estado de tiempo de espera durante un período de tiempo después de que se cierra la conexión (normalmente conocido como estado TIME_WAIT o estado de espera 2MSL). Para aplicaciones que usan una dirección o puerto de socket bien conocidos, puede que no sea posible vincular un socket a SocketAddress requerido si hay una conexión en el estado de tiempo de espera que involucra la dirección o el puerto del socket.
Habilitar SO_REUSEADDR antes de vincular el socket utilizando bind (SocketAddress) permite que el socket se vincule aunque una conexión previa esté en estado de tiempo de espera.