Como posso interromper um método ServerSocket accept ()?

143

No meu thread principal, tenho um while(listening)loop que chama accept()meu objeto ServerSocket, inicia um novo thread do cliente e o adiciona a uma coleção quando um novo cliente é aceito.

Também tenho um thread de administração que quero usar para emitir comandos, como 'exit', que fará com que todos os threads do cliente sejam desligados, desligados e desligados do thread principal, tornando a escuta false.

No entanto, a accept()chamada no while(listening)loop é bloqueada e não parece haver nenhuma maneira de interrompê-la, portanto a condição while não pode ser verificada novamente e o programa não pode sair!

Existe uma maneira melhor de fazer isso? Ou alguma maneira de interromper o método de bloqueio?

lukeo05
fonte
Para o pepole que deseja esperar x tempo e depois reagir, use setSoTimeout ().
Abdullah Orabi 22/01

Respostas:

151

Você pode ligar close()de outro segmento, e a accept()chamada emitirá a SocketException.

Simon Groenewolt
fonte
2
Obrigado, tão óbvio, nem me ocorreu! Eu estava ligando para close () depois de sair do loop.
lukeo05
4
Estranho, o fato de não haver essas informações nos documentos: o método download.oracle.com/javase/6/docs/api/java/net/… não está marcado como lançando SocketException. Ele é mencionado apenas aqui download.oracle.com/javase/1.4.2/docs/api/java/net/…
Vladislav Rastrusny
1
É bom ligar close(), quero dizer, isso lançará a exceção ao fazê-lo; portanto, existe outra maneira (que não gera exceção, também não baseada no tempo limite) para parar de atender a solicitações?
Kushal
3
E o que fazer se a conexão já tiver sido aceita e o encadeamento estiver aguardando alguns dados: while ((s = in.readLine ())! = Null)?
Alex Fedulov
1
@AlexFedulov Desligue esse soquete para entrada. readLine()retornará nulo e ocorrerão as operações normais de fechamento que o encadeamento já deve ter.
Marquês de Lorne
31

Ative o tempo limite accept(), a chamada atingirá o tempo limite do bloqueio após o tempo especificado:

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

Defina um tempo limite para Socketoperações de bloqueio :

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

A opção deve ser definida antes da entrada em uma operação de bloqueio para entrar em vigor. Se o tempo limite expirar e a operação continuar bloqueando, java.io.InterruptedIOExceptionserá gerado. O Socketnão está fechado neste caso.

Juha Syrjälä
fonte
4

Você pode simplesmente criar um soquete "void" para quebrar os servidores socket.accept ()

Lado do 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 interromper o ciclo do 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
fonte
4

A razão ServerSocket.close()lança uma exceção é porque você tem um outputstreamou um inputstream anexo a esse soquete. Você pode evitar essa exceção com segurança fechando primeiro os fluxos de entrada e saída. Em seguida, tente fechar o ServerSocket. Aqui está um exemplo:

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

Você pode chamar esse método para fechar qualquer soquete de qualquer lugar sem obter uma exceção.

Soham Malakar
fonte
7
Os fluxos de entrada e saída não estão anexados ao, ServerSocketmas ao a, Sockete estamos falando de fechar o ServerSocketnot the Socket, para que ele ServerSocketpossa ser fechado sem fechar Socketos fluxos de a.
icza 3/09/14
1

Use serverSocket.setSoTimeout (timeoutInMillis) .

Joop Eggen
fonte
Isso não funciona ... se você conseguisse funcionar, por favor, mostrasse um exemplo de código funcional?
Michael Sims
1

OK, consegui isso trabalhando de uma maneira que resolva a questão do OP mais diretamente.

Continue lendo a resposta curta para um exemplo de Thread de como eu uso isso.

Resposta curta:

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

Exemplo do mundo real:

Neste exemplo, eu tenho um ServerSocket aguardando uma conexão dentro de um Thread. Quando fecho o aplicativo, desejo desligar o encadeamento (mais especificamente, o soquete) de maneira limpa antes de deixá-lo fechar, então uso o .setSoTimeout () no ServerSocket e depois a interrupção acionada após o tempo limite para verificar e verificar se o pai está tentando desligar o encadeamento. Nesse caso, defino o soquete, defino um sinalizador indicando que o encadeamento está concluído e interrompo o loop do encadeamento, que retorna um 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;
    }
};

}

Então, na minha classe Controller ... (mostrarei apenas o código relevante, massageie-o no seu próprio código, conforme necessário)

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 isso ajude alguém no caminho.

Michael Sims
fonte
0

Outra coisa que você pode tentar, que é mais limpa, é verificar um sinalizador no loop de aceitação e, quando o thread do administrador desejar eliminar o bloqueio de threads no accept, defina o sinalizador (torne o thread seguro) e faça um soquete de cliente ligação à tomada de audição. A aceitação interromperá o bloqueio e retornará o novo soquete. Você pode descobrir alguma coisa simples sobre o protocolo que diz ao thread de escuta para sair do thread de maneira limpa. E, em seguida, feche o soquete no lado do cliente. Sem exceções, muito mais limpo.

stu
fonte
isso não faz sentido ... quando você tem um código como este socket = serverSocket.accept (); naquele momento, o bloqueio é iniciado e o loop não faz parte do nosso código; portanto, há uma maneira de fazer com que o código de bloqueio procure um sinalizador que eu defini ... pelo menos não consegui encontrar uma maneira de fazer isso. .. se você tem código de trabalho, por favor, compartilhe?
Michael Sims
Sim, parece que deixei de fora uma informação crucial que faria com que não fizesse muito sentido. Você precisaria tornar o soquete de aceitação sem bloqueio e apenas bloquear por um período de tempo antes da repetição do loop, em que área você verificaria o sinalizador de saída.
22418 Stu
Como exatamente você faz o soquete de aceitação, sem bloqueio?
Michael Sims
long = 1L; if (ioctl (socket, (int) FIONBIO, (char *) & on)))
stu
@stu Esta pergunta é para os soquetes do Java.
Kröw 23/07/19