Trabajando con URLs.
URL es el acrónimo de Uniform Resource Locator. Un URL es esencialmente una referencia (dirección) a un recurso específico en Internet. Los navegadores trabajan con URLs para que puedan localizar archivos concretos en Internet.
Un URL toma la forma de una cadena que describe cómo encontrar un recurso en Internet. Los URL tienen dos componentes principales: el protocolo necesario para acceder al recurso y su ubicación. Así, una dirección como la siguiente:
https://fundamentospoorrr.blogspot.com/2017/03/contenido-tematico.html
es un ejemplo de URL, donde:
- https es el protocolo que se está utilizando: protocolo seguro de transferencia de hipertexto (Hyper Text Transfer Protocol Secure).
- fundamentospoorrr.blogspot.com representa el dominio de nivel superior del servidor (com), el servidor o host (blogspot) y el subdominio del servidor (fundamentospoorrr).
- /2017/03/ es una ruta dentro del servidor (computadora específica).
- contenido-tematico.html es el archivo al que se está accediendo.
A menudo es más fácil, aunque no del todo exacto, pensar en un URL como el nombre de un archivo en la World Wide Web (www) porque la mayoría de las direcciones URL se refieren a un archivo en alguna máquina de la red. Sin embargo, es importante no perder de vista que los URL también pueden hacer referencia a otros recursos, como consultas de base de datos y salida de comandos por ejemplo. El siguiente video (ByteByteGo) puede resultar muy ilustrativo en este sentido:
Los programas Java que interactúan con Internet pueden utilizar URLs para encontrar los recursos a los que desean acceder. Tales programas pueden utilizar la clase URL del paquete java.net para representar una dirección URL. El Ejemplo ParseURL es una muestra de ello y de algunos de los servicios básicos de dicha clase.
El ejemplo anterior define (línea 14) una cadena que representa la dirección URL con la que se va a trabajar; mientras que en la línea 15 se crea el objeto aURL que se necesita para trabajar con direcciones URLs en Java. Note que los constructores de URL podrían lanzar una MalformedURLException (línea 13). Por otro lado, note también el uso de los métodos (líneas 18-25) definidos en la clase URL; los detalles y descripción de los mismos en el API se dejan como ejercicio al lector. Asegúrese de compilar y ejecutar el ejemplo, así como de comprender la salida que genera.
Si un objeto de la clase URL se crea bien, se le puede enviar el mensaje openStream para obtener un flujo (stream) desde el cual se puede leer el contenido del URL. Dicho método devuelve un objeto java.io.InputStream por lo que leer desde una URL es tan fácil como leer desde un flujo de entrada tradicional (como un archivo).
En este sentido y como complemento al Ejemplo ParseURL, considere ahora el Ejemplo LectorURL. A partir de un objeto URL, el método openStream regresa un InputStream que puede utilizar para leer el contenido asociado a dicha dirección. Como siguiente paso, a través de un objeto de la clase BufferedReader se lee texto de una secuencia de entrada de caracteres. En general es conveniente almacenar un flujo de caracteres en un búfer para proporcionar una lectura efectiva y eficiente de caracteres y líneas de texto o cadenas.
Ahora bien, el método openStream es en realidad un atajo de:
openConnection( ).getInputStream( )
es decir, primero se abre una conexión y de ella se obtiene un flujo de entrada. En la sección de ejercicios se proporciona una versión alternativa del Ejemplo LectorURL para que el lector los pueda comparar. Ambos son equivalentes.
En resumen, una vez que se haya creado con éxito un objeto URL, se le puede enviar el mensaje openConnection para obtener un objeto URLConnection, o una de sus subclases específicas de protocolo como HttpURLConnection. La ventaja de usar un objeto URLConnection es que se pueden configurar parámetros y propiedades que se pudieran necesitar antes de conectarse.
Finalmente, se recomienda al lector revisar información complementaria en relación con los dominios de nivel superior y los nombres de dominio en general, con la finalidad de ampliar su conocimiento en este rubro Una referencia inicial al respecto puede encontrarse en Dominios de Internet. Adicionalmente, el siguiente video (ByteByteGo) es ampliamente ilustrativo al respecto:
Trabajando con Sockets.
Existen ocasiones en que los programas requieren una comunicación de red de bajo nivel, como por ejemplo, cuando desea escribir una aplicación cliente-servidor. En las aplicaciones de tipo cliente-servidor, el servidor proporciona algún servicio como procesar consultas a una base de datos, o enviar los precios de las acciones actuales por ejemplo. El cliente por su parte, utiliza el servicio proporcionado por el servidor ya sea mostrando los resultados de la consulta de la base de datos al usuario, o haciendo recomendaciones de compra de acciones a un inversor. La comunicación que se produce entre el cliente y el servidor debe ser confiable. Es decir, no se pueden eliminar datos y deben llegar al lado del cliente en el mismo orden en que los envió el servidor.
Sockets con TCP.
TCP proporciona un canal de comunicación punto a punto confiable que las aplicaciones cliente-servidor en Internet utilizan para comunicarse entre sí. En este sentido, para comunicarse a través de TCP un programa cliente y un programa servidor establecen una conexión explícita entre ellos. Cada programa vincula un socket a su extremo de la conexión, y para comunicarse, el cliente y el servidor leen y escriben respectivamente en el socket vinculado a dicha conexión.
Un socket es un punto final de un enlace de comunicación bidireccional entre dos programas que se ejecutan en la red. El socket está vinculado a un número de puerto para que la capa TCP pueda identificar la aplicación a la que están destinados los datos.
Un punto final es una combinación de una dirección IP y un número de puerto. Cada conexión TCP se puede identificar de forma única por sus dos puntos finales; por lo que es posible tener múltiples conexiones entre un cliente y el servidor. El paquete java.net proporciona dos clases: Socket y ServerSocket, que implementan el lado del cliente y el lado del servidor de la conexión respectivamente.
La clase Socket implementa un lado de una conexión bidireccional entre un programa Java y otro programa en la red. Por otro lado, la clase ServerSocket implementa un socket que los servidores pueden usar para escuchar y aceptar conexiones de los clientes.
Como ejemplo inicial que sirva para familiarizarse con las clases involucradas, considere el Ejemplo ServidorDeFecha. Normalmente un servidor se ejecuta en una computadora específica y tiene un socket que está vinculado a un número de puerto específico (línea 27). El servidor simplemente espera (se bloquea) escuchando el socket (línea 32) hasta que un cliente haga una solicitud de conexión.
En el lado del cliente (Ejemplo ClienteDeFecha), el cliente debe conocer el nombre de la máquina anfitriona (host) en la que se está ejecutando el servidor, así como el número de puerto en el que éste está escuchando (línea 29). Para realizar una solicitud de conexión, el cliente intenta reunirse con el servidor en la máquina y el puerto del anfitrión. El cliente también necesita identificarse ante el servidor para que se vincule a un número de puerto local que utilizará durante dicha conexión (esto suele ser asignado por el sistema).
Si todo va bien, el servidor es despertado y acepta la conexión (línea 32 del servidor). Tras la aceptación, el servidor obtiene un socket vinculado y asociado al puerto local, por lo que también tiene su punto final remoto configurado en la dirección y el puerto del cliente. En el lado del cliente, se crea un socket (línea 29) mismo que el cliente utiliza para comunicarse con el servidor (líneas 31-33 y 36). El cliente y el servidor ahora pueden comunicarse escribiendo o leyendo desde los flujos (streams) correspondientes de sus sockets. Para este ejemplo, el cliente sólo recibe información (línea 36) del servidor, el cliente no le envía nada, el flujo de comunicación es del servidor (línea 38) al cliente (línea 36).
Tome en consideración que la información que se envía o recibe por los sockets son bytes. En este sentido, para dar un procesamiento adecuado como texto (que es lo que se transfiere en los ejemplos de esta sección) se hace uso de las clases InputStreamReader, BufferedReader y PrintWriter del API, mismo que es preciso revisar para obtener la descripción y ampliar los detalles correspondientes que complementen de mejor forma lo hasta aquí descrito.
Antes de continuar, se recomienda ampliamente al lector comprender en su totalidad los dos ejemplos recién descritos.
Los Ejemplos ServidorEco y ClienteEco son una generalización de lo anterior. Se invita al lector a descargarlos y ejecutarlos con la finalidad de reforzar tanto los conceptos, como los pasos a seguir para establecer una comunicación bidireccional utilizando sockets. Estos ejemplos hacen uso de la cláusula try-with-resources por lo que podría ser útil al lector el revisar los Ejercicios 2, 3, 4 y 11 de excepciones, así como los ejemplos asociados descritos en el apartado Atrapando y manejando excepciones.
La siguiente imagen (geeksforgeeks) ilustra magistralmente lo que acontece en un canal de comunicación bidireccional establecido con sockets:
Sockets con UDP.
Ahora bien, algunas aplicaciones no requieren un canal confiable de comunicación punto a punto; otras más podrían beneficiarse de un modo de comunicación que proporcione paquetes de información independientes, cuya llegada y orden de llegada no están garantizados como ocurre con TCP.
El protocolo UDP proporciona un modo de comunicación de red mediante el cual las aplicaciones se envían paquetes de datos llamados datagramas. Un datagrama es un mensaje independiente y autónomo enviado a través de la red cuya llegada, hora y contenido no están garantizados. Las clases DatagramPacket y DatagramSocket del paquete java.net implementan la comunicación de datagramas independiente del sistema mediante el uso de UDP.
El Ejemplo Servidor de Citas conformado por ServidorCitas, HiloServidorCitas y ClienteCitas, es una aplicación que ejemplifica lo anterior. El servidor recibe continuamente paquetes de datagramas a través de un socket de datagramas. Cada paquete de datagramas recibido por el servidor indica una solicitud del cliente para una cita (frase, cadena). Cuando el servidor recibe un datagrama, responde al cliente enviando un paquete de datagramas que contiene la "cita del momento" (líneas 35 a 62 del método run).
La parte del cliente es bastante simple: envía un único paquete de datagramas al servidor (líneas 24-30) indicando que le gustaría recibir una cita (archivo de citas). Luego, el cliente espera que el servidor envíe un paquete de datagramas en respuesta para finalmente imprimirla en la salida estándar (líneas 33-38).
Note el lector que al construir un DatagramPacket para enviar a través del DatagramSocket, es preciso proporcionar, además del buffer, la dirección de Internet y el número de puerto del destino del paquete; mientras que pare recibir un paquete, basta sólo con el buffer.
No hay comentarios.:
Publicar un comentario