Automatas Plc Grafcet

April 7, 2017 | Author: edvalles | Category: N/A
Share Embed Donate


Short Description

Download Automatas Plc Grafcet...

Description

UNIVERSIDAD AUTÓNOMA METROPOLITANA IZTAPALAPA

CONTROL DE VARIABLES POR MEDIO DE PLC.

Alumnos:

Asesor:

Ortiz Romero Juan Manuel. Huerta Ayala Alfredo Carlos Muñoz González Saúl

96219208

Victor Hugo Téllez Arrieta “Si puedes soñarlo... ...puedes hacerlo” Mayo 2003

UNIVERSIDAD AUTÓNOMA METROPOLITANA IZTAPALAPA AL TIEMPO

CASA ABIERTA

CONTROL DE VARIABLES POR MEDIO DE PLC. Durante el proyecto de ingeniería electrónica, en el primer período, se trabajo con el aprendizaje del software, para el control del PLC, así como su manejo; se realizaron varias practicas, comenzando con la programación, planteando algunos problemas, para después darle solución, y comprobando su desempeño físicamente con el PLC, proporcionado por el profesor. Durante el segundo periodo del proyecto de ingeniería electrónica, el trabajo realizado, se baso principalmente en el conocimiento del manejo de la comunicación PLC-PC, mediante Sockets, para de esta forma pasar a la practica y desarrollar un programa, que realizara una simulación de la transmisión de datos entre el PLC y la PC.

El paso siguiente seria el realizar la interfaz grafica y lograr la interacción entre el software y el PLC, para tener el control de diferentes variables, por ejemplo la apertura y cierre de puertas y ventanas, como un sistema de seguridad y control, con el propósito final de implementarlo en el edificio A-T, principalmente en el área de laboratorios.

El trabajo realizado, se muestra en 3 capítulos, ubicados de la siguiente forma: Capítulo 1: Primer periodo del Proyecto. Capítulo 2: Segundo periodo del Proyecto. Capítulo 3: La parte de conclusiones dada por el equipo en la segunda parte del proyecto. Esperamos sea un trabajo interesante e importante para las siguientes generaciones.

-I-

UNIVERSIDAD AUTÓNOMA METROPOLITANA IZTAPALAPA AL TIEMPO

CASA ABIERTA

CAPITULO 1 INTRODUCCIÓN AL CONTROL INDUSTRIAL Antecedentes Históricos Topología de los sistemas de control Tipos de sistemas de control

CLASIFICACIÓN DE LOS SISTEMAS DE CONTROL SEGÚN LA TECNOLOGÍA Autómata programable Control por ordenador Arquitectura de los autómatas programables

INTRODUCCIÓN A LOS AUTÓMATAS PROGRAMABLES Estructura externa Clasificación Bloques que forman un autómata programable Memorias internas Convertidor A / D Convertidor D / A Otras variables que se almacenan en la memoria interna: Memorias de programa Interfaces de entrada y salida Fuente de Alimentación

PROGRAMACIÓN DE AUTÓMATAS Introducción a la programación del autómata à Definición del sistema de control à Descripciones literales à Funciones algebraicas à Esquemas de relés à Diagramas lógicos à Diagramas de flujo à Grafcet à Definir las variables que intervienen y asignarles direcciones de memoria Lenguajes de programación à Lista de instrucciones à Diagramas de contactos y funciones à Lenguajes de alto nivel Grafcet à Introducción à Símbolos normalizados à Reglas de evolución del Grafcet

- II -

UNIVERSIDAD AUTÓNOMA METROPOLITANA IZTAPALAPA AL TIEMPO

CASA ABIERTA

Posibilidades de representación de automatismos con Grafcet Estructuras base Estructuras lógicas à Ejemplo à Implementación del Grafcet sobre autómatas programables à Niveles de Grafcet à Representación de situaciones especiales en Grafcet Secuencias exclusivas à Temporizadores y contadores en Grafcet Marchas de intervención “Marchas de ajuste del sistema” Seguridad Tratamiento de alarmas y emergencias Diseño estructurado de un sistema de control. à

à

CAPITULO 2

COMUNICACIONES EN RED Sockets à Sockets Stream (TCP) à Sockets Datagrama (UDP) à Sockets Raw PROGRAMACIÓN DE SOCKETS à Programación de URL LA CLASE INETADDRESS LA CLASE URL LA CLASE URLCONNECTION LA CLASE SOCKET à Cliente Eco à Cliente Fecha à Cliente HTTP LA CLASE DE SERVERSOCKET à Agente de seguridad à Servidor Eco à Servidor HTTP LA CLASE DATAGRAMPACKET LA CLASE DATAGRAMSOCKET à Cliente Eco à Servidor UDP CAPITULO 3

CONCLUSIONES

- III -

UNIVERSIDAD AUTÓNOMA METROPOLITANA IZTAPALAPA AL TIEMPO

CASA ABIERTA

- IV -

UNIVERSIDAD AUTÓNOMA METROPOLITANA IZTAPALAPA AL TIEMPO

CASA ABIERTA

CAPITULO 1

-1-

UNIVERSIDAD AUTÓNOMA METROPOLITANA IZTAPALAPA AL TIEMPO

CASA ABIERTA

Introducción al Control Industrial Se puede definir control como “la manipulación indirecta de las magnitudes de un sistema llamado planta a través de otro sistema llamado sistema de control”.

PLANTA

SISTEMA DE CONTROL

OPERADOR

CONSIGNAS

SEÑALES DE CONTROL

RESPUESTA

Antecedentes Históricos Los Primeros sistemas de control se desarrollaron en la Revolución industrial a finales del siglo XIX y principios del XX. Ø Basados en componentes mecánicos y electromagnéticos, básicamente engranajes, palancas, pequeños motores, relés, contadores y temporizadores. El uso de contadores, relés, temporizadores, ... para automatizar tareas fue aumentando a lo largo del tiempo, con lo cual se tuvieron diversos problemas derivados de ello, como: Ø Ø Ø Ø Ø

Armarios donde se alojaban muy grandes y voluminosos Probabilidad de avería muy alta Localización de la avería muy difícil y complicada Stock de material muy importante. Costo económico muy alto No flexibles

A partir de los años 50, se utilizaron semiconductores, y los primeros circuitos integrados sustituirían las funciones realizadas por los relés. Algunas de estas Mejoras fueron: Ø Ø

Sistemas de menor tamaño y con menor desgaste. Reducía el problema de fiabilidad y de stock.

El Problema de estos sistemas: Ø Su falta de FLEXIBILIDAD.

-2-

UNIVERSIDAD AUTÓNOMA METROPOLITANA IZTAPALAPA AL TIEMPO

CASA ABIERTA

A finales de los años 60, la industria estaba demandando cada vez más un sistema económico, robusto, flexible y fácilmente modificable. En 1968 nacieron los primeros autómatas programables (APIs o PLCs). Los primeros PLC’s estaban constituidos por memoria cableada y una unidad central diseñada por circuitos integrados. A principios de los 70, Los PLC’s incorporan el MICROPROCESADOR, los cual los llevo a: Ø

Más prestaciones, elementos de comunicación hombre-máquina más modernos, manipulación de datos, cálculos matemáticos, funciones de comunicación, etc.

En la Segunda mitad de los 70 los PLC’s tuvieron: Ø

Más capacidad de memoria, posibilidad de entradas/salidas remotas, analógicas y numéricas, funciones de control de posicionamiento, aparición de lenguajes con mayor número de instrucciones más potentes y, desarrollo de las comunicaciones con periféricos y ordenadores.

En la Década de los 80 la mejora de las prestaciones se refiere a: Ø

Ø

Velocidad de respuesta, reducción de las dimensiones, mayor concentración de número de entradas / salidas en los módulos respectivos, desarrollo de módulos de control continuo, PID, servocontroladores, y control inteligente, fuzzy Mas capacidad de diagnóstico en el funcionamiento e incremento en los tipos de lenguajes de programación: desde los lenguajes de contactos, lenguajes de funciones lógicas, lista de instrucciones basados en nemotécnicos, flujogramas, lenguajes informáticos, Grafcet, etc.

Debido al desarrollo de la electrónica. Hoy en día hay distintas variedades de autómatas que van desde: Ø Microautómatas y Nanoautómatas que se utilizan en apertura y cierre de puertas, domótica, control de iluminación, control de riego de jardines, etc. Ø Autómatas de gama alta prestaciones de un pequeño ordenador La principal virtud de un PLC es su robustez y facilidad de interconexión con el proceso. Su tendencia actual es: dotarlo de funciones específicas de control y de canales de comunicación para que puedan conectarse entre si y con ordenadores en red. Red de autómatas. CIM

-3-

UNIVERSIDAD AUTÓNOMA METROPOLITANA IZTAPALAPA AL TIEMPO

CASA ABIERTA

Estudio preliminar INICIO

Especificaciones técnicas del sistema o proceso a automatizar

Elección de las opciones tecnológicas más eficaces

Evaluación de las opciones desde el punto de vista económico

Toma de Decisiones

Lógica Cableada

Lógica Programada

Conocer con el mayor detalle posible las características, el funcionamiento, las distintas funciones, etc. De la máquina o proceso a automatizar.

Análisis técnico y económico Especificaciones técnicas del automatismo: materiales, aparatos, su adaptación al sistema, etc. Valoración económica

Toma de la decisión final - Ventajas e inconvenientes - Posibles ampliaciones - Rentabilidad de la inversión - Etc

FIN

Topología de los sistemas de control Tipos de topologías El objetivo de un sistema de control es: Gobernar la respuesta de una planta, sin que el operador intervenga directamente sobre sus elementos de salida. El operador manipula únicamente las magnitudes de consigna y el sistema de control se encarga de gobernar dicha salida a través de los accionamientos. El sistema de control opera, en general, con magnitudes de baja potencia, llamadas genéricamente señales, y gobierna unos accionamientos que son los que realmente modulan la potencia entregada a la planta.

-4-

UNIVERSIDAD AUTÓNOMA METROPOLITANA IZTAPALAPA AL TIEMPO

CASA ABIERTA

Existen dos tipos de topologías: Ø Lazo Abierto Ø Lazo Cerrado Lazo Abierto Sistema de control no recibe información del comportamiento de la planta.

Lazo Cerrado Existe una realimentación a través de los sensores desde la planta hacia el sistema de control.

Tipos de sistemas de control Clasificación, según el tipo de señales que interviene en la planta a controlar: Ø

Sist. Control analógicos. Señales de tipo continuo (0 a 10 V, 4 a 20 mA, etc.) proporcionales a unas determinadas magnitudes físicas (presión, temperatura, velocidad, etc.)

-5-

UNIVERSIDAD AUTÓNOMA METROPOLITANA IZTAPALAPA AL TIEMPO

Ø Ø

CASA ABIERTA

Sist. Control Digitales: señales binarias (todo o nada) sólo puede representar dos estados o niveles. Sist. Control híbridos analógicos- digitales: Autómatas programables

La unidad de control esta formada por un microprocesador: Ø Señales digitales de entrada y salida Ø Señales analógicas de entrada previamente convertidas (A/D) Ø Señales analógicas de salida previamente convertidas (D/A)

Clasificación de los sistemas de control según la tecnología

Tipos de sistemas de control Característica a estudiar Programada Flexibilidad Posibilidad de ampliación Tiempo de desarrollo del sistema

Lógica Cableada Baja Baja Mucho

-6-

Lógica Alta Alta Poco

UNIVERSIDAD AUTÓNOMA METROPOLITANA IZTAPALAPA AL TIEMPO

CASA ABIERTA

Mantenimiento Herramientas de simulación Coste para pequeñas series Estructuración en bloques independientes

Difícil No Alto Difícil

Fácil Si Bajo Fácil

Autómata programable Ø Ø Ø Ø Ø Ø

Juega el papel de UNIDAD DE CONTROL Incluye total o parcialmente las interfaces con las señales de la planta (niveles de tensión e intensidad industriales, transductores y periféricos electrónicos) Programable por el usuario Entradas: señales de consigna y de realimentación Salidas: señales de control Hardware estándar y modular (módulos ínterconectables, configurar sistema a la medida de las necesidades)

Control por ordenador Los Procesos complejos en los sistemas de control cuentan con: Ø Ø Ø Ø Ø

Gran capacidad de cálculo Conexión a estaciones gráficas Múltiples canales de comunicación Facilidad de adaptación Capacidad multiproceso etc.

Para ellos se han utilizado mini ordenadores, con interfaces específicas para la planta a controlar. Ø INCONVENIENTE: Es caro y poco estándar La frontera entre autómatas de gama alta y los mini ordenadores esta actualmente muy difusa Actualmente Ø

Ø

red de autómatas controlados por uno o varios ordenadores, con lo que se consigue combinar las ventajas de ambos, facilidad de interfaces estándar (autómata) y la potencia de cálculo (ordenador). El sistema resultante tiene las siguientes características o Sistema programable con una capacidad de cálculo elevada o Interfaces hombre-máquina estándar, proporcionados por el ordenador o Software estándar para el manejo de datos y gestión de la producción

-7-

UNIVERSIDAD AUTÓNOMA METROPOLITANA IZTAPALAPA AL TIEMPO

o o o o o o o

CASA ABIERTA

Posibilidad de control descentralizado Sistemas de comunicación estándar: LAN o WAN Mantenimiento fácil Interfase con la planta sencillo debido a los autómatas Visualización del proceso en tiempo real Multitud de herramientas para simulación y mantenimiento Flexibilidad

Arquitectura de los autómatas programables Introducción a los autómatas programables Ø Estructura externa del autómata Ø Bloques que forman un autómata programable Arquitectura interna de un autómata programable Ø Unidad central de proceso Ø Memoria o Memorias internas o Memoria de programa Interfaces de entrada salida Fuente de alimentación Introducción a los autómatas programables Un autómata programable es: “un equipo electrónico, basado en un microprocesador o microcontrolador, que tiene generalmente una configuración modular, puede programarse en lenguaje no informático y está diseñado para controlar procesos en tiempo real y en ambiente agresivo (ambiente industrial)” Una característica diferenciadora del autómata programable frente a otros sistemas de control programables está en la estandarización de su hardware, que permite la configuración de sistemas de control a medida.

-8-

UNIVERSIDAD AUTÓNOMA METROPOLITANA IZTAPALAPA AL TIEMPO

CASA ABIERTA

Estructura externa Aspecto físico exterior del mismo, los bloques en los que esta dividido, etc. Clasificación Estructura compacta En un solo bloque todos sus elementos : fuente de alimentación, CPU, memorias, entrada/salida, etc. Aplicaciones en el que el número de entradas/salidas es pequeño, poco variable y conocido a priori carcasa de carácter estanco, que permite su empleo en ambientes industriales especialmente hostiles Estructura modular Permite adaptarse a las necesidades del diseño, y a las posteriores actualizaciones. Configuración del sistema variable Funcionamiento parcial del sistema frente a averías localizadas, y una rápida reparación con la simple sustitución de los módulos averiados. Estructura modular del autómata S7-224.

-9-

UNIVERSIDAD AUTÓNOMA METROPOLITANA IZTAPALAPA AL TIEMPO

CASA ABIERTA

Bloques que forman un autómata programable Bloques principales Ø Bloque de entradas Adapta y codifica de forma comprensible para la CPU las señales procedentes de los dispositivos de entrada o captadores, como por ejemplo, pulsadores, finales de carrera, sensores, etc. Misión: proteger los circuitos internos del Autómata, proporcionando una separación eléctrica entre estos y los captadores.

Ø

Ø

Bloque de salidas Decodifica las señales procedentes de la CPU, las amplifica y las envía a los dispositivos de salida o actuadores, como lámparas, relés, contactores, arrancadores, electroválvulas, etc. Unidad central de proceso (CPU) Este bloque es el cerebro del autómata Su función es la interpretación de las instrucciones del programa de usuario y en función de las entradas, activa las salidas deseadas.

- 10 -

UNIVERSIDAD AUTÓNOMA METROPOLITANA IZTAPALAPA AL TIEMPO

CASA ABIERTA

Bloques necesarios para el funcionamiento del autómata Ø

Fuente de alimentación A partir de una tensión exterior proporciona las tensiones necesarias para el funcionamiento de los distintos circuitos electrónicos del autómata. Batería, condensador de alta potencia: para mantener el programa y algunos datos en la memoria si hubiera un corte de la tensión exterior.

Ø

Consola de programación PC o consolas de programación

Ø

Periféricos Son aquellos elementos auxiliares, físicamente independientes del autómata, que se unen al mismo para realizar una función específica y que amplían su campo de aplicación o facilitan su uso. Como tales no intervienen directamente ni en la elaboración ni en la ejecución del programa.

Ø

Interfaces Circuitos o dispositivos electrónicos que permiten la conexión a la Chp. de los elementos periféricos descritos.

- 11 -

UNIVERSIDAD AUTÓNOMA METROPOLITANA IZTAPALAPA AL TIEMPO

CASA ABIERTA

Esquema de la arquitectura interna de un PLC

Unidad central de proceso (CPU) La CPU (Control Processing Unit) es la encargada de ejecutar el programa de usuario y activar el sistema de entradas y salidas Ø

Tiene la misión, en algunos tipos de autómatas, de controlar la comunicación con otros periféricos externos, como son la unidad de programación, LCDs, monitores, teclados, otros autómatas u otros ordenadores, etc.

La CPU esta formada por el microprocesador ( P), la memoria y circuitos lógicos complementarios Ø

El microprocesador se sustituye por dispositivos lógicos programables (DLP), o redes de puertas lógicas (gate array), también llamados circuitos integrados de aplicación específica ASIC.

La CPU ejecuta el programa de usuario, que reside en la memoria, adquiriendo las instrucciones una a una. El funcionamiento es de tipo Interpretado, con decodificación de las instrucciones que cada vez que son ejecutadas Unidad central de proceso (CPU) Lenguaje compilado vs interpretado Ø Compilado programa fuente -> compilación -> programa objeto -> enlazado ó linkado -> programa ejecutable Ø

Interpretado

- 12 -

UNIVERSIDAD AUTÓNOMA METROPOLITANA IZTAPALAPA AL TIEMPO

CASA ABIERTA

analiza y ejecuta un programa sentencia a sentencia Bloques fundamentales de una CPU Ø Ø Ø Ø Ø Ø

ALU (Arithmetic Logic Unit) Acumulador Flags Contador de programa (PC) Decodificadores de instucciones y secuenciador Programa ROM

Esquema de los bloques fundamentales que componen una CPU

ALU Unidad aritmético lógica, es la parte de la CPU donde se —realizan los cálculos y las decisiones lógicas (combinaciones Y, O, sumas, comparaciones, etc.). ACUMULADOR Almacena el resultado de la última operación realizada por la ALU

FLAGS Indicadores de resultado de operación (mayor que, positivo, negativo, resultado cero, etc.). El estado de estos flags puede ser consultado por el programa. CONTADOR DE PROGRAMA (PC) Direccionamiento de la memoria donde se encuentran las instrucciones del programa de control, y del cual depende la secuencia de ejecución de ellas. DECODIFICADOR DE INSTRUCCIONES Y SECUENCIADOR

- 13 -

UNIVERSIDAD AUTÓNOMA METROPOLITANA IZTAPALAPA AL TIEMPO

CASA ABIERTA

Cableado y/o programado, donde se codifican las instrucciones leídas en la memoria y se generan las señales de control pertinentes. PROGRAMA ROM El fabricante suele grabar una serie de programas ejecutables fijos, firmware o software del sistema y es a estos programas a los que accederá el microprocesador para realizar las funciones ejecutivas. El software de sistema de cualquier Autómata consta de una serie de funciones básicas que realiza en determinados tiempos de cada ciclo: Ø En el inicio o conexión Ø Durante el ciclo o ejecución del programa y, Ø A la desconexión. PROGRAMA ROM Este software o programa del sistema es ligeramente variable para cada autómata, pero, en general, contiene las siguientes funciones: Ø Supervisión y control de tiempo de ciclo (watchdog), tabla de datos, alimentación, batería, etc. Ø Autotest en la conexión y durante la ejecución del programa. Inicio del ciclo de exploración de programa y de la configuración del conjunto. Ø Generación del ciclo base de tiempo. Ø Comunicación con periféricos y unidad de programación. Ø Etc. Hasta que el programa del sistema no ha ejecutado todas las acciones necesarias que le corresponden, no se inicia el ciclo de programa de usuario. MEMORIA Definición “Cualquier tipo de dispositivo que permita almacenar información en forma de bits (unos y ceros), los cuales pueden ser leídos posición a posición (bit a bit), o por bloques de 8 (byte) o dieciséis posiciones (word)” Clasificación Ø

Ø

RAM (Random Access Memory), memoria de acceso aleatorio o memoria de lectura-escritura. Pueden realizar los procesos de lectura y escritura por procedimientos eléctricos. Su información desaparece al faltarle la alimentación. ROM (Read Only Memory), o memoria de sólo lectura. En estas memorias se puede leer su contenido, pero no se puede escribir en pila; los Nombre a partir de la direccion members.es tripod de/62 52 93. 153 -> Direccion IP actual de LocalHost breogan/194.224.37.156 -> Nombre de LocalHost a partir de la direccion iv156.eintec.es/ -> Nombre actual de LocalHost iv156.eintec.es -> Direccion IP actual de LocalHost 194 224 37 156 Y el código completo del ejemplo es el que se reproduce a continuación posteriormente se comentará en detalle. import java.net.*; class java1701 { public static void main( String[ ] args ) { try { System.out.println( "-> Direccion IP de una URL, por nombre" ); InetAddress address = InetAddress.getByName( "usuarios.tripod.es" ); System.out.println( address ); // Extrae la dirección IP a partir de la cadena que se // encuentra a la derecha de la barra /, luego proporciona // esta dirección IP como argumento de llamada al método // getByName() System.out.println(

- 58 -

UNIVERSIDAD AUTÓNOMA METROPOLITANA IZTAPALAPA AL TIEMPO

CASA ABIERTA

"-> Nombre a partir de la direccion" ); int temp = address.toString().indexOf( '/' ); address = InetAddress.getByName( address.toString().substring(temp+1) ); System.out.println( address ); System.out.println( "-> Direccion IP actual de LocalHost" ); address = InetAddress.getLocalHost(); System.out.println( address ); System.out.println( "-> Nombre de LocalHost a partir de la direccion" ); temp = address.toString().indexOf( '/' ); address = InetAddress.getByName( address.toString().substring(temp+1) ); System.out.println( address ); System.out.println( "-> Nombre actual de LocalHost" ); System.out.println( address.getHostName() ); System.out.println( "-> Direccion IP actual de LocalHost" ); // Coge la dirección IP como un array de bytes byte[ ] bytes = address.getAddress(); // Convierte los bytes de la dirección IP a valores sin // signo y los presenta separados por espacios for( int cnt=0; cnt < bytes.length; cnt++ ) { int uByte = bytes[cnt] < 0 ? bytes[cnt]+256 : bytes[cnt]; System.out.print( uByte+" " ); } System.out.println(); } catch( UnknownHostException e ) { System.out.println( e ); System.out.println( "Debes estar conectado para que esto funcione bien." ); } } } Todo el código del programa está en el método principal main( ). La clase InetAdress proporciona objetos que se pueden utilizar para manipular tanto direcciones IP como nombres de dominio; sin embargo, no se pueden instanciar estos objetos directamente. La clase proporciona varios métodos estáticos que devuelven un objeto de tipo InetAddress. El método estático getByName() devuelve un objeto InetAddress representando el host que se le pasa como parámetro. Este método se puede utilizar para determinar la dirección IP de un host, - 59 -

UNIVERSIDAD AUTÓNOMA METROPOLITANA IZTAPALAPA AL TIEMPO

CASA ABIERTA

conociendo su nombre; entendiendo por nombre del host el nombre de la máquina, como “java.sun.com”, o la representación como cadena de su dirección IP, como “206.26.48.100”. El método getAllByName() devuelve un array de objetos InetAddress, y se puede utilizar para determinar todas las direcciones IP asignadas a un host. El método getLocalHost() devuelve un objeto InetAddress representando el computador local sobre el que se ejecuta la aplicación. El primer trozo de código interesante es el que obtiene un objeto InetAddress repreesentando un determinado servidor y presenta esta dirección utilizando el método sobrecargado toString() de la clase InetAddress. InetAddress address = InetAddress.getByName( members.es.trípod.de ); System.out.println( address ); El siguiente trozo de codigo es la acción contraria al anterio, en que se proporciona la dirección IP para presentar el nombre del host. Como ya se ha indicado, el método getByName( ) puede aceptar como parámetro de entrada tanto el nombre del host, como su dirección IP en forma de cadena. El código, utiliza el resultado de la llamada al método anterior para construir una cadena con la parte numérica del resultado, que es pasada al método getByName( ). Int Temp. = address.toString( ).indexof( ); Address = InetAdress.getByName( address.toString( ).substring(temp+1) ), System.out.println( address); El término localhost se utiliza para describir el computador local, la máquina en la que se está ejecutando la aplicación. Cuando se conecta a una red IP, el computador local debe tener una dirección IP, que se puede conseguir de diferentes formas; no obstante, la siguiente explicación se basa en que el computador sobre el cual se ejecuta aplicación se conecta a través de la línea telefónica con un proveedor de Internet, que es el que abre el acceso a Internet de la máquina local. El proveedor de Internet tiene reservadas una serie de direcciones IP, que puede compartir entre todos sus clientes. Cuando alguien se conecta al proveedor, automáticamente se le asigna una dirección a esa conexión, válida durante todo el tiempo que dure la sesión. Si se produce una desconexión y luego se vuelve a conectar, lo más seguro es que la dirección IP no sea la misma que se había asignado a la primera conexión. Aunque ésa sea la situación más habitual del lector, en otras ocasiones puede ser diferente. Por ejemplo, si el computador se encuentra en la red interna de computadores de una empresa, esa empresa puede tener un bloque de direcciones IP reservadas asignar permanentemente las direcciones a los computadores; en cuyo caso, cada que se ejecute el programa, la dirección IP será siempre la misma. También es posible que el lector disponga de su propia dirección permanente IP y nombre de dominio. En cualquier caso, el método getLocalHost() se puede utilizar para obtener un objeto de tipo InetAddress que represente al computador en el cual se está ejecutan la aplicación. Eso es justamente lo que muestra el código que aparece a continuación. - 60 -

UNIVERSIDAD AUTÓNOMA METROPOLITANA IZTAPALAPA AL TIEMPO

CASA ABIERTA

address = InetAddress.getLocalHost( ); System.out.println( address ); En este caso, la parte numérica que aparece al ejecutar el programa, corresponde la dirección que el proveedor de Internet ha asignado a la conexión sobre la cual se estaba corriendo el programa. Las líneas de código siguientes realizan la operación contraria a las anteriores; se utiliza el método getByNameQ para determinar el nombre por el cual el servidor de nombres de dominio reconoce a esa dirección numérica. Temp. = address.toString( ).indexof( ); Address = InetAdress.getByName( address.toString( ).substring(temp+1) ); System.out.println(address ); Una vez que se ha obtenido el objeto InetAddress, hay otra serie de métodos de la clase InetAdress que se pueden utilizar par invocar a este objeto; por ejemplo, las siguientes líneas de código muestran la invocación del método gerHostName( ), para obtener el nombre de la máquina. System.out.println( address.gerHostName( ) ); De forma semejante, el siguiente fragmento de código utiliza la invocación del método getAddress() para obtener un array de bytes conteniendo la dirección IP de la máquina. byte[] bytes = address.getAddress(); // Convierte los bytes de la dirección IP a valores sin // signo y los presenta separados por espacios for( int cnt=O; cnt < bytes.length; cnt++ ) { int uByte = bytes[cnt] < O ? bytes[cnt]+256 bytes[cnt]; system.out.print( uByte+ ); } Como no existe nada parecido a un byte sin signo en Java, la conversión del array de bytes en algo que se pueda presentar en pantalla requiere una cierta manipulación de los bytes, tal como se hace en el bucle for del código anterior.

LA CLASE URL La clase URL contiene constructores y métodos para la manipulación de URL (Universal Resource Locator): un objeto o servicio en Internet. El protocolo TCP, como ya se ha indicado, necesita dos tipos de información: la dirección IP y el número de puerto. A continuación se expone la forma en que se recibe pues la página Web principal de uno de los buscadores al teclear:

- 61 -

UNIVERSIDAD AUTÓNOMA METROPOLITANA IZTAPALAPA AL TIEMPO

CASA ABIERTA

http://www.yahoo.com En primer lugar, Yahoo tiene registrado su nombre, permitiendo que se use yahoo.com como su dirección IP, o lo que es lo mismo, cuando se indica yahoo.com es como si se hubiese especificado 205.216.146.71, su dirección IP real. La verdad es que la cosa es un poco más complicada que eso, al intervenir el servicio DNS (Domain Name Service) , que traslada wwwyahoo.com a 205.216.146.71, que es realidad lo que permite teclear www.yahoo.com, en lugar de tener que recordar su dirección IP. Como se ha visto al tratar la clase InetAddress, si se quiere obtener la dirección IP real de la red en que se está ejecutando una aplicación, se pueden realizar llamadas a los métodos gerLocalHost( ) y getAddress( ). Primero, getLocalHost( ) devuelve un objeto InerAddress, que si se usa con getAddress( ) generara un array con la dirección IP. Una cosa interesante en este punot es que una red puede mapear muchas direcciones IP. Esto puede ser necesario para un Servidor Web. Como Yahoo, que tiene que soportar grandes cantidades de tráfico y necesita mas de una dirección IP para poder atender todo ese tráfico. El nombre interno para la dirección 205.216.146.71, por ejemplo es www7.yahoo.com. el DNS puede trasladar una lista de direcciones IP asignadas a Yahoo en www.yahoo.com.esto es una cualidad útil, pero abre un agujero en cuestion de seguridad. Se conoce pues la dirección IP, falta el número del puerto. Si no se indica nada, se utilizará el que se haya definido por defecto en el archivo de configuración de los servicios del sistema. En Unix se indican en el archivo /etc/services, en WindowsNT en el archivo services y en otros sistemas puede ser diferente. El puerto habitual de los servicios Web es el 80, así que, si no se indica un puerto específico, la entrada en el servidor de Yahoo se realiza por el puerto 80. Si se teclea la URL siguiente en un navegador: http://www.yahoo.com: 80 también se recibirá la página principal de Yahoo. No hay nada que impida cambiar el puerto en el que residirá el servidor Web; sin embargo, el uso del puerto 80 es casi estándar, porque elimina pulsaciones en el teclado y, además, las direcciones URL son lo suficientemente difíciles de recordar como para añadirle encima el número del puerto. Si se necesita otro protocolo, como: ftp: //ftp.sun .com el puerto a utilizar se derivará de ese protocolo. Así el puerto FTP de Sun es el 21, según su archivo services. La primera parte, antes de los dos puntos, de la URL, indica el protocolo que se quiere utilizar en la conexión con el servidor. El protocolo HTTP (HyperText Transmission Protocol), es el utilizado para manipular documentos Web. Y si no se especifica ningún documento, muchos servidores están configurados para devolver un documento de nombre index.html. - 62 -

UNIVERSIDAD AUTÓNOMA METROPOLITANA IZTAPALAPA AL TIEMPO

CASA ABIERTA

Con todo esto, Java permite los siguientes cuatro constructores para la clase URL: public URL( String spec ) public URL( String protocol,String host,int port,Striflg file ) public URL( string protocol,String host,String file ) throws Mal formedURLException; public URL( URL context,Striflg spec ) así se podrían especificar todos los components de una dirección URL como en: URL( http , www.yahoo.com, 80 index.html ); O dejar que los sistemas utilicen los valores por defecto definidos, como en:

URL( http://www.yahoo.com ); Y en los dos casos se obtendría la visualización de la página principal de Yahoo en el navegador desde el cual se ha invocado.

El siguiente ejemplo muestra cómo se utilizan los cuatro constructores de la clase URL y los métodos que proporciona dicha clase. import java.net.*; class java1702{ public static void main( String[] args ) { java1702 obj = new java1702(); try { System.out.println( "Constructor simple para URL principal" ); obj.display( new URL( "http://usuarios.tripod.es" ) ); System.out.println( "Constructor de cadena para URL + directorio" ); obj.display( new URL( "http://usuarios.tripod.es/froufe" ) ); System.out.println( "Constructor con protocolo, host y directorio" ); obj.display( new URL( "http","usuarios.tripod.es","/froufe" ) ); System.out.println( "Constructor com protocolo, host, puerto y directorio" ); obj.display( new URL( "http","usuarios.tripod.es",80,"/froufe" ) );

- 63 -

UNIVERSIDAD AUTÓNOMA METROPOLITANA IZTAPALAPA AL TIEMPO

CASA ABIERTA

System.out.println( "Construye una direccion absoluta a partir de la \n"+ "direccion del Host y una URL relativa" ); URL baseURL = new URL( "http://usuarios.tripod.es/froufe/index.html"); obj.display( new URL( baseURL, "/froufe/introduccion/indice.html" ) ); System.out.println( "Uso de URLEncoder para crear una cadena x-www-form-url" ); System.out.println( URLEncoder.encode( "http://espacio .tilde~.mas+.com","ISO-8859-1" ) ); } catch( Exception e ) { System.out.println( e ); } } // Método para poder presentar en la pantalla partes de una // dirección URL void display( URL url ) { System.out.print( url.getProtocol()+" " ); System.out.print( url.getHost()+" " ); System.out.print( url.getPort()+" " ); System.out.print( url.getFile()+" " ); System.out.println( url.getRef() ); // Presentamos la dirección completa como una cadena System.out.println( url.toString() ); System.out.println(); } } //------------------------------------------ Final del fichero java1702.java El último método que aparece en código del ejemplo, dispay()>, es aquel que ilustra el uso de algunos de los métodos de la clase URL, y también sirve como forma práctica de ver la información que contiene un objeto URL. Este método recibe un objeto URL como parámetro y presenta todos sus componentes separados por un espacio en blanco; finalmente, utiliza el método sobrescrito toString() de la clase URL para presentar el contenido del objeto URL como un solo objeto String. Como se puede observar, hay un método para obtener cada una de las partes que componen una dirección URL, exceptuando el método getFile() que devuelve el directorio y el nombre del archivo combinados.

- 64 -

UNIVERSIDAD AUTÓNOMA METROPOLITANA IZTAPALAPA AL TIEMPO

CASA ABIERTA

El resto del código de la clase se encuentra en el método main(). El primer fragmento de código interesante es el que ilustra la instanciación de un objeto de tipo URL utilizando la versión del constructor que espera recibir la dirección URL como una cadena. La primera línea de código es la instanciación de un objeto de control de la clase para poder acceder al método display(). Luego se instancia un nuevo objeto URL utilizando la versión que admite como parámetro una cadena, y se le pasa al método display(), que va a presentar todos sus componentes en pantalla. La salida de este fragmento de código en pantalla es la siguiente: http members.es.tripod.de -1 / null http:/members.es.tripod.de/ El -1 que aparece es para indicar que no se ha especificado el puerto, y null es para indicar que no se ha indicado ninguna página o archivo en el constructor del objeto URL. El programa presenta a continuación otras forma de construir el objeto URL, que no tienen mayor trascendencia, excepto uno de ellos, que resulta muy interesante, y es el constructor que necesita dos parámetros: una dirección URL y una cadena y su interés radica en que permite construir tanto una dirección URL absoluta como relativa. Asúmase que el interés se centra en escribir un método para presentar archivos HTML para que sean interpretados pur un navegador, en vez de presentarlos como simples archivos de texto. Es normal que estos archivos contengan enlaces a direcciones URL relativas, es decir, el enlace que se proporciona toma como referencia la dirección URL base en donde reside el archivo HTML. El siguiente fragmento de código utiliza ese constructor para combinar la dirección base URL y la dirección relativa del archivo que se desea para generar una nueva dirección que apunta directamente a ese archivo URL baseURL = new URL( http://members.es.tripod.de/froufe/index.html ); obj.display( new URL( baseURL, /froufe/introduccion/indice.html ) );

La salida que generan esas líneas de código se muestra a continuación, en donde se puede comprobar la construcción de la dirección relativa que se ha indicado para convertirla en la dirección absoluta de la página, es perfecta. http mernbers.es.tripo.de -1 /froufe/introduccion/indice.html null http: //members.es.tripod.de/froufe/introduccion/indice. html Y las últimas líneas de código interesantes son las finales, en que se utiliza la clase URLEncoder. Esta clase intenta ayudar al programador en todos los problemas que causan los espacios, caracteres especiales, caracteres no-alfanuméricos, etc., que utilizan algunos sistemas operativos y que pueden hacer que los nombres de archivos no lleguen a convertirse en una dirección URL válida.

- 65 -

UNIVERSIDAD AUTÓNOMA METROPOLITANA IZTAPALAPA AL TIEMPO

CASA ABIERTA

Si se desea crear un objeto URL utilizando una cadena URL que tiene los problemas que se han indicado, primero es necesario utilizar el método encode() de la clase URLEncoder, para convertirla a una cadena URL ya entendible. Lo que hace este método estático encode() es convertir la cadena que se le pasa a un formato llamado x-www-from-urlencoded y la devuelve como un objeto String. Para realizar la conversión, todos los caracteres se van examinando uno a uno, de izquierda a derecha. Los caracteres ASCII de la ‘a’ a la ‘z’, de la ‘A’ a la ‘Z’ y del ‘0’ al ‘9’, permanecen igual. Los espacios en blanco ‘ ‘se convierten a un signo más, ‘+‘, El resto de los caracteres se convierten a una cadena de 3 caracteres de la forma “%xy”donde xy son dos dígitos en hexadecimal que representan los 8 bits mas significativos del carácter. El siguiente fragmento de código muestra cómo se realiza esta conversión: System.out . println( URLEncoder.encode( “http://espacio .tilde~.mas+.com” ) ); En la pantalla, la cadena en el formato que se ha descrito aparecerá como: http%3ª%F%2Fespacio+.tilde%7E.mas%2B.com Como bien puede observar el lector, los caracteres especiales han sido sustituidos por su valor hexadecimal precedido del signo del tanto por ciento, excepto el espacio que se ha sustituido por el signo más y el carácter más, que se ha sustituido por su valor hexadecimal, %2B. El ejemplo que se presenta a continuación, javal7O3 .java, muestra cómo se utiliza un objeto URL para conectarse a una dirección URL y leer un archivo desde esa dirección a través de un canal de entrada de tipo stream. Tenga en cuenta el lector, que esto es más ilustrativo que otra cosa, porque se puede conseguir lo mismo utilizando sockets, como ya se verá. La salida por pantalla del programa es la presentación del código HTML del archivo prueba0l. html, cuyo contenido se reproduce a continuación: Fichero de pruebas Este es un programa de prueba utilizado, de comunicaciones por red. AgustínFroufe, - 66 -

UNIVERSIDAD AUTÓNOMA METROPOLITANA IZTAPALAPA AL TIEMPO

CASA ABIERTA

[email protected], http: //members.es.tripod.de/froufe/   El listado completo del ejemplo es el que se aparece en las siguientes líneas de código. De nuevo es el método main() el que contiene todo el programa, para evitar que resulte demasiado farragoso el código e ir directamente al manejo de las clases que interesan . import java.net.*; import java.io.*; class java1703 { public static void main( String[] args ) { String cadena; try { // Creamos un objeto de tipo URL URL url = new URL( "http://usuarios.tripod.es/froufe/clases/prueba01.html" ); // Abrimos una conexión hacia esa URL, que devolverá un canal de // entrada por el cual se podrá leer todo lo que llegue BufferedReader paginaHtml = new BufferedReader( new InputStreamReader( url.openStream() ) ); // Leemos y presentamos el contenido del fichero en pantalla // línea a línea while( (cadena = paginaHtml.readLine()) != null ) { System.out.println( cadena ); } } catch( UnknownHostException e ) { e.printStackTrace(); System.out.println( "Debes estar conectado para que esto funcione bien." ); } catch( MalformedURLException e ) { e.printStackTrace(); } catch( IOException e ) { e.printStackTrace(); } } }

- 67 -

UNIVERSIDAD AUTÓNOMA METROPOLITANA IZTAPALAPA AL TIEMPO

CASA ABIERTA

Como ya se ha visto en el ejemplo javal7O2 . java, la clase URL tiene diferentes constructores, que se diferencian en la forma en que se le pasa la dirección URL. La siguiente línea de código muestra cómo se crea el objeto URL en este caso, donde se utiliza la versión del constructor de la clase URL que admite la dirección URL completa como una sola cadena. La dirección que se pasa apunta al archivo prueba 01.html, de la cuenta del autor en el servidor gratuito del proveedor tripod. La cadena que especifica la dirección URL no contiene la indicación del puerto porque se usa el de defecto, el puerto 80, para el protocolo HTTP. Una vez que se dispone del objeto URL. se pueden hacer muchas cosas con él, unas muy interesantes y otras no tanto. Una de las cosas que se puede hacer es abrir streams de entrada y salida que se conectarán con el puerto del servidor que está monitorizando la conexión. Esto no es demasiado interesante, ya que es una duplicidad de la capacidad que proporcionan los sockets, pero es muy ilustrativo de lo que se puede hacer y por eso se muestra. BufferedReader paginaHtml = new BufferedReader( new InputStreamReader( url.openStream() ) ); Esta línea de código abre una conexión con la dirección URL descrita por el objeto URL y devuelve un objeto stream como canal de entrada para la lectura de datos provenientes de esa conexión. Tenga en cuenta que solamente la llamada al método openStrea() es la que hace el procesado de la dirección, el resto de la sentencia es compleja por la utilización de las nuevas clases de lectura y escritura incorporadas al JDK. El código que muestran las líneas siguientes es mucho más simple. La información se lee línea a línea y se va presentando según se lee. El método readLine() devuelve null cuando ya no hay más datos que leer, haciendo que el programa termine. while( (cadena = paginaHtml.readLine()) != null ) { System.out.println( cadena ); } El resto del código del ejemplo consiste en el tratamiento de las excepciones. LA CLASE URLCONNECTION Es una clase abstracta que puede ser extendida, con un constructor protegido que admite un objeto URL como parámetro. Tiene unas ocho variables que contienen información muy útil sobre la conexión que se haya establecido y cerca de cuarenta métodos que se pueden utilizar para examinar y manipular el objeto que se crea con la instanciación de la clase. Si el lector pretende utilizar la clase URL por las capacidades de alto nivel que proporciona, y su intención es escribir manejadores de protocolos o cosas así, es muy probable que tenga que conocer en profundidad esta clase. Pero el objetivo que se pretende aquí no es llegar a ese extremo, sino simplemente que el lector tenga conocimiento de la existencia de esta clase y de alguno de sus métodos.

- 68 -

UNIVERSIDAD AUTÓNOMA METROPOLITANA IZTAPALAPA AL TIEMPO

CASA ABIERTA

El ejemplo java17O4. java muestra cómo se realiza la conexión a una dirección URL y se crea un objeto de tipo URLConnection. El programa usa entonces ese objeto para obtener y presentar en pantalla algunos de los aspectos de alto nivel de la dirección URL, como son el tipo de contenido, la fecha de la última modificación o la propia dirección URL. Por ejemplo, la última ejecución del programa realizada por el autor generó la siguiente salida por pantalla: % java javal7O4 http //members.es.tripod.de/froufe/clases/prueba0l.html Sun Jul 23 10:22:10 GMT+1:OO 1999 text/html

El código completo del ejemplo es el que se reproduce a continuación. import java.net.*; import java.io.*; import java.util.*; class java1704 { public static void main( String[] args ) { String cadena; try { // Creamos un objeto de tipo URL URL url = new URL( "http://usuarios.tripod.es/froufe/clases/prueba01.html" ); // Se abre una conexión hacia la dirección recogiendola en un // objeto de tipo URLConnection URLConnection conexion = url.openConnection(); // Se utiliza la conexión para leer y presentar la dirección System.out.println( conexion.getURL() ); // Se utiliza la conexión para leer y presentar la fecha de // última modificación Date fecha = new Date( conexion.getLastModified() ); System.out.println( fecha ); // Se utiliza la conexión para leer y presentar el tipo de // contenido System.out.println( conexion.getContentType() ); // Se utiliza la conexión para conesguir un objeto de tipo // InputStream, que luego se emplea para instanciar un // objeto DataInputStream BufferedReader paginaHtml = new BufferedReader( new InputStreamReader(url.openStream()) ); - 69 -

UNIVERSIDAD AUTÓNOMA METROPOLITANA IZTAPALAPA AL TIEMPO

CASA ABIERTA

// Se utiliza el objeto DataInputStream para leer y // presentar el fichero línea a línea while( (cadena = paginaHtml.readLine()) != null ) { System.out.println( cadena ); } } catch( UnknownHostException e ) { e.printStackTrace(); System.out.println( "Debes estar conectado para que esto funcione bien." ); } catch( MalformedURLException e ) { e.printStackTrace(); } catch( IOException e ) { e.printStackTrace(); } } } la clase URLConnection, como se ha indicadom es abstracta, por lo que no se puede instanciar directamente, pero sí se uede extender. Una forma habitual de conseguir un objeto de tipo URLConnection es invocar un método sobre un objeto URL que devuelva un objeto de una subclase de la clase URLConnection. URL url = new URL ( http://members.es.tripod.de/froufe7clases7prueba01.html ); El primer trozo de código que merece la pena, representado en la línea anterior, es la instanciación de un objeto URL. Lalínea de código que se muestra ahora crea un objeto de tipo URLConnection invocando al método openConection() del objeto que se ha instanciado antes. URLConnection conexión = url.openConnection(); Solamente queda como interesante el fragmento de código que se usa para invocar a tres de los métodos de la clase URLConnection. Sobre el objeto instanciado, para obtener la infomación de alto nivel que muestra la dirección URL, la última fecha en que se ha modificado y el tipo de archivo del contenido al que apunta esa dirección URL. System.out.println( conexión.getURL() ) ; Date fecha = new Date( conexión.getLasModified() ) , System.out. println( fecha ) ; System.out. println( conexion.getContentType() ); La verdad es que la información puede no ser demasiado fiable a veces, porque la impresión que da es que, por ejemplo, el tipo de contenido está basado en la extensión del archivo, y la fecha de última modificación corresponde a la capacidad que pueda tener el sistema operativo para mostrar la fecha a la hora de requerir el contenido de un directorio. No obstante, este tipo de - 70 -

UNIVERSIDAD AUTÓNOMA METROPOLITANA IZTAPALAPA AL TIEMPO

CASA ABIERTA

información puede ser útil para mantener una utilización correcta del archivo apuntado por la dirección URL. De nuevo, se recuerda al lector que la clase URLConnection dispone de más métodos para proporcionar información relevante a la conexión que se haya establecido. LA CLASE SOCKET La programación de sockets proporciona un mecanismo de muy bajo nivel para la comunicación e intercambio de datos entre dos computadores, uno considerado como cliente, que es el que inicia la conexión con el otro, servidor, que está a la espera de conexiones de clientes. El protocolo de comunicación entre ambos, determinará lo que suceda tras el establecimiento de la conexión. Para que las dos máquinas puedan entenderse, ambas deben implementar un protocolo conocido por las dos. En la programación de sockets, la comunicación es full-duplex, en ambos sentidos a la vez, entre cliente y servidor; siendo responsabilidad del sistema el llevar los datos de una máquina a otra, dejando al programador el proporcionar significado a esos datos. Parte de la información que fluye entre las dos máquinas es, pues, para implementar el protocolo, y el resto son los propios datos que se quieren transferir. Es muy sencilla la utilización de sockets para establecer la comunicación entre cliente y servidor; en realidad, no es más complicada que lo que pueda serlo el escribir datos en un archivo. Enviar y recoger los datos que se intercambian es la parte fácil del asunto; porque más allá de esto ya se encuentra el protocolo de comunicación que debe ser entendido por cliente y servidor, en caso de ser necesario implementarlo, se convierte en algo verdaderamente complicado.

Cliente Eco El primer ejemplo de programación de sockets que presentamos java1705.java, implementa un cliente que realiza una prueba de eco con un servidor, enviándole una línea de texto por el puerto 7 del servidor. Destinado a estos menesteres. Para poder ejecutar este programa es necesario estar conectado en una red, o a interner, aunque es posible que tambien pueda hacerse sobre el mismo computador si el lector usa un sistema operativo que lo soporte, Linux por ejemplo; en cualquier otro caso, el resultado de la ejecución del ejemplo será una triste UnknownHostException. El programa comienza instanciando un objeto de tipo String que contiene el nombre del servidor que se va a utilizar, seguido por la declaración e inicialización de la variable que guarda el número del puerto, que en el caso del servidor estándar de echo es el 7. A continuación se abren los canales de entrada y salida desde el socket que se mapean en las clases Reader y Writer. Una vez que se ha establecido la conexión y los canales de entrada y salida están listos para ser usados, el programa envía una línea de texto al puerto de eco del servidor. Esto hace que el servidor reenvíe esa misma línea de vuelta al cliente, y el programa la leerá y presentará en pantalla. El código completo del ejemplo es el que se muestra a continuacion.

- 71 -

UNIVERSIDAD AUTÓNOMA METROPOLITANA IZTAPALAPA AL TIEMPO

CASA ABIERTA

import java.net.*; import java.io.*; import java.util.*; class java1705 { public static void main( String[] args ) { String servidor = "www.fie.us.es"; int puerto = 7; // puerto echo try { // Abrimos un socket conectado al servidor y al // puerto estándar de echo Socket socket = new Socket( servidor,puerto ); // Conseguimos el canal de entrada BufferedReader entrada = new BufferedReader( new InputStreamReader( socket.getInputStream() ) ); // Conseguimos el canal de salida PrintWriter salida = new PrintWriter( new OutputStreamWriter( socket.getOutputStream() ),true ); // Enviamos una línea de texto al servidor salida.println( "Prueba de Eco" ); // Recogemos la línea devuelta por el servidor y la // presentamos en pantalla System.out.println( entrada.readLine() ); // Cerramos el socket socket.close(); } catch( UnknownHostException e ) { e.printStackTrace(); System.out.println( "Debes estar conectado para que esto funcione bien." ); } catch( IOException e ) { e.printStackTrace(); } } } En caso de que se seleccione un servidor que no tenga soporte para el protocolo echo en el puerto 7, la salida que generará el programa será diferente de la que cabría esperar. Por ejemplo, si el lector intenta ejecutar este programa contra el servidor ‘www.whitehouse.net”, la respuesta que obtendrá será un rechazo de la conexión, algo así como ‘ connection refused.”.

- 72 -

UNIVERSIDAD AUTÓNOMA METROPOLITANA IZTAPALAPA AL TIEMPO

CASA ABIERTA

Como se puede ver en el listado, el método main() es el que contiene todo el código de la clase, porque es muy sencillo y no merece la pena desglosarlo. Las dos primeras líneas que hay que ver del ejemplo son las que contienen las variables a las que se asigna el nombre del servidor al que se va a conectar y el número del puerto de ese servidor que se va a atacar. String servidor = www.fie.us.es ; int puerto = 7; 7/ puerto echo El resto del programa se encuentra englobado en un bloque try-catch para tratar las excepciones. La línea siguiente es la clave del programa, en ella se establece una conexión con el puerto indicado del servidor que se ha designado para instanciar un nuevo objeto de tipo Socket Socket socket = new Socket( server,port ); Una vez que este objeto existe, es posible realizar la comunicación con el servidor utilizando el protocolo que tenga predefinido para el servicio que proporciona a través de ese puerto. Lo siguiente a destacar en el código del ejemplo es el uso de las clases Reader y Writer. // conseguimos el canal de entrada BufferedReader entrada = new BufferedReader( new InputStreamReader( socket.getlnputStream() ) ); //Conseguimos el canal de salida Printwriter salida = new Printwriter( new OutputStreamwriter( socket.getoutputStream() ) ,true ); El parámetro true de la última línea hace que el canal de salida realice una descarga o limpieza de su contenido automáticamente. La realización de esta descarga o vaciado automático de los canales, es algo de vital importancia a la hora de la programación con sockets. Lo que resta del código es solamente el uso del canal de salida para enviar la línea de texto al servidor de eco, la recogida de su respuesta a través del canal de entrada y el cierre del socket; una vez presentada la cadena devuelta por el servidor. Salida.println( Prueba de Eco ; System.out.println( entrada.readLine( ) ), Socket.close( ); Y esto es todo lo que hay sobre sockets desde el punto de vista de la parte cliente, no piense que la complejidad es mucho mayor, se haga lo que se haga, tal como podrá comprobar en lo ejemplos siguientes. Ahora bien, sí que la programación de sockets se puede complicar, pero todos los problemas van a venir asociados a la necesidad de implementar un protocolo a nivel de aplicación para comunicarse correctamente con el servidor, no del uso en sí de los sockets.

- 73 -

UNIVERSIDAD AUTÓNOMA METROPOLITANA IZTAPALAPA AL TIEMPO

CASA ABIERTA

Cliente Fecha El ejemplo javal7O6. java también implementa un cliente, pero en este caso para atacar el puerto que proporciona el día y la hora sobre un servidor que dé soporte a este protocolo. El ejemplo es incluso más sencillo que el ejemplo javal7O5 .java, ya que no es necesario enviar nada al servidor para obtener respuesta, todo lo que hay que hacer es establecer la conexión. El código por tanto es bien simple, tal como se muestra en el listado completo del programa. import java.net.*; import java.io.*; import java.util.*; class java1706 { public static void main( String[] args ) { String servidor = "www.whitehouse.net"; int puerto = 13; // puerto de daytime try { // Abrimos un socket conectado al servidor y al // puerto estándar de echo Socket socket = new Socket( servidor,puerto ); System.out.println( "Socket Abierto." ); // Conseguimos el canal de entrada BufferedReader entrada = new BufferedReader( new InputStreamReader( socket.getInputStream() ) ); System.out.println( "Hora actual en www.whitehouse.net:" ); System.out.println( "\t"+entrada.readLine() ); System.out.println( "Hora Actual en Sevilla, Spain:" ); System.out.println( "\t"+new Date() ); // Cerramos el socket socket.close(); } catch( UnknownHostException e ) { System.out.println( e ); System.out.println( "Debes estar conectado para que esto funcione bien." ); } catch( IOException e ) { System.out.println( e ); } } } El programa recoge y presenta la hora del servidor “www.whitehouse.net” , y luego presenta la fecha y hoara donde reside el autor ( o donde el lector esté ejecutando el programa), por motivos de comparación solamente. La salida que se obtiene, por ejemplo puede ser: - 74 -

UNIVERSIDAD AUTÓNOMA METROPOLITANA IZTAPALAPA AL TIEMPO

CASA ABIERTA

% java java1706 socket abierto. Hora actual en www.whitehouse.net: Sun Jan 19 23:10:22 1999 Hora actual en sevilla, Spain: Sun Jan 20 05:10:23 GMT+01:00 1999 Como se puede comprobar, hay diferencia en el día debido a las zonas en que se está obteniendo la fecha. También se puede ver que hay una diferencia de un segundo entre una hora y otra, el autor sincronizó el reloj de su ordenador con el servidor de tiempo de bernina.ethz.ch, así que esa diferencia solamente puede ser atribuible a la duración de la transmisión entre el servidor y el cliente. La filosofía del programa es muy similar a la del ejemplo del cliente de eco. Se comienza con la instanciación de un objeto String para contener el nombre del servidor que va a servir la fecha, y la declaración de inicialización de la variable que va a contener el número del puerto de ese servidor que se va a atacar, en este caso el puerto 13, que es el puerto estándar para el servicio daytime. Luego el programa se agencia un canal de entrada a través de la conexión socket que ha establecido previamente y ya está listo, porque en este caso, la sola conexión es suficiente para que el servidor envíe la fecha y la hora. Así que todo lo que hay que hacer es leer la línea de entrada que contiene esa fecha y hora enviada por el servidor. El programa la presenta y también presenta la fecha y hora actuales de la máquina en que se está ejecutando el programa, para establecer una comparación con la que se ha llegado a través del socket. Como no hay diferencias significativas con el ejemplo javal7OS .java que vayan más allá del uso del método System.out.printlnO), no merece la pena detenerse a contar nada del programa, es suficiente con que el lector lea los comentarios del código. Cliente HTTP El ejemplo que se va a ver ahora, javal7O7 . java es un navegador extremadamente simple; o más correctamente, el programa es un cliente HTTP muy simple, implementado mediante sockets. El programa implementa el suficiente protocolo HTTP para poder recoger un fichero desde cualquier servidor Web. Si el lector desea realizar un navegador mas util, tambien tendra que invertir mucho mas tiempo, pero para ver el funcionamiento, es suficiente con lo que se muestra este ejemplo, cuyo codigo fuente se muestra a continuación. import java.net.*; import java.io.*; class java1707 { public static void main( String[] args ) { String servidor = "usuarios.tripod.es"; // servidor int puerto = 80; // puerto - 75 -

UNIVERSIDAD AUTÓNOMA METROPOLITANA IZTAPALAPA AL TIEMPO

CASA ABIERTA

try { // Crea un socket, conectado al servidor y al puerto // que se indica Socket socket = new Socket( servidor,puerto ); // Crea el canal de entrada desde el socket BufferedReader entrada = new BufferedReader( new InputStreamReader( socket.getInputStream() ) ); // Crea el canal de salida PrintWriter salida = new PrintWriter( new OutputStreamWriter( socket.getOutputStream() ),true ); // Envía un comando GET al servidor salida.println( "GET /froufe/clases/prueba01.html" ); // En esta cadena se va a ir leyendo el fichero String linea = null; // Bucle para leer y presentar la líneas hasta que se // reciba un null while( (linea = entrada.readLine()) != null ) System.out.println( linea ); // Se cierra el socket socket.close(); } catch( UnknownHostException e ) { e.printStackTrace(); System.out.println( "Debes estar conectado para que esto funcione bien." ); } catch( IOException e ) { e.printStackTrace(); } } } El programa se inicia definiendo el servidor y el puerto con el que se va a establecer la comunicación, abriendo a continuación un socket sobre ese puerto. Al igual que en los ejemplos anteriores, el programa crea canales de entrada y salida para la transferencia de información entre la parte del cliente y la parte del servidor. El programa envía un comando GET, actuando como cliente, al servidor indicándole el archivo que desea recibir. Este comando forma parte del protocolo HTTP, que provoca la búsqueda en el servidor del archivo indicado y su envió al cliente. Si el servidor esta montado sobre el puerto con el que se ha establecido la conexión siempre enviara algo, si encuentra el archivo enviara su contenido, y si no lo encuentra o hay algún otro problema siempre envía un mensaje de error indicando la circunstancia por la cual el archivo no ha podido ser transferido al cliente.

- 76 -

UNIVERSIDAD AUTÓNOMA METROPOLITANA IZTAPALAPA AL TIEMPO

CASA ABIERTA

El programa lee el texto que recibe por el canal de entrada y lo presenta en el dispositivo estándar de salida, por lo que se vera el código HTML que forma la pagina, es decir se vera como texto normal. Si el lector revisa el código del programa Java, recordará de ejemplos anteriores toda la parte inicial de declaración de variables para determinar el servidor y puerto al que conectarse y la apertura de sockets para los canales de entrada y salida. Así que la primera parte de código que realmente merece la pena revisar es el envío de comandos HTTP al servidor. salida. println ( “GET/froufe/clases/prueba0l. html” ); Observe el lector que el árbol de directorios que se indica al servidor para que consiga localizar el archivo es relativo a su pseudoraíz, es decir, relativo al nivel inicial en que se encuentran los documentos HTML. La petición se realiza simplemente escribiendo el comando en el canal de salida. Si el comando GET ha sido enviado correctamente, hay que esperar siempre una respuesta del servidor, aunque se trate de un mensaje de error. El siguiente fragmento de código va leyendo texto del canal de entrada, presentándolo a continuación en la pantalla, cerrando el socket cuando ya no hay más datos que leer. String linea = null; while( (linea = entrada.readLine()) 1= null ) System.out.println( linea ); socket.close(); El resto del código es ya la parte conocida de control de las excepciones. Si el lector desea incorporar más características a este ejemplo, a fin de convertirlo en un navegador útil; se le sugiere que revise las especificaciones HTML y escriba un método de presentación al estilo que normalmente se asocia a las página web. E incluso puede englobar todo el ejemplo en AWT, proporcionando una interfaz gráfica mediante un área de texto, TextArea, para la parte de presentación de la página y un campo de texto TextFill, para especificar la dirección del archivo del servidor que se desea visualizar.

La Clase de ServerSocket La clase de ServerSocket es la que se utiliza a la hora de crear servidores, al igual que, como ya se ha visto, la clase socket se utilizaba para crear clientes. El ejemplo java1709.java utiliza sockets para implementar dos sockets diferentes sobre una red IP. Desde luego el programa es solamente ilustrativo, por lo que si el lector decide utilizarlo en alguno de sus propósitos el autor declina toda reponsabilidad, siendo el lector el que asúme el riesgo de su uso. El porqué de esta advertencia reside en el uso del agente de seguridad que se implementa en éste, y algunos más, de los ejemplos que se van a presentar, que no es nada restrictivo, y puede permitir el acceso no deseado a la máquina en que se ejecuten estos programas de ejemplo. El control realizado se limita a no permitir la navegación por encima del directorio actual; pero esto es más como ejemplo para que el lector pueda probar el envío de mensaje de error al cliente, que un verdadero método de seguridad.

- 77 -

UNIVERSIDAD AUTÓNOMA METROPOLITANA IZTAPALAPA AL TIEMPO

CASA ABIERTA

Este programa implementa dos servidores operando sobre dos puertos diferentes del computador que está ejecutando el programa. Uno de los servidores es un servidor “echo” que está implementado a través de una tarea que monitoriza el puerto 7. Este servidor devuelve la cadena que reciba por el puerto 7 al mismo cliente que la ha enviado. El otro servidor es un servidor HTTP abreviado, que se ha implementado a través de una tarea que monitoriza el puerto 80, puerto estándar para el protocolo HTTP. En este servidor se ha implementado la respuesta al comando GET, que devuelve al cliente el contenido del archivo que haya solicitado, o un mensaje de error en caso de no encontrarlo. Si el lector ejecuta los ejemplos en entorno Windows, es suficiente con que arranque los ejemplos como cualquier otro programa Java, antes de lanzar la parte cliente, o al menos antes de que la parte cliente solicite algo al servidor. Sin embargo, si la ejecución se realiza en entornos Solaris, Linux, etc. probablemente se encuentre con que no hay permiso para utilizar los puertos que se asignan en los ejemplos. Esto es debido a que los puertos por debajo del 1024 están reservados para uso del sistema; no obstante, el lector puede cambiar el número de puerto ~ianto en el servidor como en el cliente y ejecutarlos. Los ejemplos javal7l0.java y java17ll.java son clientes implementados para probar los dos servidores que se crean en este ejemplo. La parte del servidor de Eco se puede probar utilizando el primero de los ejemplos, javal7l0.java, y el otro ejemplo permite probar la parte HTTP. También puede el lector comprobar el funcionamiento del agente de seguridad, solicitando archivos no válidos al servidor. El código completo del programa es el que se muestra, que luego se revisará detalladamente. import java.net.*; import java.io.*; import java.util.*; public class java1709 { public static void main( String[] args ) { // Se instancia el controlador de seguridad propio nuestro System.setSecurityManager( new MiSecurityManager() ); // Se instancia un objeto servidor para escuchar el puerto 80 ServidorHttp servidorHttp = new ServidorHttp(); // Se instancia un objeto servidor para escuchar el puerto 7 ServidorEco servidorEcho = new ServidorEco(); } }

// Esta clase se utiliza para instanciar un controlador de seguridad que // solamente permita descargar los ficheros que se encuentren en el // directorio actual o en cualquier subdirectorio de ese directorio. // Evidentemente, no instalar este servidor en una red, sin antes haber // hecho cambios para que el control sea más exhaustivo, porque aquí // se ha dejado muy abierto, ya que solamente se pretende utilizar para - 78 -

UNIVERSIDAD AUTÓNOMA METROPOLITANA IZTAPALAPA AL TIEMPO

CASA ABIERTA

// pruebas class MiSecurityManager extends SecurityManager { // Este método sobreescrito es el que controla que el servidor solamente // permita descargar los ficheros del directorio actual y sus ramas, // es decir, que no permite navegar hacia arriba en el árbol, ni // permite la descarga de una dirección absoluta, pero tiene // muchos agujeros de seguridad, así que no debe utilizarse a no ser // que se complete adecuadamente, y además se implemente la respuesta // que se va a dar en los demás métodos del controlador de Seguridad public void checkRead( String str ) { if( new File(str).isAbsolute() || (str.indexOf("..") != -1 ) ) throw new SecurityException( "\n"+str+": Acceso denegado."); } // Ahora se sobreescriben los demás métodos, que es lo que hace que el // tema de seguridad quede totalmente abierto. public void checkAccept( String s,int i ){} public void checkAccess( Thread t ){} public void checkAccess( ThreadGroup g ){} public void checkAwtEventQueueAccess(){} public void checkConnect( String s,int i ){} public void checkConnect( String s,int i,Object o ){} public void checkCreateClassLoader(){} public void checkDelete( String s ){} public void checkExec( String s ){} public void checkExit( int i ){} public void checkLink( String s ){} public void checkListen( int i ){} public void checkMemberAccess( Class c,int i ){} public void checkMulticast( InetAddress a ){} public void checkPackageAccess( String s ){} public void checkPackageDefinition( String s ){} public void checkPrintJobAccess(){} public void checkPropertiesAccess(){} public void checkPropertyAccess( String s ){} public void checkRead( FileDescriptor f ){} // public void checkRead( String s ){} // Este es el único sobreescrito public void checkRead( String s,Object o ){} public void checkSecurityAccess( String s ){} public void checkSetFactory(){} public void checkSystemClipboardAccess(){} public boolean checkTopLevelWindow( Object o ) { return true; } public void checkWrite( FileDescriptor f ){} public void checkWrite( String s ){} } - 79 -

UNIVERSIDAD AUTÓNOMA METROPOLITANA IZTAPALAPA AL TIEMPO

CASA ABIERTA

// Esta es la clase que se utiliza para instanciar un hilo de ejecución // para el servidor que se encarga de escuchar el puerto 7, que es el // definido como estándar para el protocolo de "echo" class ServidorEco extends Thread { // Constructor ServidorEco() { // Arrancamos el hilo e invocamos al método run() para que empiece // a correr start(); } public void run() { try { // Se instancia un objeto sobre el puerto 7 ServerSocket socket = new ServerSocket( 7 ); System.out.println( "Servidor escuchando el 7" ); // Entramos en bucle infinito ecuchando el puerto. Cuando se // produce una llamada, se lanza un hilo de ejecución de // ConexionEcho para atenderla while( true ) // Nos quedamos parados en el accept(), y devolvemos un socket // cuando se recibe la llamada. Este socket es el que se pasa // como parámetro al nuevo hilo de ejecución que se crea new ConexionEcho( socket.accept() ); } catch( IOException e ) { e.printStackTrace(); } } } // Esta clase se utiliza la lanzar un hilo de ejecución que atienda // la llamada recibida a través del puerto 7, el puerto de eco class ConexionEcho extends Thread { Socket socket; // Constructor ConexionEcho( Socket socket ) { System.out.println( "Recibida una llamada en el puerto 7" ); this.socket = socket; // Trabajamos por debajo de la prioridad de los otros puertos setPriority( NORM_PRIORITY-1 ); // Se arranca el hilo y se pone a correr start(); } public void run() { - 80 -

UNIVERSIDAD AUTÓNOMA METROPOLITANA IZTAPALAPA AL TIEMPO

CASA ABIERTA

System.out.println( "Lanzado el hilo de atencion del puerto 7" ); BufferedReader entrada = null; PrintWriter salida = null; try { // Conseguimos un canal de entrada desde el socket entrada = new BufferedReader( new InputStreamReader( socket.getInputStream() ) ); // Conseguimos un canal de salida hacia el socket. El canal es // con liberación automática (autoflush) salida = new PrintWriter( new OutputStreamWriter( socket.getOutputStream()),true ); // Recogemos la cadena que llegue al puerto y la devolvemos en // el mismo instante String cadena = entrada.readLine(); salida.println( cadena ); System.out.println( "Recibido: "+cadena ); socket.close(); System.out.println( "Socket cerrado" ); } catch( IOException e ) { e.printStackTrace(); } } }

// Esta es la clase que se utiliza para instanciar un hilo de ejecución // para el servidor que se encarga de escuchar el puerto 80, que es el // definido como estándar para el protocolo de "http" class ServidorHttp extends Thread { // Constructor ServidorHttp() { // Arrancamos el hilo e invocamos al método run() para que empiece // a correr start(); } public void run(){ try { // Se instancia un objeto sobre el puerto 80 ServerSocket socket = new ServerSocket( 80 ); System.out.println( "Servidor escuchando el puerto 80" ); // Entramos en bucle infinito ecuchando el puerto. Cuando se // produce una llamada, se lanza un hilo de ejecución de // ConexionHttp para atenderla while( true ) - 81 -

UNIVERSIDAD AUTÓNOMA METROPOLITANA IZTAPALAPA AL TIEMPO

CASA ABIERTA

// Nos quedamos parados en el accept(), y devolvemos un socket // cuando se recibe la llamada. Este socket es el que se pasa // como parámetro al nuevo hilo de ejecución que se crea new ConexionHttp( socket.accept() ); } catch( IOException e ) { e.printStackTrace(); } } } // Esta clase se utiliza la lanzar un hilo de ejecución que atienda // la llamada recibida a través del puerto 80, el puerto de http class ConexionHttp extends Thread { Socket socket; BufferedReader entrada = null; PrintWriter salida = null; DataOutputStream pagina = null; // Constructor ConexionHttp( Socket socket ) { System.out.println( "Recibida una llamada en el puerto 80" ); this.socket = socket; // Trabajamos por debajo de la prioridad de los otros puertos setPriority( NORM_PRIORITY-1 ); // Se arranca el hilo y se pone a correr start(); } public void run() { System.out.println( "Lanzado el hilo de atencion al puerto 80" ); try{ // Conseguimos un canal de entrada desde el socket entrada = new BufferedReader( new InputStreamReader( socket.getInputStream() ) ); // Conseguimos un canal de salida hacia el socket. El canal es // con liberación automática (autoflush) salida = new PrintWriter( new OutputStreamWriter( socket.getOutputStream() ),true ); // Ahora conseguimos un canal de salida para enviar el contenido // del ficero que haya solicitado el usuario pagina = new DataOutputStream( socket.getOutputStream() ); // Recogemos la cadena que llegue al puerto String peticion = entrada.readLine(); System.out.println( "Recibida peticion: "+peticion ); - 82 -

UNIVERSIDAD AUTÓNOMA METROPOLITANA IZTAPALAPA AL TIEMPO

CASA ABIERTA

// Analizamos esa petición e intentamos responder. Solamente se // contesta a las peticiones GET, cualquier otra no tiene // respuesta alguna StringTokenizer st = new StringTokenizer( peticion ); if( ( st.countTokens() >= 2 ) && st.nextToken().equals("GET") ) { System.out.println( "Primer tag GET, correcto" ); if( (peticion = st.nextToken()).startsWith("/") ) { System.out.println( "Siguiente toque que empieza por /, se le quita" ); peticion = peticion.substring( 1 ); } if( peticion.endsWith("/") || peticion.equals("") ) { System.out.println( "Peticion terminada en / o blanco, "+ "se le incorpora: index.html" ); peticion = peticion + "index.html"; System.out.println( "Peticion modificada: "+peticion ); } System.out.println( "Intento de desacarga de: "+peticion ); FileInputStream fichero = new FileInputStream( peticion ); // Se instancia un array de bytes igual al número de bytes que // se pueden leer del canal de entrada sin bloquearlo. // Y luego se rellena con los datos byte[] datos = new byte[fichero.available()]; fichero.read( datos ); // Se envía el array de datos al cliente pagina.write( datos ); pagina.flush(); } else // La petición que se ha hecho no es un GET salida.println( "400 Petici&oactute;n "+ "Errónea" ); socket.close(); System.out.println( "Socket cerrado" ); }catch( FileNotFoundException e ) { salida.println( "404 No Encontrado" ); try { socket.close(); System.out.println( "Socket cerrado" ); }catch( IOException evt ) { evt.printStackTrace(); } }catch( SecurityException e ){ e.printStackTrace(); - 83 -

UNIVERSIDAD AUTÓNOMA METROPOLITANA IZTAPALAPA AL TIEMPO

CASA ABIERTA

salida.println( "403 Acceso denegado" ); salida.println( e ); try { socket.close(); System.out.println( "Socket cerrado" ); }catch( IOException evt ) { evt.printStackTrace(); } }catch( IOException e ) { e.printStackTrace(); try { socket.close(); System.out.println( "Socket cerrado" ); }catch( IOException evt ) { System.out.println(evt); } } } } Como el lector puede comprobar, el programa dispone de un agente de seguridad, sin embargo, se trata de una aplicación java, no de un applet. La parte principal de la aplicación se muestra en el método main(), conteniendo las tres sentencias que permiten el uso de las clases que implementan el objetivo concreto de la creación de los servidores y del agente de seguridad public static void main( String [ ] args){ System.setSecurityManager (new MiSecurityManager() ); ServidorHttp servidorHttp = new ServidorHttp(); ServidorEco servidorEcho = new ServidorEco(); } La primera sentencia establece el agente de seguridad que va a utilizar la aplicación. La segunda sentencia instancia un objeto servidor que monitoriza el puerto 80; este servidor implemeflta parte del protocolo HTTP y permite la descarga de archivos a navegadores. Y, la tercera sentencia instancia un objeto servidor que monitoriza el puerto 7; este servidor devuelve al cliente TCP/IP la misma cadena que haya enviado al servidor. Estos dos servidores se implementafl en tareas diferentes para que puedan funcionar en paralelo de forma asíncrona. Agente de seguridad Normalmente cuando se ejecuta una aplicación Java, no hay ningún agente de seguridad, o en caso de que lo haya, no funciona de la forma restrictiva en que se hace en el ejemplo. Sin embargo, cuando se establece un agente de seguridad para una aplicación, mediante la llamada al método setSecurityManagerO, lo contrario es cierto. Es decir, cuando se coloca un agente de seguridad en una aplicación, a ese código se le prohíbe hacer cualquier cosa; la aplicación deja

- 84 -

UNIVERSIDAD AUTÓNOMA METROPOLITANA IZTAPALAPA AL TIEMPO

CASA ABIERTA

de tener privilegios, y es el programador, como diseñador del sistema de seguridad, el que decide qué privilegios va a tener disponibles y qué cosas van a estar prohibidas a esa aplicación. Esto es un proceso tedioso y complicado. Aunque no es difícil hacer que un privilegio esté disponible una vez que se haya decidido que va a estarlo, el llegar a esa conclusión requiere un gran conocimiento de cómo se relacionan los privilegios con las operaciones que se pueden realizar; por que hay ocaciones en que esta relación entre privilegio y operación no es tan obvia a simple vista. Para crear un agente de seguridad propio, hay que extender la clase SecurityManager, que contiene alrededor de una treintena de métodos que comienzan con la palabra check, como checkAccept(). Esos métodos son los que controlan el acceso a los priviliegios; cada uno de los métodos controla el acceso a un privilegio que generalmente coincide con lo que indica la segunda parte del nombre. La versión que se instaura por defecto de cada uno de estos métodos es la deshabilitación del privilegio que controla. Para poner disponible ese privilegio, hay que sobreescribir el método, bien con un método vacío, o con un método que implemente una versión propia de ese privilegio. En este ejemplo, se han sobrescrito todos los métodos, excepto uno, con un método vacío, haciendo que los privilegios asociados a esos métodos estén disponibles, sin restricción alguna. El método checkReadO) está sobrescrito para modificar el privilegio asociado, de forma que el servidor HTTP solamente pueda enviar archivos situados en su directorio pseudo-raíz y en cualquiera de los subdirectorios a partir de él. El código que se proporciona para esta versión del método se muestra en las siguientes líneas. public void checkRead( String str ) { if( new File(str).isAbsolute() I I (str.indexof( .. ) 1= -1) ) throw new SecurityException( \n +str+ : Acceso denegado. ); } Como se puede comprobar, se controla que no se pueda invocar un archivo directamente indicando el camino absoluto en donde se encuentra en el disco, ni que se pueda realizar la llamada a un archivo del directorio padre del actual, que se indica con dos puntos (. .); en caso de que se intente cualquiera de los dos métodos de acceso, se lanzará la excepción de seguridad. Tenga en cuenta el lector que esto es solamente para probar y que no debe utilizarlo seriamente ni en algo en que tenga interés, porque hay muchas circunstancias que no se controlan. El resto de los métodos se sobrescriben con un método vacío, deshabilitando las operaciones que se realizan por defecto y, dejando totalmente disponibles los privilegios que comprueban cada uno de esos métodos. Servidor Eco La siguiente parte interesante del código del programa es la clase que implementa el servidor del protocolo Echo, que es el más simple de los dos que se muestran en el ejemplo. En general, los dos servidores funcionan del mismo modo, corriendo cada uno de ellos en su propia tarea, y escuchando el puerto que corresponde a su protocolo, en espera de conexiones de clientes.

- 85 -

UNIVERSIDAD AUTÓNOMA METROPOLITANA IZTAPALAPA AL TIEMPO

CASA ABIERTA

Mientras un servidor se encuentra en este estado de espera de conexiones, estará bloqueado para consumir el mínimo de recursos del sistema. Cuando un cliente requiere una conexión en cualquiera de los dos puertos, el servidor lanza otra tarea para manejar las necesidades del cliente y luego vuelve para seguir escuchando el puerto en su propia tarea. Las tareas para dar soporte a los clientes se lanzan con prioridad mínima, de forma que estas tareas no interfieran la capacidad asignada a los servidores de reconocer y responder a cualquier otro cliente que esté solicitando una conexión. Si hay muchos clientes solicitando conexiones, el servidor que atiende al puerto lanzara una tarea por cada una de esas conexiones (dentro de las capacidades del sistema); de forma que puede haber muchas tareas ejecutándose simultáneamente, atendiendo cada una de ellas a las necesidades de un cliente específico. La primera sentencia interesante del servidor de eco es el constructor, que simplemente invoca a su propio método start() que iniciará el arranque de su propia tarea. Start(); La clave del funcionamiento de un servidor en Java se encuentra en el método accept() de la clase ServerSocket. Antes, hay que construir un objeto de esa clase y, como se puede ver en el siguiente trozo de código, el constructor de ServerSocket tiene solamente un parámetro: el número del puerto que va a ser monitorizado por el servidor que construye. ServerSocket socket = new serverSocket( 7 ); system.out.println( Servidor escuchando el 7 ); Y acto seguido se puede realizar la llamada al método accept() para recoger las conexiones que establezcan los clientes con el servidor a través del puerto que está monitorizando ese servidor. Las líneas siguientes muestran cómo se hace esto en el ejemplo. while( true ) new ConexionEcho( socket.acceptO ); Cuando se instancia un objeto de tipo ServerSocket y se invoca el método accept() sobre ese objeto, este método bloquea el servidor y se queda a la espera hasta que se produce una conexión de un cliente en el puerto que controla. Cuando esto sucede, se instancia automáticamente un objeto Socket que es devuelto por el método accept(). Observamos que hay muchos conceptos que se están repitiendo, y que si va leyendo con detenimiento, se vuelve sobre ellos una y otra vez. Esto está hecho deliberadamente, ya que hay una serie de conceptos y métodos muy simples en los que se basa todo el funcionamiento de las comunicaciones en red con Java, y que son los que verdaderamente tienen que quedar arraigados en la mente. Por ello aun a pesar de resultar un poco ladrillo, es preferible recaer una vez sobre otra en los conceptos que el autor considera fundamentales, para que una vez concluida la lectura del capítulo, al menos esos conceptos hayan quedado lo suficientemente claros y grabados en el conocimiento del lector.

- 86 -

UNIVERSIDAD AUTÓNOMA METROPOLITANA IZTAPALAPA AL TIEMPO

CASA ABIERTA

El objeto Socket devuelto por el método accept() se conecta automáticamente con el objeto Socket utilizado por el cliente que ha establecido la conexión, y se puede utilizar para establecer la comunicación con el cliente. Esto es muy importante, así que hay que detenerse un poco en ello. No hay ninguna transferencia de datos por el hecho de disponer de un objeto de tipo ServerSocket. Es en el momento en que el cliente solicita una conexión cuando se instancia automáticamente un objeto Socket que se conecta con el Socket del cliente que ha pedido la conexión. Este nuevo socket es el devuelto por el método accept() para poder establecer la comunicación con el socket del cliente. En el trozo de código anterior, se observa que cuando se recibe una conexión, se lanza una tarea del tipo ConexionEcho que atiende al cliente que ha solicitado esa conexión. Al constructor de la clase que maneja este nueva tarea, se le pasa como parámetro el objeto Socket que el método acceptO) devuelve, que ya está conectado con el cliente, y a través del cual el protocolo de comunicaciones para el servicio, echo en este caso, puede comunicarse con el cliente. Observe el lector que la tarea en la cual está corriendo el objeto ServidorEco se encuentra en medio de un bucle infinito. Luego una vez que la tarea ConexionEcho haya sido lanzada con éxito, el objeto ServidorEco volverá a su función inicial de seguir esperando a la conexión de otro cliente a través del puerto de eco. El siguiente trozo de código en que merece la pena detenerse es el constructor de la clase ConexionEcho. Los objetos de esta clase son creados a raíz de la petición de conexión de algún cliente al servidor. No obstante, estos objetos no saben nada de clientes y servidores, lo único que conocen es que hay un socket TCP/IP conectado con otro socket en otra máquina, o con otro proceso de la misma máquina. ConexionEcho( Socket socket ) { system.out.println( Recibida una llamada en el puerto 7 ); this.socket = socket; setpriority( NORM..PRI0RITY-l ); startQ; } Como puede observar el lector, tras guardar el parámetro en una variable de instancia, el constructor fija la prioridad de la tarea. Esto es para que las tareas que están monitorizando los puertos no se vean interrumpidas por las tareas que están atendiendo a las conexiones ya establecidas. Una vez ajustada la prioridad, solamente resta invocar al método start() para que la tarea se levante y empiece a correr. Como es habitual, el método start() invocará al método run() de esta tarea. El método run() de esta tarea es similar al código que ya se ha presentado en ejemplos anteriores al programar la parte cliente. Por referenciar algo, se reproducen las líneas de código en donde se lee la cadena recibida del cliente y se devuelve a ese cliente, exactamente igual que se ha recibido. Una vez hecho esto, se cierra el socket de conexión con el cliente y la tarea se muere. string cadena = entrada.readLine(); - 87 -

UNIVERSIDAD AUTÓNOMA METROPOLITANA IZTAPALAPA AL TIEMPO

CASA ABIERTA

salida.println( cadena ); System.out.println( Recibido: +cadena ); socket.close(); System.out.println( Socket cerrado ); Con esto se concluye el repaso al código que implementa el servidor Eco en el programa y se pasa a discutir la clase que atiende a las conexiones con el puerto estándar del protocolo HTTP. Servidor HTTP Este servidor HTTP es realmente simple, y solamente responde al comando GET del protocolo HTTP, cualquier otro comando será ignorado y el cliente obtendrá como respuesta un mensaje de error. En el método main() se instancia un objeto de la clase ServidorHttp, que monta una tarea que instancia un objeto de tipo ServerSocket para atender al puerto estándar del protocolo HTTP, el puerto 80. ServerSocket socket = new ServerSocket( 80 ); System.out.prifltln( Servidor escuchando el puerto 80 ); El método accept(), tal como muestran las líneas de código siguientes, se invoca sobre este objeto dentro de un bucle infinito para bloquear y monitorizar el puerto 80, esperando conexiones de clientes. Cuando esto ocurre, el método accept() instancia y devuelve un objeto Socket, que estará conectado con la máquina cliente. while( true ) new ConexionHttp( socket.acceptQ ); Este objeto Socket, se pasa como parámetro al constructor de un nuevo objeto de tipo ConexionHttp, que lanzará una tarea específica para atender a la conexión que se ha establecido con el cliente, a través de la versión abreviada del protocolo HTTP que se ha implementado en esta clase. El constructor es similar al visto para el servidor de Eco; así que, en aras de la brevedad no se incluye aquí. Si el lector sigue el código del ejemplo, no encontrará nada nuevo hasta llegar a la transmisión de información al cliente, que en este caso se hace enviando un array de bytes. El siguiente fragmento de código muestra la creación del canal de salida que va a permitir este tipo de comunicación. pagina = new DataOutputStreafll( socket.getOutputStream () ); Esto se ecncuentra dentro del método run(), corazón de la tarea que esta atendiendo a la conexión. A continuación, lo que se espera es la conexión del cliente, para lo que se utiliza el método readLine() sobre el canal de entrada que lee las peticiones y alma-cena esa petición en un objeto String.

- 88 -

UNIVERSIDAD AUTÓNOMA METROPOLITANA IZTAPALAPA AL TIEMPO

CASA ABIERTA

String peticion = entrada.readLineQ; Lo siguiente que hay que hacer es el análisis de la petición que ha realizado el cliente para comprobar si es posible atenderla o no, teniendo en cuenta que solamente se responde al comando GET. Para realizar este análisis se utiliza un objeto StringTokenizer, que indicará si hay o no un comando GET en la petición realizada por el cliente. Stringiokenizer st = new Stringiokenizer( peticion ); if( ( st.countiokens() >= 2 ) && st.nextloken().equals( GET ) ) {

En caso de que no sea una petición GET, se creará una página dentro del código para devolver el mensaje de error al cliente. Si la petición sí corresponde a un comando GET, lo que se intenta es comprobar el nombre del archivo que ha solicitado el cliente y enviárselo; y en caso de que no solicite ninguno, completar el camino que haya indicado con el archivo index.html, que es el archivo estándar que utilizan casi todos los navegadores como archivo de defecto en caso de no especificar una página determinada en el acceso a un sitio Internet. Las siguiente línea de código elimina la posibilidad de que la petición contenga barras “/” extra. if( (peticion = st.nextiokenQ).startswith( ) ) { Si la petición termina con una barra “/” o es una cadena vacía, se supone que el cliente quiere descargar el archivo index.html; así que, en este caso, se añade este nombre de archivo a la cadena que contiene la petición realizada por el cliente. if( peticion.endswith( ) II peticion.equals( ) ) { System.out.println( Peticion terminada en / o blanco, + se le incorpora: index.html ); peticion = peticion + index.html ; System.out.println( Peticion modificada: +peticion ); } Llegados a este punto, ya se sabe cuál es el archivo que hay que enviarle al cliente como respuesta a su petición, así que se intenta abrir un objeto de tipo FilelnputStream con ese nombre de archivo y en el camino que se indique. Si no se puede conseguir, se lanzará una excepción que será procesada en el bloque try-catch del final del programa. Si el archivo sí se puede leer, se creará un array de bytes igual al contenido del archivo y se leerá ahí ese contenido. FilelnputStream fichero = new FílelnputStream( petícion ); // Se instancia un array de bytes igual al número de bytes que // se pueden leer del canal de entrada sin bloquearlo. byte[] datos = new byte[tichero.ava1 fabIeUj; fichero.read( datos );

- 89 -

UNIVERSIDAD AUTÓNOMA METROPOLITANA IZTAPALAPA AL TIEMPO

CASA ABIERTA

Una vez que se ha identificado, localizado y leído el archivo en un array en memoria, el siguiente paso consiste en el uso del canal de salida que se había creado anteriormente para transmitir el contenido del array al cliente; concluyendo con la liberación forzada de todo el contenido del array, tal como se muestra en las siguientes líneas de código. pagina.write( datos ); pagina.flush(); El siguiente fragmento de código se ejecuta en el caso de que el cliente no envíe una petición GET, en cuyo caso se crea un documento sobre la marcha conteniendo el mensaje de error que indica tal circunstancia y se le envía como respuesta al cliente. } else salida.println( 400 Petici&oactute;n + Erró ; nea ); socket.closeü; Esta última sentencia cierra el socket y permite que la tarea termine normalmente, siempre asumiendo que no se ha lanzado ninguna excepción mientras; porque en el caso de que se hubiese generado alguna excepción, hay varios controladores para recoger estas excepciones, e incluso algunos, como el lector podrá observar en el código del ejemplo, generan páginas sobre la marcha que se envían al cliente, indicando la circunstancia que ha provocado el lanzamiento de la excepción que controlan. Quizá merezca la pena detenerse en el controlador de la última excepción, la de tipo IOExcepction, que se muestra en el siguiente trozo de código. }catch( IOException e ) { e.printStacklrace(); try { socket.cl ose (); System.out.println( Socket cerrado ); }catch( IoException evt ) { System.out.println(evt); } Es especial porque es necesario cerrar el socket dentro del controlador de la excepción. La verdad es que el autor no sabe a ciencia cierta cómo se puede forzar una excepción de tipo IOException, dentro del manejador de una excepción de tipo IOException lanzada anteriormente, pero vamos; es de suponer que funciona. LA CLASE DATAGRAMPACKET La clase DatagramPacket, junto con la clase DatagramSocket, es la que se utiliza para la implementación del protocolo UDP (User Datagram Protocol). En este protocolo, a diferencia de lo que ocurría en el protocolo TCP, en el cual si un paquete se dañaba durante la transmisión se reenviaba ese paquete, para asegurar una comunicación segura - 90 -

UNIVERSIDAD AUTÓNOMA METROPOLITANA IZTAPALAPA AL TIEMPO

CASA ABIERTA

entre cliente y servidor; con UDP no hay garantía alguna de que los paquetes lleguen en el orden correcto a su destino y, ni tan siquiera hay seguridad de que lleguen todos los paquetes que se hayan enviado. Sin embargo, los paquetes que consiguen llegar, lo hacen mucho más rápidamente que con TCP y, en algunos casos, la velocidad de transmisión es mucho más importante que el que lleguen todos los paquetes; por ejemplo, si lo que se están transmitiendo son señales de sensores en tiempo real para la presentación en pantalla, la velocidad es más importante que la integridad, porque si un paquete no llega o no puede recomponerse, en el instante siguiente llegará otro. La programación para uso del protocolo UDP se diferencia de la programación del protocolo TCP en que no existe el concepto de ServerSocket para los datagramas y que es el programador el que debe construirse los paquetes a enviar por UDP. Para enviar datos a través de UDP, hay que construir un objeto de tipo DatagramPacket y enviarlo a través de un objeto DatagramSocket, y al revés para recibirlos, es decir, a través de un objeto DatagramSocket se recoge el objeto DatagramPacket. Toda la información respecto a dirección, puerto y datos está contenida en el paquete. Para enviar un paquete, primero se construye ese paquete con la información que se desea transmitir, luego se almacena en un objeto DatagramSocket y, finalmente se invoca el método send() sobre ese objeto. Para recibir un paquete, primero se construye un paquete vacío y luego se le presenta a un objeto DatagramSocket para que almacene allí el resultado de la ejecución del método receiveO) sobre ese objeto. Hay que tener en cuenta que la tarea encargada de todo esto estará bloqueada en el método receive() hasta que un paquete físico de datos se reciba a través de la red; este paquete físico será el que se utilice para rellenar el paquete vacío que se había creado. También hay que tener cuidado cuando se pone a escuchar a un objeto DatagramSocket en un puerto determinado, porque va a recibir los datagramas enviados por cualquier cliente. Es decir, que si los mensajes enviados por los clientes están formados por múltiples paquetes; en la recepción pueden llegar paquetes entremezclados de varios clientes y es responsabilidad de la aplicación el ordenarlos. Para la clase DatagramPacket se dispone de dos constructores. uno utilizado cuando se quieren enviar paquetes y el otro se usa cuando se quieren recibir paquetes. Ambos requieren que se les proporcione un array de bytes y la longitud que tiene. En el caso de la recepción de datos, no es necesario nada más, los datos que se reciban se depositarán en el array; aunque en el caso de que se reciban más datos físicos de los el exceso de informaci´n se perderá y se lanzará una excpciónde tipo IllegalArgumentExceptio, que a pesar de que no sea necesaria su captura, siempre es bueno recogerla. Cuando se construye el paquete a enviar, es necesario colocar los datos en el array antes de llamar al método send(); además de eso, hay que incluir la longitud de ese array, y también se debe proporcionar un objeto de tipo InetAddress indicando la dirección de destino del paquete y el número del puerto de ese destino en el cual estará escuchando el receptor del mensaje. Es

- 91 -

UNIVERSIDAD AUTÓNOMA METROPOLITANA IZTAPALAPA AL TIEMPO

CASA ABIERTA

decir, que la dirección de destino y el puerto de escucha deben ir en el paquete, al contrario de lo que pasaba en el caso de TCP que se indicaba en el momento de construir el objeto Socket. El tamaño ftsico máximo de un datagrama es 65535 bytes, y teniendo en cuenta que hay que incluir datos de cabecera, esa longitud nunca está disponible para datos de usuario, sino que siempre es algo menor. La clase DatagramPacket proporciona varios métodos para poder extraer los datos que llegan en el paquete recibido. La información que se obtiene con cada método coincide con el propio nombre del método, aunque hay algunos casos en que es necesario saber interpretar la información que proporciona ese mismo método. El método getAddress() devuelve un objeto de tipo InetAddress que contiene la dirección del host remoto. El saber cuál es el computador de origen del envío depende de la forma en que se haya obtenido el datagrama. Si ese datagrama ha sido recibido a través de Internet, la dirección representará al computador que ha enviado el datagrama (el origen del datagrama); pero si el datagrama se ha construido localmente, la dirección representará al computador al cual se intenta enviar el datagrama (el destino del datagrama). De igual modo, el método getPort() devuelve el puerto desde el cual ha sido enviado el datagrama, o el puerto a través del cual se enviará, dependiendo de la forma en que se haya obtenido el datagrama. El método getData() devuelve un array de bytes que contiene la parte de datos del datagrama, ya eliminada la cabecera con la información de encaminamiento de ese datagrama. La forma de interpretar ese array depende del tipo de datos que contenga. Los ejemplos que se ven en este Tutorial utilizan exclusivamente datos de tipo String, pero esto no es un requerimiento, y se pueden utilizar datagramas para intercambiar cualquier tipo de datos, siempre que se puedan colocar en un array de bytes en un computador y extraerlos de ese array en la parte contraria. Es decir, que la responsabilidad del sistema se limita al desplazamiento del array de bytes de un computador a otro. y es responsabilidad del programador el asignar significado a esos bytes. El método getLength() devuelve el número de bytes que contiene la parte de datos del datagrama, y el método getOffset() devuelve la posición en la cual empieza el array de bytes dentro del datagrama completo.

LA CLASE DATAGRAMSOCKET Un objeto de la clase DatagramSocket puede utilizarse tanto para enviar como para recibir un datagrama. La clase tiene tres constructores. Uno de ellos se conecta al primer puerto libre de la máquina local; el otro permite especificar el puerto a través del cual operará el socket; y el tercero permite especificar un puerto y una dirección para identificar a una máquina concreta. Independientemente del constructor que se utilice, el puerto desde el cual se envía el datagrama siempre se incluirá en la cabecera del paquete. Normalmente, la parte del servidor utilizará el - 92 -

UNIVERSIDAD AUTÓNOMA METROPOLITANA IZTAPALAPA AL TIEMPO

CASA ABIERTA

constructor que permite indicar el puerto concreto a usar, ya que si no, la parte cliente no tendría forma de conocer el puerto por el cual le van a llegar los datagramas. La parte cliente puede utilizar cualquier constructor, pero por flexibilidad, lo mejor es utilizar el constructor que deja que el sistema seleccione uno de los puertos disponibles. El servidor debería entonces comprobar cuál es el puerto que se está utilizando para el envío de datagramas y enviar la respuesta por ese puerto. A diferencia de esta posibilidad de especificar el puerto, o no hacerlo, no hay ninguna otra distinción entre los sockets datagrama utilizados por cliente y servidor. Si el lector se encuentra un poco perdido, no desespere, porque en los ejemplos todo esto que es muy difícil explicar con palabras, se ve mucho más claramente al intuir el funcionamiento físico de la comunicación entre cliente y servidor. Para enviar un datagrama hay que invocar al método send() sobre un socket datagrama existente, pasándole el objeto paquete como parámetro. Cuando el paquete es enviado, la dirección y número de puerto del computador origen se coloca automáticamente en la porción de cabecera del paquete, de forma que esa información pueda ser recuperada en el computador destino del paquete. Para recibir datagramas, hay que instanciar un objeto de tipo DatagramSocket, conectarse a un puerto determinado e invocar al método receive() sobre ese socket. Este método bloquea la tarea hasta que se recibe un datagrama, por lo que si es necesario hacer alguna cosa durante la espera, hay que invocar al método receive() en su propia tarea. Si se trata de un servidor, hay que conectarse con un puerto específico. Si se trata de un cliente que está esperando respuestas de un servidor, hay que escuchar en el mismo puerto que fue utilizado para enviar el datagrama inicial. Si se envía un datagrama a un puerto anónimo, se puede mantener el socket abierto que fue utilizado en el envío del primer datagrama y utilizar ese mismo socket para esperar la respuesta. Tamabien se puede invocar al método getLocalPort()sobre el socket antes de cerrarlo de forma que se pueda saber y guardar el número del puerto que se ha empleado; de este modo se puede cerrar el socket original y abrir otro en el mismo puerto en el momento en que se necesite. Para responder a un datagrama, hay que obtener la dirección del origen y el número de puerto a través del cual fue enviado el datagrama, de la cabecera del paquete y luego, colocar esta información en el nuevo paquete que se construya con la información a enviar como respuesta. Una vez pasada esta información a la parte de datos del paquete, se invoca al método send() sobre el objeto DatagramSocket existente, pasándole el objeto paquete como parámetro. Es importante tener en en cuenta que los números de puerto TCP y UDP no están relacionados. Se puede utilizar el mismo número de puerto en dos procesos si uno se comunica a través de protocolo TCP y el otro lo hace a través de protocolo UDP. Es muy común que los servidores utilicen el mismo puerto para proporcionar servicios similares a través de los dos protocolos en algunos servicios estándar, como puede ser el eco.

- 93 -

UNIVERSIDAD AUTÓNOMA METROPOLITANA IZTAPALAPA AL TIEMPO

CASA ABIERTA

Cliente Eco Esta aplicación no es más que una actualización del ejemplo javal7OS . java, al que se le incluye además el protocolo UDP. El ejemplo javalll2 . java realiza dos pruebas del servicio Echo contra el mismo servidor, enviando una línea de texto al puerto estándar de este servicio, el puerto 7. En la primera prueba utiliza el protocolo TCP/IP y en la segunda emplea un datagrama UDP. import java.net.*; import java.io.*; import java.util.*; class java1712 { public static void main( String[] args ) { String servidor = "www.fie.us.es"; // servidor int puerto = 7; // puerto eco String cadTcp = "Prueba de Eco TCP"; String cadUdp = "Prueba de Eco UDP"; // Primero realizamos el test de Eco con TCP/IP try { // Abrimos un socket conectado al servidor y al // puerto estándar de echo Socket socket = new Socket( servidor,puerto ); // Conseguimos el canal de entrada BufferedReader entrada = new BufferedReader( new InputStreamReader( socket.getInputStream() ) ); // Conseguimos el canal de salida, con liberación automática PrintWriter salida = new PrintWriter( new OutputStreamWriter( socket.getOutputStream() ),true ); // Envía la línea de texto del mensaje al servidor salida.println( cadTcp ); // Y recoge la respuesta del servidor, presentándola en pantalla System.out.println( entrada.readLine() ); // se cierra el socket TCP socket.close();

// Ahora se realiza la prueba con datagramas sobre el mimso // puerto del mismo servidor // Convertimos el mensaje en un array de bytes - 94 -

UNIVERSIDAD AUTÓNOMA METROPOLITANA IZTAPALAPA AL TIEMPO

CASA ABIERTA

byte[] mensajeUdp = cadUdp.getBytes(); // Obtenemos la dirección IP del servdor InetAddress dirIp = InetAddress.getByName( servidor ); // Creamos el paquete que se va a enviar al pueto DatagramPacket paquete = new DatagramPacket( mensajeUdp,mensajeUdp.length,dirIp,puerto ); // Abrimos un socket datagrama para enviarle el mensaje DatagramSocket socketDgrama = new DatagramSocket(); // Y lo enviamos socketDgrama.send( paquete ); // Sobreescrimos el mensaje en el paquete para confirmar que el // eco es realmente lo que llega byte[] arrayDatos = paquete.getData(); for( int cnt=0; cnt < paquete.getLength(); cnt++ ) arrayDatos[cnt] = (byte)'x'; // Escribimos esta versión del mensaje System.out.println( new String( paquete.getData() ) ); // Ahora recibimos el eco en ese mismo paquete, de forma que // sobreescriba las "x" que se habían colocado socketDgrama.receive( paquete ); // Presentamos en pantalla el eco System.out.println( new String( paquete.getData() ) ); // Se cierra el socket socketDgrama.close(); } catch( UnknownHostException e ) { e.printStackTrace(); System.out.println( "Debes estar conectado para que esto funcione bien." ); } catch( SocketException e ) { e.printStackTrace(); } catch( IOException e ) { e.printStackTrace(); } } } En el programa se instancian dos objetos de tipo String, para utilizr uno deferente en dada protocolo. Luego está la parte correspondiete a la prueba de eco a través de TCP, sobre la que no se insiste más. Una vez cerrado el socket TCP, comienza la prueba de eco a través de UDP. En primer lugar se convierte el mensaje que se ha de enviar por UDP a un array de bytes. Hay que instanciar un objeto de tipo InetAddress que contenga la dirección del servidor con el que se va a realizar la conexión y el envío del datagrama con el array de bytes recién creado.

- 95 -

UNIVERSIDAD AUTÓNOMA METROPOLITANA IZTAPALAPA AL TIEMPO

CASA ABIERTA

Se crea un objeto de tipo DatagramPacket que contenga el array de bytes de la cadena a enviar, junto con la dirección del servidor y el puerto al que hay que conectarse. Se crea ahora un objeto de tipo DatagramSocket que será utilizado para enviar el paquete al servidor. Sin embargo, hay que recordar que el protocolo UDP no garantiza que el paquete llegue íntegro al servidor, o que tan siquiera llegue. Solamente resta invocar al método send() sobre el objeto DatagramSocket, pasánsole el objeto DatagramPacket como parámetro. Esto hace que la dirección local y el número de puerto se incorporen en el paquete y éste sea enviado a la dirección y número de puerto que se ha encapsulado en el objeto DatagramPacket en el momento de su creación. Los mismos objetos DatagramSocket y DatagramPacket serán los utilizados para recibir el paquete de respuesta del servidor (siempre que la suerte acomrañe). Se usa un bucle para sobreescribir los datos en el paquete con una letra para poder comprobar que una vez recibido el paquete de respuesta del servidor de eco, los datos son nuevos y no simplemente el residuo del mensaje que se había colocado originalmente en el paquete. Luego se invoca el método receive() sobre el objeto DatagramSocket, pasánsole el objeto DatagramPacket como parámetro. Esto hace que la tarea se bloquee en el mismo puerto por el que se ha enviado el paquete, hasta que llegue una respuesta. En el momento en que un paquete físico llegue desde el servidor, se extraen los datos y se colocan en el objeto DatagramPacket que se le ha proporcionado como parámetro. Una vez hecho esto, la tarea se desbloquea y el flujo de control de la aplicación sigue por la sentencia que muestra el contenido del paquete. Finalmente, se cierra el socket y el programa termina. Como el ejemplo es una simple actualización de otro de los ejemplos del Tutorial, gran parte del código ya está más que visto; y es exactamente igual al del ejemplo javal7OS . java. Así que solamente se van a repasar a continuación algunas de las líneas de código más interesantes de la parte que se aporta nueva en este ejemplo, que corresponderá, evidentemente, a la implementación del intercambio de mensajes con el servidor a través del puerto del servicio Eco, pero con protocolo UDP. El primer fragmento en que hay que detenerse es justo en la declaración del método mamo, en donde se declaran e inicializan algunas variables importantes. public static void main( string[] args ) { string servidor www.fie.us.es ; // servidor int puerto = 7; // puerto eco string cadlcp = prueba de Eco TCP ; string cadudp = prueba de Eco UDP ;

Luego esta todo el código referente al protocolo TCP, que en este caso no resulta interesante, y ya se alcanza el punto del programa en el que se convierte el mensaje en un array de bytes, y se instancia un objeto que identifique al servidor. Se utiliza el método getBytes() de la clase String para convertir el mensaje a un array de bytes. - 96 -

UNIVERSIDAD AUTÓNOMA METROPOLITANA IZTAPALAPA AL TIEMPO

CASA ABIERTA

byte[] mensajeUdp = cadudp.getBytes(); // Obtenemos la dirección IP del servidor InetAddress dirIp = InetAddress.getByName ( servidor ); La siguiente línea de código importante es la que instancia un objeto de tipo DatagramPacket para llevar toda la información del mensaje y del servidor, como es el array de bytes creado antes, su longitud, y la dirección y puerto del servidor. DatagramPacket paquete = new oatagramPacket( mensajeudp,mensajeudp.length,dirlp,puerto ); A continuación, se instancia un objeto DatagramSocket anónimo. Esto de anónimo puede resultar confuso al lector. El objeto es anónimo porque el número de puerto no está especificado. En algún sitio anteriormente se ha indicado que los objetos anónimos son aquellos que no tienen una variable de referencia asignada; que no es el caso que se produce aquí. Este objeto se usa para enviar el datagrama al servidor invocando al método send() sobre el objeto DatagramSocket. Datagramsocket socketDgrama = new DatagramSocket(); socketDgrama.send( paquete );

Las siguientes líneas de código son las que sobreescriben los datos del mensaje con una letra, para el propósito que se ha indicado antes de comprobar que el mensaje que se recibe no es en realidad el resto del que se ha mandado. Este fragmento de código también muestra al lector cómo acceder a la parte de datos de un objeto DatagramPacket en caso de que lo necesite para cualquier otro propósito. byte[] arrayDatos = paquete.getoata() for( int cnt=O; cnt < paquete.getLength(); cnt++ ) arrayDatos[cnt] = (byte) ; A partir de este punto, se asume que habrá una respuesta del servidor, así que se invoca el método receive() sobre el mismo objeto DatagramSocket que se ha utilizado para enviar el mensaje original al servidor. Esto bloquea la tarea, quedando a la espera de respuesta. Aunque no se ha indicado en ningún sitio, es posible utilizar el método setTimeout() para indicar la cantidad de tiempo que el método receiveO estará a la espera y bloqueando, lo cual permite colocar una protección al programa para que no se quede colgado esperando una respuesta que no llegue jamás. socketDgrama.receive( paquete ); Y el resto del código del ejemplo es la presentación en pantalla del mensaje recibido del servidor, que debe coincidir con el enviado, y el código de manejo de excepciones. Servidor UDP - 97 -

UNIVERSIDAD AUTÓNOMA METROPOLITANA IZTAPALAPA AL TIEMPO

CASA ABIERTA

En el ejemplo javal7l3.java se utiliza como base el ejemplo javal7O9.java, para actualizarlo y hacer que soporte el protocolo UDP. Además, esta aplicación se completa con el ejemplo javal7l4. java, que es un programa que permite comprobar su funcionamiento, constituyendo la parte cliente del servidor que se implementa. Como el programa es una actualización del ejemplo j aval7O9 . java, el lector deberá tener en cuenta las mismas advertencias que se realizaban al respecto de ese ejemplo, en lo que se refiere al agente de seguridad, en caso de que decida utilizar el ejemplo para sus propios propósitos. Aquí solamente se muestra como ilustración de lo que se puede hacer, no para que se haga uso de él para cualquier otro menester. En el ejemplo se implementan tres servidores. Uno de ellos es un servidor Eco UDP implementado a través de una tarea que monitoriza un DatagramSocket sobre el puerto 7. Este servidor devuelve el array de bytes que llegue en cada datagrama que reciba, enviando esos datos de regreso al cliente que haya originado el mensaje. El segundo servidor es un servidor Eco TCP implementado a través de una tarea que monitoriza un ServerSocket sobre el puerto 7. Este servidor también devuelve los datos que recibe al cliente que haya realizado la conexión. El tercer servidor es un servidor HTTP muy simple implementado a través de una tarea TCP que monitoriza el puerto 80. Este servidor solamente responde al comando GET que se envíe desde un navegador, devolviendo un archivo como un stream de bytes. La inclusión de estos tres tipos de servidores es para mostrar al lector la forma en que se pueden utilizar las tareas para dar servicio a múltiples puertos, haciendo además una mezcla de protocolos TCP y UDP. La parte del servidor HTTP se puede comprobar a través de un navegador indicando local host como nombre del servidor, y los otros servidores se pueden chequear mediante el ejemplo javal7l4 . java, pensado específicamente para probar los servidores Eco instalados en la propia máquina en que se ejecuten cliente y servidor. El código completo del ejemplo es el que se muestra a continuación. import java.net.*; import java.io.*; import java.util.*; public class java1713 { public static void main( String[] args ) { // Se instancia el controlador de seguridad propio nuestro System.setSecurityManager( new MiSecurityManager() ); // Se instancia un objeto servidor para escuchar el puerto 80 ServidorHttp servidorHttp = new ServidorHttp(); // Se instancia un objeto servidor para escuchar el puerto 7 ServidorEco servidorEcho = new ServidorEco(); // Se instancia un objeto servidor UDP para escuchar el puerto 7 - 98 -

UNIVERSIDAD AUTÓNOMA METROPOLITANA IZTAPALAPA AL TIEMPO

CASA ABIERTA

ServidorEcoUdp servidorEchoUdp = new ServidorEcoUdp(); } }

// Esta clase se utiliza para instanciar un controlador de seguridad que // solamente permita descargar los ficheros que se encuentren en el // directorio actual o en cualquier subdirectorio de ese directorio. // Evidentemente, no instalar este servidor en una red, sin antes haber // hecho cambios para que el control sea más exhaustivo, porque aquí // se ha dejado muy abierto, ya que solamente se pretende utilizar para // pruebas class MiSecurityManager extends SecurityManager { // Este método sobreescrito es el que controla que el servidor solamente // permita descargar los ficheros del directorio actual y sus ramas, // es decir, que no permite navegar hacia arriba en el árbol, ni // permite la descarga de una dirección absoluta, pero tiene // muchos agujeros de seguridad, así que no debe utilizarse a no ser // que se complete adecuadamente, y además se implemente la respuesta // que se va a dar en los demás métodos del controlador de Seguridad public void checkRead( String str ) { if( new File(str).isAbsolute() || (str.indexOf("..") != -1 ) ) throw new SecurityException( "\n"+str+": Acceso denegado."); } // Ahora se sobreescriben los demás métodos, que es lo que hace que el // tema de seguridad quede totalmente abierto. public void checkAccept( String s,int i ){} public void checkAccess( Thread t ){} public void checkAccess( ThreadGroup g ){} public void checkAwtEventQueueAccess(){} public void checkConnect( String s,int i ){} public void checkConnect( String s,int i,Object o ){} public void checkCreateClassLoader(){} public void checkDelete( String s ){} public void checkExec( String s ){} public void checkExit( int i ){} public void checkLink( String s ){} public void checkListen( int i ){} public void checkMemberAccess( Class c,int i ){} public void checkMulticast( InetAddress a ){} public void checkPackageAccess( String s ){} public void checkPackageDefinition( String s ){} public void checkPrintJobAccess(){} public void checkPropertiesAccess(){} public void checkPropertyAccess( String s ){} public void checkRead( FileDescriptor f ){} - 99 -

UNIVERSIDAD AUTÓNOMA METROPOLITANA IZTAPALAPA AL TIEMPO

CASA ABIERTA

// public void checkRead( String s ){} // Este es el único sobreescrito public void checkRead( String s,Object o ){} public void checkSecurityAccess( String s ){} public void checkSetFactory(){} public void checkSystemClipboardAccess(){} public boolean checkTopLevelWindow( Object o ) { return true; } public void checkWrite( FileDescriptor f ){} public void checkWrite( String s ){} } // Esta es la clase que se utiliza para instanciar un hilo de ejecución // para el servidor Udp que se encarga de escuchar el puerto 7, que es // el definido como estándar para el protocolo de "echo" class ServidorEcoUdp extends Thread { // Constructor ServidorEcoUdp() { // Arrancamos el hilo e invocamos al método run() para que empiece // a correr start(); } public void run() { try { // Se instancia un objeto sobre el puerto 7 DatagramSocket socketDgrama = new DatagramSocket( 7 ); System.out.println( "Servidor Udp escuchando el 7" ); // Entramos en bucle infinito ecuchando el puerto. Cuando se // produce una llamada, se lanza un hilo de ejecución de // ConexionEchoUdp para atenderla while( true ) { // Limitamos la cadena de eco a 1024 bytes DatagramPacket paquete = new DatagramPacket( new byte[1024],1024 ); // Nos quedamos parados en el receive(), y devolvemos un socket // cuando se recibe la llamada. Este socket es el que se pasa // como parámetro al nuevo hilo de ejecución que se crrea socketDgrama.receive( paquete ); new ConexionEcoUdp( paquete ); } } catch( IOException e ) { e.printStackTrace(); } } } // Esta clase se utiliza la lanzar un hilo de ejecución que atienda - 100 -

UNIVERSIDAD AUTÓNOMA METROPOLITANA IZTAPALAPA AL TIEMPO

CASA ABIERTA

// la llamada recibida a través del puerto 7, el puerto de eco class ConexionEcoUdp extends Thread { DatagramPacket paquete; // Constructor ConexionEcoUdp( DatagramPacket paquete ) { System.out.println( "Recibida una llamada en el puerto 7" ); this.paquete = paquete; // Trabajamos por debajo de la prioridad de los otros puertos setPriority( NORM_PRIORITY-1 ); // Se arranca el hilo y se pone a correr start(); } public void run() { System.out.println( "Lanzado el hilo UDP de atencion del puerto 7" ); // Se crea el paquete de eco basándonos en los datos del paquete // que se ha recibido como parámetro DatagramPacket paqueteEnvio = new DatagramPacket( paquete.getData(),paquete.getLength(), paquete.getAddress(),paquete.getPort() ); // Declaramos el socket datagrama DatagramSocket socketDgrama = null; try { // Abrimos un socket datagrama socketDgrama = new DatagramSocket(); // Se utiliza el nuevo socket datagrama para enviar el mensaje // y cerrar el socket socketDgrama.send( paqueteEnvio ); socketDgrama.close(); System.out.println("Socket UDP cerrado." ); }catch( UnknownHostException e ) { socketDgrama.close(); System.out.println("Socket UDP cerrado." ); e.printStackTrace(); }catch( SocketException e ) { socketDgrama.close(); System.out.println("Socket UDP cerrado." ); e.printStackTrace(); }catch( IOException e ) { socketDgrama.close(); System.out.println("Socket UDP cerrado." ); e.printStackTrace(); } } - 101 -

UNIVERSIDAD AUTÓNOMA METROPOLITANA IZTAPALAPA AL TIEMPO

CASA ABIERTA

}

// Esta es la clase que se utiliza para instanciar un hilo de ejecución // para el servidor que se encarga de escuchar el puerto 7, que es el // definido como estándar para el protocolo de "echo" class ServidorEco extends Thread { // Constructor ServidorEco() { // Arrancamos el hilo e invocamos al método run() para que empiece // a correr start(); } public void run() { try { // Se instancia un objeto sobre el puerto 7 ServerSocket socket = new ServerSocket( 7 ); System.out.println( "Servidor escuchando el 7" ); // Entramos en bucle infinito ecuchando el puerto. Cuando se // produce una llamada, se lanza un hilo de ejecución de // ConexionEcho para atenderla while( true ) // Nos quedamos parados en el accept(), y devolvemos un socket // cuando se recibe la llamada. Este socket es el que se pasa // como parámetro al nuevo hilo de ejecución que se crea new ConexionEcho( socket.accept() ); } catch( IOException e ) { e.printStackTrace(); } } } // Esta clase se utiliza la lanzar un hilo de ejecución que atienda // la llamada recibida a través del puerto 7, el puerto de eco class ConexionEcho extends Thread { Socket socket; // Constructor ConexionEcho( Socket socket ) { System.out.println( "Recibida una llamada en el puerto 7" ); this.socket = socket; // Trabajamos por debajo de la prioridad de los otros puertos setPriority( NORM_PRIORITY-1 ); // Se arranca el hilo y se pone a correr start(); } - 102 -

UNIVERSIDAD AUTÓNOMA METROPOLITANA IZTAPALAPA AL TIEMPO

CASA ABIERTA

public void run() { System.out.println( "Lanzado el hilo de atencion del puerto 7" ); BufferedReader entrada = null; PrintWriter salida = null; try { // Conseguimos un canal de entrada desde el socket entrada = new BufferedReader( new InputStreamReader( socket.getInputStream() ) ); // Conseguimos un canal de salida hacia el socket. El canal es // con liberación automática (autoflush) salida = new PrintWriter( new OutputStreamWriter( socket.getOutputStream()),true ); // Recogemos la cadena que llegue al puerto y la devolvemos en // el mismo instante String cadena = entrada.readLine(); salida.println( cadena ); System.out.println( "Recibido: "+cadena ); socket.close(); System.out.println( "Socket cerrado" ); } catch( IOException e ) { e.printStackTrace(); } } }

// Esta es la clase que se utiliza para instanciar un hilo de ejecución // para el servidor que se encarga de escuchar el puerto 80, que es el // definido como estándar para el protocolo de "http" class ServidorHttp extends Thread { // Constructor ServidorHttp() { // Arrancamos el hilo e invocamos al método run() para que empiece // a correr start(); } public void run(){ try { // Se instancia un objeto sobre el puerto 80 ServerSocket socket = new ServerSocket( 80 ); System.out.println( "Servidor escuchando el puerto 80" ); // Entramos en bucle infinito ecuchando el puerto. Cuando se - 103 -

UNIVERSIDAD AUTÓNOMA METROPOLITANA IZTAPALAPA AL TIEMPO

CASA ABIERTA

// produce una llamada, se lanza un hilo de ejecución de // ConexionHttp para atenderla while( true ) // Nos quedamos parados en el accept(), y devolvemos un socket // cuando se recibe la llamada. Este socket es el que se pasa // como parámetro al nuevo hilo de ejecución que se crea new ConexionHttp( socket.accept() ); } catch( IOException e ) { e.printStackTrace(); } } }

// Esta clase se utiliza la lanzar un hilo de ejecución que atienda // la llamada recibida a través del puerto 80, el puerto de http class ConexionHttp extends Thread { Socket socket; BufferedReader entrada = null; PrintWriter salida = null; DataOutputStream pagina = null; // Constructor ConexionHttp( Socket socket ) { System.out.println( "Recibida una llamada en el puerto 80" ); this.socket = socket; // Trabajamos por debajo de la prioridad de los otros puertos setPriority( NORM_PRIORITY-1 ); // Se arranca el hilo y se pone a correr start(); } public void run() { System.out.println( "Lanzado el hilo de atencion al puerto 80" ); try{ // Conseguimos un canal de entrada desde el socket entrada = new BufferedReader( new InputStreamReader( socket.getInputStream() ) ); // Conseguimos un canal de salida hacia el socket. El canal es // con liberación automática (autoflush) salida = new PrintWriter( new OutputStreamWriter( socket.getOutputStream() ),true ); // Ahora conseguimos un canal de salida para enviar el contenido // del ficero que haya solicitado el usuario pagina = new DataOutputStream( socket.getOutputStream() ); - 104 -

UNIVERSIDAD AUTÓNOMA METROPOLITANA IZTAPALAPA AL TIEMPO

CASA ABIERTA

// Recogemos la cadena que llegue al puerto String peticion = entrada.readLine(); System.out.println( "Recibida peticion: "+peticion ); // Analizamos esa petición e intentamos responder. Solamente se // contesta a las peticiones GET, cualquier otra no tiene // respuesta alguna StringTokenizer st = new StringTokenizer( peticion ); if( ( st.countTokens() >= 2 ) && st.nextToken().equals("GET") ) { System.out.println( "Primer tag GET, correcto" ); if( (peticion = st.nextToken()).startsWith("/") ) { System.out.println( "Siguiente toque que empieza por /, se le quita" ); peticion = peticion.substring( 1 ); } if( peticion.endsWith("/") || peticion.equals("") ) { System.out.println( "Peticion terminada en / o blanco, "+ "se le incorpora: index.html" ); peticion = peticion + "index.html"; System.out.println( "Peticion modificada: "+peticion ); } System.out.println( "Intento de desacarga de: "+peticion ); FileInputStream fichero = new FileInputStream( peticion ); // Se instancia un array de bytes igual al número de bytes que // se pueden leer del canal de entrada sin bloquearlo. // Y luego se rellena con los datos byte[] datos = new byte[fichero.available()]; fichero.read( datos ); // Se envía el array de datos al cliente pagina.write( datos ); pagina.flush(); } else // La petición que se ha hecho no es un GET salida.println( "400 Petici&oactute;n "+ "Errónea" ); socket.close(); System.out.println( "Socket cerrado" ); }catch( FileNotFoundException e ) { salida.println( "404 No Encontrado" ); try { socket.close(); System.out.println( "Socket cerrado" ); }catch( IOException evt ) { - 105 -

UNIVERSIDAD AUTÓNOMA METROPOLITANA IZTAPALAPA AL TIEMPO

CASA ABIERTA

evt.printStackTrace(); } }catch( SecurityException e ){ e.printStackTrace(); salida.println( "403 Acceso denegado" ); salida.println( e ); try { socket.close(); System.out.println( "Socket cerrado" ); }catch( IOException evt ) { evt.printStackTrace(); } }catch( IOException e ) { e.printStackTrace(); try { socket.close(); System.out.println( "Socket cerrado" ); }catch( IOException evt ) { System.out.println(evt); } } } } Ahora llega el turno a la revisión de las partes más interesantes del ejemplo, que en este caso se limitan a las líneas de código que se han incorporado nuevas al ejemplo javal7O9. java, para implementar el servidor Eco a través de UDP. Cada uno de los servidores se implementa mediante tareas, así que los tres servidores actúan concurrente y asíncronamente en diferentes tareas. Además, siempre que un objeto servidor necesite proporcionar un servicio a un cliente, lanzará otra tarea a una prioridad más baja, para dar servicio a ese cliente, siguiendo a la escucha de otros posibles clientes que requieran su atención. Dejando a un lado la parte ya vista en el ejemplo base, el primer trozo de código en que hay que detenerse es la implementación del servidor UDP. ServidorEcoudp servidorEchoudp = new ServidorEcoudp(); Lo siguiente es el propio constructor de esa clase que se utiliza para instanciar el objeto que va a proporcionar los servicios UDP de Eco a através del puerto 7. Este constructor, como se muestra, se limita a invocar a su propio método start() para levantarse y empezar a correr. El método start() invoca al método run(). Servi dorEcoudp() { startQ }

- 106 -

UNIVERSIDAD AUTÓNOMA METROPOLITANA IZTAPALAPA AL TIEMPO

CASA ABIERTA

La siguiente línea de código muesta la instanciación del objeto DatagramSocket sobre el puerto 7, que se encuentra dentro de la acción del método runü, es decir, es el comienzo de la tarea. Datagramsocket socketDgrama = new Datagramsocket ( 7 ); Solamente queda el corazón de la tarea, que está formado por el bucle infinito que instancia en primer lugar un objeto DatagramPacket vacío, para invocar al método receive() sobre el DatagramSocket, pasándole el objeto DatagramPacket como parámetro. Observe el lector que se ha limitado a 1024 byes los datos de entrada, por lo que no va a poder recibir mensajes de longitud mayor que ésa. Si es necesaria una longitud mayor, es suficiente con indicar el valor al constructor. while( true ) DatagramPacket paquete = new DatagramPacket( new byte[1024],1024 ); socketDgrama.receive( paquete ); new conexionEcoudp( paquete ); } El método receive() bloquea la tarea y se queda a la espera de que llegue un paquete datagrama. Cuando esto suceda, se rellenará el objeto DatagramPacket vacío y se instanciará una nueva tarea de tipo ConexionEcoUdp para atender la petición del cliente, pasándole también como parámetro el objeto DatagramPacket, pero en este caso relleno con los datos recibidos en el paquete enviado por el cliente. Una vez satisfecho el requerimiento del cliente, la tarea vuelve al inicio del bucle, instanciando un nuevo objeto DatagramPacket vacío y bloqueando la tarea a la espera de la llegada del siguiente paquete datagrama. Ese objeto es guardado por el constructor para usarlo más tarde, a continuación fija la prioridad por debajo del nivel de las tareas que están monitorizando los puertos, de forma que la actividad de la tarea correspondiente a ConexionEchoUdp no interfiera con otras tareas que puedan lanzarse para atender a las peticiones recibidas por esos puertos. Y, por fin, se invoca el método startO, que a su vez invoca al método run() y la tarea se pone en marcha. Todo esto es lo que se hace en el código que se muestra.

conexionEchoudp( DatagramPacket paquete ) { System.out.pnintln( Recibida una llamada en el puerto 7 ); this.paquete = paquete; // irabajamos por debajo de la prioridad de los otros puertos setPriorlty( NORM_PRIORITY-1 ); // Se arranca el hilo y se pone a correr start() } - 107 -

UNIVERSIDAD AUTÓNOMA METROPOLITANA IZTAPALAPA AL TIEMPO

CASA ABIERTA

La misión encargada a esta tarea se limita al envío de una copia de los datos que le llegan en el paquete recibido, de vuelta al cliente que se lo ha enviado. La dirección y puerto de este cliente están incluidos en el paquete, donde el DatagramSocket del cliente los colocó antes de enviarlo. El objeto DatagramPacket es casi directamente enviable de vuelta al cliente, pero para mostrar el uso de algunos de los métodos de la clase DatagramPacket, se construye un nuevo objeto para enviar de regreso al cliente, lo cual suele suceder más a menudo en servidores que realicen tareas más complejas. El código siguiente es el que permite extraer la información necesaria para generar un nuevo objeto. El último código en que merece la pena detenerse en este ejemplo es la instanciación del nuevo objeto DatagramSocket que se va a utilizar para enviar el nuevo objeto DatagramPacket creado, invocando al método send() del socket, de regreso al cliente. El resto del código es el cierre del socket y el tratamiento de las excepciones. socketDgrama = new DatagramSocket(); // se utiliza el nuevo socket datagrama para enviar el mensaje // y cerrar el socket socketDgrama.send( paqueteEnvio ); socketDgrama.close ();

- 108 -

UNIVERSIDAD AUTÓNOMA METROPOLITANA IZTAPALAPA AL TIEMPO

CASA ABIERTA

CAPITULO 3

- 109 -

UNIVERSIDAD AUTÓNOMA METROPOLITANA IZTAPALAPA AL TIEMPO

CASA ABIERTA

Conclusiones: Después de conocer tanto el funcionamiento del PLC, como de los Sockets, hemos dedicado tiempo a buscar la forma de tener comunicación en modo freeport. Para lograr esto hemos revisado varios ejemplos en los que se puede trabajar en modo freeport, tanto para transmisión como para recepción, logrando tener uno que nos permite transmitir y recibir en un mismo programa, a través de una rutina de interrupción, y trabajando con los bytes de marcas: SM0.1, SMB30; que son necesarios para configurar el puerto, y el SMB2, que contiene todos los bits recibidos, y nos permite hacer una comparación, entre lo que recibimos por el puerto y lo que queremos que encuentre. Las marcas especiales (SM) ofrecen una serie de funciones de estado y control. Sirven para intercambiar informaciones entre la CPU y el programa, pudiéndose utilizar en formato de bits, bytes, palabras o palabras dobles. SMB0: SMB0 contiene ocho bits de estado que la CPU S7-200 actualiza al final de cada ciclo. B it s d e marcas SM0.0 SM0.1 SM0.2

SM0.3

SM0.4

SM0.5

SM0.6 SM0.7

Descripción Este bit siempre está activado. Este bit se activa en el primer ciclo. Se utiliza p.ej. para llamar una subrutina de inicialización. Este bit se activa durante un ciclo si se pierden los datos remanentes. Se puede utilizar como marca de error o como mecanismo para llamar a una secuencia especial de arranque. Este bit se activa durante un ciclo cuando se pasa a modo RUN tras conectarse la alimentación. Se puede utilizar durante el tiempo de calentamiento de la instalación antes del funcionamiento normal. Este bit ofrece un reloj que está activado durante 30 segundos y desactivado durante 30 segundos, siendo el tiempo de ciclo de 1 minuto. Ofrece un retardo fácil de utilizar o un tiempo de reloj de 1 minuto. Este bit ofrece un reloj que está activado durante 0,5 segundos y desactivado durante 0,5 segundos, siendo el tiempo de ciclo de 1 segundo. Ofrece un reloj que está activado durante 0,5 segundos y desactivado durante 0,5 segundos, siendo el tiempo de ciclo de 1 minuto. Este bit es un reloj de ciclo que está activado en un ciclo y desactivado en el ciclo siguiente. Se puede utilizar como entrada de conteo de ciclos. Este bit indica la posición del selector de modos de operación (OFF = TERM; ON = RUN). Si el bit se utiliza para habilitar el modo Freeport cuando el selector esté en posición RUN, se podrá habilitar la comunicación normal con la unidad de

- 110 -

UNIVERSIDAD AUTÓNOMA METROPOLITANA IZTAPALAPA AL TIEMPO

CASA ABIERTA

programación cambiando el selector a TERM. SMB2: Es el búfer de recepción de caracteres en modo Freeport. Cada carácter recibido en dicho modo se deposita en este búfer, accesible fácilmente desde el programa KOP Byte de marcas SMB2

Descripción Este byte contiene todos los caracteres recibidos de los interfaces 0 ó 1 en modo Freeport

SMB30 Controla la comunicación Freeport SMB30 son marcas de lectura y escritura. Dichos bytes configuran la comunicación Freeport en los respectivos interfaces y permiten seleccionar si se debe asistir el modo Freeport o el protocolo de sistema.

Interface 0 Descripción Formato de SMB30 MSB LSB 7 0 Byte de control del modo Freeport p p d b b b m m SM30.6 y SM30.7 pp Selección de paridad 00 = sin paridad 01 = paridad par 10 = sin paridad 11= paridad impar SM30.5 d Bits por carácter 0 = 8 bits por carácter 1 = 7 bits por carácter SM30.2 a SM30.4 bbb Velocidad de transferencia 000 = 38.400 bits/s (para la CPU 212: = 9.200 bits/s) 001 = 19.200 bits/s 010 = 9.600 bits/s 011 = 4.800 bits/s 100 = 2.400 bits/s 101 = 1.200bits/s 110 = 600 bits/s 111 = 300 bits/s SM30.0 y SM30.1 mm Selección de protocolo 00 = Protocolo de interface punto a punto PPI/modo esclavo) 01 = Protocolo Freeport 10 = PPI/modo maestro 11 = Reservado (estándar: PI/modo esclavo) Realizamos un circuito de prueba que se muestra a continuación: - 111 -

UNIVERSIDAD AUTÓNOMA METROPOLITANA IZTAPALAPA AL TIEMPO

CASA ABIERTA

MAIN (PROGRAMA PRINCIPAL) NETWORK 1 Activa el modo freeport y define el área de memoria a transmitir Mov_B Primer _ ciclo

9

EN

ENO

IN

OUT

Config_0

Primer_ciclo corresponde a la marca SM0.1 Config_0 corresponde a la marca SMB30 Se transfiere el binario 1001 al byte de marcas SMB30, que es el registro de control del modo freeport, esta combinación binaria configura el puerto de comunicación a 9600 bits, 8 bits por carácter y sin paridad

Mov_B

1

EN

ENO

IN

OUT

Transferir la constante 1 al byte VB100 de la memoria de variables para indicar la longitud del mensaje (de un carácter ASCII). VB100

Transferir el valor hexadecimal 41 al byte VB101 de la memoria de variables para indicar que el carácter ASCII es "A" (en el código ASCII “A” = "41" en la notación hexadecimal).

Mov_B

16#41

EN

ENO

IN

OUT

VB101

NETWORK 2 Asociar y habilitar rutina de interrupción para recibir Cargar marca especial SM0.1 correspondiente a Pprimer_ciclo para procesar este segmento sólo en el primer ciclo. Asociar el evento de interrupción 8 a la rutina de interrupción INT_0

ATCH

Primer _ ciclo EN

ENO

IN

INT_0 8

EVNT

( ENI ) NETWORK 3 Transmitir Cargar la entrada E0.1. Si hay un flanco positivo

XMT

E0.1 P

VB100 0

EN

ENO

TBL

- 112 -

PORT

UNIVERSIDAD AUTÓNOMA METROPOLITANA IZTAPALAPA AL TIEMPO

CASA ABIERTA

en la entrada, transmitir por el puerto 0 el búfer de datos que comienza en el byte VB100 de la memoria de variables.

NETWORK 4 Fin del programa principal

RUTINA DE INTERRUPCIÓN

NETWORK 1 Comienza rutina de interrupción

NETWORK 2 Confirmar carácter recibido Caract_rec ==B 16#41

Caract_rec corresponde al byte de marcas SMB2. Carácter_A corresponde a la salida Q0.1. Carácter_A El byte de marcas SMB2 contiene cada carácter recibido del ( S ) puertos 0 durante la comunicación Freeport. Compara el 1 carácter en SMB2 con el valor hexadecimal 41 ("A" en código ASCII = "41" en la notación hexadecimal). Si el carácter contenido en SMB2 es "A", se activa la salida Q0.1.

NETWORK 3 Fin de la rutina de interrupción INT0

- 113 -

UNIVERSIDAD AUTÓNOMA METROPOLITANA IZTAPALAPA AL TIEMPO

CASA ABIERTA

Este circuito fue probado en el laboratorio, tomando como receptor el hyperterminal, previamente configurado a 9600 bits/s 8 bits de datos y sin paridad (al transmitir por el PLC) , y como transmisor (al recibir por el PLC).

De esta forma teniendo ya la comunicación entre el PLC y la máquina, Se puede realizar la programación de la interfaz gráfica, la cual puede ser diseñada en diversos programas, como Java, y después intereactuar con los sockets antes vistos, para lograr una perfecta comunicación entre el software, y el PLC. Pensamos que el programa deberá tener una tabla mayor a la del ejercicio realizado, la cual dependerá de la cantidad de puertas que se deseen monitorear.

Cuando una de las puerta se abra, se observara en la máquina como es que se abre la puerta, y además se indicara cual es la que se abrió, esta misma puerta podrá ser cerrada, vía remota, desde donde se realiza el monitoreo, al transmitir al PLC la indicación de cierre de puerta, por medio de teclado.

Durante la realización de este proyecto, nos enfrentamos a varios retos, debido a que las ideas no eran muy claras en un principio, pero poco a poco fuimos dándonos cuenta de la importancia de un sistema automatizado de control como lo es el PLC, algo que nos pareció sumamente importante y eficiente.

Desafortunadamente, la ultima parte falta por concluirla, esperando que con la información obtenida y los ejemplos propuestos, sean de gran utilidad para quien este interesado en continuar con el proyecto.

Estamos seguros que habrá muchas personas interesadas en él, Les deseamos toda la Suerte para que en un futuro próximo logren terminar con un Proyecto Tan importante.

- 114 -

UNIVERSIDAD AUTÓNOMA METROPOLITANA IZTAPALAPA AL TIEMPO

CASA ABIERTA

De la misma forma esperamos que el material aportado sea de utilidad para el desarrollo de otros Proyectos en la utilización de PLC.

BIBLIOGRAFÍA Agustín Froufe. “Java 2 Manual de usuario y tutorial”, Alfaomega, 2ª Edición, 2002, cap, 17. Autómatas Programables, ISA-UMH, TDOC 99 Simens, Simatic, “Manual del Usuario Visualizador de textos TD 200”K, 2000 www.iespana.es/automatizacion www.plcs.net www.olmo.pntic.mec.es/jmarti50/automatizacion www.siemens.com.mx

- 115 -

View more...

Comments

Copyright ©2017 KUPDF Inc.
SUPPORT KUPDF