Wie kann ich eine ServerSocket accept () -Methode unterbrechen?

143

In meinem Hauptthread habe ich eine while(listening)Schleife, die accept()mein ServerSocket-Objekt aufruft, dann einen neuen Client-Thread startet und ihn einer Sammlung hinzufügt, wenn ein neuer Client akzeptiert wird.

Ich habe auch einen Admin-Thread, mit dem ich Befehle wie 'exit' ausgeben möchte, wodurch alle Client-Threads heruntergefahren, selbst heruntergefahren und der Haupt-Thread heruntergefahren werden, indem das Abhören auf false eingestellt wird.

Der accept()Aufruf in der while(listening)Schleife blockiert jedoch, und es scheint keine Möglichkeit zu geben, ihn zu unterbrechen, sodass die while-Bedingung nicht erneut überprüft werden kann und das Programm nicht beendet werden kann!

Gibt es einen besseren Weg, dies zu tun? Oder eine Möglichkeit, die Blockierungsmethode zu unterbrechen?

lukeo05
quelle
Für den Pepole, der x-mal warten und dann reagieren möchte, verwenden Sie setSoTimeout ().
Abdullah Orabi

Antworten:

151

Sie können close()von einem anderen Thread aus aufrufen , und der accept()Aufruf löst a aus SocketException.

Simon Groenewolt
quelle
2
Danke, so offensichtlich, ist mir nicht einmal in den Sinn gekommen! Ich habe close () aufgerufen, nachdem ich die Schleife verlassen hatte.
lukeo05
4
Seltsam, dass diese Informationen in den Dokumenten nicht enthalten sind: Die Methode download.oracle.com/javase/6/docs/api/java/net/… wird nicht als Auslösen einer SocketException markiert. Es wird hier nur download.oracle.com/javase/1.4.2/docs/api/java/net/…
Vladislav Rastrusny
1
Ist es in Ordnung anzurufen close(), ich meine, es wird die Ausnahme auslösen, also gibt es eine andere Möglichkeit (die keine Ausnahme auslöst, auch nicht basierend auf dem Timeout), nicht mehr auf Anfragen zu warten?
Kushal
3
Und was tun, wenn die Verbindung bereits akzeptiert wurde und der Thread auf einige Daten wartet: while ((s = in.readLine ())! = Null)?
Alex Fedulov
1
@AlexFedulov Fahren Sie diesen Socket für die Eingabe herunter. readLine()gibt dann null zurück und die normalen Schließvorgänge, die der Thread bereits haben sollte, werden ausgeführt.
Marquis von Lorne
31

Wenn Sie das Zeitlimit aktivieren accept(), wird die Blockierung nach der angegebenen Zeit durch den Anruf zeitlich begrenzt:

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

Legen Sie ein Zeitlimit für Blockierungsvorgänge fest Socket:

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

Die Option muss vor dem Eingeben eines Blockierungsvorgangs festgelegt werden, damit sie wirksam wird. Wenn das Zeitlimit abläuft und der Vorgang weiterhin blockiert, java.io.InterruptedIOExceptionwird ausgelöst. Das Socketist in diesem Fall nicht geschlossen.

Juha Syrjälä
quelle
4

Sie können einfach einen "void" -Socket für break serversocket.accept () erstellen.

Serverseite

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

Methode zum Unterbrechen des Serverzyklus

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

Der Grund für die ServerSocket.close()Auslösung einer Ausnahme ist , dass an diesen Socket ein outputstreamoder ein inputstreamSocket angeschlossen ist. Sie können diese Ausnahme sicher vermeiden, indem Sie zuerst die Eingabe- und Ausgabestreams schließen. Versuchen Sie dann, das zu schließen ServerSocket. Hier ist ein Beispiel:

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

Sie können diese Methode aufrufen, um jeden Socket von überall zu schließen, ohne eine Ausnahme zu erhalten.

Soham Malakar
quelle
7
Eingabe- und Ausgabestreams sind nicht an das, ServerSocketsondern an ein angehängt, Socketund wir sprechen über das Schließen des ServerSocketnicht das Socket, sodass das ServerSocketgeschlossen werden kann, ohne die SocketStreams von a zu schließen.
icza
1

Verwenden Sie serverSocket.setSoTimeout (timeoutInMillis) .

Joop Eggen
quelle
Dies funktioniert nicht ... Wenn Sie es zum Laufen bringen würden, würden Sie bitte ein Beispiel für Arbeitscode zeigen?
Michael Sims
1

OK, ich habe das so funktioniert, dass die Frage des OP direkter angesprochen wird.

Lesen Sie weiter über die kurze Antwort hinaus, um ein Thread-Beispiel zu erhalten, wie ich dies verwende.

Kurze Antwort:

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

Beispiel aus der realen Welt:

In diesem Beispiel wartet ein ServerSocket auf eine Verbindung innerhalb eines Threads. Wenn ich die App schließe, möchte ich den Thread (genauer gesagt den Socket) auf saubere Weise herunterfahren, bevor ich die App schließen lasse. Daher verwende ich .setSoTimeout () auf dem ServerSocket und verwende dann den ausgelösten Interrupt Nach dem Timeout überprüfen Sie, ob das übergeordnete Element versucht, den Thread herunterzufahren. Wenn ja, dann setze ich den Socket schließen, setze dann ein Flag, das anzeigt, dass der Thread fertig ist, und breche dann aus der Threads-Schleife aus, die eine Null zurückgibt.

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

}

Dann, in meiner Controller-Klasse ... (Ich zeige nur relevanten Code, massiere ihn nach Bedarf in deinen eigenen Code)

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

}

Ich hoffe, das hilft jemandem auf der Straße.

Michael Sims
quelle
0

Eine andere Sache, die Sie versuchen können, um sauberer zu sein, besteht darin, ein Flag in der Accept-Schleife zu überprüfen. Wenn Ihr Admin-Thread dann die Thread-Blockierung auf dem Accept beenden möchte, setzen Sie das Flag (machen Sie es thread-sicher) und erstellen Sie dann einen Client-Socket Anschluss an die Abhörbuchse. Das Akzeptieren hört auf zu blockieren und gibt den neuen Socket zurück. Sie können ein einfaches Protokoll ausarbeiten, das dem Listening-Thread sagt, dass er den Thread sauber beenden soll. Schließen Sie dann den Socket auf der Clientseite. Keine Ausnahmen, viel sauberer.

stu
quelle
Das macht keinen Sinn ... wenn Sie Code wie diesen haben socket = serverSocket.accept (); In diesem Moment beginnt das Blockieren und die Schleife ist nicht Teil unseres Codes. Es gibt also eine Möglichkeit, diesen Blockierungscode nach einem Flag suchen zu lassen, das ich gesetzt habe ... zumindest konnte ich keinen Weg finden, dies zu tun. .. wenn Sie Arbeitscode haben, teilen Sie bitte?
Michael Sims
Ja, anscheinend habe ich eine wichtige Information ausgelassen, die nicht viel Sinn macht. Sie müssten den Accept-Socket nicht blockieren und nur für einige Zeit blockieren, bevor sich die Schleife wiederholt. In diesem Bereich würden Sie dann nach dem Exit-Flag suchen.
stu
Wie genau machen Sie den Accept Socket nicht blockierend?
Michael Sims
lang an = 1L; if (ioctl (Socket, (int) FIONBIO, (char *) & on))
stu
@stu Diese Frage bezieht sich auf Javas Sockets.
Kröw