Socket instável (?)

8 respostas
zerokelvin

Salve galera!
Abaixo posto um código, sobre o qual já trabalhei um bocado, que já modifiquei várias vezes.
É um pequeno servidor, que recebe o login, se for o correto lança uma thread e tals…

Acho que não é das melhores práticas como está exposto abaixo, utilizando strings para comunicação, mas essa é apenas mais uma versao do servidor. O que acontece é que quando lanço a thread, ele aponta:

Conexao normal
java.net.SocketException: Socket is closed
        at java.net.Socket.getOutputStream(Unknown Source)
        at Servidor.run(Servidor.java:136)

a 136 é a ObjectInputStream istream = new ObjectInputStream(conexao2.getInputStream());

Já tentei dar flush(), close() e tudo mais… o engraçado é que quando dou ostream.close() por exemplo, cai o socket todo, depois da thread também… li também que o ostream e o istream devem ser recriados a cada envio(???)…

Bom, posto ae pra vcs darem uma olhada e sugerirem algo.
valeu ae!

public class Servidor extends Thread{
      static Connection cmysql = null;
      Socket conexao2;
      ObjectOutputStream ostream = null;  
      ObjectInputStream istream = null;
      String nomeT = new String("Servidor 0");      
       public void eO(Object j){
         try{
            ostream.writeObject(j);
            ostream.flush();
            System.out.println(j.toString());
         }
             catch(Exception e){
               e.printStackTrace();
            }
      
      }
       public String getNomeT(){
         return nomeT;
      }
       public void setNomeT(String n){
         nomeT = n;
      }    
   	
       public static void main(String args[]){
         
         try {
            ServerSocket sv = new ServerSocket(8000);
            int serv = 1;
            while (true) {
               System.out.print("Esperando alguem se conectar...");
               Socket conexao = sv.accept();
               System.out.println(" Conectou!");
            	
               ObjectOutputStream ostream0 = new ObjectOutputStream(conexao.getOutputStream());  
               ObjectInputStream istream0 = new ObjectInputStream(conexao.getInputStream()); 
               try{
                  String[] a = (String[])istream0.readObject();//1
                  System.out.println("Lidos " + a[0] + a[1] + a[2]);
                  int tipo = Integer.valueOf(a[0]);
                  String user = a[1];
               	
                  String pass = a[2];
                 
                  String autorizado = new String("não");
                  switch(tipo){
                     case 1:
                        try{
                           
                           if(Login(user, pass)){
                              autorizado = "sim";
                              System.out.println("Nova thread");
                              Thread t = new Servidor(conexao, "Servidor " + serv);
                              t.start();
                              serv = serv+1;
                           }
                           
                          // ostream.flush();
                        }
                            catch(Exception e){e.printStackTrace();
                              System.out.println("Conexão não aceita");
                              autorizado = "não";
                           }
                        
                     			
                     		
                        break;
                     case 2000:
                        System.out.println("Conexao errada");
                        break;
                                   
                  }
                  ostream0.writeObject(autorizado);//4
                  
                  
               
               
               }   
                   catch(Exception e){e.printStackTrace();
                   String n = new String("Erro");
                   ostream0.writeObject(n);//4
                   }            	            
            }
         }
             catch (IOException e) {
            
               System.out.println("IOException: " + e);
            }
      
      
      }
      
       public Servidor(Socket s, String n){
         conexao2 = s;
         setNomeT(n);
         
      }
       public void run(){
         try{  
            
            System.out.println(getNomeT());
                      
          
            while(true){
            if(conexao2!=null){
            System.out.println("Conexao normal");
            }  
               ObjectOutputStream ostream = new ObjectOutputStream(conexao2.getOutputStream());  
               ObjectInputStream istream = new ObjectInputStream(conexao2.getInputStream());
               String[] s = (String[])istream.readObject();
               int tipo = Integer.valueOf(s[0]);

8 Respostas

E

Existe um método chamado shutdown na classe Socket. Isso não lhe lembra alguma coisa?

zerokelvin

sim sim… mas pelo q eu tinha entendido, a conexão é passada do main para o thread, dae fica rodando, o socket continuaria… por isso me preocupei mais com o objectoutput/input stream…achei q o erro estivesse ai. Procurei já material sobre, mas está meio escasso… se vc conhecer algum tutorial completo sobre, manda o link por favor. Fui montando isso apartir de exemplos q achei

a api já li, mas como disse, não achei q o problema fosse o socket (aliás: é?)

Então devo utilizar o shutdown quando eu terminar o envio e receimento? sempre assim?

valeu ajuda ae.

CarvalR2

zerokelvin,

Sem ofença cara, mas primeiro organiza teu código . Está difícil de ler … alguns nomes das variáveis confundem (conexao2, ostream0, … ).
Tenho impressão que seu socket está instável por que você está misturando as coisas, dada a falta de organização.

Seu servidor deve ter pelo menos duas classes.

A primeira deve possuir duas responsabilidades:

  • Fazer loop (infinito ou condicionado a algum flag) aguardando conexões;
  • Chegando uma conexão, imediatamente criar uma thread e passar a variável “Socket conexao” para esta thread, que é a sua segunda classe.

A segunda classe tem as responsabilidades:

  • Ser criada utilizando o objeto ‘conexao’ fornecido via construtor;
  • Obter o inputstream e o outputstream da conexão cliente;
  • Fazer a autenticacao;
  • manter a conversa enquanto o cliente está conectado (loop enquanto cliente conectado)

A primeira classe deve o máximo possível ser leve para sempre poder atender a novos clientes.
Então colocar código nela para verificar autorizações faz com que outros clientes não consigam conectar, pois você não estará fazendo sv.accept().
Então enquanto sua classe autentica uma conexão, outro cliente vai conectar e não consegue, porque você parou de fazer sv.accept(). Isto está errado, a menos que queira este comportamento incomum. Então passe a responsa de autenticar para a segunda classe.

Quando a primeira classe repassa a bola para a segunda classe, ela está livre para atender a novos clientes. E pronto.

A segunda classe é que terá o atributo ‘Connection cmysql’
E não terá mais nenhum atributo, pois o inputstream e outputstream ficarão dentro do método run() dela, como variáveis locais.
No inicio do metodo run() é pegar os input/output e entrar dentro do while(cliente conectado) e partir para a conversa.
O cliente desconectando, essa segunda classe morre também.

A relação da primeira classe para a segunda classe é 1 - N , sendo N o número de clientes conectados simultaneamente.

Te sugiro fazer um refactoring ai e postar de novo cara.

zerokelvin

Carval,

mando bem heheheehe tava certíssimo!
Já agradeço a ajuda ae sua e do entalag.!
refiz aqui, posto abaixo. É isso mesmo? bom, to testando e adaptando algumas partes do cliente.. acho q agora vai...

Mas pegunto mais uma coisa: preciso necessariamente usar o flush() o close() e o shutdown alguma vez entre uma entrada de dados e outra ou entre uma saida e uma entrada? valeu ae!
public class Porto{
   
       public static void main(String args[]){
         
         try {
            ServerSocket sv = new ServerSocket(8000);
           
            while (true) {
               System.out.print("Esperando alguem se conectar...");
               Socket conexao = sv.accept();
               System.out.println("Conectou!");
               Thread t = new Servidor(conexao);
               t.start();
              
            }
         
         }
             catch(Exception e){e.printStackTrace();}
      }
   
   }




    class Servidor extends Thread{
      static Connection cmysql = null;
      private Socket conexao = null;
   	    
       public Servidor(Socket s){
         conexao = s;
        
      }      
         
   	
      
    
       
       public void run(){
         try{                 	
            ObjectOutputStream ostream = new ObjectOutputStream(conexao.getOutputStream());  
            ObjectInputStream istream = new ObjectInputStream(conexao.getInputStream()); 
                   	            
            String user = (String)istream.readObject();  
            String pass = (String)istream.readObject();  
         
            int auth = 0;                  
            if(Login(user, pass)){
            System.out.println("Autorizado");
               auth = 1;
               ostream.writeObject(auth);
                                           
            
               while(true){
               try{
                  
                  int tipo = (Integer)istream.readObject();
                  
                  System.out.println("Rodando tipo " + tipo);
               
                  String banco = null;
                  String tabela = null;
                  String[] valores = null;
                  String[][] metaTabC = null;
                  String[] dadosUser = null;
                  boolean b = false;
                  int ID = 0;
                  switch(tipo){
                     case 1:
                       
                       
                     case 2:
                        .
.
.
.
.

                     case 4:

heheheheehe outra vida!

CarvalR2

hehehehe, o que que um puxão de orelha não faz… hehehehe

zerokelvin,

Seu código ficou muito mais profissional viu. Está de parabéns agora! Fácil de ler, sem firulas , simples e objetivo. E sólido!

Ok!

O flush() possivelmente não vai mostrar nenhum efeito em seu código. Geralmente a gente coloca para garantir que os bytes não serão “bufferizados”. Suponha que uma implementação da Outputstream “bufferize” os dados e não envie para o destinatário naquele exato momento. Pois bem, o flush é para dizer ao output para enviar naquele momento. Mas a implementação default do outputstream envia de imediato, pelo que me lembro.
Então você poderia chamar o flush após usar o write.

Todas as classes referente a recursos (FileReader, Outputstream, input, etc) que tiverem um método close() é porque ele deve ser utilizado. Grava isso na mente e leva para o resto da sua vida. Isso sempre causa leak de memória em softwares. E só vemos o leak quando nosso aplicativo está sob stress de load.

No seu caso, o close() deve ser chamado:

  • Na classe Porto:
Você tem que passar a declaração ServerSocket sv para fora do try atribuindo Null para ele. Quando entrar no try ele recebe new ServerSocket();

Coloca um finally {  } neste try/catch  e inclui o close da variavel sv:

if (sv !=null) { sv.close(); }
  • No metodo run() da classe Servidor:

Passa ostream e istream para fora do try atribuindo null. E dentro do try continua recebendo new Object
Coloca um finally {} no try/catch para dar close() nos streams.
Dentro do finally coloca:

if (ostream !=null) { ostream.close(); }
if (istream !=null) { istream.close(); }

Ou seja, você pega os streams somente uma vez quando starta sua thread que vai atender ao cliente.
E fecha os recursos somente quando terminar a conversa com o cliente.

Esse shutdown eu nao uso não.

Dá o feedback se funcionou.

Até+

CarvalR2

ah, no finally do try/catch no metodo run() tambem tem que colocar o close() da atributo conexao.

zerokelvin

Como ficou:

public class Porto{
   
       public static void main(String args[]){
         ServerSocket sv = null;
         
         try {
            sv = new ServerSocket(8000);
           
            while (true) {
               System.out.print("Esperando alguem se conectar...");
               Socket conexao = sv.accept();
               System.out.println("Conectou!");
               Thread t = new Servidor(conexao);
               t.start();
              
            }
         
         }
             catch(Exception e){
               e.printStackTrace();
            }
         finally{
            if(sv!=null){
               try{
                  sv.close();
               }
                   catch(Exception e){
                     e.printStackTrace();
                  }
            }
         }
      }
   
   }




    class Servidor extends Thread{
      static Connection cmysql = null;
      private Socket conexao = null;
      
AchaCapitulo acapitulo = new AchaCapitulo();
      Estuda estuda = new Estuda();
   	  
       public Servidor(Socket s){
         conexao = s;
      }      
         
   	
      
    
       public void run(){
      	
         
         ObjectOutputStream ostream = null;  
         ObjectInputStream istream = null;
              
      
         
         try{                 	
            ostream = new ObjectOutputStream(conexao.getOutputStream());  
            istream = new ObjectInputStream(conexao.getInputStream()); 
                   	            
            String user = (String)istream.readObject();  
            String pass = (String)istream.readObject();  
         
            int auth = 0;                  
            if(Login(user, pass)){
               System.out.println("Autorizado");
               auth = 1;
               ostream.writeObject(auth);
                                           
               acapitulo.conexaoMatrix = cmysql;
               while(conexao!=null){
                  try{
                  
                     int tipo = (Integer)istream.readObject();
                     if  (tipo == -1) {  
                        this.stop();	
                        conexao.close();    
                     }  
                     System.out.println("Rodando tipo " + tipo);
                  
                     String banco = null;
                     String tabela = null;
                     String[] valores = null;
                     String[][] metaTabC = null;
                     String[] dadosUser = null;
                     boolean b = false;
                     int ID = 0;
                     switch(tipo){
                        case 1:
                        case 2:
                        case 3:  
    }	
            
            }
            else{ostream.writeObject(auth);
            }
                        
         }
             catch(Exception e){e.printStackTrace();}
         finally{
            try{
               if (ostream !=null) { ostream.close(); }
               if (istream !=null) { istream.close(); }
               if (cmysql !=null) { cmysql.close(); }  
            }
                catch(Exception e){
                  e.printStackTrace();
               }
         }
      }

Obs: AchaCapitulo e Estuda são classes que manipulam o banco, preciso delas como atributo da classe, pq é acessada por metodos fora do run().

Puts cara, valeu memo. A partir de amanha começo a testar tudo isso com umas 15 maquinas na rede… posto as modificações necessárias se houver alguma. Mas já adianto: fiz nas ultimas + ou - 10 horas de programação o que não tinha feito em 4 dias heheheheheeheheh

AGORA UMA AVISO AOS CARAS Q ESTÃO COMEÇANDO NESSA DE STREAMS: li em alguns foruns que era necessário renovar os streams, que o flush era sempre necessário e tals, embora a api não explicitasse isso. Me confundi bem, pq os exemplos disponíveis são simples demais e o acumulo em fóruns RELATIVAMENTE escasso… BASTA garantir a harmonia da comunicação entre o servidor e o cliente, o mesmissimo objeto funcionará, sem limpeza com flush nem nada… meu programa está MTO complexo, e está se comunicando bem com o servidor, nas últimas horas nao registrei nenhum erro.

Mais uma pergunta Carval:
Precisei passar minha conexão de uma classe para outra, no cliente. Imaginei que eu deveria fechar o lado cliente dela (para isso usam o tal shutdown, pq o close fecha os dois lados) e reabrir na outra classe… isso deu errado, fechar o ostream ou apenas abrir outro na outra classe também deu errado… dai passei os streams por um método setStreams(ObjectOutputStream, Imp…) que seta isso na outra classe…

só tem esse jeito? é o certo?
abs!

CarvalR2

falae zerokelvin,

Sua implementação está ok agora . não vi mais nenhum problema.

Quanto a sua dúvida, não há como dizer que não esteja certo. Agora a discussão passa a ser mais em torno de boas práticas de arquitetura.
Pensando em um software em camadas (ex.: view, controller, model) … ou ainda camada visual, camada de negocio e camada de persistencia, seria interessante você isolar o meio de comunicação das suas classes de negócio.
Assim você teria uma camada de negócio que conversa com a camada de persistência.

Sendo que a camada de persistência é que sabe qual o “”“meio”"" de persistencia que utilizará.

Vamos supor que o requisito seja implementar um cliente que faça uma comunicação com o servidor. Mas o meio pelo qual vão conversar pode ser alterado no futuro. Podendo ser a comunicação via webservice, banco de dados, LDAP, ou socket.

Então é interssante você isolar o meio de comunicação em uma camada de persistência, de forma que a camada de negócio (suas classes de negócio) faça a conversa, mas sem especificar a forma em que se deu a conversa.

Sendo assim, você poderia ter um pattern DAO aplicado em seu projeto.
E a camada business conversa com a camada de persistencia, atraves do pattern DAO.

Fiz este rascunho de classes para você analisar ai:

public abstract class CanalServidorDao {

private static CanalServidorDao instancia;

public CanalServidorDao getInstance() {
	// verifica se ha configuracao em arquivo properties. 
	// Se nao houver, pega implementação default.
	// Usa pattern Singleton
	
	if (instancia != null) {
		return instancia;
	}

	instancia = this.getConfiguracaoArquivoProperties();
	instancia = instancia != null ? instancia : new CanalServidorDaoSocketImpl();

	instancia.inicializarCanal();

	return instancia;
}

private CanalServidorDao getConfiguracaoArquivoProperties() {
	// acessa arquivo properties. se arquivo nao existir, retorna null
	// acessa propriedade instancia_classe_canal_servidor
	// se nao existir a propriedade, retorna null
	...
}

public void inicializarCanal();

public void escreverMensagem(String...) throws CanalServidorInterrompido;

public String receberMensagem() throws CanalServidorInterrompido;

public void finalizarCanal();

}

public class CanalServidorDaoSocketImpl extends CanalServidorDao {

private static final int porta = 8000; // talvez possa estar em arquivo .properties
private static final String hostServidor = "0.0.0.0"; // talvez possa estar em arquivo .properties

// o arquivo properties poderia ficar em um local unico da rede, para que todos os clientes
// possam acessar e carregar o endereco do servidor.
// assim se o servidor mudar de endereco, nao sera tao oneroso.

Socket conexao;	

public void inicializarCanal(Map<String,String> parametros) { 
	if (conexao == null || conexao.isClosed()) {
		.... 
	}
}

public void escreverMensagem(String...) throws CanalServidorInterrompido { 
	.... 
}

public String receberMensagem() throws CanalServidorInterrompido { 
	.... 
}

public void finalizarCanal() { 
	try { conexao.close(); conexao = null;} catch (...) {}
}

}

Se você utilizar em sua camada de negócio da forma abaixo, estará isolando o meio de comunicação.
E futuramente nada impede de você utilizar outra implementação (por exemplo CanalServidorDaoWebServiceImpl) , desde que esta classe extenda a classe abstrata, “realizando o contrato estabelecido por ela”.

Nas suas classes, voce utilizaria assim:

CanalServidorDao canal = CanalServidorDao.getInstance();

canal.escreverMensagem();

canal.receberMensagem();

Esta implementação supõe que seu cliente terá apenas 1 e somente uma conexão com o servidor. Ou seja, a cada instância de JVM, você terá apenas uma instância de CanalServidorDao, pois esta classe implementa o pattern Singleton.

Criado 8 de junho de 2010
Ultima resposta 9 de jun. de 2010
Respostas 8
Participantes 3