Socket com múltiplos clientes

15 respostas
A
Boa tarde  <img src="https://cdn.jsdelivr.net/gh/twitter/twemoji@14/assets/72x72/s.pnglight_smile.png?v=9" title=":slight_smile:" class="emoji" alt=":slight_smile:"> ,

andei procurando em vários posts, apostilas e tutoriais, inclusive aqui no GUJ, mas continuo com uma dúvida referente ao Socket em Java: Como tratar cada cliente individualmente.

Vou descrever a situação e o que eu preciso:

*Em uma rede, 20 computadores estarão executando linux somente em modo texto;

*Essas máquinas, ao iniciarem irão fazer uma conexão remota a um servidor terminal server;

*O que eu quero é desenvolver uma aplicação socket, onde cada uma dessas máquinas ficam com um client do socket.

*No server do socket, quero fazer a administração, que é mandar o comando desligar/reiniciar, para máquinas individualmente, ou para todas as máquinas.

*Esse é o problema: não estou conseguindo armazenar os clientes conectados, nem mandar mensagem para um determinado cliente.

Abaixo segue o código fonte, toda sugestão será bem-vinda e desde  deixo meus agradecimentos.

Código do servidor:
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Vector;
import javax.swing.JOptionPane;

public class TelaAdministracao extends javax.swing.JFrame {

    public TelaAdministracao() throws IOException {
        initComponents();
        jbEncerraServer.setEnabled(false);
    }

    public void iniciaServer() throws IOException {
        try {
            jbIniciaServer.setEnabled(false);
            jbEncerraServer.setEnabled(true);
            while (true) {
                cliente = serverSocket.accept();
                jlStatus.setText("Número de clientes conectados: " + conexoes);
                in = new DataInputStream(cliente.getInputStream());
                out = new DataOutputStream(cliente.getOutputStream());
                JOptionPane.showMessageDialog(null, "Um cliente se conectou!");
                conexoes++;
                jlStatus.setText("Número de clientes conectados: " + conectados.size());
            }
        } catch (IOException ex) {
            JOptionPane.showMessageDialog(null, "Nenhum cliente se conectou!");
        }
    }

    public void encerraServer() throws IOException {
        in.close();
        out.close();
        cliente.close();
        serverSocket.close();
        JOptionPane.showMessageDialog(null, "O servidor socketLab foi encerrado!");
    }

    @SuppressWarnings("unchecked")
    // &lt;editor-fold defaultstate="collapsed" desc="Generated Code"&gt;
    private void initComponents() {

        jbIniciaServer = new javax.swing.JButton();
        jbEncerraServer = new javax.swing.JButton();
        jbEnviaComando = new javax.swing.JButton();
        jPanel1 = new javax.swing.JPanel();
        jlStatus = new javax.swing.JLabel();

        setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);

        jbIniciaServer.setText("Iniciar server");
        jbIniciaServer.setPreferredSize(new java.awt.Dimension(30, 30));
        jbIniciaServer.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jbIniciaServerActionPerformed(evt);
            }
        });

        jbEncerraServer.setText("Desligar server");
        jbEncerraServer.setPreferredSize(new java.awt.Dimension(30, 30));
        jbEncerraServer.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jbEncerraServerActionPerformed(evt);
            }
        });

        jbEnviaComando.setText("Enviar um comando");
        jbEnviaComando.setPreferredSize(new java.awt.Dimension(30, 30));
        jbEnviaComando.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jbEnviaComandoActionPerformed(evt);
            }
        });

        jPanel1.setBorder(javax.swing.BorderFactory.createEtchedBorder());

        jlStatus.setText(" ");

        javax.swing.GroupLayout jPanel1Layout = new javax.swing.GroupLayout(jPanel1);
        jPanel1.setLayout(jPanel1Layout);
        jPanel1Layout.setHorizontalGroup(
            jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(jPanel1Layout.createSequentialGroup()
                .addComponent(jlStatus)
                .addContainerGap(393, Short.MAX_VALUE))
        );
        jPanel1Layout.setVerticalGroup(
            jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addComponent(jlStatus)
        );

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
        getContentPane().setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addComponent(jPanel1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
            .addGroup(layout.createSequentialGroup()
                .addContainerGap()
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING, false)
                    .addComponent(jbEnviaComando, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                    .addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup()
                        .addComponent(jbIniciaServer, javax.swing.GroupLayout.PREFERRED_SIZE, 102, javax.swing.GroupLayout.PREFERRED_SIZE)
                        .addGap(18, 18, 18)
                        .addComponent(jbEncerraServer, javax.swing.GroupLayout.PREFERRED_SIZE, 118, javax.swing.GroupLayout.PREFERRED_SIZE)))
                .addContainerGap(136, Short.MAX_VALUE))
        );

        layout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {jbEncerraServer, jbIniciaServer});

        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addContainerGap()
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(jbIniciaServer, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                    .addComponent(jbEncerraServer, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
                .addGap(31, 31, 31)
                .addComponent(jbEnviaComando, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 180, Short.MAX_VALUE)
                .addComponent(jPanel1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
        );

        layout.linkSize(javax.swing.SwingConstants.VERTICAL, new java.awt.Component[] {jbEncerraServer, jbIniciaServer});

        pack();
    }// &lt;/editor-fold&gt;

    private void jbIniciaServerActionPerformed(java.awt.event.ActionEvent evt) {                                          
        new Thread(new Runnable() {

            public void run() {
                try {
                    iniciaServer();
                } catch (IOException ex) {
                    JOptionPane.showMessageDialog(null, "Não foi possível iniciar o servidor socketLab!");
                }
            }
        }).start();
    }                                         

    private void jbEncerraServerActionPerformed(java.awt.event.ActionEvent evt) {                                             
        try {
            encerraServer();
            jbIniciaServer.setEnabled(true);
            jbEncerraServer.setEnabled(false);
        } catch (IOException ex) {
            JOptionPane.showMessageDialog(null, "Não foi possível encerrar o servidor socketLab!");
        }
    }                                            

    private void jbEnviaComandoActionPerformed(java.awt.event.ActionEvent evt) {                                           
        try {
            out.writeUTF("Desligar");
            System.out.println(cliente.getInetAddress());
        } catch (IOException ex) {
            JOptionPane.showMessageDialog(null, "Falha ao enviar o comando desligar!");
        }
    }                                          

    public static void main(String args[]) throws IOException {
        java.awt.EventQueue.invokeLater(new Runnable() {

            public void run() {
                try {
                    new TelaAdministracao().setVisible(true);
                } catch (IOException ex) {
                    JOptionPane.showMessageDialog(null, "Erro ao abrir o sistema.");
                }
            }
        });
    }
    // Variables declaration - do not modify
    private javax.swing.JPanel jPanel1;
    private javax.swing.JButton jbEncerraServer;
    private javax.swing.JButton jbEnviaComando;
    private javax.swing.JButton jbIniciaServer;
    private javax.swing.JLabel jlStatus;
    // End of variables declaration
    ServerSocket serverSocket = new ServerSocket(5000);
    Socket cliente;
    DataInputStream in;
    DataOutputStream out;
    int conexoes = 0;
    @SuppressWarnings("UseOfObsoleteCollectionType")
    private Vector conectados;
}

Código do cliente:

import java.io.*;
import java.net.*;

public class Cliente {

    Socket client;
    DataInputStream in;
    DataOutputStream out;

    public static void main(String args[]) {
        Cliente client = new Cliente();
    }

    public Cliente() {
        try {
            conectaSocket();
            recebeMsg();
        } catch (UnknownHostException ex) {
            System.out.println("Erro! Servidor não encontrado!");
        } catch (IOException ex) {
            System.out.println("Erro de entrada ou saída!");
        }
    }

    public void conectaSocket() throws IOException{
        try {
            client = new Socket("10.1.50.8", 5000);
        } catch (UnknownHostException ex) {
            conectaSocket();
        }
        in = new DataInputStream(client.getInputStream());
        out = new DataOutputStream(client.getOutputStream());
    }

    public void enviaMsg() throws IOException {
        out.writeInt(1);
        out.writeUTF("Um");
    }

    public void recebeMsg() throws IOException {
        System.out.println("Cliente diz: Aguardando comando...");
        String comando = in.readUTF();
        System.out.println("Cliente diz: Vou " + comando);
        if (comando.equals("Desligar")) {
            Runtime.getRuntime().exec("init 0");         
        }
    }

    public void desconectaSocket() throws IOException {
        in.close();
        out.close();
        client.close();
    }
}

15 Respostas

V
  1. No server crie um ArrayList<Socket>;
  2. Toda vez que o accept te retornar nesse cliente, adicione-o nesse arraylist.

Viu? Não é tão difícil assim.

J

ViniGodoy:
1. No server crie um ArrayList<Socket>;
2. Toda vez que o accept te retornar nesse cliente, adicione-o nesse arraylist.

Viu? Não é tão difícil assim.

Ao invés de usar ArryList é a melhor usar Hashtable, pois é thread safe collection.

A

Javart:
ViniGodoy:
1. No server crie um ArrayList<Socket>;
2. Toda vez que o accept te retornar nesse cliente, adicione-o nesse arraylist.

Viu? Não é tão difícil assim.

Ao invés de usar ArryList é a melhor usar Hashtable, pois é thread safe collection.

Vou dar uma pesquisada sobre o Hashtable e o Arraylist, depois posto aqui se consegui ou não…
Obrigado

V

Apenas a thread do Socket deve manipular esse cliente. Portanto, não faz muita diferença usar uma collection que seja ou não thread-safe.
De qualquer forma, ainda que outra thread compartilhe, ela provavelmente terá que fazer isso dentro de um método sincronizado, o que elimina a necessidade de uma collection sincronizada em si.

Se for para usar algo assim, seria na fila de mensagens entre as threads dos clientes.

A classe HashTable também não é recomendada pela Sun desde o Java 1.2, quando entrou a collections Framework. No lugar, recomendam o uso do HashMap e do ConcurrentHashMap.
Outra opção é criar um wrapper de sincronização através do método Collections.synchronizedMap.

J

A classe HashTable também não é recomendada pela Sun desde o Java 1.2, quando entrou a collections Framework. No lugar, recomendam o uso do HashMap e do ConcurrentHashMap.
Outra opção é criar um wrapper de sincronização através do método Collections.synchronizedMap.

Você esta colocando um recurso sem saber justifica-lo, em seu aspecto de Thread Safety and Shared Resources , se não for Thread safe vai entrar em condição de Race condition não vai atuar em segmento seguro, você esta colocando recurso sobre o que é principio que Hashtable é especialização de <<interface>>Map, ou melhor dizendo você esta concordando comigo assumindo que a ArrayList não é seu melhor recurso, você já não esta defendendo a coleção<<interface>>list, que na verdade acabou caindo em contradição.

V

Duran, fico satisfeito em ver que além de postar notícias, agora você também aparentemente começou a estudar informática.
Continue nesse ritmo e em algum tempo poderá dar contribuições que realmente somem à comunidade.

Pois bem, deixa eu te explicar sobre thread-safety. O HashTable e o Vector possuem todos os métodos sincronizados. Isso quer dizer que eles garantem que multiplas threads não poderão percorrer seus métodos simultaneamente. Porém, por mais estranho que isso possa parecer, em 90% dos casos, isso não garante thread-safety. O motivo é simples: eles não garantem nada do que pode ocorrer entre a chamada de dois métodos. Por exemplo:

public void sendMessage(String texto) { //Código naive para mandar uma msg pro Marcio int ind = suaLista.indexOf("Marcio Duran"); if (!lista.get(ind).isClosed()) { lista.get(int).println(texto); } }

Vamos supor que suaLista seja um Vector, ou mesmo um HashTable (e nesse caso você estaria usando um get, no lugar de um indexOf).
Veja o que pode ocorrer:

  1. A thread A vai rodar o método indexOf e obtém o monitor do Vector ou HashTable;
  2. A thread A roda a primeira linha, e obtém o PrintWriter associado ao Socket do Márcio Duran.
  3. A thread A libera o monitor do Vector ou HashTable;
  4. A thread A vai rodar o método get, e volta a obter o monitor;
  5. A thread A obtém o Socket do Marcio Duran;
  6. A thread A libera o monitor;
  7. A thread A testa se o socket está fechado. Ele não está.

OCORRE PREEMPÇÃO

  1. A thread B rodar um código que quica o Marcio Duran do servidor. Para isso obtém o lock do map ou hashTable;
  2. A thread B kicka o Márcio, fecha o socket;
  3. A thread B Libera o monitor;

OCORRE PREEMPÇÃO

  1. A thread A vai rodar o método get e obtém o monitor
  2. A thread A roda o método println e obtém um SocketClosedException;

(um fluxo alternativo poderia ser 8. B obtém o monitor; 9. B exclui o usuário da lista; 10. B libera o monitor; PREEMPÇÃO; 11. A obtem o monitor; 12. A chama o get; 13. A recebe nullPointerException);

E o programador inocente, que pensava que o Hashtable ou Vector salvaria a pátria, dança. E não entende como um código “seguro contra race-conditions” falhou tão miseravelmente. Como pode estar falhando, se o indexOf retornou um índice válido, e se o socket foi testado como aberto. E como todo bom código multi-thread, o erro só ocorrerá de vez enquando, no cliente, e os logs não o ajudarão muito.

O problema aqui é que um conjunto de operações atômicas individuais não garantem uma operação atômica maior.

O que o programador terá que fazer para se prevenir, é sincronizar todo o trecho:

public void sendMessage(String texto) { //Código naive para mandar uma msg pro Marcio int ind = suaLista.indexOf("Marcio Duran"); if (!lista.get(ind).isClosed()) { lista.get(int).println(texto); } }

Veja o que ocorreria na mesma execução:

  1. A thread A vai rodar o método indexOf e obtém o monitor do Vector ou HashTable;
  2. A thread A roda a primeira linha, e obtém o PrintWriter associado ao Socket do Márcio Duran.
  3. A thread A vai rodar o método get;
  4. A thread A testa se o socket está fechado. Ele não está.

OCORRE A PREEMPÇÃO
6. A thread B tenta obter o monitor para quicar o Marcio. Não consegue, pois a Thread A ainda o contém. Ela então dorme;

OCORRE A PREEMPÇÃO

7. A thread A faz o get;

8. Manda a mensagem para o Duran e finaliza;

9. Libera o monitor;
OCORRE A PREEMPÇÃO

10. B kicka o Duran;

11. B retira ele da lista;

12. B finaliza.

Só sincronizando todos os trechos onde há concorrência, você tem essa garantia. Porém, se você sincronizar todos os trechos onde a lista é usada, você só estará trabalhando com a lista num contexto onde há somente uma única thread operando sobre a coleção por vez. Isso significa, que sua lista ou map não precisam ser sincronizados, já que não existirá acessos paralelos a ela. Portanto, não há necessidade de usar Hashtable ou Vector.

Quando usamos coleções sincronizadas? Somente quando a coleção for diretamente acessada, sem nenhuma outra chamada intermediária. E isso é raro de ocorrer.
Geralmente, acontece quando queremos implementar filas de mensagens entre duas threads, no caso clássico do algoritmo do produtor/consumidor.

Para isso, a Sun criou as coleções do pacote java.util.synchronized, que são muito mais eficientes. E é por essa razão que eu falei que será muito raro, mesmo em código concorrente, deixar de usar ArrayList ou HashMap.
Além disso, a Sun também não recomenda o uso de Vector e Hashtable pelos seguintes motivos:

  1. Constituem parte de uma API antiga, de compatibilidade;
  2. Possuem a interface inchada, cheia de métodos duplicados;
  3. Possuem implementações menos eficientes que suas substitutas na collections framework;
D

Ótima explicação Vini.
Márcio, tbm estou impressionado que você tenha tentado participar com algo útil no fórum.
Parabéns!

Resumo da ópera: Não adianta sincronizar apenas uma operação que faz parte de uma operação maior, deve-se sincronizar um conjunto de operações.

Imagine que você tem um corredor. Nele existem 3 portas que podem ser abertas/fechadas (operação). Existem duas pessoas (duas threads) que precisam chegar ao final do corredor, pegar algo e voltar, só que só pode haver uma pessoa por vez no corredor, ou seja, apenas uma pessoa pode ter o lock do corredor. Se você sincronizar apenas a operação de abrir/fechar porta, pode ser que na hora que a pessoa 1 estiver fechando a segunda porta, a pessoa 2 esteja abrindo a primeira. A pessoa 2 não consegue abrir/fechar uma porta ao mesmo tempo que a pessoa 1, mas consegue entrar no corredor, o que não deveria acontecer. O que você faz então? Sincroniza toda a operação: “abre/fecha porta 1, abre/fecha porta 2, abre/fecha porta 3, pega o que precisa, abre/fecha porta 3, abre/fecha porta 2, abre/fecha porta 1”. Sendo assim, você não precisa sincronizar as operações que fazem parte da operação maior, mas sim todo o processo.

Então como o Vini disse. Não adianta achar que só porque os métodos de uma coleção são thread-safe é que a operação que usa esses método será sincronizada, a não ser, é claro, na situação que o Vini mencionou, onde somente o método sincronizado da coleção é usado.

[]´s

J

Falando na especificação JDK 1.5 , sim ConcurrentHashMap é o candidato certo ao invés de HashTable para não cair com problemas de escalabilidade, [color=red]todavia[/color] se fosse dar votos simplesmente para HashMap teríamos ainda situação de neglect to synchronize compound pertinentes a concorrência , resultado é que, enquanto estes programas parecem funcionar sob carga leve, sob carga pesada podem começar a jogar NullPointerException ou ConcurrentModificationException, novamente situações thread condicional synchronizedList e synchronizedMap, - Mas vamos lá ; em melhor observação ConcurrentHashMap temos [color=blue]preserving “thread safety”[/color], vantagem onde em situações de aplicações “altamente concorrente” evita lançar ConcurrentModificationException.
Agora sim , estamos mais aproximados de nossas convicções sobre concorrência.

V

Duran, quando usar o google translator, pelo menos cite suas fontes:

“developers assume that because these collections are synchronized, they are fully thread-safe, and they neglect to synchronize compound operations properly. The result is that while these programs appear to work under light load, under heavy load they may start throwing NullPointerException or ConcurrentModificationException.”
Retirado de: http://www.ibm.com/developerworks/java/library/j-jtp07233.html

Javart:
Mas vamos lá ; em melhor observação ConcurrentHashMap temos [color=blue]preserving “thread safety”[/color], vantagem onde em situações de aplicações “altamente concorrente” evita lançar ConcurrentModificationException.
Agora sim , estamos mais aproximados de nossas convicções sobre concorrência.

The ConcurrentHashMap and CopyOnWriteArrayList implementations provide much higher concurrency while preserving thread safety (…)

Retirado de: http://www.ibm.com/developerworks/java/library/j-jtp07233.html

Você ainda recortou e colou textos esparsos desse (e acho que de outros) documentos, o que ocasionou uma série de frases sem muito sentido.
Vamos falar a verdade, querer se passar pelo Brian Goetz? Pelo menos tenha a bondade de ler, entender o texto, e fazer uma tradução decente!

PS: O termo que você deixou em inglês, “neglect to synchronize compound” foi exatamente o problema que expliquei.

D

Poxa Márcio, pensei que finalmente você estava tentando ajudar de verdade… :frowning:

J

Era só ter feito a observação com ConcurrentHashMap foi o que quiz dizer , não coloquei as fontes mas usei termos em Inglês o que já ajudaria você procurar ou mesmo saber onde extrair tal informação e confirmo sim retirei do link que você observou, claro que você usou o Google para rastrear termo que lhe jogaram na página.

Mas fica ai , uma pesquisa e informação pra quem quiser questionar melhor o assunto sobre concorrência, na próxima faço a observação do artigo ou onde procurei fazer afirmação.

J

Fontes de pesquisa:

:arrow: Thread e Concorrência
:arrow: ConcurrentHashMap e Escalabilidade

A

Pessoal,

Aproveitando a carona no tópico, gostaria de tirar uma dúvida que tive recentemente, mas não ficou claro :
http://www.asm51.eng.br/phpbb/viewtopic.php?t=11037

Obseervei que o programa do colega acustodio utiliza uma mesma porta 5.000 para cada cliente. Isso não poderia gerar problemas ?
O que entendi das estratégias propostas acima, seria uma alocação das tarefas para não haver concorrencia, mas usando a mesma porta.
Estou falando besteira ?

ViniGodoy,

Parabéns pela eloquencia e paciencia, atributos de um moderador do seu nível…

+++

V

O que ocorre, André, é o seguinte:

  1. O servidor aguarda a conexão numa porta, digamos, a porta 5000;
  2. Quando um cliente se conecta nessa porta, o servidor o transfere para uma nova porta, e mantém a comunicação nesse canal, liberando a porta 5000;
  3. Cada cliente adicional será transferido para uma porta diferente, o que garante um canal de comunicação por participante da conversão.

No java, cada canal desses é representado pelo objeto Socket, retornado pelo método Accept do ServerSocket. Por isso, recomendei que o acustodio guardasse o retorno desse método numa lista. Assim, cada item da lista será um participante diferente do servidor.

A

Obrigado…não havia entendido esse conceito, mas agora ficou mais claro.

AT+

Criado 9 de dezembro de 2010
Ultima resposta 12 de dez. de 2010
Respostas 15
Participantes 5