Usando Sockets Leandro de Camargo Araujo Lima Aprendendo a criar uma aplicação cliente/servidor. Sockets. O que são, pra que servem e como funcionam A estrutura Socket foi uma inovação apresentada pelo sistema Berkeley Unix. Através desta estrutura, o programador por ler e gravar bytes como uma stream qualquer de dados. Além disto, ?esconde? os detalhes de baixo nível das redes tais como tipo de transmissão, tamanho de pacote, retransmissão e etc. Através de um socket podemos realizar várias operações, como exemplo: Estabelecer conexões entre máquinas Enviar e receber dados Encerrar conexões Esperar por conexões em determinada porta O socket é na verdade um elemento de software que provê uma interface de rede para a aplicação. Vamos tratar dos sockets TCP, porém Java permite a utilização de sockets UDP e fornece meios para que você possa utilizar outros tipos não definidos através da classe SocketImpl e da interface SocketImplFactory. Os sockets estão localizados no pacote java.net. Basicamente precisamos das classes Socket e ServerSocket para conseguir implementar uma aplicação básica. A classe Socket implementa o socket cliente. Para construir um socket precisamos saber qual é o IP que desejamos conectar e a porta de conexão (que varia de 0 a 65535). A classe ServerSocket fornece a interface de rede necessária para que a aplicação possa funcionar como um servidor TCP. Para criar um ServerSocket precisamos saber qual é a porta que será utilizada. Comumente utiliza-se portas acima de 1000 pois as inferiores são utilizadas pelo sistema operacional. Para ilustrar o uso dos sockets, iremos construir uma aplicação bastante simples para comunicação de dois computadores. Um computador ficará aguardando alguma conexão e irá exibir em tela o que foi recebido. Veremos então a classe Servidor e a classe Cliente. 01 02 03 04 05 06 07 08 09 10 import import import import import java.io.BufferedReader; java.io.IOException; java.io.InputStreamReader; java.net.ServerSocket; java.net.Socket; public class Servidor { public static void main(String[] args) { 11 //Declaro o ServerSocket 12 ServerSocket serv=null; 13 14 //Declaro o Socket de comunicação 15 Socket s= null; 16 17 //Declaro o leitor para a entrada de dados 18 BufferedReader entrada=null; 19 20 try{ 21 22 //Cria o ServerSocket na porta 7000 se estiver disponív el 23 serv = new ServerSocket(7000); 24 25 //Aguarda uma conexão na porta especificada e cria reto rna o socket que irá comunicar com o cliente 26 s = serv.accept(); 27 28 //Cria um BufferedReader para o canal da stream de entr ada de dados do socket s 29 entrada = new BufferedReader(new InputStreamReader(s.ge tInputStream())); 30 31 //Aguarda por algum dado e imprime a linha recebida qua ndo recebe 32 System.out.println(entrada.readLine()); 33 34 //trata possíveis excessões de input/output. Note que as ex cessões são as mesmas utilizadas para as classes de java.io 35 }catch(IOException e){ 36 37 //Imprime uma notificação na saída padrão caso haja alg o errado. 38 System.out.println("Algum problema ocorreu para criar o u receber o socket."); 39 40 }finally{ 41 42 try{ 43 44 //Encerro o socket de comunicação 45 s.close(); 46 47 //Encerro o ServerSocket 48 serv.close(); 49 50 }catch(IOException e){ 51 } 52 } 53 54 55 56 57 58 } 59 } Vamos explicar os pontos importantes da classe acima. No techo 1 2 3 4 5 6 7 8 //Declaro o ServerSocket ServerSocket serv=null; //Declaro o Socket de comunicação Socket s= null; //Declaro o leitor para a entrada de dados BufferedReader entrada=null; apenas declaramos as variáveis que iremos utilizar. Na trecho 1 //Cria o ServerSocket na porta 7000 se estiver disponível 2 serv = new ServerSocket(7000); , criamos o server socket na porta 7000. No trecho 1 //Aguarda uma conexão na porta especificada e cria retorna o socket que irá comunicar com o cliente 2 s = serv.accept(); utilizamos o método accept() que espera por uma conexão e continua somente quando recebe uma. Então retorna um socket para comunicar com o cliente que acaba de se conectar. O método accept() do ServerSocket, quando invocado, faz com que a Thread atual seja "paralisada" até que uma conexão seja recebida. É comum, em ambientes reais, lançarmos uma Thread a cada conexão recebida pelo ServerSocket. Isto é feito para que possamos tratar vários clientes conectados simultaneamente. Veremos como fazer isto em um outro tutorial. No trecho 1 //Cria um BufferedReader para o canal da stream de entrada de dados do socket s 2 entrada = new BufferedReader(new InputStreamReader(s.getInputStream( ))); criamos um leitor dos dados de entrada baseado no canal de entrada de dados do socket. O BufferedReader lê texto de uma text-input stream e os deixa em buffer para leitura eficiente das informações. O InputStreamReader lê os bytes que estão chegando e os transforma em caracteres para que o BufferedReader possa entender. No trecho 1 //Aguarda por algum dado e imprime a linha recebida quando recebe 2 System.out.println(entrada.readLine()); chamamos o método readline() da classe BufferedReader. Através deste método, o programa aguarda a chegada de algum dado no canal de entrada e lê uma linha. Após linha ser recebida ela é impressa na saída padrão do sistema com o popular System.out.println(). Nas linhas 1 2 3 4 5 //Encerro o socket de comunicação s.close(); //Encerro o ServerSocket serv.close(); , fechamos os sockets. Note que fizemos isto dentro de um bloco do tipo finally pois isto garante que os recursos serão liberados. É muito importante liberar este tipo de recurso sempre que não forem mais necessários, pois são finitos e representam um custo considerável de manutenção. Agora vejamos a classe Cliente. 01 02 import java.io.IOException; 03 import java.io.PrintStream; 04 import java.net.Socket; 05 06 public class Cliente { 07 08 public static void main(String[] args) { 09 10 //Declaro o socket cliente 11 Socket s = null; 12 13 //Declaro a Stream de saida de dados 14 PrintStream ps = null; 15 16 try{ 17 18 //Cria o socket com o recurso desejado na porta especif icada 19 s = new Socket("127.0.0.1",7000); 20 21 //Cria a Stream de saida de dados 22 ps = new PrintStream(s.getOutputStream()); 23 24 //Imprime uma linha para a stream de saída de dados 25 ps.println("Estou enviando dados para o servidor"); 26 27 //Trata possíveis exceções 28 }catch(IOException e){ 29 30 System.out.println("Algum problema ocorreu ao criar ou enviar dados pelo socket."); 31 32 }finally{ 33 34 try{ 35 36 //Encerra o socket cliente 37 38 39 40 41 42 43 44 } s.close(); }catch(IOException e){} } } Nas linhas 1 2 3 4 5 //Declaro o socket cliente Socket s = null; //Declaro a Stream de saida de dados PrintStream ps = null; apenas declaramos as variáveis que iremos utilizar. Na linha 1 //Cria o socket com o recurso desejado na porta especificada 2 s = new Socket("127.0.0.1",7000); criamos o socket. De acordo com o construtor que utilizamos, criamos um socket para comunicação com o IP 127.0.0.1 (IP LoopBack*) na porta 7000 (que a porta que nosso servidor irá ?escutar?. Na linha 1 //Cria a Stream de saida de dados 2 ps = new PrintStream(s.getOutputStream()); criamos um objeto do tipo PrintStream para poder imprimir dados para o canal de saída do socket. Na linha 1 //Imprime uma linha para a stream de saída de dados 2 ps.println("Estou enviando dados para o servidor"); utilizamos o método println() da classe PrintStream para imprimir uma String que será enviada através do socket para o Servidor. O método println() da classe PrintStream converte os caracteres digitados para o formato adequado de envio através do socket. Verifique a especificação da API para verificar todos os tipos de dados que podem ser impressos por este método. Na linha 1 //Encerra o socket cliente 2 s.close(); fechamos o socket dentro do bloco finally. Agora, que entendemos o código podemos executá-los Copie os arquivos do tutorial para uma pasta e compile-os da forma comum. javac Servidor.java javac Cliente.java Agora, abra 2 janelas de prompt. Vamos executar cada classe em um prompt diferente (para testar em um mesmo equipamento). Em uma das janelas digite java Servidor e na outra digite java Cliente. Você deverá perceber que ao executar a classe Cliente, o texto ?Estou enviando dados para o servidor? será exibido na janela da classe Servidor. Pronto, esta é a nossa aplicação funcionando em rede de forma simples. Faça testes, verifique o que acontece quando o servidor não está rodando, altere os Ips caso esteja em rede, ou se estiver conectado a internet teste com seus amigos, leia a especificação das APIs e, quem sabe, adicionar recursos... Demos o passo inicial, agora é com você! *IP LoopBack ? Termo utilizado para representar uma forma de comunicação com o próprio equipamento (geralmente utilizado para efetuar testes). Para acessá-lo, utilizamos 127.0.0.1 ou simplesmente localhost.