Comment puis-je interrompre une méthode ServerSocket accept ()?

143

Dans mon thread principal, j'ai une while(listening)boucle qui appelle accept()mon objet ServerSocket, puis démarre un nouveau thread client et l'ajoute à une collection lorsqu'un nouveau client est accepté.

J'ai également un thread d'administration que je souhaite utiliser pour émettre des commandes, telles que `` exit '', ce qui entraînera l'arrêt de tous les threads clients, l'arrêt d'eux-mêmes et l'arrêt du thread principal, en activant l'écoute sur false.

Cependant, l' accept()appel dans la while(listening)boucle se bloque et il ne semble pas y avoir de moyen de l'interrompre, donc la condition while ne peut pas être vérifiée à nouveau et le programme ne peut pas quitter!

Y a-t-il une meilleure manière de faire cela? Ou un moyen d'interrompre la méthode de blocage?

lukeo05
la source
Pour le pepole qui veut attendre x fois puis réagir, utilisez setSoTimeout ().
Abdullah Orabi le

Réponses:

151

Vous pouvez appeler à close()partir d'un autre thread, et l' accept()appel lancera un SocketException.

Simon Groenewolt
la source
2
Merci, si évident, ne m'est même pas venu à l'esprit! J'appelais close () après avoir quitté la boucle.
lukeo05
4
Étrange, qu'il n'y ait pas cette information dans la documentation: la méthode download.oracle.com/javase/6/docs/api/java/net/… n'est pas marquée comme lançant SocketException. Il est uniquement mentionné ici download.oracle.com/javase/1.4.2/docs/api/java/net/…
Vladislav Rastrusny
1
Est-il correct d'appeler close(), je veux dire que cela lèvera l'exception en le faisant, alors y a-t-il un autre moyen (qui ne lève pas d'exception, pas non plus basé sur le délai d'expiration) pour arrêter d'écouter les demandes?
Kushal
3
Et que faire si la connexion a déjà été acceptée et que le thread attend des données: while ((s = in.readLine ())! = Null)?
Alex Fedulov
1
@AlexFedulov Arrêtez cette prise pour l'entrée. readLine()retournera alors null et les opérations de fermeture normales que le thread devrait déjà avoir en place se produiront.
Marquis of Lorne
31

Activez le délai d'expiration accept(), puis l'appel expirera le blocage après l'heure spécifiée:

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

Définissez un délai d'attente pour les Socketopérations de blocage :

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

L'option doit être définie avant d'entrer une opération de blocage pour prendre effet. Si le délai expire et que l'opération continue à se bloquer, java.io.InterruptedIOExceptionest déclenché. Le Socketn'est pas fermé dans ce cas.

Juha Syrjälä
la source
4

Vous pouvez simplement créer un socket "void" pour break serversocket.accept ()

Du côté serveur

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éthode pour interrompre le cycle du serveur

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

La raison ServerSocket.close()lève une exception est que vous avez un outputstreamou un inputstream attaché à ce socket. Vous pouvez éviter cette exception en toute sécurité en fermant d'abord les flux d'entrée et de sortie. Essayez ensuite de fermer le fichier ServerSocket. Voici un exemple:

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

Vous pouvez appeler cette méthode pour fermer n'importe quel socket de n'importe où sans obtenir une exception.

Soham Malakar
la source
7
Les flux d'entrée et de sortie ne sont pas attachés au ServerSocketmais à a Socketet nous parlons de fermer le ServerSocketpas le Socket, donc le ServerSocketpeut être fermé sans fermer Socketles flux de a.
icza
1

Utilisez serverSocket.setSoTimeout (timeoutInMillis) .

Joop Eggen
la source
Cela ne fonctionne pas ... si vous le faites fonctionner, pourriez-vous nous montrer un exemple de code fonctionnel?
Michael Sims
1

OK, j'ai fait fonctionner cela d'une manière qui répond plus directement à la question du PO.

Continuez à lire après la réponse courte pour un exemple de fil de discussion sur la façon dont j'utilise cela.

Réponse courte:

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.
      */
}

Exemple du monde réel:

Dans cet exemple, j'ai un ServerSocket en attente d'une connexion à l'intérieur d'un thread. Lorsque je ferme l'application, je veux arrêter le thread (plus précisément, le socket) de manière propre avant de laisser l'application se fermer, donc j'utilise le .setSoTimeout () sur le ServerSocket puis j'utilise l'interruption qui est lancée après l'expiration du délai pour vérifier et voir si le parent essaie d'arrêter le thread. Si tel est le cas, je ferme le socket, puis un indicateur indiquant que le thread est terminé, puis je sort de la boucle Threads qui renvoie un null.

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

}

Ensuite, dans ma classe Controller ... (je montrerai uniquement le code pertinent, le masserai dans votre propre code si nécessaire)

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

}

J'espère que cela aidera quelqu'un sur la route.

Michael Sims
la source
0

Une autre chose que vous pouvez essayer, qui est plus propre, est de vérifier un indicateur dans la boucle d'acceptation, puis lorsque votre thread d'administration veut tuer le blocage de thread sur l'acceptation, définissez l'indicateur (rendez-le thread safe), puis créez un socket client connexion à la prise d'écoute. L'acceptation arrêtera de bloquer et renverra la nouvelle socket. Vous pouvez élaborer un protocole simple indiquant au thread d'écoute de quitter proprement le thread. Et puis fermez le socket côté client. Aucune exception, beaucoup plus propre.

étu
la source
cela n'a aucun sens ... quand vous avez du code comme celui-ci socket = serverSocket.accept (); à ce moment-là, le blocage commence et la boucle ne fait pas partie de notre code, il y a donc moyen de faire en sorte que ce code de blocage recherche un drapeau que j'ai défini ... au moins je n'ai pas été en mesure de trouver un moyen de le faire. .. si vous avez du code fonctionnel, veuillez le partager?
Michael Sims
Ouais, il semblerait que j'ai laissé de côté une information cruciale qui n'aurait pas beaucoup de sens. Vous devez rendre le socket d'acceptation non bloquant et ne bloquer que pendant un certain temps avant que la boucle ne se répète, dans quelle zone vous vérifieriez alors l'indicateur de sortie.
STU
Comment, exactement, faites-vous le socket d'acceptation, non bloquant?
Michael Sims
long sur = 1L; if (ioctl (socket, (int) FIONBIO, (char *) & on))
stu
@stu Cette question concerne les sockets Java.
Kröw
0

Vous pouvez simplement passer la limite de délai (milli secondes) en tant que paramètre lors de l'appel de la fonction d'acceptation.

par exemple, serverSocket.accept (1000); ferme automatiquement la demande après 1 seconde

Dhruv Goyal
la source