Cuando hablamos de networking, no podemos dejar de mencionar el modelo OSI.
En términos de este modelo, hoy estamos más interesados en la capa de transporte (4).
Este es el nivel en el que trabajamos con datos que se mueven "del punto A al punto B". La tarea principal de la capa de transporte es garantizar que un mensaje se entregue en el destino, manteniendo la secuencia correcta. Los dos protocolos de capa de transporte más comunes son: TCP y UDP. Funcionan conceptualmente de diferentes maneras, pero cada una tiene sus propias ventajas que les permiten resolver problemas específicos.
Primero, veamos cómo funciona TCP.
TCP (Protocolo de control de transmisión) es un protocolo de red que garantiza que se establezca una conexión entre hosts antes de que se intercambien datos.
Este es un protocolo muy confiable, ya que cada vez que envía otro paquete de datos, debe verificar que se recibió el paquete anterior.
Los paquetes transmitidos se ordenan y, si hay problemas con un determinado paquete (es decir, la parte receptora no confirma que el paquete ha llegado), el paquete se envía de nuevo. Como resultado, la tasa de transferencia es relativamente baja, porque se requiere más tiempo para el seguimiento estricto y para garantizar el pedido correcto.
Aquí es donde entra en juego su "hermano", el protocolo UDP. A diferencia de TCP, UDP realmente no se preocupa por el orden y el estado de cada paquete. Simplemente envía datos sin confirmación de entrega. Además, no establece una conexión y no depende de ningún modo del estado de la conexión.
Su propósito es simplemente enviar datos a una dirección. Y esto da lugar a la principal desventaja del protocolo, la baja fiabilidad, ya que simplemente puede perder datos. Además, el destinatario debe estar preparado para el hecho de que los datos puedan llegar desordenados. Dicho esto, el protocolo también tiene una ventaja, una mayor tasa de transferencia, debido a que el protocolo se limita a enviar datos.
También hay diferencias en cómo se transmiten los datos en sí. En TCP, los datos se transmiten, lo que significa que los datos no tienen límites. En UDP, los datos se transmiten como datagramas y tienen límites, y el destinatario verifica la integridad de los datos, pero solo si el mensaje se recibe correctamente.
Resumamos:
TCP es un protocolo fiable y preciso que evita la pérdida de datos. Un mensaje siempre se entregará con la máxima precisión, o no se entregará en absoluto. El destinatario no necesita lógica para ordenar los datos, ya que los datos entrantes ya estarán ordenados. | UDP no es tan confiable, pero es un protocolo de transferencia de datos más rápido. Las partes emisoras y receptoras necesitan alguna lógica adicional para poder trabajar con este protocolo. Pero echemos un vistazo a cómo funciona usando el ejemplo de un juego de computadora o un juego móvil que se juega a través de la red. Es posible que ya no nos importe lo que debería haber llegado hace 5 segundos, y podemos omitir un par de paquetes si no llegan a tiempo: el juego puede retrasarse, ¡pero aún puedes jugar! |
En Java, para trabajar con datagramas transmitidos sobre UDP, usamos objetos de las clases DatagramSocket y DatagramPacket .
Para intercambiar datos, el emisor y el receptor crean sockets de datagramas, es decir, instancias de la clase DatagramSocket . La clase tiene varios constructores. La diferencia entre ellos es dónde se conectará el socket creado:
DatagramSocket () | Se conecta a cualquier puerto disponible en la máquina local |
DatagramSocket (puerto int) | Se conecta al puerto especificado en la máquina local |
DatagramSocket (puerto int, dirección InetAddress) | Se conecta al puerto especificado en una dirección en la máquina local (dirección) |
La clase contiene muchos métodos para acceder y administrar los parámetros del socket (los veremos un poco más adelante), así como métodos para recibir y enviar datagramas:
enviar (paquete de paquetes de datagramas) | Envía datagramas empaquetados en paquetes |
recibir (paquete DatagramPacket) | Recibe datagramas empaquetados en paquetes |
DatagramPacket es una clase que representa un paquete de datagramas. Los paquetes de datagramas se utilizan para implementar un servicio de entrega de paquetes sin conexión. Cada mensaje se enruta de una máquina a otra basándose únicamente en la información contenida en ese paquete. Múltiples paquetes enviados de una máquina a otra pueden enrutarse de manera diferente y pueden llegar en cualquier orden. La entrega de paquetes no está garantizada.
Constructores:
DatagramPacket(byte[] buf, longitud int) | Crea un DatagramPacket para aceptar paquetes de longitud length . |
DatagramPacket(byte[] buf, longitud int, dirección InetAddress, puerto int) | Crea un paquete de datagramas para enviar paquetes de longitud al número de puerto especificado en el host especificado. |
DatagramPacket(byte[] buf, int offset, int longitud) | Crea un DatagramPacket para aceptar paquetes de longitud length , especificando un desplazamiento en el búfer. |
DatagramPacket(byte[] buf, int offset, int length, dirección InetAddress, puerto int) | Crea un paquete de datagramas para enviar paquetes de longitud con compensación de compensación al número de puerto especificado en el host especificado. |
DatagramPacket(byte[] buf, int offset, int length, dirección SocketAddress) | Crea un paquete de datagramas para enviar paquetes de longitud con compensación de compensación al número de puerto especificado en el host especificado. |
DatagramPacket(byte[] buf, longitud int, dirección SocketAddress) | Crea un paquete de datagramas para enviar paquetes de longitud al número de puerto especificado en el host especificado. |
Recordamos que el enfoque UDP no establece una conexión. Los paquetes se envían con la esperanza de que el destinatario los esté esperando. Pero puede establecer una conexión usando el método connect(InetAddress addr, int port) de la clase DatagramSocket .
Se establece una conexión unidireccional con el host en función de una dirección y un puerto: ya sea para enviar o recibir datagramas. La conexión se puede terminar usando el método de desconexión() .
Intentemos escribir un código de servidor basado en DatagramSocket para recibir datos:
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();
}
}
}
Creamos un objeto DatagramSocket para escuchar en el puerto 1050. Cuando recibe un mensaje, lo imprime en la consola. Transmitiremos la palabra "Hola", por lo que limitamos el tamaño del búfer a cinco bytes.
Ahora crearemos la clase de remitente:
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);
}
}
En el método sendMessage , creamos un DatagramPacket y un DatagramSocket y enviamos nuestro mensaje. Tenga en cuenta que el método close() se usa para cerrar DatagramSocket después de enviar el mensaje.
Cada segundo, la consola del destinatario muestra el mensaje "Hola" entrante enviado por el remitente. Esto significa que nuestra comunicación está funcionando correctamente.
GO TO FULL VERSION