Quando falamos em networking, não podemos deixar de mencionar o modelo OSI.
Em termos desse modelo, hoje estamos mais interessados na camada de transporte (4).
Este é o nível em que trabalhamos com dados que se deslocam "do ponto A ao ponto B". A principal tarefa da camada de transporte é garantir que uma mensagem seja entregue ao destino, mantendo a sequência correta. Os dois protocolos de camada de transporte mais comuns são: TCP e UDP. Eles funcionam conceitualmente de maneiras diferentes, mas cada um tem suas próprias vantagens que permitem resolver problemas específicos.
Primeiro, vamos ver como o TCP funciona.
TCP (Transmission Control Protocol) é um protocolo de rede que garante que uma conexão entre os hosts seja estabelecida antes da troca de dados.
Este é um protocolo muito confiável, pois toda vez que envia outro pacote de dados, deve verificar se o pacote anterior foi recebido.
Os pacotes transmitidos são ordenados e, se houver problemas com um determinado pacote (ou seja, o destinatário não confirma que o pacote chegou), o pacote é enviado novamente. Como resultado, a taxa de transferência é relativamente baixa, pois é necessário mais tempo para o monitoramento rigoroso e para garantir a ordenação correta.
É aqui que entra seu "irmão", o protocolo UDP. Ao contrário do TCP, o UDP realmente não se importa com a ordem e o status de cada pacote. Ele simplesmente envia dados sem confirmação de entrega. Além do mais, ele não estabelece uma conexão e não depende de forma alguma do status da conexão.
Sua finalidade é simplesmente enviar dados para um endereço. E isso dá origem à principal desvantagem do protocolo, a baixa confiabilidade, pois pode simplesmente perder pedaços de dados. Além disso, o destinatário deve estar preparado para o fato de que os dados podem chegar fora de ordem. Dito isto, o protocolo também tem uma vantagem, uma taxa de transferência mais elevada, devido ao facto de o protocolo se limitar a enviar dados.
Também existem diferenças em como os próprios dados são transmitidos. No TCP, os dados são transmitidos, o que significa que os dados não têm limites. No UDP, os dados são transmitidos como datagramas e possuem limites, e o destinatário verifica a integridade dos dados, mas somente se a mensagem for recebida com sucesso.
Vamos resumir:
O TCP é um protocolo confiável e preciso que evita a perda de dados. Uma mensagem sempre será entregue com a máxima precisão, ou não será entregue. O destinatário não precisa de lógica para solicitar dados, pois os dados recebidos já serão solicitados. | O UDP não é tão confiável, mas é um protocolo de transferência de dados mais rápido. As partes de envio e recebimento precisam de alguma lógica adicional para trabalhar com este protocolo. Mas vamos dar uma olhada em como funciona usando o exemplo de um jogo de computador ou jogo para celular jogado pela rede. Podemos não nos importar mais com o que deveria ter chegado 5 segundos atrás e podemos pular alguns pacotes se eles não chegarem a tempo - o jogo pode atrasar, mas você ainda pode jogar! |
Em Java, para trabalhar com datagramas transmitidos por UDP, utilizamos objetos das classes DatagramSocket e DatagramPacket .
Para trocar dados, o remetente e o destinatário criam soquetes de datagrama, ou seja, instâncias da classe DatagramSocket . A classe tem vários construtores. A diferença entre eles é onde o socket criado irá se conectar:
DatagramaSocket() | Conecta-se a qualquer porta disponível na máquina local |
DatagramSocket (porta int) | Conecta-se à porta especificada na máquina local |
DatagramSocket (porta int, endereço InetAddress) | Conecta-se à porta especificada em um endereço na máquina local (addr) |
A classe contém muitos métodos para acessar e gerenciar os parâmetros do soquete (vamos vê-los um pouco mais tarde), bem como métodos para receber e enviar datagramas:
enviar (pacote DatagramPacket) | Envia datagramas compactados em pacotes |
receber (pacote DatagramPacket) | Recebe datagramas compactados em pacotes |
DatagramPacket é uma classe que representa um pacote de datagramas. Pacotes de datagramas são usados para implementar um serviço de entrega de pacotes sem conexão. Cada mensagem é roteada de uma máquina para outra com base apenas nas informações contidas naquele pacote. Vários pacotes enviados de uma máquina para outra podem ser roteados de forma diferente e podem chegar em qualquer ordem. A entrega de pacotes não é garantida.
Construtores:
DatagramPacket(byte[] buf, comprimento int) | Cria um DatagramPacket para aceitar pacotes de comprimento length . |
DatagramPacket(byte[] buf, comprimento int, endereço InetAddress, porta int) | Cria um pacote de datagrama para enviar pacotes de comprimento para o número de porta especificado no host especificado. |
DatagramPacket(byte[] buf, int offset, int length) | Cria um DatagramPacket para aceitar pacotes de comprimento length , especificando um deslocamento no buffer. |
DatagramPacket(byte[] buf, deslocamento int, comprimento int, endereço InetAddress, porta int) | Cria um pacote de datagrama para enviar pacotes de comprimento com deslocamento de deslocamento para o número de porta especificado no host especificado. |
DatagramPacket(byte[] buf, deslocamento int, comprimento int, endereço SocketAddress) | Cria um pacote de datagrama para enviar pacotes de comprimento com deslocamento de deslocamento para o número de porta especificado no host especificado. |
DatagramPacket(byte[] buf, comprimento int, endereço SocketAddress) | Cria um pacote de datagrama para enviar pacotes de comprimento para o número de porta especificado no host especificado. |
Lembramos que a abordagem UDP não estabelece uma conexão. Os pacotes são enviados na esperança de que o destinatário os esteja esperando. Mas você pode estabelecer uma conexão usando o método connect(InetAddress addr, int port) da classe DatagramSocket .
Uma conexão unidirecional é estabelecida com o host com base em um endereço e porta: para enviar ou receber datagramas. A conexão pode ser encerrada usando o método desconectar() .
Vamos tentar escrever código de servidor baseado em DatagramSocket para receber dados:
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
class Recipient {
public static void main(String[] args) {
try {
DatagramSocket ds = new DatagramSocket(1050);
while (true) {
DatagramPacket pack = new DatagramPacket(new byte[5], 5);
ds.receive(pack);
System.out.println(new String(pack.getData()));
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
Criamos um objeto DatagramSocket para escutar na porta 1050. Ao receber uma mensagem, ele a imprime no console. Vamos transmitir a palavra "Olá", então limitamos o tamanho do buffer a cinco bytes.
Agora vamos criar a classe do remetente:
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
class Sender {
private String host;
private int port;
Sender(String host, int port) {
this.host = host;
this.port = port;
}
private void sendMessage(String mes) {
try {
byte[] data = mes.getBytes();
InetAddress address = InetAddress.getByName(host);
DatagramPacket pack = new DatagramPacket(data, data.length, address, port);
DatagramSocket ds = new DatagramSocket();
ds.send(pack);
ds.close();
} catch (IOException e) {
System.err.println(e);
}
}
public static void main(String[] args) {
Sender sender = new Sender("localhost", 1050);
String message = "Hello";
Timer timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
sender.sendMessage(message);
}
}, 1000, 1000);
}
}
No método sendMessage , criamos um DatagramPacket e um DatagramSocket e enviamos nossa mensagem. Observe que o método close() é usado para fechar o DatagramSocket após o envio da mensagem.
A cada segundo, o console do destinatário exibe a mensagem "Olá" enviada pelo remetente. Isso significa que nossa comunicação está funcionando corretamente.
GO TO FULL VERSION