Jak mogę przerwać działanie metody accept () ServerSocket?

143

W moim głównym wątku mam while(listening)pętlę, która wywołuje accept()mój obiekt ServerSocket, a następnie uruchamia nowy wątek klienta i dodaje go do kolekcji, gdy nowy klient jest akceptowany.

Mam również wątek administratora, którego chcę używać do wydawania poleceń, takich jak „exit”, co spowoduje zamknięcie wszystkich wątków klienta, zamknięcie się i zamknięcie głównego wątku, ustawiając słuchanie na false.

Jednak accept()wywołanie w while(listening)pętli blokuje się i wydaje się, że nie ma sposobu, aby je przerwać, więc warunek while nie może być ponownie sprawdzony, a program nie może wyjść!

Czy jest lepszy sposób na zrobienie tego? Albo jakiś sposób na przerwanie metody blokowania?

lukeo05
źródło
Dla ludzi, którzy chcą poczekać x time, a następnie zareagować, użyj setSoTimeout ().
Abdullah Orabi

Odpowiedzi:

151

Możesz zadzwonić close()z innego wątku, a accept()wywołanie wyrzuci plik SocketException.

Simon Groenewolt
źródło
2
Dzięki, tak oczywiste, nawet mi nie przyszło do głowy! Dzwoniłem do close () po wyjściu z pętli.
lukeo05
4
Dziwne, że nie ma tej informacji w docs: metoda download.oracle.com/javase/6/docs/api/java/net/… nie jest oznaczona jako wyrzucająca SocketException. Jest tu tylko wspomniane download.oracle.com/javase/1.4.2/docs/api/java/net/…
Vladislav Rastrusny
1
Czy można wywołać close(), mam na myśli, że wyrzuci wyjątek podczas robienia tego, więc czy istnieje inny sposób (który nie zgłasza wyjątku, również nie jest oparty na limicie czasu), aby zatrzymać nasłuchiwanie żądań?
Kushal
3
A co zrobić, jeśli połączenie zostało już zaakceptowane, a wątek czeka na jakieś dane: while ((s = in.readLine ())! = Null)?
Alex Fedulov
1
@AlexFedulov Zamknij to gniazdo dla wejścia. readLine()zwróci wtedy wartość null i wystąpią normalne operacje zamykania, które wątek powinien już mieć na miejscu.
Markiz Lorne
31

Ustaw limit czasu na accept(), a połączenie wyłączy blokowanie po określonym czasie:

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

Ustaw limit czasu Socketoperacji blokowania :

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

Opcja musi być ustawiona przed wprowadzeniem operacji blokowania, aby odniosła skutek. Jeśli limit czasu wygaśnie, a operacja będzie nadal blokować, java.io.InterruptedIOExceptionzostanie zgłoszony. W Sockettym przypadku nie jest zamknięty.

Juha Syrjälä
źródło
4

Możesz po prostu utworzyć gniazdo "void" dla przerwania serwera server.accept ()

Po stronie serwera

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

Metoda przerwania cyklu serwera

void acceptClients() {
     try {
          Socket s = new Socket(myIp, PORT);
          s.getOutputStream().write(END_WAITING);
          s.getOutputStream().flush();
          s.close();
     } catch (IOException e) {
     }
}
Nickolay Savchenko
źródło
4

Przyczyną ServerSocket.close()zgłaszania wyjątku jest to, że masz dołączone outputstreamlub inputstreamdołączone do tego gniazda. Możesz bezpiecznie uniknąć tego wyjątku, najpierw zamykając strumienie wejściowe i wyjściowe. Następnie spróbuj zamknąć ServerSocket. Oto przykład:

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

Możesz wywołać tę metodę, aby zamknąć dowolne gniazdo z dowolnego miejsca bez uzyskiwania wyjątku.

Soham Malakar
źródło
7
Strumienie wejściowe i wyjściowe nie są dołączone do ServerSocketale do a Socketi mówimy o zamykaniu ServerSocketnot the Socket, więc ServerSocketmożna je zamknąć bez zamykania Socketstrumieni a.
icza
1

OK, mam to działające w sposób, który bardziej bezpośrednio odpowiada na pytanie PO.

Czytaj dalej obok krótkiej odpowiedzi, aby zobaczyć przykładowy wątek, w jaki sposób tego używam.

Krótka odpowiedź:

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

Przykład z prawdziwego świata:

W tym przykładzie mam ServerSocket oczekujący na połączenie wewnątrz Thread. Kiedy zamykam aplikację, chcę zamknąć wątek (a dokładniej gniazdo) w czysty sposób, zanim pozwolę aplikacji zamknąć, więc używam .setSoTimeout () na ServerSocket, a następnie używam wyrzucanego przerwania po upływie limitu czasu, aby sprawdzić, czy rodzic próbuje zamknąć wątek. Jeśli tak, to ustawiam zamknięcie gniazda, następnie ustawiam flagę wskazującą, że wątek jest zakończony, a następnie wychodzę z pętli Threads, która zwraca wartość 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;
    }
};

}

Następnie w mojej klasie kontrolera ... (pokażę tylko odpowiedni kod, wmasuję go we własny kod w razie potrzeby)

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

}

Mam nadzieję, że to pomoże komuś w dalszej drodze.

Michael Sims
źródło
0

Inną rzeczą, którą możesz wypróbować, która jest czystsza, jest sprawdzenie flagi w pętli accept, a następnie, gdy twój wątek administratora chce zabić blokowanie wątku na akceptacji, ustaw flagę (uczyń go bezpiecznym dla wątku), a następnie utwórz gniazdo klienta podłączenie do gniazda nasłuchowego. Akceptacja przestanie blokować i zwróci nowe gniazdo. Możesz wypracować prosty protokół, który mówi wątkowi nasłuchującemu, aby czysto wychodził z wątku. A następnie zamknij gniazdo po stronie klienta. Bez wyjątków, dużo czystsze.

stu
źródło
to nie ma sensu ... kiedy masz kod taki jak ten socket = serverSocket.accept (); w tym momencie zaczyna się blokowanie i pętla nie jest częścią naszego kodu, więc jest sposób, aby kod blokujący szukał flagi, którą ustawiłem ... przynajmniej nie byłem w stanie znaleźć sposobu, aby to zrobić. .. jeśli masz działający kod, udostępnij go?
Michael Sims
Tak, wydaje się, że pominąłem jedną kluczową informację, która sprawiłaby, że nie miałoby to większego sensu. Musiałbyś sprawić, by gniazdo akceptacji nie było blokujące i blokowało się tylko przez pewien czas, zanim pętla się powtórzy, w którym miejscu następnie sprawdzisz flagę wyjścia.
Stu
Jak dokładnie zrobić gniazdo akceptacji jako nieblokujące?
Michael Sims,
długi na = 1L; if (ioctl (gniazdo, (int) FIONBIO, (char *) & on))
stu
@stu To pytanie dotyczy gniazd Java.
Kröw