Sie sind auf Seite 1von 58

Lenguaje

Programación
Java
Guía del Estudiante
Mapa del Curso

Lenguaje Básico de Programación

Caracteristicas Identificadores Expresiones Manejo de


De Java Palabras Reserv. Control de Flujo Arreglos
Tipos de datos de programas

Programación Orientada a Objetos

Caracteristicas
Clases y Objetos avanzadas

Manejo de Errores

Excepciones

Applets

Introduccion
Java
Applets

Desarrollo de Aplicaciones Graficas

Construyendo Modelo de Librería de Java


GUIs Eventos Componentes Foundation
AWT AWT Classes

Multiprogramación

Threads

Comunicaciones

Archivos
Streams I/O Networking
Modulo 1 – Lenguaje Básico de Programación.

Características de Java.
Las características principales que nos ofrece Java respecto a cualquier otro lenguaje de
programación, son:

Simplicidad
Java ofrece toda la funcionalidad de un lenguaje potente, pero sin las características menos
usadas y más confusas de éstos. Java elimina muchas de las características de otros lenguajes
como C++, para mantener reducidas las especificaciones del lenguaje y añadir características
muy útiles como el garbage collector (reciclador de memoria dinámica).
En Java ya no es necesario preocuparse de liberar memoria.
Reduce en un 50% los errores más comunes de programación con lenguajes como C y C++ al
eliminar muchas de las características de éstos, entre las que destacan:
 aritmética de punteros
 no existen referencias
 registros (struct)
 definición de tipos (typedef)
 macros (#define)
 necesidad de liberar memoria (free)

Orientado a Objetos
Java implementa la tecnología básica de C++ con algunas mejoras Java trabaja con sus datos
como objetos y con interfaces a esos objetos. Soporta las tres características propias del
paradigma de la orientación a objetos: encapsulación, herencia y polimorfismo.

Distribuido
Java se ha construido con extensas capacidades de interconexión TCP/IP. Existen librerías de
rutinas para acceder e interactuar con protocolos como http y ftp. Esto permite a los
programadores acceder a la información a través de la red con tanta facilidad como a los
ficheros locales.

Independiente de la Plataforma
Para establecer Java como parte integral de la red, el compilador Java compila su código a un
fichero objeto de formato independiente de la arquitectura de la máquina en que se ejecutará.
Cualquier máquina que tenga el sistema de ejecución (run-time) puede ejecutar ese código
objeto, sin importar en modo alguno la máquina en que ha sido generado.
Actualmente existen sistemas run-time para Solaris 2.x, SunOs 4.1.x, Windows 95, Windows
NT, Linux, Irix, Aix, Mac, Apple y probablemente haya grupos de desarrollo trabajando en el
porting a otras plataformas.
El código fuente Java se "compila" a un código de bytes de alto nivel independiente de la
máquina. Este código (byte-codes) está diseñado para ejecutarse en una máquina hipotética
que es implementada por un sistema run-time, que sí es dependiente de la máquina.
En una representación en que tuviésemos que indicar todos los elementos que forman parte de
la arquitectura de Java sobre una plataforma genérica, obtendríamos una figura como la
siguiente:

En ella podemos ver que lo verdaderamente dependiente del sistema es la Máquina Virtual
Java (JVM) y las librerías fundamentales, que también nos permitirían acceder directamente al
hardware de la máquina. Además, habrá APIs de Java que también entren en contacto directo
con el hardware y serán dependientes de la máquina.
Robusto
Java realiza verificaciones en busca de problemas tanto en tiempo de compilación como en
tiempo de ejecución. La comprobación de tipos en Java ayuda a detectar errores, lo antes
posible, en el ciclo de desarrollo. Java obliga a la declaración explícita de métodos, reduciendo
así las posibilidades de error. Maneja la memoria para eliminar las preocupaciones por parte
del programador de la liberación o corrupción de memoria. También implementa los arrays
auténticos, en vez de listas enlazadas de punteros, con comprobación de límites, para evitar la
posibilidad de sobreescribir o corromper memoria resultado de punteros que señalan a zonas
equivocadas. Estas características reducen drásticamente el tiempo de desarrollo de
aplicaciones en Java.
Además, para asegurar el funcionamiento de la aplicación, realiza una verificación de los byte-
codes, que son el resultado de la compilación de un programa Java. Es un código de máquina
virtual que es interpretado por el intérprete Java. No es el código máquina directamente
entendible por el hardware, pero ya ha pasado todas las fases del compilador: análisis de
instrucciones, orden de operadores, etc., y ya tiene generada la pila de ejecución de órdenes.
Java proporciona, pues:
Comprobación de punteros
Comprobación de límites de arrays
Excepciones
Verificación de byte-codes

Multiprogramación
Al ser multithreaded (multihilvanado, en mala traducción), Java permite muchas actividades
simultáneas en un programa. Los threads (a veces llamados, procesos ligeros), son
básicamente pequeños procesos o piezas independientes de un gran proceso. Al estar los
threads construidos en el lenguaje, son más fáciles de usar y más robustos que sus homólogos
en C o C++.
El beneficio de ser multithreaded consiste en un mejor rendimiento interactivo y mejor
comportamiento en tiempo real. Aunque el comportamiento en tiempo real está limitado a las
capacidades del sistema operativo subyacente (Unix, Windows, etc.), aún supera a los
entornos de flujo único de programa (single-threaded) tanto en facilidad de desarrollo como en
rendimiento.

Identificadores – Palabras Reservadas - Tipos de Datos


Tipos de Datos

Hay ocho tipos básicos de datos llamados “primitivos”.


Enteros con signo.
byte (8 bits).
short (16 bits).
int (32 bits).
long (64 bits).

Numéricos de punto flotante:


float
double

Carácter simple: se utiliza para caracteres individuales como letras, números, puntuación, y
otros símbolos.
char

Booleanos: que pueden albergar los valores “true” ó “false”.


boolean
Tipos de Clases

String: Es un tipo de clase usado para almacenar texto


Ejemplo
colorDePelo = “castaño”;
Cuando una variable tiene una clase como su tipo, la variable se refiere a un objeto de esa
clase o a una de sus subclases.

Si se declara a un tipo de variable “Object” significa que puede contener cualquier objeto.

Declaración de variables

Se declaran anteponiendo el tipo de dato o clase a la cual pertenecen.


Para las variables del mismo tipo, se pueden declarar en la misma línea separándolas por
comas..

Ejemplo
int total;
String ciudad, calle, provincia = “Buenos Aires” ;
boolean activo = true;

Nomenclatura de Variables
Los nombres de variables deben comenzar con una letra, un carácter de subrayado ( _ ) o un
signo pesos ($). No pueden empezar con un número. Después del primer carácter pueden
incluir cualquier combinación.
Java es sensible a minúsculas y mayúsculas, esto permite tener una variable llamada X y otra
llamada x.

El estándar recomendado es para el nombramiento de variables es:


La primera letra minúscula.
Cada palabra después del nombre de la variable empieza con mayúscula. (Ej. areaCodigo)
Todas las demás letras son minúsculas

Comentarios
Hay tres formas de aplicar comentarios en la definición de una clase, que son:
Para comentarios de una línea
//. Todo lo que se encuentre a partir de ahí hasta el final de la línea es considerado comentario.
Para comentarios de mas de una línea: Se utilizan los símbolos /* - */ con los que se encierra el
comentario.
Para los comentarios que son autodocumentables se utilizan los símbolos /** - */.

Ejemplos
// Comentario de una línea
/* Comentario de
mas de
una línea.
*/
/**
Documentación de un método o atributo....
*/
Literales
Un literal es cualquier número, texto o información que representa directamente un valor.

Literales numéricos
Para representar un literal de tipo long anteponga una L al número (long total = 10L;)
Para representar un literal negativo anteponga un – (long total = -10L;)
Para representar un literal con notación octal anteponga un 0 al número (int total = 010;)
Para representar un literal con notación hexadecimal anteponga un 0x al número (int total
= 0xFF;)
Para representar un literal de punto flotante se utiliza un punto (.) para el punto decimal
(double numero = 2.50;), todas estas literales se consideran double.
Para especificar un literal de punto flotante como float anteponga la letra f (f o F) (float
numero = 3.1416F;)
Para representar notación exponencial utilice la letra e (double x = 12e22;)

Literales booleanos
Los valores true y false son literales, y son los únicos valores posibles para las variables
booleanas

Literales de caracteres
Es un carácter sencillo entre comillas sencillas como ‘a’, ‘#’ y ‘3’.

Literales de cadena
Es una cadena de caracteres entre comillas dobles como “Esto es una cadena”.
Como las cadenas en Java son objetos reales, contienen métodos para combinar cadenas,
modificarlas y determinar si dos cadenas poseen el mismo valor.

Java almacena el valor de una cadena como un objeto String, por lo tanto solo se debe
crear un nuevo objeto explícitamente, por lo que son tan fáciles de utilizar como los tipos
de dato básicos.

Expresiones – Control de Flujo - Arreglos


Expresiones y Operadores

Una expresión es una instrucción que produce un valor.


Por ejemplo:
int x = 3;
int y = 4;
int z = x * y; // Expresión

Un operador es un símbolo especial utilizado para funciones matemáticas, algunos tipos


de instrucciones de asignación y comparaciones lógicas.
Por ejemplo el * de la expresión anterior.

Operadores Aritméticos
Suma +
Resta -
Multiplicación *
División /
Módulo %

El lado derecho de una expresión siempre se calcula antes de que se dé la asignación. Por
esta razón la siguiente expresión es posible:
int x = 5;
x = x + 2;
Donde x ahora es 7.
Incremento y Decremento

Sirven para añadir o sustraer 1 a una variable.


Para incrementar se utiliza ++ (int x = 7; x = x++; ahora x vale 8).
Para decrementar se utiliza - - (int x = 7; x = x - -; ahora x vale 6).

Comparaciones

Son operaciones para hacer comparaciones entre las variables, variables y literales, u
otros tipos de información. Devuelven siempre un valor booleano.
Igual ==
Distinto ¡=
Menor que <
Mayor que >
Menor que o igual a <=
Mayor que o igual a >=

Operadores Lógicos

Las expresiones que producen valores booleanos, como las operaciones de comparación, se
pueden combinar para formar expresiones más complejas. Esto se maneja a través de
operadores lógicos, los cuales para las combinaciones lógicas:

AND: & o && (con && el lado derecho de la expresión nunca se evalúa), devuelve true solo si
las dos expresiones son verdaderas.

OR: | o || (con || el lado derecho de las expresión nunca se evalúa), devuelve true si alguna de
las expresiones son verdaderas.

XOR: ^, devuelve true solo si las dos expresiones booleanas que combina tienen valores
opuestos. Si ambas son true o false, devuelve false.

NOT. !, invierte el valor de una expresión booleana de la misma manera en que un símbolo
menos invierte el signo positivo en un número.

Precedencia de operadores

Es el orden de evaluación que posee Java cuando hay mas de un operador en una expresión.
El orden del primero al último es:
Operaciones de incremento y decremento
Operaciones aritméticas
Comparaciones
Operaciones lógicas
Expresiones de asignación

Para cambiar el orden en que se evalúan las operaciones se deben colocar paréntesis en las
expresiones que se quieren evaluar primero.
Aritmética de cadenas

El operador + también puede ser utilizado para concatenar cadenas.


El operador + combina cadenas, objetos y variables para formar una cadena sencilla.
También esta el operador corto += que añade algo al final de la cadena.
Ejemplo
miNombre += “Señor”;

Controles de Flujo

Estructuras de decisión o salto

Instrucciones if – else

Estructura:

if (expresión boolean) {
//Bloque de código para expresiones verdaderas;
}
else {
// Bloque de código para expresiones falsas;
}

Siempre debe tomar una expresión booleana. El else es opcional y puede ser omitido si no
existen acciones a tomar en caso de no cumplirse la condición

Instrucción switch

Estructura:

Switch (expresión 1) {
Case constante2:
Bloque de código;
Break;
Case constante3:
Bloque de código;
Break;
Default:
Bloque de código;
Break;
}

La expresión 1 debe ser compatible con un tipo de dato entero. Los tipos de punto flotante, long
o clases no están permitidas.

El default es opcional, y es utilizado para especificar un segmento de código que es ejecutado


si la variable o expresión no matchea con ninguno de los case.

Si no hubiera un break como última instrucción dentro del código del case, el programa entrara
al código del próximo case sin evaluar la expresión.
Estructuras de Iteración

Las estructuras de lazo permiten repetir la ejecución del bloque de código que contengan.
En java existen tres tipos: for, while y do.
For y while evalúan la condición de lazo antes de ejecutar el bucle.
Do evalúa después de ejecutar el bucle. En este caso el bucle se ejecuta al menos una vez.

Instrucción for

Estructura:
For (expresión inicial; expresión booleana; alter_expresion3) {
Bloque de código;
}

En esta estructura está permitido utilizar comas para separar variables,


Ejemplo
for (int i = 0, j = 0; j < 10; i++, j++) {
Bloque de código;
}
La variable i es solo accesible dentro del bloque del for.

Instrucción while

Estructura:
While (boolean) {
Bloque de código;
}

Hay que asegurarse de que la variable utilizada en el while esté definida y sea verdadera antes
de la ejecución del mismo.

Instrucción do

Estructura:
Do {
Bloque de código;
} while (expresión booleana);

Hay que asegurarse de que la variable utilizada en el while esté definida y sea verdadera antes
de la ejecución del mismo.

Instrucciones especiales dentro de las estructuras de lazo:

Break [label]; // Es utilizada para salir de las instrucciones del switch, de las estructureas de
lazo y de los bloques con etiqueta de forma prematura.
Continue [label]; // Es usada para saltear el código y llegar hasta el final dentro de las
estructuras de lazo.
Label : instrucción; // Identifica con un nombre una instrucción válida a la que se le tranferirá el
control.
Ejemplo:

Do {
Bloque de código;
If (condición es true)
Break;
}
while (expresión booleana);

Do {
Bloque de código;
If (condición es true)
continue;
}
while (expresión booleana);

loop: //label
Do {
instrucción;
Do {
instrucción;
instrucción;
If (condición es true)
Break loop;
}
while (expresión booleana);
instrucción;
}
while (expresión booleana);

Palabras reservadas
Las siguientes son las palabras reservadas que están definidas en Java y que no se pueden
utilizar como indentificadores:

abstract continue for new switch


boolean default goto null synchronized
break Do if package this
byte double implements private threadsafe
byvalue Else import protected throw
case extends instanceof public transient
catch False int return true
char Final interface short try
class Finally long static void
const Float native super while
Arreglos

Declaración:
Se utilizan para agrupar objetos del mismo tipo y nombrarlos con un nombre común.

Ejemplo:
char s[];
Point p[]; //Point es una clase

La declaración de un array crea una referencia que puede ser utilizada para referirlo, o sea que
la memoria del array solo se utiliza cuando éste es “creado” por medio de la instrucción new.

Creación:
Los arrays se crean como el resto de los objetos, utilizando la instrucción new.

Ejemplo:
s = new char[20];
p = new Point [100];

La instrucción p[0] = new Point[]; Indica que se está creando el elemento uno del array,
el sub cero ([0]), es el indice del array.

Inicialización:
Cuando se crea un array se debe inicializar cada uno de sus elementos, no deben
utilizarse antes de este paso.

Ejemplo:
String nombres[] = {
“Juan”,
“Maria”,
“Laura”
};

Arreglos Multidimensionales:

En java se pueden crear arrays de arrays, y arrays de arrays de arrays.

Ejemplo:
Int dosDim [][] = new int [4][];
dosDim[0] = new int [5];
dosDim[1] = new int [5];

En el primer caso se crea un array de 4 elementos, cada elemento es una referencia nula a un
elemento de tipo array de enteros, y será inicializado separadamente. Por esta razón es posible
crear arrays de arrays no rectangulares.
Ejemplo:
int dosDim [][] = new int [4][];
dosDim[0] = new int [5];
dosDim[1] = new int [2];
dosDim[2] = new int [4];
dosDim[3] = new int [8];

Hay otra forma más corta de inicializar un array de array:

int dosDim [] [] = new int [4] [5];

El atributo length es utilizado para determinar el largo del array, y es muy útil a la hora de iterar
dentro del mismo.
Ejemplo:
int lista [] = new int [10];
for (int i = 0; i< lista.length; i++) {
System.out.println (lista[i]);
}

Redimensionamiento de arrays

Al crearse un array éste no puede ser redimensionado, pero sí se puede utilizar la misma
variable para referenciar un nuevo array.

Ejemplo:
int miArray[] = new int [6];
miArray = new int [10];

El primer array es perdido a menos que otra referencia lo haya guardado.

Copia de arrays

Java provee un método especial de la clase System, arraycopy(), para copiar arreglos.

Ejemplo:
System.arraycopy(miArray, 0, otroArray, 0, miArray.length);
Modulo 2 - Programación Orientada a Objetos.

Conceptos Principales

 Encapsulacion.
 Herencia.
 Polimorfismo.

Clase: Definicion de atributos y comportamientos de un objeto de la realidad. Cada clase se


define en un solo archivo de código fuente (.java) que lleva el nombre de la misma.

Objeto: Instancia particular de una clase.

Definición de Clases:
[package nombrepaquete]
[import nombreclase]

[Modificadores] class NombreClase [extends SuperClase] [implements


Interfase] {

[definición de atributos]
[definición de metodos]
}

Ejemplos:
Definición de una clase:
public class Empleado {
String Nombre;
String Apellido;
String Gerencia;
}

Las variables Nombre, Apellido y Gerencia son los atributos de la clase empleado.

Definición de un Objeto y utilización de sus atributos.


Empleado emp = new Empleado();
emp.Nombre = “Juan”;
emp.Apellido = “Perez”;
emp.Gerencia = “Administración”;
Definición de Método:

[Modificadores] tiporetorno NombreMetodo ( [Lista de Argumentos] ) {


Bloque de Código;
}

Ejemplo

public class Empleado {


String Nombre;
String Apellido;
String Gerencia;

public void addDays (int days) {


Bloque de código del método;
}

Este método llamado addDays recibe como parámetro la variable de tipo entera (int) days y no
devuelve valores (void).

Empleado emp = new Empleado();


emp.Nombre = “Juan”;
emp.Apellido = “Perez”;
emp.Gerencia = “Administración”;
emp.addDays(4);

This
La palabra reservada This es utilizada para hacer referencia al objeto actual.

Ejemplo
public class MyDate{
int day, month, year;
public int mostrarDia(){
return this.day;
}
}

Encapsulamiento

Para encapsular los atributos de una clase se utiliza el modificador private antepuesto al tipo de
dato del mismo. Y se construyen dos métodos (para los casos en que aplique) con el
modificador public, que alteren y muestren el contenido del mismo.
Ejemplo

public class MyDate {


private int day;
private int month;
private int year;

// Método de alteración del contenido del atributo.


public void setDay(int newvalueofday){
//validaciones del nuevo valor.
this.day = newvalueofday;
}
// Método para mostrar el valor.
public int getDay(){
return this.day;
}
}

Sobrecarga de Métodos

En ciertas circunstancias se necesitan desarrollar varios métodos en una misma clase que
básicamente hacen la misma tarea, pero con diferentes argumentos. Podemos considerar un
método simple que solo imprima una línea de texto. Este método lo podemos llamar
imprimirLinea().
Ahora supongamos que necesitamos diferentes métodos de impresión para cada uno de los
siguientes tipos de datos: int, float,String, ya que es razonable que cada uno se imprima de
manera distinta. Se podrían crear tres métodos, uno para cada tipo de datos que se podrían
llamar imprimirLineaInt(); imprimirLineaFloat();imprimirLineaString() respectivamente aunque
esto es tedioso.

Java permite reutilizar los nombres de los métodos solo si, difieren en los argumentos que
recibe o el tipo de dato que retorna, en caso contrario la clase no compilara.

Ejemplo.

public void imprimirLinea (int i);


public void imprimirLinea (float f);
public void imprimirLinea (String s);

Constructores

Los constructores son métodos especiales que se ejecutan por única vez en el momento de
creación de una instancia de una clase (objeto), con el fin de inicializar el contenido de sus
atributos.
Los constructores son identificados por las siguientes características:
a) El nombre del método se corresponde exactamente con el nombre de la clase.
b) El método no devuelve ningún tipo de dato.
Ejemplos
1) Un solo Constructor:

public class Xyx{

// Declaración de atributos...

public Xyz(){
// Inicialización del objeto
}

// resto de los métodos.


}

2) Sobrecarga de Constructores:

public class Employee{

// Declaración de atributos
private String name;
private int salary;

public Employee(String n, int s) {


this.name = n;
this.salary = s;
}
public Employee(String n) {
this(n,0);
}
public Employee() {
this(“Unknow”);
}

// resto de los métodos.


}

Los constructores son invocados solo con la utilización de la instrucción new.


Si la clase no tiene una definición explicita de constructor, el lenguaje de programación le
provee uno en tiempo de ejecución. Este constructor no recibe ningún parámetro y no efectúa
ninguna operación, solo se utiliza para poder instanciar nuevos objetos con la instrucción new.

Herencia

En Java solo se puede heredar de una sola clase (herencia simple), esto permite definir una
clase en base a otra ya creada. Esto se define mediante la palabra extends, y se heredan
todos los métodos y los atributos de la superclase. No son heredables los constructores.
Si no se definiera en forma explicita la herencia desde una clase se heredara de la clase Object
por defecto.
Ejemplo.

public class Persona {


private String nombre;
private String apellido;

public void cambiarNombre( String nuevonombre) {


nombre = nuevonombre;
}
}

public class Empleado extends Persona {


private String cargo;
public String getCargo(){
return cargo;
}
}

Empleado emp = new Empleado();


emp.cambiarNombre(“Carlos”);

Super
La palabra super se utiliza para referirse a métodos y/o atributos de la superclase.
Generalmente se la utilizar para reescribir métodos heredados sin reemplazar su
funcionamiento sino agregándole la nueva funcionalidad.

Ejemplo

public class Empleado {


private string nombre;
private int sueldo;

public String getDetalles(){


return “Nombre: “ + nombre + “ – Sueldo: “ + sueldo;
}
}

public class Gerente extends Empleado {


private String gerencia;
public String getDetalles() {
return super.getDetalles() + “ – Gerencia: ” + gerencia;
}
}

Gerente ger = new Gerente();


ger.nombre = “Jose”;
ger.sueldo = 3000;
ger.gerencia = “Administración”;
System.out.println(ger.getDetalles());

La salida será:

Nombre: Jose – Sueldo: 3000 – Gerencia: Administracion


Clases Abstractas
Una de las características más útiles de cualquier lenguaje orientado a objetos es la posibilidad
de declarar clases que definen como se utiliza solamente, sin tener que implementar métodos.
Esto es muy útil cuando la implementación es específica para cada usuario, pero todos los
usuarios tienen que utilizar los mismos métodos. Un ejemplo de clase abstracta en Java es la
clase Graphics:
public abstract class Graphics {
public abstract void drawLine( int x1,int y1,int x2,
int y2 );
public abstract void drawOval( int x,int y,int width,
int height );
public abstract void drawArc( int x,int y,int width,
int height,int startAngle,int arcAngle );
. . .
}
Los métodos se declaran en la clase Graphics, pero el código que ejecutará el método está en
algún otro sitio:
public class MiClase extends Graphics {
public void drawLine( int x1,int y1,int x2,int y2 ) {
<código para pintar líneas -específico de
la arquitectura->
}
}
Cuando una clase contiene un método abstracto tiene que declararse abstracta. No obstante,
no todos los métodos de una clase abstracta tienen que ser abstractos. Las clases abstractas
no pueden tener métodos privados (no se podrían implementar). Una clase abstracta tiene que
derivarse obligatoriamente, no se puede hacer un new de una clase abstracta.

Interfaces
Los métodos abstractos son útiles cuando se quiere que cada implementación de la clase
parezca y funcione igual, pero necesita que se cree una nueva clase para utilizar los métodos
abstractos.
Los interfaces proporcionan un mecanismo para abstraer los métodos a un nivel superior.
Un interface contiene una colección de métodos que se implementan en otro lugar. Los
métodos de una clase son public, static y final.
La principal diferencia entre interface y abstract es que un interface proporciona un mecanismo
de encapsulación de los protocolos de los métodos sin forzar al usuario a utilizar la herencia.
Por ejemplo:
public interface VideoClip {
// comienza la reproducción del video
void play();
// reproduce el clip en un bucle
void bucle();
// detiene la reproducción
void stop();
}
Las clases que quieran utilizar el interface VideoClip utilizarán la palabra implements y
proporcionarán el código necesario para implementar los métodos que se han definido para el
interface:
class MiClase implements VideoClip {
void play() {
<código>
}
void bucle() {
<código>
}
void stop() {
<código>
}
Al utilizar implements para el interface es como si se hiciese una acción de copiar-y-pegar del
código del interface, con lo cual no se hereda nada, solamente se pueden usar los métodos.
La ventaja principal del uso de interfaces es que una clase interface puede ser implementada
por cualquier número de clases, permitiendo a cada clase compartir el interfaz de programación
sin tener que ser consciente de la implementación que hagan las otras clases que implementen
el interface.
class MiOtraClase implements VideoClip {
void play() {
<código nuevo>
}
void bucle() {
<código nuevo>
}
void stop() {
<código nuevo>
}

Agrupando Clases

Empaquetando
Java provee un mecanismo de agrupación de clases relacionadas en “Paquetes”.
Se puede indicar en la definición de la clase a que paquete o grupo pertenece la misma
utilizando la instrucción package.
Solo una declaración de paquete se permite por clase y por archivo de código fuente.
Los nombres de paquetes deben ser jerárquicos y las jerarquías estar separadas por puntos.
La definición de paquete debe estar al comienzo del archivo de código fuente.

Ejemplo

package telecom.sistemas;
public class Empleado {
//Contenido de la clase.
}

Importación

Para poder utilizar el contenido de los paquetes en nuestras clases debemos decirle al
compilador java donde encontrarlas. De hecho el nombre del paquete forma parte del nombre
de la clase, y uno se puede referir a la clase como telecom.sistemas.Empleado o simplemente
como la clase Empleado.
Puedo agregar tantas importaciones como necesite.

Ejemplos

import java.applet.Applet;
import java.awt.*;

public class Empleado extends Applet {


contenido de la clase:
}
Ejemplo Completo

public class Persona {


//Atributos.
private String nombre;
private String apellido;
private int edad;

//Metodos Constructores.
public Persona(String nom, String ape, int eda){
this.setNombre(nom);
this.setApellido(ape);
this.setEdad(eda);
}

public Persona(String nom, String ape){


this(nom,ape,1);
}

public Persona(String nom){


this(nom,"Sin Apellido");
}

public Persona(){
this("Sin Nombre");
}

//Metodos de la clase.
public String getDetalles(){
return "Nombre: " + this.getNombre() + " - Apellido: " +
this.getApellido() + " - Edad:" + this.getEdad();
}

//Metodos para acceder a la clase.


public int getEdad(){
return this.edad;
}

public boolean setEdad(int nuevaEdad){


if (nuevaEdad > 90) {
return false;
}
else {
this.edad = nuevaEdad;
return true;
}
}
public String getNombre(){
return this.nombre;
}

public boolean setNombre(String nuevoNombre){


if (nuevoNombre == "") {
return false;
}
else {
this.nombre = nuevoNombre;
return true;
}
}
public String getApellido(){
return this.apellido;
}

public boolean setApellido(String nuevoApellido){


if (nuevoApellido == "") {
return false;
}
else {
this.apellido = nuevoApellido;
return true;
}
}
}

public class Empleado extends Persona {


// Atributos.
private int sueldo;
private String gerencia;

//Metodos Constructores
public Empleado(String ger, int sue){
this.setGerencia(ger);
this.setSueldo(sue);
}
public Empleado(String ger){
this(ger,0);
}
public Empleado(){
this("Sin Gerencia");
}

//Metodos de la clase.
public String getDetalles(){
return super.getDetalles() + " - Sueldo: " + this.getSueldo() +
" - Gerencia: " + this.getGerencia();
}

//Metodos para Acceder a los atributos.


public int getSueldo(){
return this.sueldo;
}

public boolean setSueldo(int nuevoSueldo){


if (nuevoSueldo == 0) {
return false;
}
else {
this.sueldo = nuevoSueldo;
return true;
}
}

public String getGerencia(){


return this.gerencia;
}

public boolean setGerencia(String nuevaGerencia){


if (nuevaGerencia == "") {
return false;
}
else {
this.gerencia = nuevaGerencia;
return true;
}
}
}

import Persona;
import Empleado;
public class EjemploHerencia {

public static void main(String args[]){


Empleado emp = new Empleado();

System.out.println("Detalles: " + emp.getDetalles());

emp.setNombre("Gustavo");
emp.setApellido("Garcia");
emp.setEdad(27);
emp.setGerencia("CRM Masivo");
emp.setSueldo(1000);

System.out.println("Detalles: " + emp.getDetalles());

}
}
Modulo 3 – Manejo de Errores
Excepciones

Una clase de tipo “Excepcion” define las condiciones de error leves que pueda contener el
programa. También es posible escribir código que maneje excepciones y haga continuar la
ejecución del programa.

Cualquier condición anormal que pueda trabar la normal ejecución del programa es un error o
una excepción. Estos pueden ocurrir cuando:

El archivo que se está tratando de abrir no existe.


La conexión a la red se interrumpe.
La clase buscada no se encuentra.
Etc.

Una clase de tipo “Error” define condiciones de error serias que no deben ser recuperadas y
deben permitir que el programa termine.

Java permite que los errores encontrados sean recuperados y utilizados en el programa, de
esta forma se pueden manejar.

Por ejemplo:

public class HolaMundo {


public static void main (String args[]) {
int i = 0;
String greetings [] = {
“Hola mundo!”,
“No, me equivoque!”,
“HOLA MUNDO!!”
};

while (i < 4) {
System.out.println (greetings [i]);
i++;
}
}
}

Al ejecutarse el ciclo por cuarta vez el programa terminará con el siguiente mensaje de error:
java.lang.ArrayIndexOutOfBoundsException: 3 at HolaMundo.main (HolaMundo.java:12)

El manejo de excepciones permite al programa tomar la excepción, manejarla y continuar con


la ejecución del mismo.

Manejo de Excepciones

Instrucciones try y catch

Para manejar una excepción en particular se debe colocar todo el código que la puede producir
dentro de la instrucción try, y crear una lista de posibles excepciones para cada tipo de error
con catch.
try {
// Código del programa que puede ejecutar alguna excepción.
}
catch (MiTipoDeExcepcion e ) {
//Código a ejecutarse si MiTipoDeExcepcion es disparada.
}
catch (Exception e) {
// Código a ejecutarse si una excpeción general es disparada.
}

El mecanismo del llamado por pila:

Si hay clases que son llamadas por otras, y generan una excepción, ésta es buscada primero
en la clase que la genera, si no la encuentra, busca en la clase que la llamó, y así
sucesivamente hasta encontrarla, si no la encuentra genera un mensaje de error y termina el
programa.

Instrucción finally

Define un bloque de código que siempre se ejecuta, independientemente de si el programa


efectuó una excepción.
Ejemplo:

try {
ComenzarRiego () ;
AguaRiego ();
}
finally {
TerminarRiego();
}

En el ejemplo anterior TerminarRiego es ejecutada aunque haya existido alguna exceptión


mientras ComenzarRiego se ejecutaba. El código dentro de las llaves del try es llamado “código
protegido”.
La instrucción finally pudiera no ser ejecutada solo en el caso de que el método System.exit ()
fuera ejecutado dentro del código protegido, ya que ésta deriva la ejecución normal de
programa, terminándolo.
Si la instrucción return fuera ejecutada dentro del try, el código de finally sería ejecutado antes
de ésta.

Categorías de Excepciones

La clase java.lang.Throwable es la superclase que contiene todos los objetos que pueden
utilizar mecanismos de manejo de errores.
Las tres clases más importantes son:
Error: Indica un problema severo difícil de recuperar, por ejemplo cuando el programa corre
fuera de memoria.

RuntimeException: Indica un problema de diseño o implementación, por ejemplo,


ArrayIndexOutOfBoundsException, nunca debería darse si los índices del array no pasaran el
largo del mismo.

Exception: Otras excepciones indican dificultades en tiempo de ejecución que usualmente son
causadas por efectos del entorno y pueden ser manejadas. Por ejemplo archivos no
encontrados o URL invalida.

Excepciones Comunes:
Java provee excepciones predefinidas como:

ArithmeticException – El resultado de dividir por cero una operación de enteros.


NullPointerException – Intentar acceder a algún método o atributo de un objeto sin que éste
esté instanciado.
NegativeArraySizeException – Intentar crear un array son un tamaño negativo.
ArrayIndexOutOfBoundsException – Intentar acceder a un elemento del array mayor al tamaño
del mismo.
ScurityException – Se suele generar en un browser, la clase SecurityManager genera esa
excepción por applets que intentan : Acceder a una fila local, Abrir un socket en el servidor que
no es el mismo que sirve el applet, Ejecutar otro programa en el entorno de ejecución.

Jerarquía de Excepciones

OutOfMemory
Error
VirtualMachine
Error
StackOverflow
Error
Error

AWTError

Arithmetic
Excpetion

Throwable

NullPointer
RuntimeExcepti
Exception
on

IndexOu
tOfBound
Exception

EOFException

IOException
FileNotFound
Exception

La excepción Throwable no debe ser usada, en su defecto, se deben utilizar las subclases
antes descriptas.
Ejemplo completo:

public class HolaMundo {


public static void main (String args[]) {
int i = 0;
String greetings [] = {
“Hola mundo!”,
“No, me equivoque!”,
“HOLA MUNDO!!”
};
while (i < 4) {
try {
System.out.println (greetings [i]);
}
catch (ArrayIndexOutOfBoundsException e) {
System.out.println (“Actualice los índices”);
I = -1;
}
finally {
System.out.println (“Esto siempre se imprime”);
}
i++;
}
}
}
Modulo 4 – Applets

Applet
Un applet es "una pequeña aplicación accesible en un servidor, que se transporta por la red, se
instala automáticamente y se ejecuta in situ como parte de un documento web". Es una
aplicación pretendidamente corta basada en un formato gráfico sin representación
independiente: es decir, se trata de un elemento a embeber en otras aplicaciones; es un
componente en su sentido estricto.

Ciclo de vida de un Applet


Cuando un applet se carga en el browser, comienza su ciclo de vida, que pasaría por las
siguientes fases:
Se crea una instancia de la clase que controla el applet..
El applet se inicializa.
El applet comienza a ejecutarse.
El applet empieza a recibir llamadas.
Primero recibe una llamada init (inicializar), seguida de un mensaje start (empezar) y paint
(pintar). Estas llamadas pueden ser recibidas asincrónicamente.

El método init() se llama cada vez que el browser carga por primera vez la clase. Si el applet
llamado no lo sobrecarga, init() no hace nada. Fundamentalmente en este método se debe fijar
el tamaño del applet, aunque en el caso de Netscape el tamaño que vale es el que se indique
en la línea del fichero html que cargue el applet. También se deben realizar en este método las
cargas de imágenes y sonidos necesarios para la ejecución del applet. Y, por supuesto, la
asignación de valores a las variables globales a la clase que se utilicen. En el caso de los
applet, este método únicamente es llamado por el sistema al cargar el applet.

El método start() es la llamada para arrancar el applet cada vez que es visitado. La clase
Applet no hace nada en este método. Las clases derivadas deben sobrecargarlo para
comenzar la animación, el sonido, etc. Esta función es llamada automáticamente cada vez que
la zona de visualización en que está ubicado el applet se expone a la visión, a fin de optimizar
en uso de los recursos del sistema y no ejecutar algo que no puede ser apreciado. Esto es,
imaginemos que cargamos un applet en un navegador minimizado; el sistema llamará al
método init(), pero no a start(), que sí será llamado cuando restauremos el navegador a un
tamaño que permita ver el applet. Naturalmente, start() se puede ejecutar varias veces: la
primera tras init() y las siguientes (porque init() se ejecuta solamente una vez) tras haber
aplicado el método stop().

El método stop() es la llamada para detener la ejecución del applet. Se llama cuando el applet
desaparece de la pantalla. La clase Applet tampoco hace nada en este método, que debería
ser sobrecargado por las clases derivadas para detener la animación, el sonido, etc. Esta
función es llamada cuando el navegador no incluye en su campo de visión al applet; por
ejemplo, cuando abandona la página en que está insertado, de forma que el programador
puede paralizar las funciones que no resulten necesarias respecto de un applet no visible, y
luego recuperar su actividad mediante el método start().

El método destroy() se llama cuando ya no se va a utilizar más el applet, cuando se necesita


que sean liberados todos los recursos dispuestos por el applet, por ejemplo, cuando se cierra el
navegador. La clase Applet no hace nada en este método. Las clases derivadas deberían
sobrecargarlo para hacer una limpieza final.

El método paint ( Graphics g ) es llamado cada vez que el área de dibujo del applet necesita
ser refrescada. La clase Applet simplemente dibuja un rectángulo gris en el área, es la clase
derivada, obviamente, la que debería sobrecargar este método para representar algo
inteligente en la pantalla. Cada vez que la zona del applet es cubierta por otra ventana, se
desplaza el applet fuera de la visión o el applet cambia de posición debido a un
redimensionamiento del navegador, el sistema llama automáticamente a este método, pasando
como argumento un objeto de tipo Graphics que delimita la zona a ser pintada; en realidad se
pasa una referencia al contexto gráfico en uso, y que representa la ventana del applet en la
página web.

El metodo update( Graphics g ) es el que realmente se llama cuando se necesita una


actualización de la pantalla. La clase Applet simplemente limpia el área y llama al método
paint(). Esta funcionalidad es suficiente para la mayoría de los casos; aunque, de cualquier
forma, las clases derivadas pueden sustituir esta funcionalidad para sus propósitos especiales.

Llamando al metodo repaint() se podrá forzar la actualización de un applet.

Ejemplo

import java.awt.Graphics;
import java.applet.Applet;

public class HolaMundo extends Applet {


public void paint( Graphics g ) {
g.drawString( "Hola Curso Java!",25,25 ) ;
}
}

Ejemplo de archivo contenedor


Holamundo.html
<APPLET>:
<APPLET CODE="HolaMundo.class" WIDTH=100 HEIGHT=50>
</APPLET>

Esta marca html llama al applet HolaMundo.class y establece su ancho y alto inicial.
Los atributos obligatorios que acompañan a la etiqueta <APPLET> son:
Code: Indica el fichero de clase ejecutable, que tiene la extensión .class.
Width: Indica la anchura inicial que el navegador debe reservar para el applet en pixels.
Height: Indica la altura inicial en pixels.
Ejemplo completo

import java.awt.*;
import java.applet.Applet;

public class HolaMundo extends Applet {


//Atributos de la clase.
Font f;

//Metodos Constructores.
public void init(){
f = new Font("Arial",Font.BOLD,36);
}

// Sobreescribo Metodos de Applet.


public void paint( Graphics screen ) {
screen.setFont(f);
screen.setColor(Color.red);
screen.drawString( "Hola Mundo Applet!",5,40 ) ;
}
}

Holamundo.html
<HTML>
<HEAD>
<TITLE>Hola Mundo version Applet</TITLE>
</HEAD>
<BODY>
<APPLET>
<APPLET CODE="HolaMundo.class" WIDTH=600 HEIGHT=100>
</APPLET>
</BODY>
</HTML>
Modulo 5 – Desarrollo de Aplicaciones Graficas
Introducción al AWT

AWT es el acrónimo de Abstract Window Toolkit.Se trata de una biblioteca de clases Java para
el desarrollo de Interfaces de Usuario Gráficas.

La estructura básica del AWT se basa en Componentes y Contenedores. Estos últimos


contienen Componentes posicionados a su respecto y son Componentes a su vez, de forma
que los eventos pueden tratarse tanto en Contenedores como en Componentes, corriendo por
cuenta del programador el encaje de todas las piezas, así como la seguridad de tratamiento de
los eventos adecuados.

Debido a que el lenguaje de programación Java es independiente de la plataforma en que se


ejecuten sus aplicaciones, el AWT también es independiente de la plataforma en que se
ejecute. El AWT proporciona un conjunto de herramientas para la construcción de interfaces
gráficas que tienen una apariencia y se comportan de forma semejante en todas las
plataformas en que se ejecute. Los elementos de interface proporcionados por el AWT están
implementados utilizando toolkits nativos de las plataformas, preservando una apariencia
semejante a todas las aplicaciones que se creen para esa plataforma.

La estructura de la versión actual del AWT podemos resumirla en los puntos que exponemos a
continuación:
Los Contenedores contienen Componentes, que son los controles básicos
No se usan posiciones fijas de los Componentes, sino que están situados a través de una
disposición controlada (layouts)
El común denominador de más bajo nivel se acerca al teclado, ratón y manejo de eventos
Alto nivel de abstracción respecto al entorno de ventanas en que se ejecute la aplicación (no
hay áreas cliente, ni llamadas a X, ni hWnds, etc.)
La arquitectura de la aplicación es dependiente del entorno de ventanas, en vez de tener un
tamaño fijo

Componentes y Contenedores
Una interface gráfica está construida en base a elementos gráficos básicos, los componentes.
Típicos ejemplos de estos Componentes son los botones, barras de desplazamiento, etiquetas,
listas, cajas de selección o campos de texto. Los Componentes permiten al usuario interactuar
con la aplicación y proporcionar información desde el programa al usuario sobre el estado del
programa. En el AWT, todos los Componentes de la interface de usuario son instancias de la
clase Component o uno de sus subtipos.
Los Componentes no se encuentran aislados, sino agrupados dentro de Contenedores. Los
Contenedores contienen y organizan la situación de los Componentes; además, los
Contenedores son en sí mismos Componentes y como tales pueden ser situados dentro de
otros Contenedores. También contienen el código necesario para el control de eventos,
cambiar la forma del cursor o modificar el icono de la aplicación. En el AWT, todos los
Contenedores son instancias de la clase Container o uno de sus subtipos.
Los Componentes deben circunscribirse dentro del Contenedor que los contiene. Esto hace
que el anidamiento de Componentes (incluyendo Contenedores) en Contenedores crean
árboles de elementos, comenzando con un Contenedor en la raiz del árbol y expandiéndolo en
sus ramas. A continuación presentamos el árbol que representa la interface que corresponde
con la aplicación gráfica generada anteriormente.

PANELES
La clase Panel es el más simple de los Contenedores de Componentes gráficos. En realidad,
se trataba de crear una clase no-abstracta (Container sí lo es) que sirviera de base a los applet
y a otras pequeñas aplicaciones. La clase Panel consta de dos métodos propios: el constructor,
cuyo fin es crear un nuevo Panel con un LayoutManager de tipo FlowLayout (el de defecto), y el
método addNotify() que, sobrecargando la función del mismo nombre en la clase Container,
llama al método createPanel() del Toolkit adecuado, creando así un PanelPeer. El AWT
enviará así al Panel (y por tanto al applet) todos los eventos que sobre él ocurran. Esto que
puede parecer un poco rebuscado, obedece al esquema arquitectónico del AWT; se trata del
bien conocido esquema de separación interface/implementación que establece por un lado una
clase de interface y por otro distintas clases de implementación para cada una de las
plataformas elegidas.
El uso de Paneles permite que las aplicaciones puedan utilizar múltiples layouts, es decir, que
la disposición de los componentes sobre la ventana de visualización pueda modificarse con
mucha flexibilidad. Permite que cada Contenedor pueda tener su propio esquema de fuentes
de caracteres, color de fondo, zona de diálogo, etc.

herramientas y a la barra de estado con new; al contrario que en C++, en Java todos los
objetos deben ser creados con el operador new:
add( "North",tb = new ToolBar() );
add( "South",sb = new StatusBar() );
También vamos a incorporar un nuevo evento a nuestro controlador, para que maneje los
eventos de tipo ACTION_EVENT que le llegarán cuando se pulsen los botones de la barra de
herramientas o se realice alguna selección, etc.
case Event.ACTION_EVENT:
{
be.verEstado( evt.arg.toString() );
return true;
}
Cuando la aplicación reciba este tipo de evento, alterará el contenido de la barra de estado
para mostrar la información de la selección realizada o el botón pulsado.

Al final, la apariencia de la aplicación en pantalla es la que presenta la figura anterior.


LAYOUTS
Los layout managers o manejadores de composición, en traducción literal, ayudan a adaptar los
diversos Componentes que se desean incorporar a un Panel, es decir, especifican la apariencia
que tendrán los Componentes a la hora de colocarlos sobre un Contenedor. Java dispone de
varios, en la actual versión, tal como se muestra en la imagen:

¿Por qué Java proporciona estos esquemas predefinidos de disposición de componentes? La


razón es simple: imaginemos que deseamos agrupar objetos de distinto tamaño en celdas de
una rejilla virtual: si confiados en nuestro conocimiento de un sistema gráfico determinado,
codificamos a mano tal disposición, deberemos preveer el redimensionamiento del applet, su
repintado cuando sea cubierto por otra ventana, etc., además de todas las cuestiones
relacionadas con un posible cambio de plataforma (uno nunca sabe a donde van a ir a parar los
propios hijos, o los applets).
Modulo 6 – MultiProgramacion
Considerando el entorno multithread, cada thread (hilo, flujo de control del programa)
representa un proceso individual ejecutándose en un sistema. A veces se les llama procesos
ligeros o contextos de ejecución. Típicamente, cada thread controla un único aspecto dentro de
un programa, como puede ser supervisar la entrada en un determinado periférico o controlar
toda la entrada/salida del disco. Todos los threads comparten los mismos recursos, al contrario
que los procesos en donde cada uno tiene su propia copia de código y datos (separados unos
de otros). Gráficamente, los threads se parecen en su funcionamiento a lo que muestra la figura
siguiente:

Flujo de Programas
Programas de flujo único
Un programa de flujo único o mono-hilvanado (single-thread) utiliza un único flujo de control
(thread) para controlar su ejecución. Muchos programas no necesitan la potencia o utilidad de
múltiples flujos de control. Sin necesidad de especificar explícitamente que se quiere un único
flujo de control, muchos de los applets y aplicaciones son de flujo único.

Por ejemplo, en nuestra aplicación estándar de saludo:


public class HolaMundo {
static public void main( String args[] ) {
System.out.println( "Hola Mundo!" );
}
}
Aquí, cuando se llama a main(), la aplicación imprime el mensaje y termina. Esto ocurre dentro
de un único thread.

Programas de flujo múltiple


En nuestra aplicación de saludo, no vemos el thread que ejecuta nuestro programa. Sin
embargo, Java posibilita la creación y control de threads explícitamente. La utilización de
threads en Java, permite una enorme flexibilidad a los programadores a la hora de plantearse
el desarrollo de aplicaciones. La simplicidad para crear, configurar y ejecutar threads, permite
que se puedan implementar muy poderosas y portables aplicaciones/applets que no se puede
con otros lenguajes de tercera generación. En un lenguaje orientado a Internet como es Java,
esta herramienta es vital.
Si se ha utilizado un navegador con soporte Java, ya se habrá visto el uso de múltiples threads
en Java. Habrá observado que dos applet se pueden ejecutar al mismo tiempo, o que puede
desplazar la página del navegador mientras el applet continúa ejecutándose. Esto no significa
que el applet utilice múltiples threads, sino que el navegador es multithreaded.
Las aplicaciones (y applets) multithreaded utilizan muchos contextos de ejecución para cumplir
su trabajo. Hacen uso del hecho de que muchas tareas contienen subtareas distintas e
independientes. Se puede utilizar un thread para cada subtarea.
Mientras que los programas de flujo único pueden realizar su tarea ejecutando las subtareas
secuencialmente, un programa multithreaded permite que cada thread comience y termine tan
pronto como sea posible. Este comportamiento presenta una mejor respuesta a la entrada en
tiempo real.
Ejemplo de Hola Mundo version Threads

public class MyThread extends Thread {


private int intervalo;
private String descripcion;

public void setIntervalo(int nuevointervalo){


intervalo = nuevointervalo;
}
public int getIntervalo(){
return intervalo;
}
public void setDescripcion(String nuevadescripcion){
descripcion = nuevadescripcion;
}
public String getDescripcion(){
return descripcion;
}
public MyThread(String nuevadescripcion){
this.setDescripcion(nuevadescripcion);
}

public void run(){


while(true){
try {
this.setIntervalo((int)(Math.random()*1000));
this.sleep(this.getIntervalo());
System.out.println("Descripcion: " + this.getDescripcion()
+ " - Intervalo:" + this.getIntervalo());
}
catch (Exception e) {
System.out.println("Error en Tbread");
}
}
}
}

import MyThread;
public class TesterMyThread {

public static void main (String args[]){


try {
MyThread th1 = new MyThread("Hola Mundo - Thread 1");
MyThread th2 = new MyThread("Hola Mundo - Thread 2");
MyThread th3 = new MyThread("Hola Mundo - Thread 3");

th1.start();
th2.start();
th3.start();

}
catch (Exception e) {
System.out.println("Error en Main");
}
}
}
CREACION Y CONTROL DE THREADS
Creación de un Thread
Hay dos modos de conseguir threads en Java. Una es implementando la interface Runnable, la
otra es extender la clase Thread.
La implementación de la interface Runnable es la forma habitual de crear threads. Las
interfaces proporcionan al programador una forma de agrupar el trabajo de infraestructura de
una clase. Se utilizan para diseñar los requerimientos comunes al conjunto de clases a
implementar. La interface define el trabajo y la clase, o clases, que implementan la interface
realizan ese trabajo. Los diferentes grupos de clases que implementen la interface tendrán que
seguir las mismas reglas de funcionamiento.
Hay una cuantas diferencias entre interface y clase. Primero, una interface solamente puede
contener métodos abstractos y/o variables estáticas y finales (constantes). Las clases, por otro
lado, pueden implementar métodos y contener variables que no sean constantes. Segundo,
una interface no puede implementar cualquier método. Una clase que implemente una interface
debe implementar todos los métodos definidos en esa interface. Una interface tiene la
posibilidad de poder extenderse de otras interfaces y, al contrario que las clases, puede
extenderse de múltiples interfaces. Además, una interface no puede ser instanciada con el
operador new; por ejemplo, la siguiente sentencia no está permitida:
Runnable a = new Runnable(); // No se permite
El primer método de crear un thread es simplemente extender la clase Thread:
class MiThread extends Thread {
public void run() {
...
}
El ejemplo anterior crea una nueva clase MiThread que extiende la clase Thread y sobrecarga
el método Thread.run() por su propia implementación. El método run() es donde se realizará
todo el trabajo de la clase. Extendiendo la clase Thread, se pueden heredar los métodos y
variables de la clase padre. En este caso, solamente se puede extender o derivar una vez de la
clase padre. Esta limitación de Java puede ser superada a través de la implementación de
Runnable:
public class MiThread implements Runnable {
Thread t;
public void run() {
// Ejecución del thread una vez creado
}
}
En este caso necesitamos crear una instancia de Thread antes de que el sistema pueda
ejecutar el proceso como un thread. Además, el método abstracto run() está definido en la
interface Runnable tiene que ser implementado. La única diferencia entre los dos métodos es
que este último es mucho más flexible. En el ejemplo anterior, todavía tenemos oportunidad de
extender la clase MiThread, si fuese necesario. La mayoría de las clases creadas que
necesiten ejecutarse como un thread , implementarán la interface Runnable, ya que
probablemente extenderán alguna de su funcionalidad a otras clases.
No pensar que la interface Runnable está haciendo alguna cosa cuando la tarea se está
ejecutando. Solamente contiene métodos abstractos, con lo cual es una clase para dar idea
sobre el diseño de la clase Thread. De hecho, si vemos los fuentes de Java, podremos
comprobar que solamente contiene un método abstracto:
package java.lang;
public interface Runnable {
public abstract void run() ;
}
Y esto es todo lo que hay sobre la interface Runnable. Como se ve, una interface sólo
proporciona un diseño para las clases que vayan a ser implementadas. En el caso de
Runnable, fuerza a la definición del método run(), por lo tanto, la mayor parte del trabajo se
hace en la clase Thread. Un vistazo un poco más profundo a la definición de la clase Thread
nos da idea de lo que realmente está pasando:
public class Thread implements Runnable {
...
public void run() {
if( tarea != null )
tarea.run() ;
}
}
...
}
De este trocito de código se desprende que la clase Thread también implemente la interface
Runnable. tarea.run() se asegura de que la clase con que trabaja (la clase que va a ejecutarse
como un thread) no sea nula y ejecuta el método run() de esa clase. Cuando esto suceda, el
método run() de la clase hará que corra como un thread.
Arranque de un Thread
Las aplicaciones ejecutan main() tras arrancar. Esta es la razón de que main() sea el lugar
natural para crear y arrancar otros threads. La línea de código:
t1 = new TestTh( "Thread 1",(int)(Math.random()*2000) );
crea un nuevo thread. Los dos argumentos pasados representan el nombre del thread y el
tiempo que queremos que espere antes de imprimir el mensaje.
Al tener control directo sobre los threads, tenemos que arrancarlos explícitamente. En nuestro
ejemplo con:
t1.start();
start(), en realidad es un método oculto en el thread que llama al método run().
Manipulación de un Thread
Si todo fue bien en la creación del thread, t1 debería contener un thread válido, que
controlaremos en el método run().
Una vez dentro de run(), podemos comenzar las sentencias de ejecución como en otros
programas. run() sirve como rutina main() para los threads; cuando run() termina, también lo
hace el thread. Todo lo que queramos que haga el thread ha de estar dentro de run(), por eso
cuando decimos que un método es Runnable, nos obliga a escribir un método run().
En este ejemplo, intentamos inmediatamente esperar durante una cantidad de tiempo aleatoria
(pasada a través del constructor):
sleep( retardo );
El método sleep() simplemente le dice al thread que duerma durante los milisegundos
especificados. Se debería utilizar sleep() cuando se pretenda retrasar la ejecución del thread.
sleep() no consume recursos del sistema mientras el thread duerme. De esta forma otros
threads pueden seguir funcionando. Una vez hecho el retardo, se imprime el mensaje "Hola
Mundo!" con el nombre del thread y el retardo.
Suspensión de un Thread
Puede resultar útil suspender la ejecución de un thread sin marcar un límite de tiempo. Si, por
ejemplo, está construyendo un applet con un thread de animación, querrá permitir al usuario la
opción de detener la animación hasta que quiera continuar. No se trata de terminar la
animación, sino desactivarla. Para este tipo de control de thread se puede utilizar el método
suspend().
t1.suspend();
Este método no detiene la ejecución permanentemente. El thread es suspendido
indefinidamente y para volver a activarlo de nuevo necesitamos realizar una invocación al
método resume():
t1.resume();
Parada de un Thread
El último elemento de control que se necesita sobre threads es el método stop(). Se utiliza para
terminar la ejecución de un thread:
t1.stop();
Esta llamada no destruye el thread, sino que detiene su ejecución. La ejecución no se puede
reanudar ya con t1.start(). Cuando se desasignen las variables que se usan en el thread, el
objeto thread (creado con new) quedará marcado para eliminarlo y el garbage collector se
encargará de liberar la memoria que utilizaba.
En nuestro ejemplo, no necesitamos detener explícitamente el thread. Simplemente se le deja
terminar. Los programas más complejos necesitarán un control sobre cada uno de los threads
que lancen, el método stop() puede utilizarse en esas situaciones.
Si se necesita, se puede comprobar si un thread está vivo o no; considerando vivo un thread
que ha comenzado y no ha sido detenido.
t1.isAlive();
Este método devolverá true en caso de que el thread t1 esté vivo, es decir, ya se haya llamado
a su método run() y no haya sido parado con un stop() ni haya terminado el método run() en su
ejecución.

ARRANCAR Y PARAR THREADS


Ahora que ya hemos visto por encima como se arrancan, paran y manipulan threads, vamos a
mostrar un ejemplo un poco más gráfico, se trata de un contador, cuyo código
(App1Thread.java) es el siguiente:
import java.awt.*;
import java.applet.Applet;

public class App1Thread extends Applet implements Runnable {


Thread t;
int contador;

public void init() {


contador = 0;
t = new Thread( this );
t.start();
}

public void run() {


while( true )
{
contador++;
repaint();
try {
t.sleep( 10 );
} catch( InterruptedException e ) {
;
};
}
}

public boolean mouseDown( Event evt,int x,int y ) {


t.stop();
return( true );
}

public void paint( Graphics g ) {


g.drawString( Integer.toString( contador ),10,10 );
System.out.println( "Contador = "+contador );
}

public void stop() {


t.stop();
}
}

Este applet arranca un contador en 0 y lo incrementa, presentando su salida tanto en la


pantalla gráfica como en la consola. Una primera ojeada al código puede dar la impresión de
que el programa empezará a contar y presentará cada número, pero no es así. Una revisión
más profunda del flujo de ejecución del applet, nos revelará su verdadera identidad.
En este caso, la clase App1Thread está forzada a implementar Runnable sobre la clase
Applet que extiende. Como en todos los applets, el método init() es el primero que se ejecuta.
En init(), la variable contador se inicializa a cero y se crea una nueva instancia de la clase
Thread. Pasándole this al constructor de Thread, el nuevo thread ya conocerá al objeto que va
a correr. En este caso this es una referencia a App1Thread. Después de que hayamos creado
el thread, necesitamos arrancarlo. La llamada a start(), llamará a su vez al método run() de
nuestra clase, es decir, a App1Thread.run(). La llamada a start() retornará con éxito y el thread
comenzará a ejecutarse en ese instante. Observar que el método run() es un bucle infinito. Es
infinito porque una vez que se sale de él, la ejecución del thread se detiene. En este método se
incrementará la variable contador, se duerme 10 milisegundos y envía una petición de refresco
del nuevo valor al applet.
Es muy importante dormirse en algún lugar del thread, porque sino, el thread consumirá todo el
tiempo de la CPU para su proceso y no permitirá que entren otros métodos de otros threads a
ejecutarse. Otra forma de detener la ejecución del thread es hacer una llamada al método
stop(). En el contador, el thread se detiene cuando se pulsa el ratón mientras el cursor se
encuentre sobre el applet. Dependiendo de la velocidad del ordenador, se presentarán los
números consecutivos o no, porque el incremento de la variable contador es independiente del
refresco en pantalla. El applet no se refresca a cada petición que se le hace, sino que el
sistema operativo encolará las peticiones y las que sean sucesivas las convertirán en un único
refresco. Así, mientras los refescos se van encolando, la variable contador se estará todavía
incrementando, pero no se visualiza en pantalla.
SUSPENDER Y REANUDAR THREADS
Una vez que se para un thread, ya no se puede rearrancar con el comando start(), debido a
que stop() concluirá la ejecución del thread. Por ello, en ver de parar el thread, lo que podemos
hacer es dormirlo, llamando al método sleep(). El thread estará suspendido un cierto tiempo y
luego reanudará su ejecución cuando el límite fijado se alcance. Pero esto no es útil cuando se
necesite que el thread reanude su ejecución ante la presencia de ciertos eventos. En estos
casos, el método suspend() permite que cese la ejecución del thread y el método resume()
permite que un método suspendido reanude su ejecución. En la siguiente versión de nuestra
clase contador, App2Thread.java, modificamos el applet para que utilice los métodos
suspend() y resume():
public class App2Thread extends Applet implements Runnable {
Thread t;
int contador;
boolean suspendido;

...

public boolean mouseDown( Event evt,int x,int y ) {


if( suspendido )
t.resume();
else
t.suspend();
suspendido = !suspendido;

return( true );
}
...
Para controlar el estado del applet, hemos introducido la variable suspendido. Diferenciar los
distintos estados de ejecución del applet es importante porque algunos métodos pueden
generar excepciones si se llaman desde un estado erróneo. Por ejemplo, si el applet ha sido
arrancado y se detiene con stop(), si se intenta ejecutar el método start(), se generará una
excepción IllegalThreadStateException.
ESTADOS DE UN THREAD
Durante el ciclo de vida de un thread, éste se puede encontrar en diferentes estados. La figura
siguiente muestra estos estados y los métodos que provocan el paso de un estado a otro. Este
diagrama no es una máquina de estados finita, pero es lo que más se aproxima al
funcionamiento real de un thread .
Modulo 7 – Comunicaciones
Todos los lenguajes de programación tienen alguna forma de interactuar con los sistemas de
ficheros locales; Java no es una excepción.
Cuando se desarrollan applets para utilizar en red, hay que tener en cuenta que la
entrada/salida directa a fichero es una violación de seguridad de acceso. Muchos usuarios
configurarán sus navegadores para permitir el acceso al sistema de ficheros, pero otros no.
Por otro lado, si se está desarrollando una aplicación Java para uso interno, probablemente
será necesario el acceso directo a ficheros.
Ficheros
Antes de realizar acciones sobre un fichero, necesitamos un poco de información sobre ese
fichero. La clase File proporciona muchas utilidades relacionadas con ficheros y con la
obtención de información básica sobre esos ficheros.
Creación de un objeto File
Para crear un objeto File nuevo, se puede utilizar cualquiera de los tres constructores
siguientes:
File miFichero;
miFichero = new File( "/etc/kk" );
o
miFichero = new File( "/etc","kk" );
o
File miDirectorio = new File( "/etc" );
miFichero = new File( miDirectorio,"kk" );
El constructor utilizado depende a menudo de otros objetos File necesarios para el acceso. Por
ejemplo, si sólo se utiliza un fichero en la aplicación, el primer constructor es el mejor. Si en
cambio, se utilizan muchos ficheros desde un mismo directorio, el segundo o tercer constructor
serán más cómodos. Y si el directorio o el fichero es una variable, el segundo constructor será
el más útil.
Comprobaciones y Utilidades
Una vez creado un objeto File, se puede utilizar uno de los siguientes métodos para reunir
información sobre el fichero:
Nombres de fichero
String getName()
String getPath()
String getAbsolutePath()
String getParent()
boolean renameTo( File nuevoNombre )
Comprobaciones
boolean exists()
boolean canWrite()
boolean canRead()
boolean isFile()
boolean isDirectory()
boolean isAbsolute()
Información general del fichero
long lastModified()
long length()
Utilidades de directorio
boolean mkdir()
String[] list()
Vamos a desarrollar una pequeña aplicación que muestra información sobre los ficheros
pasados como argumentos en la línea de comandos, InfoFichero.java:
import java.io.*;

class InfoFichero {

public static void main( String args[] ) throws IOException {


if( args.length > 0 )
{
for( int i=0; i < args.length; i++ )
{
File f = new File( args[i] );
System.out.println( "Nombre: "+f.getName() );
System.out.println( "Camino: "+f.getPath() );
if( f.exists() )
{
System.out.print( "Fichero existente " );
System.out.print( (f.canRead() ?
" y se puede Leer" : "" ) );
System.out.print( (f.canWrite() ?
" y se puese Escribir" : "" ) );
System.out.println( "." );
System.out.println( "La longitud del fichero son "+
f.length()+" bytes" );
}
else
System.out.println( "El fichero no existe." );
}
}
else
System.out.println( "Debe indicar un fichero." );
}
}

STREAMS DE ENTRADA
Hay muchas clases dedicadas a la obtención de entrada desde un fichero. Este es el esquema
de la jerarquía de clases de entrada por fichero:

Objetos FileInputStream
Los objetos FileInputStream típicamente representan ficheros de texto accedidos en orden
secuencial, byte a byte. Con FileInputStream, se puede elegir acceder a un byte, varios bytes o
al fichero completo.
Apertura de un FileInputStream
Para abrir un FileInputStream sobre un fichero, se le da al constructor un String o un objeto
File:
FileInputStream mi FicheroSt;
miFicheroSt = new FileInputStream( "/etc/kk" );
También se puede utilizar:
File miFichero FileInputStream miFicheroSt;
miFichero = new File( "/etc/kk" );
miFicheroSt = new FileInputStream(
miFichero );
Lectura de un FileInputStream
Una vez abierto el FileInputStream, se puede leer de él. El método read() tiene muchas
opciones:
int read();
Lee un byte y devuelve -1 al final del stream.
int read( byte b[] );
Llena todo el array, si es posible. Devuelve el número de bytes leídos o -1 si se alcanzó el final
del stream.
int read( byte b[],int offset,int longitud );
Lee longitud bytes en b comenzando por b[offset]. Devuelve el número de bytes leídos o -1 si
se alcanzó el final del stream.
Cierre de FileInputStream
Cuando se termina con un fichero, existen dos opciones para cerrarlo: explícitamente, o
implícitamente cuando se recicla el objeto (el garbage collector se encarga de ello).
Para cerrarlo explícitamente, se utiliza el método close():
miFicheroSt.close();
Ejemplo: Visualización de un fichero
Si la configuración de la seguridad de Java permite el acceso a ficheros, se puede ver el
contenido de un fichero en un objeto TextArea. El código siguiente contiene los elementos
necesarios para mostrar un fichero:
FileInputStream fis;
TextArea ta;

public void init() {


byte b[] = new byte[1024];
int i;

// El buffer de lectura se debe hacer lo suficientemente grande


// o esperar a saber el tamaño del fichero
String s;

try {
fis = new FileInputStream( "/etc/kk" );
} catch( FileNotFoundException e ) {
/* Hacer algo */
}

try {
i = fis.read( b );
} catch( IOException e ) {
/* Hacer algo */
}

s = new String( b,0 );


ta = new TextArea( s,5,40 );
add( ta );
}
Hemos desarrollado un ejemplo, Agenda.java, en el que partimos de un fichero agenda que
dispone de los datos que nosotros deseamos de nuestros amigos, como son: nombre, teléfono
y dirección. Si tecleamos un nombre, buscará en el fichero de datos si existe ese nombre y
presentará la información que se haya introducido. Para probar, intentar que aparezca la
información de Pepe.
Objetos DataInputStream
Los objetos DataInputStream se comportan como los FileInputStream. Los streams de datos
pueden leer cualquiera de las variables de tipo nativo, como floats, ints o chars. Generalmente
se utilizan DataInputStream con ficheros binarios.
Apertura y cierre de DataInputStream
Para abrir y cerrar un objeto DataInputStream, se utilizan los mismos métodos que para
FileInputStream:
DataInputStream miDStream;
FileInputStream miFStream;

// Obtiene un controlador de fichero


miFStream = new FileInputStream "/etc/ejemplo.dbf" );
//Encadena un fichero de entrada de datos
miDStream = new DataInputStream( miFStream );

// Ahora se pueden utilizar los dos streams de entrada para


// acceder al fichero (si se quiere...)
miFStream.read( b );
i = miDStream.readInt();

// Cierra el fichero de datos explícitamente


//Siempre se cierra primero el fichero stream de mayor nivel
miDStream.close();
miFStream.close();
Lectura de un DataInputStream
Al acceder a un fichero como DataInputStream, se pueden utilizar los mismos métodos read()
de los objetos FileInputStream. No obstante, también se tiene acceso a otros métodos
diseñados para leer cada uno de los tipos de datos:
byte readByte()
int readUnsignedByte()
short readShort()
int readUnsignedShort()
char readChar()
int readInt()
long readLong()
float readFloat()
double readDouble()
String readLine()
Cada método leerá un objeto del tipo pedido.
Para el método String readLine(), se marca el final de la cadena con \n, \r, \r\n o con EOF.
Para leer un long, por ejemplo:
long numeroSerie;
...
numeroSerie = miDStream.readLong();
Streams de entrada de URLs
Además del acceso a ficheros, Java proporciona la posibilidad de acceder a URLs como una
forma de acceder a objetos a través de la red. Se utiliza implícitamente un objeto URL al
acceder a sonidos e imágenes, con el método getDocumentBase() en los applets:
String imagenFich = new String( "imagenes/pepe.gif" );
imagenes[0] = getImage( getDocumentBase(),imagenFich );
No obstante, se puede proporcionar directamente un URL, si se quiere:
URL imagenSrc;
imagenSrc = new URL( "http://enterprise.com/~info" );
imagenes[0] = getImage( imagenSrc,"imagenes/pepe.gif" );
Apertura de un Stream de entrada de URL
También se puede abrir un stream de entrada a partir de un URL. Por ejemplo, se puede utilizar
un fichero de datos para un applet:
ImputStream is;
byte buffer[] = new byte[24];
is = new URL( getDocumentBase(),datos).openStream();
Ahora se puede utilizar is para leer información de la misma forma que se hace con un objeto
FileInputStream:
is.read( buffer,0,buffer.length );
NOTA: Debe tenerse muy en cuenta que algunos usuarios pueden haber configurado la
seguridad de sus navegadores para que los applets no accedan a ficheros.
STREAMS DE SALIDA
La contrapartida necesaria de la lectura de datos es la escritura de datos. Como con los
Streams de entrada, las clases de salida están ordenadas jerárquicamente:
Examinaremos las clases FileOutputStream y DataOutputStream para complementar los
streams de entrada que se han visto. En los ficheros fuente del directorio
$JAVA_HOME/src/java/io se puede ver el uso y métodos de estas clases, así como de los
streams de entrada ($JAVA_HOME es el directorio donde se haya instalado el Java
Development Kit, en sistemas UNIX).
Objetos FileOutputStream
Los objetos FileOutputStream son útiles para la escritura de ficheros de texto. Como con los
ficheros de entrada, primero se necesita abrir el fichero para luego escribir en él.
Apertura de un FileOutputStream
Para abrir un objeto FileOutputStream, se tienen las mismas posibilidades que para abrir un
fichero stream de entrada. Se le da al constructor un String o un objeto File.
FileOutputStream miFicheroSt;
miFicheroSt = new FileOutputStream( "/etc/kk" );
Como con los streams de entrada, también se puede utilizar:
File miFichero FileOutputStream miFicheroSt;

miFichero = new File( "/etc/kk" );


miFicheroSt = new FileOutputStream( miFichero );
Escritura en un FileOutputStream
Una vez abierto el fichero, se pueden escribir bytes de datos utilizando el método write(). Como
con el método read() de los streams de entrada, tenemos tres posibilidades:
void write( int b );
Escribe un byte.
void write( byte b[] );
Escribe todo el array, si es posible.
void write( byte b[],int offset,int longitud );
Escribe longitud bytes en b comenzando por b[offset].
Cierre de FileOutputStream
Cerrar un stream de salida es similar a cerrar streams de entrada. Se puede utilizar el método
explícito:
miFicheroSt.close();
O, se puede dejar que el sistema cierre el fichero cuando se recicle miFicheroSt.
Ejemplo: Almacenamiento de Información
Este programa, Telefonos.java, pregunta al usuario una lista de nombres y números de
teléfono. Cada nombre y número se añade a un fichero situado en una localización fija. Para
indicar que se ha introducido toda la lista, el usuario especifica "Fin" ante la solicitud de entrada
del nombre.
Una vez que el usuario ha terminado de teclear la lista, el programa creará un fichero de salida
que se mostrará en pantalla o se imprimirá. Por ejemplo:
95-4751232,Juanito
564878,Luisa
123456,Pepe
347698,Antonio
91-3547621,Maria
El código fuente del programa es el siguiente:
import java.io.*;

class Telefonos {
static FileOutputStream fos;
public static final int longLinea = 81;

public static void main( String args[] ) throws IOException {


byte tfno[] = new byte[longLinea];
byte nombre[] = new byte[longLinea];

fos = new FileOutputStream( "telefono.dat" );


while( true )
{
System.err.println( "Teclee un nombre ('Fin' termina)" );
leeLinea( nombre );
if( "fin".equalsIgnoreCase( new String( nombre,0,0,3 ) ) )
break;

System.err.println( "Teclee el numero de telefono" );


leeLinea( tfno );
for( int i=0; tfno[i] != 0; i++ )
fos.write( tfno[i] );
fos.write( ',' );
for( int i=0; nombre[i] != 0; i++ )
fos.write( nombre[i] );
fos.write( '\n' );
}
fos.close();
}

private static void leeLinea( byte linea[] ) throws IOException {


int b = 0;
int i = 0;

while( (i < ( longLinea-1) ) &&


( ( b = System.in.read() ) != '\n' ) )
linea[i++] = (byte)b;
linea[i] = (byte)0;
}
}
Streams de salida con buffer
Si se trabaja con gran cantidad de datos, o se escriben muchos elementos pequeños, será una
buena idea utilizar un stream de salida con buffer. Los streams con buffer ofrecen los mismos
métodos de la clase FileOutputStream, pero toda salida se almacena en un buffer. Cuando se
llena el buffer, se envía a disco con una única operación de escritura; o, en caso necesario, se
puede enviar el buffer a disco en cualquier momento.
Creación de Streams de salida con buffer
Para crear un stream BufferedOutput, primero se necesita un stream FileOutput normal;
entonces se le añade un buffer al stream:
FileOutputStream miFileStream;
BufferdOutpurStream miBufferStream;
// Obtiene un controlador de fichero
miFileStream = new FileOutputStream( "/tmp/kk" );
// Encadena un stream de salida con buffer
miBufferStream = new BufferedOutputStream( miFileStream );
Volcado y Cierre de Streams de salida con buffer
Al contrario que los streams FileOutput, cada escritura al buffer no se corresponde con una
escritura en disco. A menos que se llene el buffer antes de que termine el programa, cuando se
quiera volcar el buffer explícitamente se debe hacer mediante una llamada a flush():
// Se fuerza el volcado del buffer a disco
miBufferStream.flush();
// Cerramos el fichero de datos. Siempre se ha de cerrar primero el
// fichero stream de mayor nivel
miBufferStream.close();
miFileStream.close();
Streams DataOutput
Java también implementa una clase de salida complementaria a la clase DataInputStream.
Con la clase DataOutputStream, se pueden escribir datos binarios en un fichero.
Apertura y cierre de objetos DataOutputStream
Para abrir y cerrar objetos DataOutputStream, se utilizan los mismos métodos que para los
objetos FileOutputStream:
DataOutputStream miDataStream;
FileOutputStream miFileStream;
BufferedOutputStream miBufferStream;

// Obtiene un controlador de fichero


miFileStream = new FileOutputStream( "/tmp/kk" );
// Encadena un stream de salida con buffer (por eficiencia)
miBufferStream = new BufferedOutputStream( miFileStream );
// Encadena un fichero de salida de datos
miDataStream = new DataOutputStream( miBufferStream );

// Ahora se pueden utilizar los dos streams de entrada para


// acceder al fichero (si se quiere)
miBufferStream.write( b );
miDataStream.writeInt( i );

// Cierra el fichero de datos explícitamente. Siempre se cierra


// primero el fichero stream de mayor nivel
miDataStream.close();
miBufferStream.close();
miFileStream.close();
Escritura en un objeto DataOutputStream
Cada uno de los métodos write() accesibles por los FileOutputStream también lo son a través
de los DataOutputStream. También encontrará métodos complementarios a los de
DataInputStream:
void writeBoolean( boolean b );
void writeByte( int i );
void writeShort( int i );
void writeChar( int i );
void writeInt( int i );
void writeFloat( float f );
void writeDouble( double d );
void writeBytes( String s );
void writeChars( string s );
Para las cadenas, se tienen dos posibilidades: bytes y caracteres. Hay que recordar que los
bytes son objetos de 8 bits y los caracteres lo son de 16 bits. Si nuestras cadenas utilizan
caracteres Unicode, debemos escribirlas con writeChars().
Contabilidad de la salida
Otra función necesaria durante la salida es el método size(). Este método simplemente
devuelve el número total de bytes escritos en el fichero. Se puede utilizar size() para ajustar el
tamaño de un fichero a múltiplo de cuatro. Por ejemplo, de la forma siguiente:
...
int numBytes = miDataStream.size() % 4;
for( int i=0; i < numBytes; i++ )
miDataStream.write( 0 );
...

En este capítulo no nos vamos a extender demasiado en profundidades sobre la comunicación


y funcionamiento de redes, aunque sí proporcionaremos un breve baño inicial para sentar, o
recordar, los fundamentos de la comunicación en red, tomando como base Unix.
Presentaremos un ejemplo básico de cliente/servidor sobre sockets TCP/IP, proporcionando un
punto de partida para el desarrollo de otras aplicaciones cliente/servidor basadas en sockets,
que posteriormente implementaremos.
SOCKETS
Los sockets son puntos finales de enlaces de comunicaciones entre procesos. Los procesos los
tratan como descriptores de ficheros, de forma que se pueden intercambiar datos con otros
procesos transmitiendo y recibiendo a través de sockets.
El tipo de sockets describe la forma en la que se transfiere información a través de ese socket.
Sockets Stream (TCP, Transport Control Protocol)
Son un servicio orientado a conexión donde los datos se transfieren sin encuadrarlos en
registros o bloques. Si se rompe la conexión entre los procesos, éstos serán informados.
El protocolo de comunicaciones con streams es un protocolo orientado a conexión, ya que para
establecer una comunicación utilizando el protocolo TCP, hay que establecer en primer lugar
una conexión entre un par de sockets. Mientras uno de los sockets atiende peticiones de
conexión (servidor), el otro solicita una conexión (cliente). Una vez que los dos sockets estén
conectados, se pueden utilizar para transmitir datos en ambas direcciones.
Sockets Datagrama (UDP, User Datagram Protocol)
Son un servicio de transporte sin conexión. Son más eficientes que TCP, pero no está
garantizada la fiabilidad. Los datos se envían y reciben en paquetes, cuya entrega no está
garantizada. Los paquetes pueden ser duplicados, perdidos o llegar en un orden diferente al
que se envió.
El protocolo de comunicaciones con datagramas es un protocolo sin conexión, es decir, cada
vez que se envíen datagramas es necesario enviar el descriptor del socket local y la dirección
del socket que debe recibir el datagrama. Como se puede ver, hay que enviar datos adicionales
cada vez que se realice una comunicación.
Sockets Raw
Son sockets que dan acceso directo a la capa de software de red subyacente o a protocolos de
más bajo nivel. Se utilizan sobre todo para la depuración del código de los protocolos.
Diferencias entre Sockets Stream y Datagrama
Ahora se nos presenta un problema, ¿qué protocolo, o tipo de sockets, debemos usar - UDP o
TCP? La decisión depende de la aplicación cliente/servidor que estemos escribiendo. Vamos a
ver algunas diferencias entre los protocolos para ayudar en la decisión.
En UDP, cada vez que se envía un datagrama, hay que enviar también el descriptor del socket
local y la dirección del socket que va a recibir el datagrama, luego éstos son más grandes que
los TCP. Como el protocolo TCP está orientado a conexión, tenemos que establecer esta
conexión entre los dos sockets antes de nada, lo que implica un cierto tiempo empleado en el
establecimiento de la conexión, que no existe en UDP.
En UDP hay un límite de tamaño de los datagramas, establecido en 64 kilobytes, que se
pueden enviar a una localización determinada, mientras que TCP no tiene límite; una vez que
se ha establecido la conexión, el par de sockets funciona como los streams: todos los datos se
leen inmediatamente, en el mismo orden en que se van recibiendo.
UDP es un protocolo desordenado, no garantiza que los datagramas que se hayan enviado
sean recibidos en el mismo orden por el socket de recepción. Al contrario, TCP es un protocolo
ordenado, garantiza que todos los paquetes que se envíen serán recibidos en el socket destino
en el mismo orden en que se han enviado.
Los datagramas son bloques de información del tipo lanzar y olvidar. Para la mayoría de los
programas que utilicen la red, el usar un flujo TCP en vez de un datagrama UDP es más
sencillo y hay menos posibilidades de tener problemas. Sin embargo, cuando se requiere un
rendimiento óptimo, y está justificado el tiempo adicional que supone realizar la verificación de
los datos, los datagramas son un mecanismo realmente útil.
En resumen, TCP parece más indicado para la implementación de servicios de red como un
control remoto (rlogin, telnet) y transmisión de ficheros (ftp); que necesitan transmitir datos de
longitud indefinida. UDP es menos complejo y tiene una menor sobrecarga sobre la conexión;
esto hace que sea el indicado en la implementación de aplicaciones cliente/servidor en
sistemas distribuidos montados sobre redes de área local.
USO DE SOCKETS
Podemos pensar que un Servidor Internet es un conjunto de sockets que proporciona
capacidades adicionales del sistema, los llamados servicios.
Puertos y Servicios
Cada servicio está asociado a un puerto. Un puerto es una dirección numérica a través de la
cual se procesa el servicio. Sobre un sistema Unix, los servicios que proporciona ese sistema
se indican en el fichero /etc/services, y algunos ejemplos son:
daytime 13/udp
ftp 21/tcp
telnet 23/tcp telnet
smtp 25/tcp mail
http 80/tcp
La primera columna indica el nombre del servicio. La segunda columna indica el puerto y el
protocolo que está asociado al servicio. La tercera columna es un alias del servicio; por
ejemplo, el servicio smtp, también conocido como mail, es la implementación del servicio de
correo electrónico.
Las comunicaciones de información relacionada con Web tienen lugar a través del puerto 80
mediante protocolo TCP. Para emular esto en Java, usaremos la clase Socket. La fecha
(daytime). Sin embargo, el servicio que coge la fecha y la hora del sistema, está ligado al
puerto 13 utilizando el protocolo UDP. Un servidor que lo emule en Java usaría un objeto
DatagramSocket.
LA CLASE URL
La clase URL contiene contructores y métodos para la manipulación de URL (Universal
Resource Locator): un objeto o servicio en Internet. El protocolo TCP necesita dos tipos de
información: la dirección IP y el número de puerto. Vamos a ver como podemos recibir pues la
página Web principal de nuestro buscador favorito al teclear:
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 indicamos yahoo.com es como si hubiesemos
indicado 205.216.146.71, su dirección IP real.
La verdad es que la cosa es un poco más complicada que eso. Hay un servicio, el DNS
(Domain Name Service), que traslada www.yahoo.com a 205.216.146.71, lo que nos permite
teclear www.yahoo.com, en lugar de tener que recordar su dirección IP.
Si queremos obtener la dirección IP real de la red en que estamos corriendo, podemos realizar
llamadas a los métodos getLocalHost() y getAddress(). Primero, getLocalHost() nos devuelve
un objeto iNetAddress, que si usamos con getAddress() generará un array con los cuatro
bytes de la dirección IP, por ejemplo:
InetAddress direccion = InetAddress.getLocalHost();
byte direccionIp[] = direccion.getAddress();
Si la dirección de la máquina en que estamos corriendo es 150.150.112.145, entonces:
direccionIp[0] = 150
direccionIp[1] = 150
direccionIp[2] = 112
direccionIp[3] = 145
Una cosa interesante en este punto 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 más de una dirección IP para poder atender a 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 por ahora abre un agujero en cuestión de seguridad.
Ya conocemos la dirección IP, nos falta el número del puerto. Si no se indica nada, se utilizará
el que se haya definido por defecto en el fichero de configuración de los servicios del sistema.
En Unix se indican en el fichero /etc/services, en Windows-NT en el fichero services y en otros
sistemas puede ser diferente.
El puerto habitual de los servicios Web es el 80, así que si no indicamos nada, entraremos en
el servidor de Yahoo por el puerto 80. Si tecleamos la URL siguiente en un navegador:
http://www.yahoo.com:80
también recibiremos la página principal de Yahoo. No hay nada que nos 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 necesitamos otro protocolo, como:
ftp://ftp.microsoft.com
el puerto se derivará de ese protocolo. Así el puerto FTP de Microsoft es el 21, según su fichero
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.
Con todo esto, Java permite los siguientes cuatro constructores para la clase URL:
public URL( String spec ) throws MalformedURLException;
public URL( String protocol,String host,int port,String file ) throws MalformedURLException;
public URL( String protocol,String host,String file ) throws MalformedURLException;
public URL( URL context,String spec ) throws MalformedURLException;
Así que podríamos especificar todos los componenetes del URL como en:
URL( "http","www.yahoo.com","80","index.html" );
o dejar que los sistemas utilicen todos los valores por defecto que tienen definidos, como en:
URL( "http://www.yahoo.com" );
y en los dos casos obtendríamos la visualización de la página principal de Yahoo en nuestro
navegador.
DOMINIOS DE COMUNICACIONES
El mecanismo de sockets está diseñado para ser todo lo genérico posible. El socket por sí
mismo no contiene información suficiente para describir la comunicación entre procesos. Los
sockets operan dentro de dominios de comunicación, entre ellos se define si los dos procesos
que se comunican se encuentran en el mismo sistema o en sistemas diferentes y cómo pueden
ser direccionados.
Dominio Unix
Bajo Unix, hay dos dominios, uno para comunicaciones internas al sistema y otro para
comunicaciones entre sistemas.
Las comunicaciones intrasistema (entre dos procesos en el mismo sistema) ocurren (en una
máquina Unix) en el dominio Unix. Se permiten tanto los sockets stream como los datagrama.
Los sockets de dominio Unix bajo Solaris 2.x se implementan sobre TLI (Transport Level
Interface).
En el dominio Unix no se permiten sockets de tipo Raw.
Dominio Internet
Las comunicaciones intersistemas proporcionan acceso a TCP, ejecutando sobre IP (Internet
Protocol). De la misma forma que el dominio Unix, el dominio Internet permite tanto sockets
stream como datagrama, pero además permite sockets de tipo Raw.
Los sockets stream permiten a los procesos comunicarse a través de TCP. Una vez
establecidas las conexiones, los datos se pueden leer y escribir a/desde los sockets como un
flujo (stream) de bytes. Algunas aplicaciones de servicios TCP son:
File Tranfer Protocol, FTP
Simple Mail Transfer Protocol, SMTP
TELNET, servicio de conexión de terminal remoto
Los sockets datagrama permiten a los procesos utilizar el protocolo UDP para comunicarse a y
desde esos sockets por medio de bloques. UDP es un protocolo no fiable y la entrega de los
paquetes no está garantizada. Servicios UDP son:
Simple Network Management Protocol, SNMP
Trivial File Transfer Protocol, TFTP (versión de FTP sin conexión)
Versatile Message Transaction Protocol, VMTP (servicio fiable de entrega punto a punto de
datagramas independiente de TCP)
Los sockets raw proporcionan acceso al Internet Control Message Protocol, ICMP, y se utiliza
para comunicarse entre varias entidades IP.
MODELO DE COMUNICACIONES CON JAVA
En Java, crear una conexión socket TCP/IP se realiza directamente con el paquete java.net. A
continuación mostramos un diagrama de lo que ocurre en el lado del cliente y del servidor:
El modelo de sockets más simple es:
El servidor establece un puerto y espera durante un cierto tiempo (timeout segundos), a que el
cliente establezca la conexión. Cuando el cliente solicite una conexión, el servidor abrirá la
conexión socket con el método accept().
El cliente establece una conexión con la máquina host a través del puerto que se designe en
puerto#
El cliente y el servidor se comunican con manejadores InputStream y OutputStream
Hay una cuestión al respecto de los sockets, que viene impuesta por la implementación del
sistema de seguridad de Java. Actualmente, los applets sólo pueden establecer conexiones
con el nodo desde el cual se transfirió su código. Esto está implementado en el JDK y en el
intérprete de Java de Netscape. Esto reduce en gran manera la flexibilidad de las fuentes de
datos disponibles para los applets. El problema si se permite que un applet se conecte a
cualquier máquina de la red, es que entonces se podrían utilizar los applets para inundar la red
desde un ordenador con un cliente Netscape del que no se sospecha y sin ninguna posibilidad
de rastreo.

APERTURA DE SOCKETS
Si estamos programando un cliente, el socket se abre de la forma:
Socket miCliente;
miCliente = new Socket( "maquina",numeroPuerto );
Donde maquina es el nombre de la máquina en donde estamos intentando abrir la conexión y
numeroPuerto es el puerto (un número) del servidor que está corriendo sobre el cual nos
queremos conectar. Cuando se selecciona un número de puerto, se debe tener en cuenta que
los puertos en el rango 0-1023 están reservados para usuarios con muchos privilegios
(superusuarios o root). Estos puertos son los que utilizan los servicios estándar del sistema
como email, ftp o http. Para las aplicaciones que se desarrollen, asegurarse de seleccionar un
puerto por encima del 1023.
En el ejemplo anterior no se usan excepciones; sin embargo, es una gran idea la captura de
excepciones cuando se está trabajando con sockets. El mismo ejemplo quedaría como:
Socket miCliente;
try {
miCliente = new Socket( "maquina",numeroPuerto );
} catch( IOException e ) {
System.out.println( e );
}
Si estamos programando un servidor, la forma de apertura del socket es la que muestra el
siguiente ejemplo:
Socket miServicio;
try {
miServicio = new ServerSocket( numeroPuerto );
} catch( IOException e ) {
System.out.println( e );
}
A la hora de la implementación de un servidor también necesitamos crear un objeto socket
desde el ServerSocket para que esté atento a las conexiones que le puedan realizar clientes
potenciales y poder aceptar esas conexiones:
Socket socketServicio = null;
try {
socketServicio = miServicio.accept();
} catch( IOException e ) {
System.out.println( e );
}

CREACION DE STREAMS
Creación de Streams de Entrada
En la parte cliente de la aplicación, se puede utilizar la clase DataInputStream para crear un
stream de entrada que esté listo a recibir todas las respuestas que el servidor le envíe.
DataInputStream entrada;
try {
entrada = new DataInputStream( miCliente.getInputStream() );
} catch( IOException e ) {
System.out.println( e );
}
La clase DataInputStream permite la lectura de líneas de texto y tipos de datos primitivos de
Java de un modo altamente portable; dispone de métodos para leer todos esos tipos como:
read(), readChar(), readInt(), readDouble() y readLine(). Deberemos utilizar la función que
creamos necesaria dependiendo del tipo de dato que esperemos recibir del servidor.
En el lado del servidor, también usaremos DataInputStream, pero en este caso para recibir las
entradas que se produzcan de los clientes que se hayan conectado:
DataInputStream entrada;
try {
entrada =
new DataInputStream( socketServicio.getInputStream() );
} catch( IOException e ) {
System.out.println( e );
}
Creación de Streams de Salida
En el lado del cliente, podemos crear un stream de salida para enviar información al socket del
servidor utilizando las clases PrintStream o DataOutputStream:
PrintStream salida;
try {
salida = new PrintStream( miCliente.getOutputStream() );
} catch( IOException e ) {
System.out.println( e );
}
La clase PrintStream tiene métodos para la representación textual de todos los datos
primitivos de Java. Sus métodos write y println() tienen una especial importancia en este
aspecto. No obstante, para el envío de información al servidor también podemos utilizar
DataOutputStream:
DataOutputStream salida;
try {
salida = new DataOutputStream( miCliente.getOutputStream() );
} catch( IOException e ) {
System.out.println( e );
}
La clase DataOutputStream permite escribir cualquiera de los tipos primitivos de Java, muchos
de sus métodos escriben un tipo de dato primitivo en el stream de salida. De todos esos
métodos, el más útil quizás sea writeBytes().
En el lado del servidor, podemos utilizar la clase PrintStream para enviar información al
cliente:
PrintStream salida;
try {
salida = new PrintStream( socketServicio.getOutputStream() );
} catch( IOException e ) {
System.out.println( e );
}
Pero también podemos utilizar la clase DataOutputStream como en el caso de envío de
información desde el cliente.

CIERRE DE SOCKETS
Siempre deberemos cerrar los canales de entrada y salida que se hayan abierto durante la
ejecución de la aplicación. En la parte del cliente:
try {
salida.close();
entrada.close();
miCliente.close();
} catch( IOException e ) {
System.out.println( e );
}
Y en la parte del servidor:
try {
salida.close();
entrada.close();
socketServicio.close();
miServicio.close();
} catch( IOException e ) {
System.out.println( e );
}

SERVIDOR DE ECO
En el siguiente ejemplo, vamos a desarrollar un servidor similar al que se ejecuta sobre el
puerto 7 de las máquinas Unix, el servidor echo. Básicamente, este servidor recibe texto desde
un cliente y reenvía ese mismo texto al cliente. Desde luego, este es el servidor más simple de
los simples que se pueden escribir. El ejemplo que presentamos, ecoServidor.java, maneja
solamente un cliente. Una modificación interesante sería adecuarlo para que aceptase
múltiples clientes simultáneos mediante el uso de threads.
import java.net.*;
import java.io.*;

class ecoServidor {
public static void main( String args[] ) {
ServerSocket s = null;
DataInputStream sIn;
PrintStream sOut;
Socket cliente = null;
String texto;

// Abrimos una conexión con breogan en el puerto 9999


// No podemos elegir un puerto por debajo del 1023 si no somos
// usuarios con los máximos privilegios (root)
try {
s = new ServerSocket( 9999 );
} catch( IOException e ) {
}

// Creamos el objeto desde el cual atenderemos y aceptaremos


// las conexiones de los clientes y abrimos los canales de
// comunicación de entrada y salida
try {
cliente = s.accept();
sIn = new DataInputStream( cliente.getInputStream() );
sOut = new PrintStream( cliente.getOutputStream() );

// Cuando recibamos datos, se los devolvemos al cliente


// que los haya enviado
while( true )
{
texto = sIn.readLine();
sOut.println( texto );
}
} catch( IOException e ) {
System.out.println( e );
}
}
}

CLIENTE/SERVIDOR TCP/IP
Mínimo Servidor TCP/IP
Veamos el código que presentamos en el siguiente ejemplo, minimoServidor.java, donde
desarrollamos un mínimo servidor TCP/IP, para el cual desarrollaremos después su
contrapartida cliente TCP/IP. La aplicación servidor TCP/IP depende de una clase de
comunicaciones proporcionada por Java: ServerSocket. Esta clase realiza la mayor parte del
trabajo de crear un servidor.
import java.awt.*;
import java.net.*;
import java.io.*;

class minimoServidor {
public static void main( String args[] ) {
ServerSocket s = (ServerSocket)null;
Socket s1;
String cadena = "Tutorial de Java!";
int longCad;
OutputStream s1out;

// Establece el servidor en el socket 4321 (espera 300


segundos)
try {
s = new ServerSocket( 4321,300 );
} catch( IOException e ) {
System.out.println( e );
}

// Ejecuta un bucle infinito de listen/accept


while( true ) {
try {
// Espera para aceptar una conexión
s1 = s.accept();
// Obtiene un controlador de fichero de salida
asociado
// con el socket
s1out = s1.getOutputStream();

// Enviamos nuestro texto


longCad = sendString.length();
for( int i=0; i < longCad; i++ )
s1out.write( (int)sendString.charAt( i ) );

// Cierra la conexión, pero no el socket del servidor


s1.close();
} catch( IOException e ) {
System.out.println( e );
}
}
}
}

Mínimo Cliente TCP/IP


El lado cliente de una aplicación TCP/IP descansa en la clase Socket. De nuevo, mucho del
trabajo necesario para establecer la conexión lo ha realizado la clase Socket. Vamos a
presentar ahora el código de nuestro cliente más simple, minimoCliente.java, que encaja con el
servidor presentado antes. El trabajo que realiza este cliente es que todo lo que recibe del
servidor lo imprime por la salida estándar del sistema.
import java.awt.*;
import java.net.*;
import java.io.*;

class minimoCliente {
public static void main( String args[] ) throws IOException {
int c;
Socket s;
InputStream sIn;

// Abrimos una conexión con breogan en el puerto 4321


try {
s = new Socket( "breogan",4321 );
} catch( IOException e ) {
System.out.println( e );
}

// Obtenemos un controlador de fichero de entrada del socket y


// leemos esa entrada
sIn = s.getInputStream();
while( ( c = sIn.read() ) != -1 )
System.out.print( (char)c );

// Cuando se alcance el fin de fichero, cerramos la conexión y


// abandonamos
s.close();
}
}

EJECUTAR TCP/IP EN Windows '95


Las indicaciones que se proporcionan a continuación, van a permitirnos fijar los parámetros de
Windows '95 para que se puedan ejecutar programas cliente y servidor, sin necesidad de que
el ordenador en el cual se está ejecutando Windows '95 esté conectado a una red de área
local. Esto puede resultar útil para mucha gente que está probando Java en su casa y no
dispone de red, o incluso para aquellos programadores o estudiosos que quieren probar sus
nuevos programas distribuidos pero no disponen de red o de Internet.
Advertencia: Si los siguientes parámetros se van a fijar en un ordenador portátil que en
ocasiones sí se conecte a una red, sería conveniente anotar los parámetros actuales de la
configuración de Windows '95, para que sean fácilmente recuperables cuando este ordenador
se vuelva a conectar a la red.
Configuración del TCP/IP de Windows '95
Hay que seguir los pasos que vamos a relatar a continuación, suponemos que se está
ejecutando la versión española de Windows '95, en otras versiones puede que las opciones
tengan nombre diferente:
En el Panel de Control, seleccionar Red
Picar Agregar, luego Protocolo y luego Agregar
Seleccionar Microsoft y luego TCP/IP y picar en Aceptar
En este momento probablemente se solicite la introducción de los discos de Windows '95, o del
CD-ROM
Seleccionar la pestaña Configuración de la ventana Red
Seleccionar TCP/IP en la lista que aparece y picar Propiedades
Seleccionar la pestaña Dirección IP
Crearse una dirección IP, como por ejemplo: 102.102.102.102
Crearse una Máscara de subred, como: 255.255.255.0
Seleccionar la pestaña Configuración DNS y desactivar DNS
Los valores correspondientes a las otras cuatro pestañas pueden dejarse los que hay por
defecto
Picar Aceptar
(Opcional) Seleccionar la pestaña Identificación de la ventana Red
(Opcional) Introducir un nombre para el ordenador, como por ejemplo: breogan
(Opcional) Picar Aceptar
Crear una entrada en la "red"
Editar el fichero hosts.sam que está en el directorio de Windows
Al final del fichero incorporar la dirección IP y el nombre del ordenador que se han introducido
antes, en nuestro caso: 102.102.102.102 breogan
Asegurarse de que la dirección IP y el nombre coinciden con la dirección IP que se ha fijado en
el paso 7a de antes y que el nombre es el mismo que el indicado en el paso 12 anterior
Salvar el fichero con el nombre "hosts" y reiniciar Windows '95
Comprobación de la red
Abrir una sesión MS-DOS
Teclear "ping breogan"
Debería aparecer:
Pinging breogan [102.102.102.102] with 32 bytes of data:
Reply from 102.102.102.102: bytes=32 time=1ms TTL=32
Reply from 102.102.102.102: bytes=32 time<10ms TTL=32
Reply from 102.102.102.102: bytes=32 time<10ms TTL=32
Reply from 102.102.102.102: bytes=32 time<10ms TTL=32
Teclear "tracert 102.102.102.102"
Debería aparecer:
Tracing route to 102.102.102.102 over a maximum of 30 hops
1 <10 ms 1 ms <10 ms 102.102.102.102
Trace complete.
En este instante, si todo ha ido bien, el ordenador está listo para funcionar como si estuviera en
red. Dos o más programas que se comuniquen en red a través de sockets debería poder
ejecutarse ahora dentro de los dominios del ordenador que acabamos de configurar
Problemas más frecuentes
Los tres problemas que pueden presentarse cuando intentemos comprobar el funcionamiento
correcto de la red interna que acabamos de montar son:
Cuando hacemos "ping" obtenemos "Bad IP address breogan"
Intentar teclear "ping 102.102.102.102". Si ahora sí se obtiene réplica, la causa del problema es
que el los pasos 12 de la Configuración y 3 de la Creación de la entrada en la tabla de hosts,
no se ha introducido correctamente el nombre de la máquina. Comprobar esos pasos y que
todo coincide.
El programa cliente o el servidor fallan al intentar el "connect"
La causa podría estar en que se produzca un fallo por fichero no encontrado en el directorio
Windows/System de las librerías WINSOCK.DLL o WSOCK32.DLL. Muchos programas que se
utilizan en Internet reemplazan estos ficheros cuando se instalan. Asegurarse de que están
estos ficheros y que son los originales que vienen con la distribución de Windows '95.
El programa servidor dice que no puede "bind" a un socket
Esto sucede porque tiene el DNS activado y no puede encontrar ese DNS o servidor de
direcciones, porque estamos solos en la red. Asegurarse de que en el paso 8 de la
Configuración la opción de DNS está deshabilitada.

Das könnte Ihnen auch gefallen