SOCKETS O modelo mais utilizado para aplicações distribuídas em redes de computadores é chamado cliente/servidor, no qual, a comunicação costuma se dar através de uma mensagem de solicitação do cliente enviada para o Servidor, pedindo para que alguma tarefa seja executada. Em seguida, o servidor executa a tarefa e envia a resposta. O mecanismo mais utilizado atualmente para esse modelo que possibilita a comunicação entre aplicações é chamado de Socket. 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. (DE CAMARGO). Através de um socket é possível realizar diversas operações, como exemplo: Estabelecer conexões entre máquinas. Enviar e receber dados. Encerrar conexões. Esperar por conexões em determinada porta. Socket é um endpoint de uma comunicação de mão dupla unidos entre dois programas executados na rede. Uma Socket é ligado a um número da porta de forma que a camada de TCP pode identificar a aplicação que vai destinar os dados para serem enviados. (ALL ABOUT SOCKETS). Um endpoint é uma combinação de um endereço de IP e um número da porta. Toda conexão de TCP pode ser identificada exclusivamente por seu dois endpoints. Neste modo pode haverá conexões múltiplas entre seu host e o servidor. (ALL ABOUT SOCKETS). A tecnologia socket é utilizada no desenvolvimento de aplicações do modelo cliente/servidor, para realizar a comunicação em rede. Cada socket tem um número que consiste no IP do host mais um numero de 16 bits local para este host, denominado porta. Para que a comunicação funcione é necessário que uma conexão seja estabelecida entre um socket da máquina transmissora e um socket da máquina receptora. Normalmente, um servidor é executado em um computador específico e tem um socket que é ligada a um número da porta específico. O servidor fica espera, escutando o socket do cliente para fazer uma solicitação de conexão. A figura 4 mostra basicamente uma requisição de conexão cliente/servidor. Figura 4: Requisição de conexão cliente/servidor. O cliente sabe qual a máquina servidora na qual o servidor está executando e o número da porta no qual o servidor está aguardando. Para solicitar uma conexão, o cliente tenta se comunicar com o servidor da máquina servidora e a porta. O cliente também precisa se identificar ao servidor para que a conexão com a porta local seja efetuada. Isto normalmente é determinado pelo sistema. Se tudo ocorrer certo, o servidor aceita a conexão, e ao aceitar, o servidor gera um novo socket à mesma porta local e também com seu endpoint remoto definido como o endereço e porta do cliente. A figura 5 mostra basicamente uma conexão entre cliente/servidor. Figura 5: Conexão entre cliente/servidor. No lado do cliente, se a conexão for aceita, um socket é criado prosperamente e o cliente pode usar o socket para comunicar com o servidor. Modos de utilização de Sockets Os seguintes modos de utilização de sockets que Java oferece é o modo orientado a conexão, que funciona sobre o protocolo TCP (Transmission Control Protocol, ou protocolo de controle de transmissão), e o modo orientado a datagrama, que funciona sobre o protocolo UDP (User Datagram Protocol, ou protocolo de datagrama de usuários). Os dois modos funcionam sobre o protocolo IP (Internet Protocol). Modo orientado a conexão TCP/IP Para que os computadores de uma rede possam trocar informações entre si é necessário que todos os computadores adotem as mesmas regras para o envio e o recebimento de informações. Este conjunto de regras é conhecido como Protocolo de comunicação. Falando de outra maneira podemos afirmar: “Para que os computadores de uma rede possam trocar informações entre si é necessário que todos estejam utilizando o mesmo protocolo de comunicação”. No protocolo de comunicação estão definidas todas as regras necessárias para que o computador de destino, “entenda” as informações no formato que foram enviadas pelo computador de origem. Dois computadores com diferentes protocolos instalados, não serão capazes de estabelecer uma comunicação e nem serão capazes de trocar informações. (BATTISTI, 2003). O processo de comunicação no modo orientado à conexão ocorre quando o servidor escolhe uma determinada porta e fica aguardando conexões nesta porta. O cliente deve saber previamente qual a máquina servidora (host) e a porta que o servidor está aguardando conexões. Então o cliente solicita uma conexão em um host/porta, de acordo com a figura 6. Figura 6: Requisição de conexão. Se tudo ocorrer conforme o esperado, ou seja, se não existir nenhum problema, o servidor aceita a conexão gerando um socket em uma porta qualquer do lado servidor, criando assim um canal de comunicação entre o cliente e o servidor, como demonstrado na figura 7. Figura 7: Comunicação entre Servidor e Cliente. Tipicamente o comportamento do servidor é ficar em um loop aguardando novas conexões e gerando sockets para atender as solicitações de clientes. Características do TCP Orientado à conexão: A aplicação envia um pedido de conexão para o destino e usa a "conexão" para transferir dados. Ponto a ponto: uma conexão TCP é estabelecida entre dois pontos. Confiabilidade: Existem várias técnicas que o TCP usa para proporcionar uma entrega confiável dos pacotes de dados, que é a grande vantagem que tem em relação ao UDP, no qual, é o motivo do seu uso extensivo nas redes de computadores. O TCP permite a recuperação de pacotes perdidos, a recuperação de dados corrompidos, a eliminação de pacotes duplicados, e pode recuperar a ligação em caso de problemas no sistema e na rede. (TRANSMISSION CONTROL PROTOCOL). Full duplex: É possível a transferência simultânea em ambas direções (cliente-servidor) durante toda a sessão. Handshake: Mecanismo de estabelecimento e finalização de conexão a três e quatro tempos, respectivamente, o que permite a autenticação e encerramento de uma sessão completa. O TCP garante que, no final da conexão, todos os pacotes foram bem recebidos. (TRANSMISSION CONTROL PROTOCOL). Entrega ordenada: A aplicação faz a entrega ao TCP de blocos de dados com um tamanho arbitrário num fluxo (ou stream) de dados, caracteristicamente em byte. O TCP parte estes dados em segmentos de tamanho especificado pelo valor MTU, que se refere ao tamanho do maior datagrama que uma camada de um protocolo de comunicação pode transmitir, no qual, o datagrama é a estrutura de dados unitária de transmissão em uma rede. Porém, a circulação dos pacotes ao longo da rede (utilizando um protocolo de encaminhamento, na camada inferior, como o IP) pode fazer com que os pacotes não cheguem ordenados. O TCP garante a reconstrução do stream no destinatário mediante os números de seqüência. (TRANSMISSION CONTROL PROTOCOL). Controle de fluxo: O TCP usa o campo janela ou window para controlar o fluxo. O receptor, à medida que recebe os dados, envia mensagens ACK (=Acknowledgement), confirmando a recepção de um segmento; como funcionalidade extra, estas mensagens podem especificar o tamanho máximo do buffer no campo (janela) do segmento TCP, determinando a quantidade máxima de bytes aceite pelo receptor. O transmissor pode transmitir segmentos com um número de bytes que deverá estar confinado ao tamanho da janela permitido: o menor valor entre sua capacidade de envio e a capacidade informada pelo receptor. (TRANSMISSION CONTROL PROTOCOL). Modo orientado a datagrama UDP/IP O UDP é um acrónimo do termo inglês User Datagram Protocol que significa protocolo de datagramas de utilizador (ou usuário) é um protocolo de transporte que presta um serviço de comunicação não orientado a conexão e sem garantia de entrega. As aplicações que usam o UDP devem tomar medias para garantir a recuperação dos dados perdidos, a organização dos dados recebidos fora de ordem, a eliminação dos dados recebidos em duplicidade e o controle de fluxo. (ALBUQUERQUE, 2001, p.11). Sockets UDP/IP são muito mais rápidos e mais simples que sockets TCP/IP, porém menos confiáveis. Em UDP não existe o estabelecimento de conexão, sendo que a comunicação ocorre apenas com o envio de mensagens. Uma mensagem é um datagrama, pelo qual é composto de um remetente (sender), um destinatário ou receptor (receiver), e a mensagem (content). Em UDP, caso o destinatário não esteja aguardando uma mensagem, ela é perdida. A figura 8 apresenta o envio de um datagrama de uma suposta Máquina1 para outra Máquina2 em uma rede. Figura 8: Envio de um datagrama. Característica da UDP Simplicidade: O UDP é mais simples que o TCP, ou seja, ele não se preocupa com a conexão e a chegada correta dos dados no destino. Mensagens desordenada: Não ordena as mensagens, ou seja, elas vão sendo agrupadas conforme vão chegando. Otimização na transmissão e recepção dos dados: O UDP, por ser um protocolo pequeno, proporcionar um ganho de velocidade na transmissão e recepção de dados. Inconfiabilidade: O protocolo UDP não garante se os dados chegaram ao destino correspondente. Exemplo de duas aplicações em Sockets Neste trabalho serão mostrado dois exemplos de uma aplicação em Sockets utilizando a linguagem Java, uma no modo orientado a conexão TCP/IP e outra no modo orientado a Datagrama UDP/IP no qual os dois funcionam sobre o protocolo IP. Exemplo de uma aplicação em Socket no modo TCP/IP No exemplo a seguir serão apresentadas as ações necessárias para implementar comunicação sobre TCP através de um socket cliente e um socket servidor, ou seja, será implementado uma classe Servidor e uma classe Cliente. Para implementar a classe Servidor, o primeiro objetivo é criar o server socket na porta 7000, depois, deverá ser utilizado 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 solicitado, faz com que a Thread atual seja "paralisada" até que uma conexão seja recebida. É comum, em ambientes reais, lançar uma Thread a cada conexão recebida pelo ServerSocket, pois isto é feito para que possa tratar vários clientes conectados simultaneamente. (DE CAMARGO). Um leitor dos dados de entrada baseado no canal de entrada de dados do socket deverá ser criado que ira trabalhar com o BufferedReader que lê texto de uma text-input stream e os deixa em buffer para leitura eficiente das informações e o InputStreamReader lê os bytes que estão chegando e os transforma em caracteres para que o BufferedReader possa entender. O método readline() da classe BufferedReader deverá ser chamado, pois 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 um comando popular System.out.println(). Depois é só fechar os sockets que precisará ser feito dentro de um bloco do tipo finally, pois isto garante que os recursos serão liberados. Então, a aplicação devera ser feito da seguinte maneira, como mostra a listagem 1, no qual representa a classe Servidor. import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.net.ServerSocket; import java.net.Socket; public class Servidor { public static void main(String[ ] args) { // Declara o ServerSocket ServerSocket serv=null; // Declara o Socket de comunicação Socket s= null; // Declara o leitor para a entrada de dados BufferedReader entrada=null; try{ // Cria o ServerSocket na porta 7000 se estiver disponível serv = new ServerSocket(7000); // Aguarda uma conexão na porta especificada e cria retorna o // socket que irá comunicar com o cliente s = serv.accept(); // Cria um BufferedReader para o canal da stream de entrada de // dados do sockets entrada = new BufferedReader(new InputStreamReader (s.getInputStream())); // Aguarda por algum dado e imprime a linha recebida quando recebe System.out.println(entrada.readLine()); } // trata possíveis excessões de input/output. Note que as // excessões são as mesmas utilizadas para as classes de java.io catch(IOException e){ // Imprime uma notificação na saída padrão caso haja algo // errado. System.out.println(“Algum problema ocorreu para criar ou receber o socket.”); } finally{ try{ // Encerro o socket de comunicação s.close(); // Encerro o ServerSocket serv.close(); } catch(IOException e){ } } } } Listagem 1: Classe Servidor utilizando Sockets TCP/IP. Agora a classe Cliente será implementada, no qual, será criado primeiro o socket, de acordo com o construtor que foi utilizado, para a comunicação com o IP 127.0.0.1 (IP LoopBack) na porta 7000 (que a porta que do servidor irá “escutar”. IP LoopBack é um termo usado para representar uma forma de comunicação com o próprio computador (geralmente utilizado para efetuar testes), que para acessá-lo, é utilizado 127.0.0.1 ou simplesmente localhost. (DE CAMARGO). Um objeto do tipo PrintStream será criado para poder imprimir dados para o canal de saída do socket. O método println() da classe PrintStream será utilizado para imprimir uma String que será enviada através do socket para o Servidor, no qual, esse método converte os caracteres digitados para o formato adequado de envio através do socket. Depois é só fechar o socket dentro de um bloco finally. Para que possa entender melhor a classe Cliente, a listagem 2, descreve como deve ser realizado. import java.io.IOException; import java.io.PrintStream; import java.net.Socket; public class Cliente { public static void main(String[] args) { // Declara o socket cliente Socket s = null; // Declara a Stream de saida de dados PrintStream ps = null; try{ // Cria o socket com o recurso desejado na porta especificada s = new Socket("127.0.0.1",7000); // Cria a Stream de saida de dados ps = new PrintStream(s.getOutputStream()); // Imprime uma linha para a stream de saída de dados ps.println("Olá Servidor...!"); // Trata possíveis exceções } catch(IOException e){ System.out.println("Algum problema dados pelo socket."); } finally{ try{ // Encerra o socket cliente ocorreu ao criar ou enviar s.close(); } catch(IOException e){} } } } Listagem 2: Classe Cliente utilizando Sockets TCP/IP. Agora para que possa entender melhor o aplicativo, deverá seguir os seguintes passos: 1) Compile as classes Servidor e Cliente: Javac Servidor.java Javac Cliente.java 2) Execute a classe Servidor. start java Servidor. 3) Execute a classe Cliente. java Cliente. O resultado será “Olá Servidor...!” na janela de prompt onde foi executado a classe Servidor. Exemplo de uma aplicação em Socket no modo UDP/IP No próximo exemplo a seguir serão apresentadas as ações necessárias para implementar a comunicação sobre UDP através de um socket cliente e um socket servidor, como no exemplo anterior, ou seja, será implementado uma classe Servidor e uma classe Cliente. Para implementar a classe Servidor, o primeiro objetivo é criar o server socket na porta 7000, depois, cria o datagrama para receber uma mensagem passando um buffer e o tamanho do buffer para receber a mensagem, caso o conteúdo da mensagem recebida for maior que o buffer a mensagem será perdido. Depois deverá ser utiliza o comando serverSocket.receive() que terá a responsabilidade de fica aguardando o recebimento de uma mensagem e então é só fechar o servidor. Então, a aplicação devera ser feito da seguinte maneira, como mostra a listagem 3, no qual representa a classe Servidor. import java.io.*; import java.net.*; public class Servidor{ public static void main(String args[]) { try{ // Passo 1 - Crindo um socket servidor: // Escutando na porta 7000. DatagramSocket serverSocket = new DatagramSocket(7000); // Passo 2 - Recebendo um datagrama: DatagramPacket content = new DatagramPacket(new byte[512], 512); // Aguarda até o recebimento de uma mensagem. serverSocket.receive(content); String inContent = new String(content.getData()); System.out.println(inContent); // Passo 3 - Fechando o servidor: serverSocket.close(); }catch(IOException e){ System.out.println("Erro: "+ e); } } } Listagem 3: Classe Servidor utilizando Sockets UDP/IP. Agora a classe Cliente será implementada, no qual um socket deverá ser criado sem utilizar uma porta em especial, depois cria e envia o datagrama ao destinatário na porta 7000 e logo em seguida fecha o socket, como mostra a listagem 4 que representa a classe Cliente. import java.io.*; import java.net.*; public class Cliente{ public static void main(String args[]) { try{ // Passo 1 - Criar o socket: // Não precisa de uma porta em especial. DatagramSocket clientSocket = new DatagramSocket(); // Passo2 - Criando e enviando o datagrama: InetAddress addr = InetAddress.getByName("127.0.0.1"); String content = "Ola Servidor...!"; byte[] buffer = content.getBytes(); // Enviar datagram para destinatário na porta 7000. DatagramPacket question = new DatagramPacket(buffer, buffer.length, addr, 7000); // Envia o datagrama. clientSocket.send(question); // Passo 3 - Fechando o socket: clientSocket.close(); }catch(IOException e){ System.out.println("Erro: "+ e); } } } Listagem 4: Classe Cliente utilizando Sockets UDP/IP. Os passos para executar a aplicação ocorrerá da mesmas maneira do modo orientado a conexão TCP/IP, como mostrado logo abaixo: 1) Compile as classes Servidor e Cliente: Javac Servidor.java Javac Cliente.java 2) Execute a classe Servidor. start java Servidor. 3) Execute a classe Cliente. java Cliente. O resultado será “Olá Servidor...!”.