Problema com ServerSocket método accept em Thread

Olá galera,

Bom estou com um problema, já descobri a causa e até implementei uma solução, mas não me pareceu a melhor, por isso conta coma ajuda
do pessoal mais experiente. o Código é mais ou menos esse:
Tenho essa Thread que funciona como um servidor

public class Server extends Thread {

    private ServerSocket serverSocket;
    private boolean executar = true;

    public Server() throws IOException {
        this.serverSocket = new ServerSocket(5454);        
    }

    @Override
    public void run() {
        while (executar) {
            try {                
                Socket socket = serverSocket.accept();   

//trata conexão            
            } catch (IOException ex) {
                Logger.getLogger(Server.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }

    public void terminar() {
        this.executar = false;
        if (!serverSocket.isClosed()) {
            try {
				this.serverSocket.close();                
            } catch (IOException ex) {
                Logger.getLogger(Server.class.getName()).log(Level.SEVERE, "Não foi possível finalizar o Server", ex);
            }
        }
    }

O problema é o seguinte, quando inicio essa Thread, ela fica parada no método server.accept(), por que ele é blocante, porém se neste momento eu chamar
o método terminar, e a execução da Thread estiver parada no accept(), ocorre o erro:
java.net.SocketException: socket closed
at java.net.PlainSocketImpl.socketAccept(Native Method)
at java.net.PlainSocketImpl.accept(PlainSocketImpl.java:390)
at java.net.ServerSocket.implAccept(ServerSocket.java:462)
at java.net.ServerSocket.accept(ServerSocket.java:430)
at testesocket.Server.run(Main.java:60)

isso porque o ServerSocket foi fechado e ainda tentou aceitar lançando o erro. Eu até consegui um jeito que funcionou mas acho que pode existir outro melhor, no caso minha solução foi a seguinte, no método terminar, antes de dar um close, eu crio um socket local para conectar com o server, liberando-o da linha accept.

new Socket("127.0.0.1",5454);
this.serverSocket.close();   

O código é mais ou menos assim, na verdade é um sistema de chat da empresa, que não seria legal colocar o código aqui, mas a lógica é a mesma. Imaginem que esse método terminar é a ação para ficar offline, e este server socket é um console de atendimento esperando conexões.

O que eu queria saber, é se tem alguma forma de antes de dar o close, eu fazer com que o serverSocket saia da linha de accept para que eu possa fecha-lo sem dar esse erro?

A solução é essa mesmo. Eu só colocaria testaria ali no catch o flag executar, para evitar logar a exception como um erro, caso ele valesse false. Afinal, é uma exception esperada, conhecida e intencional, não um erro. A exceção só deveria ser logada caso esse valor ainda fosse true.

Outra coisa, você deve declarar sua variável executar como volatile, ou colocar o acesso a ela em getters e setters sincronizados.

olá vini! sabia que você seria o primeiro a responder, inclusive fui teu aluno na UFPR.

O que você sugeriu é mais ou menos isso?

public class Server extends Thread {

    private ServerSocket serverSocket;
    private volatile boolean executar = true;

    public Server() throws IOException {
        this.serverSocket = new ServerSocket(5454);        
    }

    @Override
    public void run() {
        while (executar) {
            try {                
                Socket socket = serverSocket.accept();               
            } catch (IOException ex) {
				this.executar = false;
                Logger.getLogger(Server.class.getName()).log(Level.INFO, "Server parou de aceitar.");
            }
        }
    }

    public void terminar() {
        this.executar = false;
        if (!serverSocket.isClosed()) {
            try {
                                new Socket("127.0.0.1", serverSocket.getLocalPort());
				this.serverSocket.close();                
            } catch (IOException ex) {
                Logger.getLogger(Server.class.getName()).log(Level.SEVERE, "Não foi possível finalizar o Server", ex);
            }
        }
    }

Essa é a única solução mesmo?

Foi meu aluno na UFPR? De computação gráfica ou de SO?

O que eu sugeri foi isso aqui:

@Override public void run() { while (executar) { try { Socket socket = serverSocket.accept(); } catch (IOException ex) { if (!executar) { //Evitar logar o erro quando realmente pedimos para fechar Logger.getLogger(Server.class.getName()).log(Level.INFO, "Server parou de aceitar."); } } }

Não tem jeito melhor não. Se você usar os SocketChannels, você verá por lá os selectors.
Aí sim, vc poderia fazer uma versão não bloqueante de alguns métodos do socket, e evitar esse tipo de tratamento feio.

Ah, agora que vi que tem seu nome na sua assinatura. :slight_smile:
Lembro de vc.

Fui aluno de SO. Seria legal se a minha turma tivesse tido computaçao gráfica, mas agora já acabei!

Eu tinha feito desse jeito, e fiz alguns testes, deu certo por enquanto, tirei a flag do método terminar() e deixei ela
no catch, já que o erro é proposital, também parei de disparar o Socket no método terminar(), ela estava lá só pra não
disparar erro, e mudei a mensagem de erro para info, só para dizer que o server está fechado, mas acho que fica muito amplo
setar a flag no catch, qualquer erro q cair ali termina a Thread:

public class Server extends Thread {

    private ServerSocket serverSocket;
    private volatile boolean executar = true;

    public Server() throws IOException {
        this.serverSocket = new ServerSocket(5454);        
    }

    @Override
    public void run() {
        while (executar) {
            try {                
                Socket socket = serverSocket.accept(); 
				//trata socket...				
            } catch (IOException ex) {
				this.executar = false;
                Logger.getLogger(Server.class.getName()).log(Level.INFO, "Server parou de aceitar.");
            }
        }
    }

    public void terminar() {      
        if (!serverSocket.isClosed()) {
            try {
				this.serverSocket.close();                
            } catch (IOException ex) {
                Logger.getLogger(Server.class.getName()).log(Level.SEVERE, "Não foi possível finalizar o Server", ex);
            }
        }
    }

Acho que vou fazer do jeito que você falou! seria legal se esse ServerSocket tivesse um método, tipo stopAccept() ou algo parecido,
daria para fazer algo bem mais elegante! mas acho que isso só e vai acontecer se eu implementar minha própria ServerSocket! melhor deixar pra lá!

Obrigado pela ajuda Vini!