¿Cómo puedo interrumpir un método ServerSocket accept ()?

143

En mi hilo principal, tengo un while(listening)bucle que invoca accept()mi objeto ServerSocket, luego inicia un nuevo hilo de cliente y lo agrega a una Colección cuando se acepta un nuevo cliente.

También tengo un hilo de administración que quiero usar para emitir comandos, como 'salir', que hará que todos los hilos del cliente se apaguen, se apaguen y apaguen el hilo principal, haciendo que la escucha sea falsa.

Sin embargo, la accept()llamada en el while(listening)bucle se bloquea, y no parece haber ninguna forma de interrumpirla, por lo que la condición while no se puede verificar nuevamente y el programa no puede salir.

¿Hay una mejor manera de hacer esto? ¿O alguna forma de interrumpir el método de bloqueo?

lukeo05
fuente
Para el pepole que quiere esperar x tiempo y luego reaccionar, use setSoTimeout ().
Abdullah Orabi

Respuestas:

151

Puede llamar close()desde otro hilo, y la accept()llamada arrojará un SocketException.

Simon Groenewolt
fuente
2
Gracias, tan obvio, ¡ni siquiera se me ocurrió! Estaba llamando a close () después de salir del bucle.
lukeo05
44
Extraño, que no hay esta información en los documentos: download.oracle.com/javase/6/docs/api/java/net/… método no está marcado como arrojando SocketException. Solo se menciona aquí download.oracle.com/javase/1.4.2/docs/api/java/net/…
Vladislav Rastrusny
1
¿Está bien llamar? close()Quiero decir que arrojará la excepción al hacerlo, entonces, ¿hay alguna otra manera (que no arroje una excepción, tampoco basada en el tiempo de espera) para dejar de escuchar las solicitudes?
Kushal
3
¿Y qué hacer si la conexión ya fue aceptada y el hilo está esperando algunos datos: while ((s = in.readLine ())! = Null)?
Alex Fedulov
1
@AlexFedulov Apaga ese socket para la entrada. readLine()entonces regresará nulo y ocurrirán las operaciones de cierre normales que el hilo ya debería tener en su lugar.
Marqués de Lorne
31

Configure el tiempo de espera activado accept(), luego la llamada expirará el bloqueo después del tiempo especificado:

http://docs.oracle.com/javase/7/docs/api/java/net/SocketOptions.html#SO_TIMEOUT

Establezca un tiempo de espera en las Socketoperaciones de bloqueo :

ServerSocket.accept();
SocketInputStream.read();
DatagramSocket.receive();

La opción debe establecerse antes de ingresar a una operación de bloqueo para que surta efecto. Si el tiempo de espera expira y la operación continuaría bloqueándose, java.io.InterruptedIOExceptionse eleva. El Socketno está cerrado en este caso.

Juha Syrjälä
fuente
4

Simplemente puede crear un socket "vacío" para romper serversocket.accept ()

Lado del servidor

private static final byte END_WAITING = 66;
private static final byte CONNECT_REQUEST = 1;

while (true) {
      Socket clientSock = serverSocket.accept();
      int code = clientSock.getInputStream().read();
      if (code == END_WAITING
           /*&& clientSock.getInetAddress().getHostAddress().equals(myIp)*/) {
             // End waiting clients code detected
             break;
       } else if (code == CONNECT_REQUEST) { // other action
           // ...
       }
  }

Método para romper el ciclo del servidor

void acceptClients() {
     try {
          Socket s = new Socket(myIp, PORT);
          s.getOutputStream().write(END_WAITING);
          s.getOutputStream().flush();
          s.close();
     } catch (IOException e) {
     }
}
Nickolay Savchenko
fuente
4

La razón ServerSocket.close()arroja una excepción es porque tiene un outputstreamo un inputstream adjunto a ese socket. Puede evitar esta excepción de forma segura cerrando primero los flujos de entrada y salida. Entonces intente cerrar el ServerSocket. Aquí hay un ejemplo:

void closeServer() throws IOException {
  try {
    if (outputstream != null)
      outputstream.close();
    if (inputstream != null)
      inputstream.close();
  } catch (IOException e1) {
    e1.printStackTrace();
  }
  if (!serversock.isClosed())
    serversock.close();
  }
}

Puede llamar a este método para cerrar cualquier socket desde cualquier lugar sin obtener una excepción.

Soham Malakar
fuente
77
Los flujos de entrada y salida no están unidos al ServerSocketpero a Sockety estamos hablando de cerrar el ServerSocketno el Socket, por lo que ServerSocketpueden cerrarse sin cerrar Socketlos flujos de a.
icza
1

OK, conseguí que esto funcione de una manera que aborde la pregunta del OP más directamente.

Sigue leyendo más allá de la respuesta corta para ver un ejemplo de Thread sobre cómo uso esto.

Respuesta corta:

ServerSocket myServer;
Socket clientSocket;

  try {    
      myServer = new ServerSocket(port)
      myServer.setSoTimeout(2000); 
      //YOU MUST DO THIS ANYTIME TO ASSIGN new ServerSocket() to myServer‼!
      clientSocket = myServer.accept();
      //In this case, after 2 seconds the below interruption will be thrown
  }

  catch (java.io.InterruptedIOException e) {
      /*  This is where you handle the timeout. THIS WILL NOT stop
      the running of your code unless you issue a break; so you
      can do whatever you need to do here to handle whatever you
      want to happen when the timeout occurs.
      */
}

Ejemplo del mundo real:

En este ejemplo, tengo un ServerSocket esperando una conexión dentro de un Thread. Cuando cierro la aplicación, quiero cerrar el hilo (más específicamente, el zócalo) de manera limpia antes de dejar que la aplicación se cierre, así que uso el .setSoTimeout () en el ServerSocket y luego uso la interrupción que se produce después del tiempo de espera para verificar y ver si el padre está intentando cerrar el hilo. Si es así, configuro cerrar el zócalo, luego establezco un indicador que indica que el subproceso está hecho, luego salgo del bucle de subprocesos que devuelve un valor nulo.

package MyServer;

import javafx.concurrent.Task;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;

import javafx.concurrent.Task;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;

public class Server {

public Server (int port) {this.port = port;}

private boolean      threadDone        = false;
private boolean      threadInterrupted = false;
private boolean      threadRunning     = false;
private ServerSocket myServer          = null;
private Socket       clientSocket      = null;
private Thread       serverThread      = null;;
private int          port;
private static final int SO_TIMEOUT    = 5000; //5 seconds

public void startServer() {
    if (!threadRunning) {
        serverThread = new Thread(thisServerTask);
        serverThread.setDaemon(true);
        serverThread.start();
    }
}

public void stopServer() {
    if (threadRunning) {
        threadInterrupted = true;
        while (!threadDone) {
            //We are just waiting for the timeout to exception happen
        }
        if (threadDone) {threadRunning = false;}
    }
}

public boolean isRunning() {return threadRunning;}


private Task<Void> thisServerTask = new Task <Void>() {
    @Override public Void call() throws InterruptedException {

        threadRunning = true;
        try {
            myServer = new ServerSocket(port);
            myServer.setSoTimeout(SO_TIMEOUT);
            clientSocket = new Socket();
        } catch (IOException e) {
            e.printStackTrace();
        }
        while(true) {
            try {
                clientSocket = myServer.accept();
            }
            catch (java.io.InterruptedIOException e) {
                if (threadInterrupted) {
                    try { clientSocket.close(); } //This is the clean exit I'm after.
                    catch (IOException e1) { e1.printStackTrace(); }
                    threadDone = true;
                    break;
                }
            } catch (SocketException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return null;
    }
};

}

Luego, en mi clase de controlador ... (solo mostraré el código relevante, masajee su propio código según sea necesario)

public class Controller {

    Server server = null;
    private static final int port = 10000;

    private void stopTheServer() {
        server.stopServer();
        while (server.isRunning() {
        //We just wait for the server service to stop.
        }
    }

    @FXML private void initialize() {
        Platform.runLater(()-> {
            server = new Server(port);
            server.startServer();
            Stage stage = (Stage) serverStatusLabel.getScene().getWindow();
            stage.setOnCloseRequest(event->stopTheServer());
        });
    }

}

Espero que esto ayude a alguien en el camino.

Michael Sims
fuente
0

Otra cosa que puede probar, que es más limpia, es verificar una bandera en el bucle de aceptación, y luego, cuando su hilo de administración quiera eliminar el bloqueo de hilo en la aceptación, configure la bandera (haga que sea seguro para el hilo) y luego haga un socket de cliente conexión a la toma de escucha. La aceptación dejará de bloquear y devolverá el nuevo socket. Puede resolver algo de protocolo simple que le dice al hilo de escucha que salga del hilo limpiamente. Y luego cierre el zócalo en el lado del cliente. Sin excepciones, mucho más limpio.

stu
fuente
esto no tiene sentido ... cuando tiene un código como este socket = serverSocket.accept (); en ese momento, comienza el bloqueo y el bucle no es parte de nuestro código, por lo que hay forma de que ese código de bloqueo busque una bandera que configuré ... al menos no he podido encontrar una manera de hacerlo. .. si tiene código de trabajo, por favor comparta?
Michael Sims
Sí, parece que omití una información crucial que haría que no tuviera mucho sentido. Debería hacer que el zócalo de aceptación no se bloquee y solo se bloquee durante un período de tiempo antes de que se repita el ciclo, en qué área verificaría la bandera de salida.
stu
¿Cómo, exactamente, haces el socket de aceptación, sin bloqueo?
Michael Sims
de largo = 1L; if (ioctl (socket, (int) FIONBIO, (char *) & on))
estu
@stu Esta pregunta es para los sockets de Java.
Kröw