Comunicação via TCP/IP

1. Introdução

Socket é um recurso presente nas principais linguagens de programação como o Java, PHP, C++, etc, possibilita a comunição via  TCP/IP entre um computador servidor e vários computadores clientes. Um socket essencialmente é uma conexão de dados transparente entre dois computadores numa rede, onde um dos computadores é chamado servidor que abre um socket e presta atenção às conexões, os outros computadores denominam-se clientes, estes chamam o socket servidor para iniciar a conexão. Primeiro o socket do servidor é aberto e fica monitorando as conexões, depois o socket do cliente chama o socket do servidor para iniciar a conexão.

Os computadores em rede direcionam os streams de dados recebidos da rede para programas receptores específicos, associando cada programa a um número diferente, que é a porta do programa. Da mesma forma, quando o tráfego de saída é gerado, o programa de origem recebe um número de porta para a transação. Determinados números de porta são reservados no TCP/IP para protocolos específicos, por exemplo, 80 para HTTP.

Para que seja efetuada a comunicação em rede através de sockets, é necessário ter conhecimento do número de IP ou HOST do computador e o número de porta do aplicativo ao qual se quer realizar a conexão.

O endereço IP identifica uma máquina específica na Internet e o número de porta é uma maneira de diferenciar os processos que estão sendo executados no mesmo computador.
É exatamente a combinação do IP com o número de porta do aplicativo que se denomina Sockets.

2. Pacote java.net

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.


3. Protocolo TCP/IP
Para que exista comunicação em rede, faz-se necessário o uso de um protocolo de comunicação. Um protocolo funciona como “linguagem” entre computadores em rede e para validar esta comunicação, é preciso que os computadores utilizem o mesmo protocolo.
Existem diversos tipos de protocolos. As aplicações de Sockets são feitas baseadas no protocolo TCP/IP.
O TCP/IP atualmente é o protocolo mais usado em redes locais. Isto se deve, basicamente, à popularização da Internet. Uma das grandes vantagens do TCP/IP em relação aos outros protocolos existentes é que ele é roteável, isto é, foi criado pensando em redes grandes e de longa distância, onde pode haver vários caminhos para o dado atingir o computador receptor.
Outro fato que tornou o TCP / IP popular é que ele possui arquitetura aberta e qualquer fabricante pode adotar a sua própria versão de TCP/IP em seu sistema operacional. Todos os fabricantes de sistemas operacionais adotaram o TCP/IP, transformando-o em um protocolo universal, possibilitando que todos os sistemas possam comunicar-se entre si sem dificuldade.
Na transmissão de dados com o protocolo TCP/IP, ele divide as informações que serão transmitidas através da rede em "pacotes" de dados. Todo pacote que for enviado do servidor para o cliente ou vice-versa terá além dos dados que estão sendo enviados, uma série de dados de controle, tais como o número do pacote, código de validação dos dados e também o número da porta que o software utiliza. Quando o pacote chega em seu destinatário, o sistema lê no pacote o número da porta e sabe para qual aplicativo irá encaminhar o pacote. Esses controles são conferidos ao chegarem na outra ponta, pelo protocolo do receptor. Isso garante que um dado qualquer chegue a outro ponto da mesma forma que foi transmitido. Sendo assim, a integridade dos dados é mantida, independente do meio utilizado na transmissão, seja ele por linha telefônica, canal de dados, canais de voz, satélite ou qualquer outro meio de transmissão, já que o controle é feito nas pontas. Se na transmissão ocorre algum erro, o protocolo deve enviá-los novamente até que cheguem corretamente.
O TCP/IP é, na realidade, um conjunto de protocolos. Os mais conhecidos dão justamente o nome deste conjunto: TCP (Transmission Control Protocol, Protocolo de Controle da Transmissão) e IP (Internet Protocol), que operam nas camadas de Transporte e Internet, respectivamente.

3.1. O protocolo TCP/IP oferece dois modos de utilização de Sockets

- Modo orientado a conexão, que funciona sobre o protocolo TCP

- Modo orientado a datagrama, que funciona sobre o protocolo UDP, que se localizam na camada de transporte do protocolo TCP/IP.

O servidor fica em um loop aguardando novas conexões e gerando sockets para atender as solicitações de clientes. Os segmentos TCP são encapsulados e enviados em pacotes de dados.
Uma forma de visualizar o funcionamento de socket TCP seria compará-lo a uma ligação telefônica onde alguém faz uma ligação para outra pessoa e quando esta atende, é criado um canal de comunicação entre os dois falantes.

Socket TCP (stream)
O processo de comunicação entre aplicativos no modo orientado à conexão ocorre da seguinte forma:
O servidor escolhe uma determinada porta e fica aguardando conexões desta porta. O cliente deve saber previamente qual a máquina servidora (HOST ou IP) e a porta que o servidor está aguardando conexões para solicitar uma conexão.

Socket UDP

O User Datagram Protocol (UDP) é usado por alguns programas em vez de TCP para o transporte rápido de dados entre HOSTS dentro de uma rede TCP/IP. Em UDP não se estabelece conexão, pois a comunicação ocorre apenas com o envio de dados. Neste tipo de Socket, uma mensagem é um datagrama, que é composto de um remetente, um destinatário ou receptor e a mensagem. Caso o destinatário não esteja aguardando uma mensagem, ela é perdida, pois este protocolo não realiza a verificação de transmissão de dados.

Raw Sockets: Envia o pacote sem utilizar as camadas de transporte, como  foi visto anteriormente é usada de forma mais rústica na camada de rede (IP)  e na Internet Control Message Protocol (ICMP). Esta interface de socket não
fornece serviços ponto-a -ponto tradicional.

4. Tarefas da máquina cliente
1 - Criar a conexão, socket cliente e sockete servidor.
2 - Criar um fluxo de comunicação no socket.
3 - Utilizar o fluxo, conversa com a outra máquina.
4 - Fechar o fluxo.
5 - Fechar o socket.

5. Tarefas da máquina servidora
1 - Criar um socket de escuta (LISTENING), em uma determinada porta.
2 - Processar requisições de conexões.
3 - Processar pedido de fluxo.
4 - Utilizar o fluxo, conversa com a outra máquina.
5 - Fechar o fluxo.
6 - Fechar o socket.

6. Construtores das classes para uma simples conexão TCP/IP do lado do cliente

Socket(nomeServ, porta)
nomeServ (String): nome do computador servidor
porta (int): porta de comunicação.

Socket(nomeServ, porta, true/false)
nomeServ (String): nome do computador servidor
porta (int): porta de comunicação
true/false (boolean): se o socket é com fluxo (true) TCP/IP ou não (false) UDP
 
Socket(InetAddress, porta)
InetAddress: endereço Internet (Servidor)
porta (int): porta para conexão.

Socket(InetAddress, porta, true/false)
InetAddress: endereço da Internet (Servidor)
porta (int): porta de comunicação
true/false: se o socket é com fluxo (true) TCP/IP ou não (false) UDP

6.1. Métodos para a utilização de sockets do lado cliente

OutputStream getOutputStream()
Retorna a saida de fluxo do socket

InputStream getInputStream()
Retorna o entrada de fluxo do socket

InetAdress getInetAdress()
Retorna o InetAdress relacionado ao socket

int getLocalPort()
Retorna a porta aberta para o socket

close()
Fecha o socket


7. Constutor do lado servidor para a conexão

ServerSocket(porta)
Porta que ficarí em escuta (LISTENIG)

7.1. Os métodos básico do servidor

accept()
Aguarda conexão em determinda porta coloca em escuta (LISTENING)

close()
Fecha o socket

E como no lado cliente temos a classe Socket, só que do lado servidor ele apenas pega o canal de comuniação. Ele faz isso quando aceita uma conexão  usando o método accept(), que retorna um Socket. Esse socket sim é o canal
de comunicação com a máquina cliente. Ou seja, também usaremos a classe Socket no servidor.


8. Ações para implementar um socket cliente
8.1 - Abrir a conexão:
import java.io.* ; // streams
import java.net.* ; // sockets

Socket clientSocket = new Socket (“www.javasoft.com”, 80);


8.2. Pegando os streams de entrada e saída:
DataInputStream inbound = new DataInputStream (clientSocket.getInputStream( ) );
DataOutputStream outbound = new DataOutputStream(clientSocket.getOutputStream( ) );

8.3. Utilizando os streams de entrada e saída:
outbound.writeInt( 3 );
outbound.writeUTF( “Hello” );
int k =inbound.readInt( );
String s =inbound.readUTF() ;

8.4. Fechando os streams de entrada e saída:
inbound.close () ;
outbound.close () ;
Fechando o socket:
clientSocket.close() ;

9. Ações para implementar um socket servidor
9.1. Criar o server socket:
ServerSocket serverSocket = new ServerSocket (80, 5);

9.2. Aguardar conexoes de clientes:
Socket clientSocket = serverSocket.accept ();

9.3. Criar streams de entrada e saída do cliente:
DataInputStream inbound = new DataInputStream(clientSocket.getInputStream( ) ) ;
DataOutputStream outbound = new DataOutputStream(clientSocket.getOutputStream( ) ) ;

9.4. Conversando com o cliente:
int k = inbound.readInt( );
String s = inbound.readUTF() ;
outbound.writeInt( 3 );
outbound.writeUTF( “Hello” );

9.5. Fechando streams e socket cliente:
inbound.close () ;
outbound.close () ;
clientSocket.close() ;
9.6. Fechando o socket servidor:
serverSocket.close() ;


10. Esquema 1: cliente/servidor


8.2. Esquema 2: cliente/servidor


8.3. Esquema 3: cliente/servidor

socket Cria um novo descritor para comunicação
connect Iniciar conexão com servidor
write Escreve dados em uma conexão
read Lê dados de uma conexão
close Fecha a conexão
bind Atribui um endereço IP e uma porta a um socket

listen
Coloca o socket em modo passivo, para “escutar” portas
accept
Bloqueia o servidor até chegada de requisição de conexão
recvfrom
Recebe um datagrama e guarda o endereço do emissor
sendto
Envia um datagrama especificando o endereço


9. Classes BufferedReader e PrintWriter

Para facilitar o envio e o recebimento de dados temos as classes BufferedReader e PrintWriter.
O constutor padrão das classes recebem InputStream e OutputStrem como parâmetro,  respectivamente.

9.1. O construtor padrão para a classe BufferedReader

BufferedReader(InputStreamReader(InputStream))
Fluxo de comunicação

9.2. Alguns métodos importantes da classe BufferedReader

readLine()
Retorna uma String

read()
Retorna um único caractere

a close()
Fecha o fluxo


9.3. O construtor padrão para a classe PrintStream

PrintStream(OutputStream)
Fluxo de comunicação

9.4. Alguns métodos importantes da classe PrintStream

println()
Envia uma String

write()
Envia um byte

close()
Fecha o fluxo

10. Exemplos

10.1. Exemplo 1:
Arquivo: Servidor.java
package socket1;

import java.net.ServerSocket;
import java.net.Socket;

public class Servidor {   
    public static void main(String argv[]) {
        try {
            ServerSocket socketRecepcao = new ServerSocket(3000);
            System.out.println("Servidor esperando conexão na porta 3000");
            Socket socketConexao = socketRecepcao.accept();
            System.out.println("Conexão estabelecida na porta " + socketConexao.getPort());
            socketConexao.close();
         socketRecepcao.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
Servidor esperando conexão na porta 3000

Arquivo: Cliente.java
package socket1;

package socket1;

import java.net.Socket;

public class Cliente {

    public static void main(String argv[]) throws Exception {
        Socket socketCliente = new Socket("localhost",3000);
        System.out.println("Socket cliente é criado e finalizando...");       
        socketCliente.close();
    }
}
Socket cliente é criado e finalizando...

10.2. Exemplo 2:
Arquivo: Servidor.java
package socket2;

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

public class Servidor {
    public ServerSocket server;
    public Socket sock;
    public DataInputStream in;
    public DataOutputStream out;

    /** Cria uma nova instânica de Main */
    public Servidor() {
        try {
            //Cria um socket servidor na porta 8080.
            this.server = new ServerSocket(8080);
            //O método accept retorna um socket para comunicação com o proximo cliente.
            this.sock = this.server.accept();
            //Cria um canal para receber dados.
            this.in = new DataInputStream(sock.getInputStream());
            //Cria um canal para enviar dados.
            this.out = new DataOutputStream(sock.getOutputStream());
        } catch (IOException e) {
            System.out.println(e.getMessage());
        }
    }

    public static void main(String args[]) {
        try {
            // Chama o método construtor na classe
            Servidor serv = new Servidor();
            //Espera pelo recebimento de um inteiro.
            int valor = serv.in.readInt();
            //Espera pelo recebimento de uma String.
            String texto = serv.in.readUTF();
            System.out.println(valor);
            System.out.println(texto);
            //Envia o inteiro 6000.
            serv.out.writeInt(6000);
            //Envia a String “Olá - Socket Servidor.”.
            serv.out.writeUTF("Olá - Socket Servidor.");
            //Fecha o canal de entrada.
            serv.in.close();
            //Fecha o canal de saída.
            serv.out.close();
            //Fecha o Socket que está a atender o cliente.
            serv.sock.close();
            //Fecha o servidor.
            serv.server.close();
        } catch (IOException e) {
            System.out.println(e.getMessage());
        }
    }
}


Arquivo: Cliente.java
package socket2;

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

public class Cliente {
    public Socket client;
    public DataInputStream in;
    public DataOutputStream out;

    public Cliente() {
        try {
            //Conectar ao servidor localhost na porta 8080.
            this.client = new Socket("127.0.0.1", 8080);
            //Cria um canal para receber dados.
            this.in = new DataInputStream(client.getInputStream());
            //Cria um canal para enviar dados.
            this.out = new DataOutputStream(client.getOutputStream());
        } catch (IOException e) {
            System.out.println(e.getMessage());
        }
    }

    public static void main(String args[]) {
        try {
            Cliente cli = new Cliente();
            //Envia o inteiro 3000.
            cli.out.writeInt(3000);
            //Envia a String “Olá - Socket Cliente.”.
            cli.out.writeUTF("Olá - Socket Cliente.");
            //Espera pelo recebimento de um inteiro.
            int valor = cli.in.readInt();
            //Espera pelo recebimento de uma String.
            String texto = cli.in.readUTF();
            System.out.println(valor);
            System.out.println(texto);
            //Fecha o canal de entrada.
            cli.in.close();
            //Fecha o canal de saída.
            cli.out.close();
            //Fecha o Socket.
            cli.client.close();
        } catch (IOException e) {
            System.out.println(e.getMessage());
        }
    }
}

6000
Olá - Socket Servidor.



10.3. Exemplo:
Arquivo: Servidor.java
package socket2;

import java.io.*;
import java.net.*;
import java.util.Scanner;

public class Cliente {
    public static void main(String[] args) {
        try {
            Socket conexao = new Socket("127.0.0.1", 2500);
            Scanner entrada = new Scanner(conexao.getInputStream());
            PrintStream saida = new PrintStream(conexao.getOutputStream());
            String linha;
            Scanner teclado = new Scanner(System.in);
            while (true) {
                System.out.println("Digite algo>>");
                linha = teclado.nextLine();
                saida.print(linha);
                if (!entrada.hasNext()) {
                    System.out.println("Conexao encerrada!");
                    break;
                }
                linha = entrada.nextLine();
                System.out.println(linha);
            }
        } catch (IOException erro) {
            System.out.println("IOException: " + erro);
        }
    }
}

 Esperando conexão...

Arquivo: Cliente.java
package socket2;

import java.io.IOException;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;

public class Servidor {
    private static Scanner entrada;

    public static void main(String[] args) {
        try {
            ServerSocket soquete = new ServerSocket(2500);
            while (true) {
                System.out.println("Esperando conexão...");
                Socket conexao = soquete.accept();
                System.out.println("Conectado");
                entrada = new Scanner(conexao.getInputStream());
                PrintStream saida = new PrintStream(conexao.getOutputStream());
                String linha = entrada.nextLine();
                while (linha != null && !(linha.trim().equals(""))) {
                    saida.println("Eco: " + linha.toUpperCase());
                    linha = entrada.nextLine();
                }
                conexao.close();
            }
        } catch (IOException erro) {
            System.out.println("IOException: " + erro);
        }
    }
}


Esperando conexão...
Conectado

10.4. Exemplo 4:
Arquivo: Servidor.java
package socket4;

import java.io.*; 
import java.net.*; 
import java.util.Date; 
 
class Servidor { 
    public static void main(String args[]) 
    { 
        try { 
            // Cria um socket de servidor e fica escutando na porta 2000 
            ServerSocket s = new ServerSocket(2000); 
            while(true) { 
                System.out.println("\nEsperando conexão"); 
                Socket conexao = s.accept(); // espera a chegada de uma conexão 
                                             // e a aceita quando chegar 
               System.out.println("\nConectado!"); 
                //obtem o fluxo de saida do socket e o associa à variável saida 
               PrintStream saida = new PrintStream(conexao.getOutputStream()); 
                saida.println( (new Date()).toString() ); 
               conexao.close(); 
            } 
        } 
        catch(java.io.IOException ex) { 
            System.out.println(ex.getMessage()); 
        } 
    } 
}


Esperando conexão

Arquivo: Cliente.java
package socket4;

import java.io.*; 
import java.net.*; 
 
class Cliente { 
    public static void main(String args[]) 
    { 
        try { 
            // conecta no servidor (local) na porta 2000 
            Socket sock = new Socket("127.0.0.1",2000); 
            // associa o fluxo de entrada do socket à variável entrada 
            BufferedReader entrada = new BufferedReader(new InputStreamReader(sock.getInputStream())); 
            String dataHora = entrada.readLine(); //obtem dados do servidor (data/hora) 
            System.out.println("\nData/hora no servidor "+dataHora+"\n"); 
            sock.close(); // encerra a conexao 
        } 
        catch(java.io.IOException ex) { 
            System.out.println(ex.getMessage()); 
        } 
    } 



 Data/hora no servidor Thu Aug 08 19:01:24 BRT 2013

10.5. Exemplo 5:
Arquivo: Servidor.java
package socket5;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;

public class Servidor {
    public static void main(String[] args) throws IOException {
        ServerSocket servidor = new ServerSocket(2500);
        System.out.println("Porta 2500 aberta!");

        Socket cliente = servidor.accept();
        System.out.println("Nova conexão com o cliente "
                + cliente.getInetAddress().getHostAddress());

        Scanner s = new Scanner(cliente.getInputStream());
        while (s.hasNextLine()) {
            System.out.println(s.nextLine());
        }

        s.close();
        servidor.close();
        cliente.close();
    }
}


Porta 2500 aberta!

Arquivo: Cliente.java
package socket5;

import java.io.IOException;
import java.io.PrintStream;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Scanner;

public class Cliente {
    public static void main(String[] args) throws UnknownHostException,
            IOException {
        Socket cliente = new Socket("127.0.0.1", 2500);
        System.out.println("O cliente se conectou ao servidor!");

        Scanner teclado = new Scanner(System.in);
        PrintStream saida = new PrintStream(cliente.getOutputStream());

        while (teclado.hasNextLine()) {
            saida.println(teclado.nextLine());
        }

        saida.close();
        teclado.close();
        cliente.close();
    }
}


Execução do Cliente.java:
Resultado:
O cliente se conectou ao servidor!

Entrada de texto no console:
Frase digitada

Resultado após digitar a frase anterior e teclar <enter>:
Porta 2500 aberta!
Nova conexão com o cliente 127.0.0.1
Frase digitada