6 Sockets no orientados a conexión¶
Sockets no orientados a conexión¶
Los sockets no orientados a conexión o datagram sockets utilizan el protocolo UDP para la comunicación. Este protocolo no garantiza la entrega de los mensajes ni el orden en el que se reciben.
Los mensajes, conocidos como datagramas (datagrams), se envían de un proceso emisor a otro receptor sin establecer una conexión previa, y sin existir un protocolo de reenvío basado en acuses de recibo. Cada vez que el emisor envía un mensaje es necesario que indique explícitamente la dirección IP y el puerto del destinatario del paquete.
Además, con el mensaje, el receptor recibe también la IP y el puerto del conector del emisor desde el que se ha enviado. El receptor podrá extraer esta información y usarla para enviar una respuesta al emisor.
El puerto desde que el emisor envía sus datagramas no tiene por qué coincidir con el puerto en el que debe recibirlos el receptor.
Este tipo de sockets es usado cuando nos importa mucho más la rapidez del envío que la garantía de entrega, como en el caso de de transmisión de audio y vídeo en tiempo real, donde no es posible el reenvío de paquetes retrasados, o en el caso del servicio de DNS (Domain Name Server).
Veamos una comparación entre el protocolo UDP y el protocolo TCP:
Protocolo UDP | Protocolo TCP | |
---|---|---|
Descripción | Envoltura simple y rápida, que sirve básicamente de interfaz de las aplicaciones con respecto a la capa de red y no hace prácticamente nada más. | Protocolo de funcionalidad completa que permite a las aplicaciones enviar datos de forma confiable sin tener que preocuparse por los problemas en la capa de red. |
Conexión | Sin conexión. Los datos son enviados sin configuración previa. | Orientado a conexión. La conexión debe ser establecida antes de poder realizar una transmisión. |
Interfaz de datos para la aplicación | Basada en mensajes. Los datos son enviados en paquetes específicos indicados por la aplicación. | Basado en un flujo de conexión. Los datos son enviados por la aplicación sin una estructura particular. |
Confiabilidad y acuse de recibo | No confiable. Intenta realizar el envío lo mejor posible, pero sin usar acuse de recibo. | Envío confiable de mensajes. Todos los datos requieren acuse de recibo. |
Retransmisiones | No se realizan retransmisiones. La aplicación debe detectar la pérdida de datos y retransmitir si es necesario. | El envío de todos los datos es gestionado por el protocolo, de manera que los datos que se hayan perdido son retransmitidos automáticamente. |
Sobrecarga | Muy baja. | Baja, pero más alta que en el protocolo UDP. |
Velocidad de transmisión | Muy alta. | Alta, pero no tan alta como UDP. |
Cantidad de datos adecuada | Pequeña o moderada (hasta unos pocos cientos de bytes). | De pequeña a muy grande (hasta gigabytes). |
Tipos de aplicaciones | Aplicaciones donde la velocidad en la entrega es más importante que el hecho de que se entreguen todos los datos. | Aplicaciones donde la entrega de datos debe ser confiable. |
Para representar los datagramas, Java nos proporciona la clase DatagramPacket
. Internamente un objeto DatagramPacket
contendrá un byte[]
, conocido como buffer, en el que podremos almacenar el mensaje a enviar o el mensaje recibido. Normalmente, emisor y receptor están de acuerdo en el tamaño máximo de los mensajes, de manera que todos son de la misma longitud.
El constructor de esta clase DatagramPacket(bufferBytes, bufferLength, targetInetAddress, targetPort)
, recibe los bytes correspondientes al mensaje que se quiere enviar, que será almacenado en el buffer interno, la longitud del mensaje, la dirección IP del destinatario en forma de objeto InetAddress
y el puerto del destinatario al que se debe enviar el datagrama.
Construcción de un DatagramPacket
La clase DatagramSocket
permite crear un socket o conector en el que recibir datagramas y desde el que poder enviarlos. El constructor de esta clase DatagramSocket(port)
recibe el puerto en el que queremos recibirlos y desde el que queremos enviarlos.
Una vez creado el objeto DatagramSocket
podemos enviar datagramas a través de él, usando su método send(datagramPacket)
, que recibe el objeto DatagramPacket
que queremos enviar.
Si lo que queremos es recibir un datagrama a través del objeto DatagramSocket
, entonces podemos llamar a su método receive(datagramPacket)
, que recibe un objeto DatagramPacket
en el que "escribir" el datagrama recibido. Se trata de una llamada bloqueante, ya que el hilo en el que se ejecuta será bloqueado hasta recibir el datagrama.
Como vemos, el método receive()
no retorna nada, sino que tenemos que proporcionarle un objeto DatagramPacket
para que rellene su buffer interno. Por este motivo, previamente deberemos crear un objeto DatagramPacket
usando el constructor DatagramPacket(bufferBytes, bufferLength)
, al que pasaremos un byte[]
correspondiente al buffer que debe rellenar con el mensaje recibido, así como la longitud de dicho buffer.
Si el mensaje recibido tiene una longitud superior al buffer suministrado al datagramPacket, el mensaje será simplemente truncado.
DatagramSocket datagramSocket = new DatagramSocket(60100);
bytes[] buffer = new bytes[1024];
DatagramPacket datagramPacket = new DatagramPacket(buffer, buffer.length);
datagramSocket.receive(datagramPacket);
Un vez recibido el datagrama, podemos usar el método getData()
para obtener su buffer, y el método getLength() para obtener su longitud. De igual forma, podemos usar el método getAddress()
para obtener la InetAddress
desde la que se envío, y el método getPort()
para obtener el puerto desde el que se hizo.
int dataLength = datagramPacket.getLength();
byte[] data = datagramPacket.getData();
String message = new String(data, 0, dataLength).trim();
InetAddress senderInetAddress = datagramPacket.getAddress();
int senderPort = datagramPacket.getPort();
Como ya hemos indicado, el método receive()
es, por defecto, bloqueante de manera que el hilo queda bloqueado hasta que se reciba un datagrama. Si queremos especificar un tiempo máximo de espera (timeout), podemos usar el método setSoTimeout(timeoutInMilis)
antes de llamar a receive()
. Si pasado dicho tiempo después de llamar a receive()
no se ha recibido un datagrama, el propio método receive()
lanzará la excepción SocketTimeoutException
.
Cuando ya no queramos enviar o recibir más datagramas a través del objeto DatagramSocket
deberemos llamar a su método close()
.
Si en vez de cadenas de texto queremos enviar datagramas que contengan objetos, entonces tendremos que convertir dichos objetos en byte[]
para enviarlos, y posteriormente, una vez recibido el byte[]
volver a convertirlo a objeto. Así, usaremos las clases ObjectOutputStream
y ByteArrayOutputStream
para convertir un objeto en un byte[]
, y las clases ObjectInputStream
y ByteArrayInputStream
para convertir un byte[]
en un objeto. A continuación se muestran dos métodos de utilidad para ello:
public final class ByteArrayUtils {
private ByteArrayUtils() {
}
public static byte[] serialize(Object object) throws IOException {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream =
new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(object);
return byteArrayOutputStream.toByteArray();
}
public static Object deserialize(byte[] byteArray)
throws IOException, ClassNotFoundException {
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArray);
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
return objectInputStream.readObject();
}
}