
|
Implementación del buffer tipo FIFO (Cola de espera, el primero es primero en salir)El ejemplo anterior necesita conocimientos de ejecución paralela de dos procesos, uno para leer del socket (conexión de red) y otro para atender a la telefonista, eso no lo veremos todavía así que cambiaremos el enunciado para implementar esta cola de espera. La telefonista recibe los teléfonos y tardará solo 1 minuto como máximo por llamada, es decir los números saldrán de a 1 por minuto, pudiendo descansar en el tiempo sobrante, si no hay números esperar, y si hay muchos guardarlos temporalmente. Utilizaremos una conexión de red, donde la telefonista tendrá un programa en espera que escuchará en la red y la secretaria el que enviará los datos al establecer una conexión. Telefonista: gus@gusgus ~$ java com.compunauta.aprendiendojava.Cap3_sock_tel Escuchando el puerto:4567 Esperando conexión... Conectado... Esperando teléfonos Secretaria llamando al tel:123 Secretaria llamando al tel:432 Ultima llamada, nos vamos... programa terminado gus@gusgus ~$ Secretaria: gus@gusgus ~$ java com.compunauta.aprendiendojava.Cap3_sock_sec Intentando conectar con la telefonista Nos conectamos con la telefonista:127.0.0.1:4567 Ingrese números -1 termina 123 432 -1 Programa terminado gus@gusgus ~$ Codificación: Como dijimos antes la implementación y codificación de este ejemplo necesitará de una conexión de red para poder comprender mejor el uso de este tipo de estructuras, no es necesario que dispongamos de una red para llevar a cabo este programa, ya que una PC puede actuar como una red de 1 sola máquina, en este caso usaremos la dirección de red local (loopback) que existe en casi todos los sistemas operativos en los que corre Java. Esta dirección especial, permite conectarse por medio de los protocolos de red a la misma máquina en caso que estemos en un laboratorio de informática y dispongamos de la información de las direcciones ip de las otras PCs de la red sería interesante que se trabajara en grupos de dos, para poder apreciar la conexión remota. Nota: Queda como tarea para el lector revisar los métodos y propiedades de la clase o tipo de datos Socket y ServerSocket. Para establecer una conexión de red es necesario que alguien esté escuchando en un puerto, estos puertos son un concepto abstracto que podemos asimilarlo comparándolo con un puerto real donde llegan barcos, dichos barcos una vez que llegan son atendidos y siempre que haya quien atenderlos podrá entrar otro. En este caso solo esperaremos una única conexión en el puerto, y cuando esta se establezca no esperaremos ninguna otra. La clase ServerSocket nos permitirá fabricar un objeto que podrá esperar una conexión, y cuando esta llegue podemos abrirla por medio del objeto resultante Socket (conexión). El puerto de escucha en este ejemplo será: 4567, muchos firewalls y mecanismos de protección de red pueden bloquear el acceso a estos puertos, así que cualquier cosa preguntamos al administrador de la red, por otro lado no podemos usar un puerto más bajo inferior a los 1024 porque será necesario por lo general permisos especiales en el sistema operativo. Telefonista:
De las líneas 15 a la 19 se declaran las variables globales que serán accesibles por todos los métodos estáticos de la clase como ser el puerto de escucha, el tamaño del buffer y el verdadero buffer donde utilizamos el tamaño definido para crearlo, recordemos que una vez definido el arreglo que actuará de buffer no podemos expandirlo o reducirlo, por ello el cálculo previo para estimar su máxima cantidad de datos. También se define el tiempo en milisegundos que esperaremos antes de mostrar en pantalla el siguiente teléfono. A partir de la línea 21 comienza el método principal, el cual se ejecutará paso seguido de inicializar las variables globales. Revisemos la línea 23, la declaración de la variable skt, no estamos inicializando el objeto de la biblioteca, sino que lo estamos apuntando a un objeto especial de tipo nulo, este objeto llamado null es un objeto vacío que no posee propiedades de ningún tipo y cada vez que queramos operar sobre el producirá un error de puntero nulo, entonces ¿porqué lo necesitamos?. El compilador no es lo suficientemente inteligente como para detectar que lo inicializaremos en un bloque de código futuro y necesitamos declararlo en este punto de nuestro método principal, porque todo lo que se declara en bloques especiales como try{}catch(){} es local y desaparece fuera de ellos. En la línea 25 declaramos el objeto de la biblioteca ServerSocket que escuchará y devolverá un objeto del tipo Socket, usamos el mismo objeto nulo para inicializarlo. Desde la línea 27 a la 32 intentamos crear el objeto ServerSocket y lo que es más importante, detectar si hubo o no un error por parte del sistema ya que no podremos continuar si se produce un error, como podemos ver en nuestro bloque de control de errores salimos si el sistema no nos permite abrir dicho puerto, en el caso que tengamos algún tipo de protección de red tendremos que desactivarla temporalmente para poder trabajar. Entre las líneas 34 y 39 nos encargamos de esperar una conexión remota, nuevamente es necesario capturar las posibles excepciones de error que puedan producirse, es obligatorio por el compilador manejar las excepciones declaradas por ServerSocket, como podemos ver, la función accept(); de ServerSocket aceptará una conexión y esa conexión la almacenaremos en el apuntador skt, teniendo ahora correctamente inicializado dicho apuntador podemos usar las características de la conexión. A partir de la línea 42 comenzamos la operación de lectura de la conexión de red y de la impresión en pantalla de los teléfonos. Ya habíamos visto los objetos del tipo InputStream para la lectura de texto desde el teclado, en este caso lo usaremos para la lectura desde la conexión, solo que para practicar, utilizaremos un objeto de la misma familia pero del tipo ObjectInputStream que nos permitiría enviar objetos a través del canal o flujo de datos. El objeto skt provee la función getInputStream(); que nos devolverá el Objeto del tipo InputStream que necesita el objeto de la biblioteca del tipo ObjectInputStream para ser construido. En la línea 45 declaramos una variable de tipo entero largo (long) timer que utilizaremos para medir el tiempo y así crear nuestro temporizador, le damos un valor inicial. En la línea 46 declaramos una variable booleana timer_on que nos servirá de bandera para saber el estado del temporizador, si es que está activo (true) o parado (false). Lo inicializamos en parado porque no tenemos teléfonos hasta que la secretaria los comience a escribir. En la línea 47 se comienza un bloque del tipo “mientras”, while(){} el cual deseamos que sea infinito porque leeremos datos hasta que la secretaria envíe la señal de fin, pero mientras tengamos teléfonos para mostrar en el buffer no podremos dejar de mostrarlos, así que para mejor comprensión del código, el bloque while(true){} se ejecutará por siempre a menos que el programa decida terminar. Revisemos el código de la línea 48 a la 52, en este bloque comparamos dos condiciones importantes para decidir si el programa debe salir o no, la primera es una condición doble, por eso está encerrada entre paréntesis, es decir si la conexión está cerrada y la cantidad de elementos del buffer es inferior a 1 o sucede que el elemento cero del buffer es el número -1 que indica la secretaria que terminó entonces nos vamos porque es el último elemento y no es necesario mostrarlo. El siguiente bloque desde la línea 54,55 almacenamos un número entero desde la conexión de red datos, sólo si es que hay datos disponibles, eso lo revisamos con la función available(); que no se quedará esperando, si existen o no datos disponibles de todas maneras el programa sigue su curso. El timer, entre las líneas 58 a la 65, funciona de la siguiente manera, cuando queremos comenzar a medir el tiempo, almacenamos dentro de la variable timer la hora actual medida en milisegundos, ese dato lo obtenemos con la función del objeto system, currentTimeMillis(), si al timer, le sumamos la cantidad de tiempo que queremos esperar, ese valor siempre será más grande que la hora actual, hasta que se pase el tiempo, eso es lo que comparamos en el bloque if(){}, caso contrario nos preparamos para ver si es necesario encender el timer y mostrar teléfonos, que sólo sucederá si hay más de 1 elemento en el buffer, recordemos que en buff_elem almacenamos la cantidad de elementos de nuestra memoria temporal. En las líneas 67,68 incluimos otro bloque de control de errores obligatorio porque utilizaremos otra función de la biblioteca de Java que pertenece a la clase Thread (hilo) esa función detiene la ejecución del programa una cierta cantidad de milisegundos, el uso de este pequeño retardo de 100 milisegundos es para no sobrecargar el procesador, ya que estaríamos ejecutando nuestro bucle while(true){} infinidades de veces en 1 único segundo lo cual no es necesario ya que la secretaria no es tan veloz y dejamos libre el procesador para el sistema operativo. En la línea 69 termina el bloque eterno while(true){} y el bloque de control de errores que lo contiene, dado el caso que se produzca un error dentro del bloque será necesario terminar el programa. Los métodos auxiliares: public static void put_tel(int tel){ Esta función agregará un teléfono en la memoria temporal, en nuestro buffer, haciendo una cierta detección de errores, en el caso que el buffer no sea lo suficientemente grande como para almacenar el próximo dato, en este caso el programa se sale con error. Si esto no sucede, la línea más importante de esta función es: buffer[buff_elem++]=tel; Donde como vimos al principio de este libro el signo ++ a la derecha de buff_elem simboliza que primero se utilizará el valor y después se incrementará, entonces en una sola línea representamos las dos operaciones buffer[buff_elem]tel; buff_elem++; public static int get_tel(){ Esta función extraerá un elemento del buffer, pero al mismo tiempo que lo extrae debe posicionar los demás elementos hacia el inicio del arreglo. Tampoco hacemos la comprobación de que no haya elementos en el buffer porque la hacemos en el código cada vez que vemos si hay teléfonos para mostrar, aunque deberíamos por buena práctica porque si este es un prototipo y nuestro programa irá creciendo, entonces tal vez se nos escape revisar. Almacenamos de manera temporal el teléfono próximo antes de recorrer los datos: int tel=buffer[0]; Después de eso quitamos uno al apuntador de elementos, y lo quitamos antes porque para subir los datos sumaremos uno al índice que recorremos para acomodar los datos y tendríamos que estar revisando que el índice no supere buff_elem-1, como esa condición se compararía cada vez que pasamos por un valor del índice estaríamos ejecutando innecesariamente una resta que podríamos hacer antes. Una vez terminado devolvemos el teléfono que almacenamos antes que desapareciera. Nota: Es incorrecto el manejo de error en la función put_tel, ya que en Java existe un modelo de excepciones que deben ser lanzadas y capturadas como en cualquier bloque de control de errores, si no corroboramos el índice Java producirá una IndexOutOfBoundException que de todas maneras terminará el programa. leavecomment |
|
|||||||||||||||||||||||
*Hasta que esta leyenda no desaparezca el libro no ha sido terminado, descarge en pdf:
http://compunauta.com/forums/linux/programacion/java/ebook.html