Java Socket: Entendendo a classe Socket e a ServerSocket em detalhes

Veja neste artigo como funcionam as classes Socket e ServerSocket para estabelecimento de conexão cliente-servidor em Java.

Com a constante necessidade de trabalhar em ambientes distribuídos surgiram alternativas, em Java, para que isso fosse possível. Uma dela e que estudaremos neste artigo é a Socket. Há diversos artigos e tutoriais na internet explicando como funciona o Socket e como realizar a conexão Cliente-Servidor, mas nosso objetivo vai mais além, pois queremos mostrar o funcionamento de cada método nessa poderosa classe e como utilizá-lo.

De acordo com a própria Oracle: “Socket é um ponto de comunicação entre duas máquinas”, ou seja, podemos enviar mensagens entre a máquina A e a máquina B através de uma conexão estabelecida com o Socket.

Para que essa comunicação seja possível nós precisamos criar a classe Servidora que é responsável por esperar a conexão do cliente e a classe Cliente que irá conectar-se no Servidor. Antes de iniciarmos a prática e começar a criar nosso ambiente de comunicação, vamos entender as classes que usaremos e seus métodos.

Quer ser um programador Java de sucesso? Confira nosso checklist e saiba como!

ServerSocket class

A primeira classe importante é a ServerSocket e ela é responsável por esperar a conexão do cliente. Esta classe possui um construtor onde passamos a porta que desejamos usar para escutar as conexões.

Listagem 1. Construtor

ServerSocket server = new ServerSocket(3322);

Veja na Listagem 1 que estamos criando um objeto ServerSocket passando como parâmetro o argumento 3322 que corresponde ao número da porta que será aguardada uma conexão do cliente. Existem outros construtores mas este é o mais utilizado.


Agora já sabemos como funcionam os principais e mais utilizados métodos da classe ServerSocket, porém ainda precisamos entender o uso da classe Socket.

Socket class

Ao contrário da classe ServerSocket que funciona como um Servidor escutando o cliente, a classeSocket é o cliente propriamente dito. Muito dos métodos estudados na classe ServerSocket estão presentes na Socket e não repetiremos os mesmos conceitos, tais como: bind, close, getChannel e etc, mas vamos ver alguns um pouco diferente que não abordamos anteriormente.

Construção da Aplicação

Dado os conceitos que foram apresentados, com o detalhamento e uso dos métodos mais importantes e utilizados com maior frequência, podemos agora começar a desenvolver nossa aplicação para comunicação através do Socket.

Primeiramente vamos construir nossa classe Server com base no que foi ensinado Observe a Listagem 9.

Listagem 9. Server.class

package br.com.loginremoto.util; import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; import java.util.Scanner; import java.util.logging.Level; import java.util.logging.Logger; public class Server { public static void main(String args[]){ try { ServerSocket server = new ServerSocket(3322); System.out.println("Servidor iniciado na porta 3322"); Socket cliente = server.accept(); System.out.println("Cliente conectado do IP "+cliente.getInetAddress(). getHostAddress()); Scanner entrada = new Scanner(cliente.getInputStream()); while(entrada.hasNextLine()){ System.out.println(entrada.nextLine()); } entrada.close(); server.close(); } catch (IOException ex) { Logger.getLogger(Server.class.getName()).log(Level.SEVERE, null, ex); } } }

Vamos entender o código acima:


  1. Primeiramente criar um objeto do tipo SocketServer na porta 3322, assim podemos escutar conexões do cliente nesta porta. É muito importante que você escolha uma porta que já não esteja em uso por algum serviço do seu sistema operacional. Por exemplo: Se você usar o tomcat na porta 8080, não poderá utilizar esta porta para testar a sua conexão Socket.
  2. Depois nós chamamos o método “server.accept()” que irá “bloquear” a execução do restante da lógica até que uma conexão seja estabelecida. Se você quiser que vários clientes possam se conectar simultaneamente você poderia envolver o “server.accept()” em um laço “while” para que ele possa aceitar diversas conexões até que determinada condição seja atingida.
  3. Ao usarmos o getInputStream() estamos capturando o que o cliente digitou, e neste ponto uma conexão já foi estabelecida, só precisamos trafegar os dados neste “canal de comunicação”. Nosso objetivo aqui é apenas usar o “while” e mostrar no console do servidor o que está sendo digitado no cliente, mas você poderia fazer vários tipos de tratamentos no lado do servidor com os dados recebidos.
  4. Por fim nós fechamos a conexão do servidor e o Scanner, assim o fluxo de transmissão é interrompido.

Repare que nossa classe acima possui um método “main()” e devemos executá-la para que o Servidor comece a escutar uma conexão do cliente, ou seja, executamos a classe acima e depois executamos o cliente que fará a conexão automática neste servidor.

Nosso cliente será composto por um formulário simples com uma caixa de texto (JtextArea) e um Jbutton, assim poderemos enviar tudo que for digitado na caixa de texto para o console do servidor.

Figura 1. Formulário do Cliente

Na Figura 1 temos a demonstração de como criamos a interface do nosso usuário. Veja que ela é bem simples e leve, ao digitar qualquer texto basta clicar em Enviar Mensagem que esta chegará no Servidor. Vejamos agora na Listagem 10 como foi feita a construção do nosso formulário e a explicação dos métodos.

Listagem 10. Formulário Fclient

package br.com.loginremoto.gui; import java.io.IOException; import java.io.PrintStream; import java.net.Socket; import java.util.logging.Level; import java.util.logging.Logger; /** * * @author ronaldo */ public class FClient extends javax.swing.JFrame { private Socket cliente; /** * Creates new form FClient */ public FClient() { initComponents(); initCliente(); } private void initCliente(){ try { cliente = new Socket("127.0.0.1",3322); } catch (IOException ex) { Logger.getLogger(FCliente.class.getName()).log(Level.SEVERE, null, ex); } } /** * This method is called from within the constructor to initialize the form. * WARNING: Do NOT modify this code. The content of this method is always * regenerated by the Form Editor. */ @SuppressWarnings("unchecked") // <editor-fold defaultstate="collapsed" desc="Generated Code"> private void initComponents() { jScrollPane1 = new javax.swing.JScrollPane(); jTextArea1 = new javax.swing.JTextArea(); jButton1 = new javax.swing.JButton(); setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE); jTextArea1.setColumns(20); jTextArea1.setRows(5); jScrollPane1.setViewportView(jTextArea1); jButton1.setText("Enviar Mensagem"); jButton1.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { jButton1ActionPerformed(evt); } }); javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane()); getContentPane().setLayout(layout); layout.setHorizontalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() .addContainerGap() .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 376, Short.MAX_VALUE) .addGroup(layout.createSequentialGroup() .addComponent(jButton1) .addGap(0, 0, Short.MAX_VALUE))) .addContainerGap()) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() .addContainerGap() .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 228, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(jButton1) .addContainerGap(25, Short.MAX_VALUE)) ); pack(); }// </editor-fold> private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) { try { PrintStream saida = new PrintStream(cliente.getOutputStream()); saida.println(jTextArea1.getText()); jTextArea1.setText(""); } catch (IOException ex) { Logger.getLogger(FCliente.class.getName()).log(Level.SEVERE, null, ex); } } /** * @param args the command line arguments */ public static void main(String args[]) { /* Set the Nimbus look and feel */ //<editor-fold defaultstate="collapsed" desc=" Look and feel setting code (optional) "> /* If Nimbus (introduced in Java SE 6) is not available, stay with the default look and feel. * For details see http://download.oracle.com/javase/tutorial/uiswing/lookandfeel/plaf.html */ try { for (javax.swing.UIManager.LookAndFeelInfo info : javax.swing.UIManager.getInstalledLookAndFeels()) { if ("Nimbus".equals(info.getName())) { javax.swing.UIManager.setLookAndFeel(info.getClassName()); break; } } } catch (ClassNotFoundException ex) { java.util.logging.Logger.getLogger(FClient.class.getName()).log(java.util.logging.Level.SEVERE, null, ex); } catch (InstantiationException ex) { java.util.logging.Logger.getLogger(FClient.class.getName()).log(java.util.logging.Level.SEVERE, null, ex); } catch (IllegalAccessException ex) { java.util.logging.Logger.getLogger(FClient.class.getName()).log(java.util.logging.Level.SEVERE, null, ex); } catch (javax.swing.UnsupportedLookAndFeelException ex) { java.util.logging.Logger.getLogger(FClient.class.getName()).log(java.util.logging.Level.SEVERE, null, ex); } //</editor-fold> /* Create and display the form */ java.awt.EventQueue.invokeLater(new Runnable() { public void run() { new FClient().setVisible(true); } }); } // Variables declaration - do not modify private javax.swing.JButton jButton1; private javax.swing.JScrollPane jScrollPane1; private javax.swing.JTextArea jTextArea1; // End of variables declaration }
  1. Perceba que temos logo no início um atributo “private Socket cliente” que usaremos para conexão cliente-servidor. Este atributo será útil para armazenamos a instância de conexão que foi estabelecida com o servidor e não precisar ficar reconectando a todo instante.
  2. No método construtor Fcliente() fazemos chamada a outro método: initCliente().
  3. O initCliente() é responsável por inicializar a conexão cliente com o servidor 3322 que criamos anteriormente, o IP 127.0.0.1 indica que o servidor está na mesma máquina que o cliente, ou seja, local. Nada impede que você faça o teste usando um outro computador como servidor ou até mesmo em uma rede externa.
  4. O método initComponents() é responsável por inicializar e dispor os componentes no nosso formulário, que é um Jframe.
  5. Temos a ação de clique do botão disparando o método jButton1ActionPerformed(), ou seja, quando o usuário digitar seu texto e clicar no botão Enviar Mensagem, esse método será chamado. Neste método temos o uso do getOutputStream() que é responsável por enviar os dados ao servidor, para isso nós capturamos o que o usuário escreveu através do getText() do componente JtextArea e colocando dentro do método println() do objeto PrintStream, assim cada vez que for clicado no botão Enviar Mensagem a mesma aparecerá no console do servidor.
  6. Por fim temos o método main() que nos possibilita executar o Formulário em questão e realizar as operações necessárias.

A nossa limitação é que apenas um cliente por vez pode se conectar ao servidor, isso ocorre porque quando um cliente se conecta ele ocupa a única Thread que o Servidor possui e se outro tentar consequentemente ele não conseguirá. A solução para isso é trabalhar com Multi-threading para aceitar várias conexões simultâneas, mas você deve ficar atento a quantidade máxima de conexões que o servidor pode suportar por isso é importante parametrizar tal recurso. Deixaremos como desafio para que você implemente a solução com vários clientes conectados, a dica é você usar o método accept() dentro de um laço para que várias conexões possam ser aceitas, assim o servidor estará “sempre” esperando uma nova conexão.

Neste artigo vimos o uso da classe Socket e a ServerSocket para construção de uma aplicação básica que faz comunicação entre cliente e servidor. A aplicação de tais conceitos é muito útil em diversas situações, por exemplo:

E ainda há muitas outras aplicações no qual o Socket é utilizado.

Alguns leitores mais críticos podem se perguntar porque não usar outro mecanismo de comunicação como, por exemplo o muito conhecido RMI (Remote Method Invocation) em vez do Socket. Não entraremos em detalhes sobre o RMI neste artigo, mas o RMI possui uma estrutura de comunicação mais robusta já com multithreading, garbage collection distribuído e muitos outros recursos que o Socket não possui nativamente. Porém para aplicações que necessitam de uma comunicação mais simples, o Socket supri as necessidades, usar o RMI para tais aplicações é como “matar uma formiga com uma bomba”, ou seja, desnecessário. Vale lembrar que nem sempre o mais completo e mais robusto pode ser melhor para o seu cenário, ainda mais quando este é simples e não exige toda essa robustez.

Artigos relacionados