INTRODUCCIÓN AL DESARROLLO DE APLICACIONES DE RED EN JAVA
Carlos Eduardo Gómez Montoya, MSc
Profesor adscrito al Programa Ingeniería de Sistemas y Computación Facultad de Ingeniería Universidad del Quindío
Introducción al desarrollo de aplicaciones de red en Java
Este trabajo se distribuye bajo una licencia Creative Commons Colombia. Se permite copiar, distribuir, y comunicar públicamente siempre y cuando se haga mención del autor original de la obra: Carlos Eduardo Gómez Montoya. Octubre de 2010 Armenia, Quindío – Colombia
PRESENTACIÓN
Introducción al desarrollo de aplicaciones de red en Java es un trabajo académico presentado como requisito de ascenso en el escalafón docente de la Universidad del Quindío, de la categoría Profesor Asociado a la categoría Profesor Titular.
Este trabajo se ha desarrollado durante los últimos tres años y medio para ofrecer a los estudiantes de Ingeniería de Sistemas y Computación un documento de nivel introductorio que les permita iniciar en el desarrollo de aplicaciones de red aprovechando las librerías y recursos que proporciona el paquete java.net del lenguaje Java. No cubre temas avanzados ni busca profundizar en temas especializados.
El trabajo desarrollado es la materialización de una propuesta presentada al Consejo de la Facultad de Ingeniería de la Universidad del Quindío, la cual fue aprobada sin modificaciones, con la siguiente temática: 1) Introducción; 2) Fundamentos de programación de aplicaciones de red; 3) Protocolos de capa de transporte; 4) Creación de aplicaciones de red usando el protocolo TCP; 5) Creación de aplicaciones de red usando el protocolo UDP; y 6) Creación de aplicaciones de red para grupos (multicast).
Otros temas relacionados con el desarrollo de aplicaciones de red en Java, tales como Remote Method Invocation (RMI), Enterprise Java Beans (EJBs) y Web Services; están fuera del alcance de esta propuesta y serán tema de trabajo futuro.
El presente trabajo tiene el siguiente contenido: 1) Una introducción a los conceptos fundamentales de redes de computadores, necesarios para entender la creación de aplicaciones de red, incluyendo los protocolos de la capa de transporte TCP y UDP; 2)
La API de Java para la programación de sockets incluida en el paquete java.net; 3) Aplicaciones de red con sockets TCP; 4) Proyecto File Transfer; 5) Aplicaciones de red con sockets UDP; 6) Proyecto TransactionManager; 7) Aplicaciones de red con sockets Multicast; y 8) Proyecto MulticastChat.
Entre los conceptos fundamentales de redes se explican las arquitecturas clásicas para desarrollar aplicaciones de red, los conceptos de encapsulación de protocolos, direcciones para los hosts, inlcuyendo las direccione MAC y las direcciones IP (tanto IPv4 como IPv6), números de puerto y nombres de hosts. También la comunicación de procesos, el concepto de socket y los protocolos de capa de transporte TCP y UDP.
De la API de Java para la creación de aplicaciones de red se presentan los fundamentos básicos para identificar las interfaces de red y las direcciones IP y MAC que tenga configuradas un host; seguido por los principios básicos de la programación de sockets con el protocolo TCP, el protocolo UDP y la comunicación de grupos multicast.
Los ejemplos (15 en total) han sido escritos cuidadosamente con fines didácticos para que cada uno de ellos explique un solo concepto. Algunos de estos ejemplos son clásicos que se pueden encontrar en todos los libros y tutoriales que tratan el mismo tema. Sin embargo, estos ejemplos han sido reescritos para conservar un estilo coherente a lo largo del trabajo. Otros ejemplos son creados por el autor. Cada uno de los ejemplos tiene un objetivo diferente: Identificar las interfaces de red del host; identificar las direcciones IP y MAC de un host; enviar un mensaje de texto utilizando el protocolo TCP y el protocolo UDP; enviar datos primitivos a través de una conexión TCP; enviar un objeto de Java tanto con el protocolo TCP como con el protocolo UDP; enviar un objeto especificado por el usuario utilizando TCP y UDP; enviar un archivo de un host a otro mediante una conexión TCP; crear un transmisor multicast un receptor multicast. Adicionalmente se han creado ejemplos muy sencillos de servidores multihilo. En cada una de las secciones se han propuesto ejercicios para que los estudiantes puedan poner a prueba las habilidades adquiridas al utilizar este material educativo.
Los tres proyectos 1) FileTransfer, 2) TransactionManager y 3) MulticastChat, son desarrollos originales del autor y han sido denominados proyectos porque pretenden mostrar el desarrollo de aplicaciones de red con un grado de complejidad intermedio y que sirvan de motivación para los estudiantes. Estos proyectos se han escrito de manera que el estudiante pueda completarlos fácilmente siguiendo las instrucciones que los acompañan. A partir de estos proyectos se han escrito ejercicios muy interesantes con nuevos requerimientos.
Es importante destacar que el código fuente sigue algunas de las principales convenciones de codificación de Java propuestas por Sun Microsystems (Sun Microsystems, Inc, 1997) y universalmente aceptadas (de facto), especialmente en la forma como se denominan las clases, los objetos, los métodos, las variables y las constantes. Se ha escrito el código en inglés por preferencia del autor, aunque la documentación del código está completamente escrita en español. Es importante resaltar también el uso de las sangrías, tratando de hacer el código legible y fácil de entender por parte de los estudiantes a quienes va dirigido este trabajo.
¿Por qué escribir sobre el desarrollo de aplicaciones de red en Java?
La tendencia actual está orientada cada vez más hacia la comunicación de aplicaciones a través de redes de comunicación y todos los días surgen nuevas necesidades de aplicaciones que deben ser desarrolladas. Java es uno de los lenguajes de programación con mayor aceptación en la actualidad, tanto en Colombia como a nivel internacional y tiene variadas opciones para el desarrollo de aplicaciones de red.
A pesar que las librerías y recursos que proporciona el paquete java.net no son un tema reciente, y que existe en Internet mucha documentación acerca de este tema, es muy frecuente encontrar ejemplos poco didácticos, difíciles de seguir y en muchas ocasiones, documentación sin estructura en los que hay que el estudiante debe hacer mucho esfuerzo para entender. El objetivo de este trabajo es reunir los conceptos más
importantes y necesarios, junto con los ejemplos más significativos de acuerdo con la experiencia de varios años como docente en el área de redes de computadores.
Por otra parte, la existencia de tecnologías como RMI, EJBs y Web Services, las cuales crean capas de abstracción más altas que encapsulan los detalles propios de la comunicación, facilita el desarrollo de aplicaciones de red. Sin embargo, estas tecnologías no son la solución más apropiada a todo tipo de necesidades, debido a su elevado consumo de recursos, en comparación con las aplicaciones de red desarrolladas con las librerías incluidas en el paquete java.net.
¿A quién va dirigido este trabajo?
Introducción al desarrollo de aplicaciones de red en Java se ha ofrecido con licencia Creative Commons para ser compartido con toda la comunidad académica. Sin embargo, está dirigido a estudiantes de Ingeniería de Sistemas que estén estudiando redes de computadores, especialmente la capa de aplicación del modelo de referencia TCP/IP, o asignaturas específicas que necesiten el desarrollo de aplicaciones de red. Igualmente, puede ser útil a los docentes que trabajan en este tema. Es importante que quienes van a utilizar este trabajo tengan conocimientos de programación orientada a objetos y lenguaje de programación Java.
DVD Anexo
En el DVD que acompaña este trabajo se ha guardado una copia de cada uno de los 15 ejemplos y los 3 proyectos como proyectos de Eclipse, además del documento en formato pdf y docx, y las imágenes de la carátula.
Acerca de la bibliografía
En su mayoría, las referencias bibliográficas utilizadas en el desarrollo de este trabajo se han concentrado en los conceptos teóricos presentados en los capítulos iniciales, así como en algunos pocos ejemplos que fueron tomados de otros autores. Es de resaltar que en el resto del trabajo no hay referencias bibliográficas porque se trata del aporte del autor a la enseñanza del desarrollo de aplicaciones de red en Java.
Acerca del autor
Carlos Eduardo Gómez Montoya. Licenciado en Matemáticas y Computación de la Universidad del Quindío, Especialista en Redes de Comunicación de la Universidad del Valle y Magíster en Ingeniería de la Universidad de los Andes en el área de Sistemas y Computación con línea de investigación Redes, Sistemas Distribuidos y Paralelismo.
Profesor de planta, tiempo completo, de la Universidad del Quindío adscrito al programa Ingeniería de Sistemas y Computación. Fundador y Director del Grupo de Investigación en Redes, Información y Distribución – GRID de la Facultad de Ingeniería de Universidad del Quindío.
Autor de diferentes obras en el área de la Ingeniería de Sistemas e informática. carloseg@uniquindio.edu.co.
AGRADECIMIENTOS Llevar a feliz término este trabajo ha sido una labor de tres años y medio de duración. No puedo finalizar sin mencionar algunas personas que contribuyeron para que saliera adelante.
Quiero expresar mis agradecimientos:
A Julián Esteban Gutiérrez Posada, Christian Andrés Candela Uribe y Luis Eduardo Sepúlveda Rodríguez, por su permanente apoyo y sus aportes para la realización y revisión del trabajo.
A mi padre, Gilberto Gómez Gómez, por su voz de aliento en todo momento.
A todos los estudiantes a quienes he tenido la oportunidad de orientar en el curso de Redes de computadores en la Universidad del Quindío, principales colaboradores y a quienes les he preparado este material.
Finalmente a mi Familia, Olga Lucía, Pablo y Martín, las personas más sacrificadas por la cantidad de tiempo en el que no los he acompañado por estar dedicado a este trabajo.
Contenido Página 1
I
N
T
1
R
1
.
1
.
2
1
.
2
.
1
.
2
.
1
.
2
.
3
N
1
.
2
.
4
N
1
.
3
1
.
3
.
1
.
3
.
1
.
4
1
.
5
1
.
6
2
A
O
C
.
2
.
2
.
2
Q
O
ú
o
2
r
S
S
C
R
I
1
I
2
E
V
E
M
P
L
O
3
E
J
E
M
P
L
O
.
4
E
J
E
M
P
L
O
2
.
5
2
.
5
.
2
.
5
.
2
.
6
2
.
6
.
2
.
6
.
S
O
1
C
2
O
1
K
l
C
S
2
C
E
a
s
l
C
a
T
e
s
K
e
E
.
3
T
.
.
U
.
.
.
.
:
D
:
O
D
.
.
.
.
.
.
t
.
.
.
.
.
o
.
.
.
.
.
.
.
.
.
A
.
.
.
.
h
.
.
.
.
.
s
.
.
.
L
.
.
s
.
.
.
C
D
.
t
.
I
E
.
o
.
P
R
.
.
.
A
E
.
.
.
.
C
S
.
I
.
.
.
Ó
.
.
.
.
.
N
.
.
.
D
.
.
.
.
E
.
.
.
R
.
.
.
E
.
.
D
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
1
2
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
4
.
.
8
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
1
0
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
1
4
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
1
5
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
1
7
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
1
7
R
a
.
E
s
O
p
p
.
A
.
P
a
.
D
o
c
.
N
.
.
C
a
a
E
d
d
S
e
e
O
a
t
r
S
p
a
l
n
i
s
c
a
p
c
o
i
r
ó
t
n
e
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
1
9
1
9
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
2
0
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
2
4
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
2
7
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
2
7
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
2
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
3
0
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
3
3
.
.
E
D
R
E
Y
F
C
R
.
E
E
R
A
C
A
C
I
S
D
I
E
Ó
S
P
I
A
C
C
C
E
M
L
E
D
N
A
R
R
C
A
I
E
I
N
L
N
S
E
E
O
E
D
D
C
O
N
O
S
P
E
H
E
I
L
S
D
H
T
O
L
E
L
S
O
T
C
A
C
L
A
L
L
A
O
.
S
.
.
C
.
.
E
A
.
I
.
.
L
.
.
N
.
.
.
E
.
T
A
D
D
R
E
S
S
8
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
3
5
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
3
6
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
3
8
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
3
9
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
4
0
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
4
1
r
P
.
.
s
R
T
D
.
T
P
t
P
E
I
e
.
P
N
e
.
.
t
a
.
S
r
c
.
D
I
v
l
C
.
:
k
r
S
.
.
l
s
a
.
U
.
D
U
C
c
e
.
S
2
S
T
.
E
o
.
O
A
S
.
T
1
S
.
o
l
O
C
J
.
L
A
E
.
L
O
A
F
.
e
.
e
e
.
a
N
d
.
O
.
r
h
s
.
C
u
Ó
.
E
.
p
e
.
I
a
e
d
.
O
C
R
o
.
n
p
I
s
.
C
J
o
S
O
E
T
i
l
ó
s
C
o
c
T
T
D
N
i
O
O
A
.
S
i
d
.
D
Á
d
s
C
c
E
T
R
P
v
K
O
P
o
e
.
A
c
s
e
I
t
r
o
r
N
a
n
.
R
B
l
o
r
b
o
e
O
P
m
U
P
i
.
S
u
c
N
U
O
s
e
Ó
T
T
c
m
I
C
p
e
M
1
E
a
r
C
P
c
i
O
T
E
n
D
C
I
C
E
2
U
U
N
1
C
A
2
R
D
S
.
o
.
.
c
.
.
.
C
l
a
s
e
D
a
t
a
g
r
a
m
C
l
a
s
e
D
a
t
a
g
r
a
m
k
.
.
e
.
.
.
t
.
.
P
S
.
a
o
c
c
k
k
e
e
t
t
2
.
7
2
.
7
3
S
.
.
3
.
3
.
3
C
1
A
3
O
C
P
L
1
E
l
I
E
2
K
a
C
J
T
s
e
A
E
S
A
M
C
M
P
I
u
O
P
R
l
N
L
t
i
E
O
A
c
a
S
4
C
s
D
:
S
O
t
S
E
E
M
U
o
c
R
R
k
E
V
N
I
e
t
D
I
.
C
D
O
C
A
.
.
O
C
.
.
.
.
N
R
I
.
.
.
S
Y
C
Ó
.
N
.
.
O
L
.
.
.
C
I
E
.
.
.
K
E
.
.
.
E
N
G
N
.
.
.
T
T
.
R
.
.
.
S
.
U
.
T
E
D
.
P
.
.
C
.
O
.
P
E
E
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
4
3
4
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
4
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
4
7
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
4
7
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
5
6
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
6
1
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
6
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
8
C
O
E
J
E
M
P
L
O
5
:
S
E
R
V
I
D
O
R
Y
C
L
I
E
N
T
E
P
A
R
A
L
A
3
E
J
E
M
P
L
O
6
:
S
E
R
V
I
D
O
R
Y
C
L
I
E
N
T
E
P
A
R
A
L
A
T
R
A
N
S
M
I
S
I
Ó
N
D
E
O
B
J
E
T
O
S
–
.
4
E
J
E
M
P
L
O
:
S
E
R
V
I
D
O
R
Y
C
L
I
E
N
T
E
P
A
R
A
L
A
T
R
A
N
S
M
I
S
I
Ó
N
D
E
O
B
J
E
T
O
S
–
3
.
5
E
J
E
M
P
L
O
8
:
S
E
R
V
I
D
O
R
Y
C
L
I
E
N
T
E
P
A
R
A
L
A
T
R
A
N
S
M
I
S
I
Ó
N
D
E
3
.
6
E
J
E
M
P
L
O
9
:
S
E
R
V
I
D
O
R
3
.
E
J
E
R
7
4
P
4
.
4
.
4
.
R
1
2
3
5
.
5
.
5
.
5
5
O
P
A
S
O
E
L
I
T
2
C
C
I
A
O
S
O
1
R
I
C
C
S
J
I
F
I
.
.
.
L
.
.
.
E
.
.
.
T
.
.
R
.
.
.
A
.
.
.
P
L
I
C
A
C
I
Ó
N
:
A
P
L
I
C
A
C
I
Ó
N
I
O
O
S
.
N
E
.
.
.
.
S
.
.
.
D
.
.
.
.
E
.
.
.
R
.
.
.
S
A
I
.
N
:
C
C
.
.
M
.
.
F
.
.
E
.
.
.
.
D
.
I
E
.
.
R
L
S
.
.
E
C
.
U
L
.
.
C
O
L
O
Y
C
L
I
E
N
T
E
P
A
E
R
D
A
O
L
S
A
E
T
N
R
T
A
E
R
A
N
O
R
S
S
C
M
H
I
I
S
I
V
Ó
O
1
2
S
N
.
D
.
A
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
8
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
8
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
8
9
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
9
9
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
1
0
4
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
1
1
1
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
1
1
1
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
1
2
0
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
1
2
6
T
I
.
.
.
E
D
.
N
O
.
.
.
S
R
.
.
.
O
.
.
.
C
.
K
E
T
S
U
D
P
S
E
R
V
I
D
O
R
Y
C
L
I
E
N
T
E
D
E
J
E
M
P
L
O
1
1
:
S
E
R
V
I
D
O
R
Y
C
L
I
E
N
T
E
P
A
R
A
L
A
T
R
A
N
S
M
I
S
I
Ó
N
D
E
O
B
J
E
T
O
S
–
3
E
J
E
M
P
L
O
1
:
S
E
R
V
I
D
O
R
Y
C
L
I
E
N
T
E
P
A
R
A
L
A
T
R
A
N
S
M
I
S
I
Ó
N
D
E
O
B
J
E
T
O
S
–
.
4
E
J
E
M
P
L
O
1
:
S
E
R
V
I
D
O
R
.
5
E
J
E
R
P
6
.
6
.
6
.
7
O
1
2
3
A
.
R
E
C
P
A
S
O
P
A
S
O
E
J
P
1
Y
E
L
E
R
I
J
A
M
O
1
2
C
C
E
T
I
C
P
T
R
.
.
.
.
A
.
.
N
.
.
.
S
.
.
.
A
.
.
.
C
.
.
L
I
C
A
C
I
Ó
N
:
A
P
L
I
C
A
C
I
Ó
N
O
S
.
N
O
E
1
.
.
.
S
4
.
.
D
:
.
.
.
.
.
.
E
R
.
.
.
R
E
C
.
.
.
.
.
O
.
.
.
.
.
.
.
.
.
E
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
I
.
.
.
.
.
.
.
.
.
.
K
T
.
.
L
.
.
.
R
E
Y
C
C
O
L
I
E
N
T
E
P
A
R
A
U
N
C
H
A
T
S
E
N
C
I
L
L
1
2
O
4
7
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
1
3
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
1
4
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
1
4
2
3
7
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
1
4
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
1
5
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
1
6
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
1
6
7
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
1
6
7
E
I
E
O
.
E
R
C
L
.
.
O
.
H
G
E
O
U
.
A
D
S
M
I
T
I
.
.
N
V
N
R
.
T
N
R
.
L
A
I
O
O
.
M
E
.
U
.
L
C
T
.
N
S
D
P
.
M
C
.
E
E
.
I
P
O
.
T
A
I
L
.
:
C
I
.
2
.
:
S
S
.
0
O
O
.
1
I
V
.
O
C
I
.
L
I
H
.
P
C
C
4
.
M
3
R
7
.
E
2
.
E
J
2
.
7
.
V
.
I
D
.
N
.
H
A
.
E
.
I
M
.
R
.
T
U
E
6
7
E
A
P
1
Y
P
E
A
5
O
C
7
S
C
T
A
S
S
M
T
U
.
.
.
L
.
.
.
T
.
.
.
I
.
.
.
C
.
A
.
.
.
S
.
.
.
T
.
.
.
.
.
8
7
5
7
.
2
E
8
P
8
.
8
.
8
.
8
R
J
E
O
1
Y
P
A
P
E
S
L
C
O
T
O
1
:
A
O
3
P
A
S
O
.
4
P
A
S
O
8
.
5
P
A
S
O
5
:
8
.
6
P
A
S
O
6
:
E
8
.
P
A
S
O
:
P
8
.
E
R
E
J
F
E
E
R
C
E
R
L
L
I
A
T
C
I
A
N
C
S
A
C
M
S
I
I
T
Ó
S
C
H
G
N
O
R
A
R
M
T
Á
.
F
I
U
.
.
C
.
.
L
.
A
T
I
C
R
I
C
E
O
I
S
S
A
E
.
S
N
T
A
C
I
L
Ó
I
N
C
A
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
1
7
0
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
1
7
3
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
1
7
4
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
1
7
7
7
R
S
S
J
Ó
L
N
D
E
N
A
E
E
A
I
.
.
B
C
.
.
:
P
.
.
4
A
.
.
E
A
.
.
D
L
.
.
L
E
.
.
A
T
.
.
N
U
.
.
A
C
.
.
C
E
.
.
L
J
.
.
E
N
.
.
D
E
.
.
N
M
.
.
Ó
S
.
.
I
O
.
.
C
L
.
.
A
E
.
.
E
D
.
.
R
O
.
.
C
J
.
.
:
E
.
.
3
N
.
.
E
A
.
.
D
M
.
.
L
M
.
.
A
O
.
.
N
N
.
.
A
L
.
.
C
E
.
.
L
D
.
.
E
D
.
.
D
U
.
.
N
T
.
.
Ó
I
.
.
I
C
.
.
C
I
T
.
A
L
S
.
E
O
A
.
R
S
C
.
C
I
N
T
:
7
R
:
U
P
S
8
2
5
M
A
7
1
O
P
9
2
M
D
T
E
S
I
R
L
D
A
A
U
E
Y
D
S
S
N
A
U
A
U
P
A
L
I
A
R
I
I
D
Ó
R
O
A
N
A
A
L
P
A
G
L
E
R
E
R
U
R
A
P
L
E
O
O
L
M
S
C
U
M
H
A
L
E
T
N
T
S
I
A
C
J
A
E
S
S
T
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
1
7
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
1
7
9
7
9
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
1
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
1
8
1
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
1
8
1
3
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
1
8
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
1
8
7
LISTA DE FIGURAS P
á
g
i
n
a
Figura 1. Arquitectura cliente – servidor
3
Figura 2. Arquitectura
3
p
e
e
r
–
t
o
–
p
e
e
r
Figura 3. Pila de protocolos TCP/IP
6
Figura 4. Encapsulación
9
Figura 5. Encapsulación de protocolos
10
Figura 6. Relación entre procesos en ejecución, sockets y protocolo de capa de transporte 20 Figura 7. Comunicación a través de la red
20
Figura 8. Three-way-handshake
21
Figura 9. Conexión de sockets
22
Figura 10. Ejemplo 1 – Salida en consola
30
Figura 11. Ejemplo 2 – Salida en consola
32
Figura 12. Ejemplo 3 – Salida en consola
35
Figura 13. Comunicación entre cliente y servidor para el envío de String
50
Figura 14. Ejemplo 4 – Entrada cliente
55
Figura 15. Ejemplo 4 – Salida en consola
56
Figura 16. Comunicación entre cliente y servidor para el envío de enteros
59
Figura 17. Ejemplo 5 – Entrada cliente
61
Figura 18. Ejemplo 5 – Salida en consola
61
Figura 19. Ejemplo 6 – Salida en consola
67
Figura 20. Ejemplo 7 – Salida en consola
74
Figura 21. Ejemplo 8 – Entrada cliente
82
Figura 22. Ejemplo 8 – Salida en consola
82
Figura 23. Proyecto FileTransfer – Diagrama de colaboración
87
Figura 24. Proyecto FileTransfer – Interfaz gráfica de usuario
88
Figura 25. Proyecto FileTransfer – Lista de archivos disponibles en el servidor
88
Figura 26. Proyecto FileTransfer – Fin de la descarga de un archivo
89
Figura 27. Proyecto FileTransfer – Componentes gráficos de la GUI del cliente
90
Figura 28. Ejemplo 10 – Entrada cliente
119
Figura 29. Ejemplo 10 – Salida en consola
120
Figura 30. Ejemplo 11 – Salida en consola
126
Figura 31. Ejemplo 12 – Salida en consola
132
Figura 32. Proyecto TransactionManager – Diagrama de colaboración
147
Figura 33. Proyecto TransactionManager – Interfaz gráfica de usuario
148
Figura 34. Proyecto TransactionManager – Componentes gráficos de la GUI
149
Figura 35. Ejemplo 14 – Entrada
172
Figura 36. Ejemplos 14 y 15 – Salida en consola
172
Figura 37. Proyecto MulticastChat – Diagrama de colaboración
173
Figura 38. Proyecto MulticastChat – Entrada del nombre del usuario
174
Figura 39. Proyecto MulticastChat – Apariencia de la GUI
174
Figura 40. Proyecto MulticastChat – Componentes gráficos de la GUI
175
INTRODUCCIÓN
1
1. INTRODUCCIÓN La razón de ser de las redes de computadores, bien sea una red empresarial o una red familiar son las aplicaciones (KUROSE & ROSS, 2010). ¿De qué serviría tener una red de computadores si no existieran aplicaciones que pudieran aprovecharla?
Un número cada día más significativo de personas usa diariamente Internet para su trabajo, estudio, comunicación o entretenimiento. Con la popularidad cada vez mayor de las redes, nuevas aplicaciones de red surgen día a día. En las distintas compañías la red es usada para la comunicación de sus empleados, para difundir mensajes de interés o incluso para entrenamiento y capacitación. En todos los casos, son las aplicaciones las que permiten realizar las diversas actividades. Cuando estas aplicaciones se ejecutan en distintos dispositivos (por ejemplo en computadores o teléfonos celulares inteligentes) y se comunican a través de una red, reciben el nombre de aplicaciones de red.
Una aplicación de red es un conjunto de programas que se ejecutan en diferentes hosts y que se comunican entre sí por medio de la red. Por ejemplo, en el caso de la Web, hay dos programas distintos que se comunican: el navegador (browser) que corre en el host del usuario, y el servidor Web que corre en algún host conectado a Internet (KUROSE & ROSS, 2010).
Las aplicaciones de red requieren servicios proporcionados por la infraestructura de red especialmente en cuanto a la confiabilidad en la transferencia de datos, la capacidad de transmisión y la coordinación de tiempo, entre otras. Algunas aplicaciones pueden soportar pérdida de datos en alguna proporción, como es el caso de las aplicaciones multimedia; otras son sensibles al tiempo, por ejemplo en los juegos interactivos; mientras que otras necesitan una coordinación entre los tiempos
2
INTRODUCCIÓN AL DESARROLLO DE APLICACIONES DE RED EN JAVA
de llegada de los distintos fragmentos de un mensaje de longitud considerable, como en la transmisión de video almacenado (KUROSE & ROSS, 2010).
Para crear aplicaciones de red se utilizan diferentes lenguajes de programación, tales como C, C++, Python, y Java, entre otros. Existen dos protocolos de capa de transporte que soportan las aplicaciones distribuidas, el protocolo TCP y el protocolo UDP. Cada uno de ellos ofrece un servicio diferente y dependiendo de la aplicación, puede ser necesario uno u otro.
1.1 ARQUITECTURA DE UNA APLICACIÓN DE RED
La arquitectura de una aplicación de red determina la forma como está estructurada la aplicación en función de la infraestructura de red donde se va a ejecutar, y es diseñada por el equipo de desarrollo de la aplicación.
Dependiendo de la forma como se construyen las aplicaciones de red, se destacan la arquitectura cliente–servidor y la arquitectura peer–to–peer (KUROSE & ROSS, 2010).
La arquitectura cliente servidor (Figura 1) fue propuesta como una alternativa al convencional sistema mainframe utilizado en grandes empresas (WAI, 2007). En esta arquitectura, los roles de las aplicaciones son marcadamente diferentes. Una aplicación llamada servidor es un programa de propósito específico dedicado a ofrecer un servicio y “siempre” está en línea esperando conexiones de un conjunto de clientes muchas veces con características limitadas que inician el contacto con el servidor en busca del servicio prestado por el servidor (COMER, 1997).
INTRODUCCIÓN
3
Figura 1. Arquitectura cliente–servidor
Un ejemplo de aplicaciones cliente–servidor pueden ser el “Apache Web Server” (The Apache Software Fundation) y el navegador “Mozilla Firefox” (Mozilla-Firefox), donde Apache es el servidor Web y Firefox es el cliente del protocolo HTTP. En esta arquitectura, los clientes no tienen ninguna conexión directa entre sí. Por ejemplo, dos navegadores no se comunican directamente. Otra característica de esta arquitectura es que el servidor generalmente tiene asignada una dirección IP fija, por lo que un cliente “siempre” puede contactar al servidor (KUROSE & ROSS, 2010).
Por su parte, en una arquitectura peer–to–peer (Figura 2), existe comunicación directa entre pares de hosts (peers) los cuales se conectan a la red en forma intermitente.
Figura 2. Arquitectura peer–to–peer
4
INTRODUCCIÓN AL DESARROLLO DE APLICACIONES DE RED EN JAVA
Los peers generalmente son aplicaciones que corren en computadores de usuarios individuales en su mayoría ubicados en hogares, universidades y oficinas. Esta arquitectura es usada para el desarrollo de aplicaciones de archivos compartidos, de mensajería instantánea, de comunicación de voz, entre muchas otras. En la arquitectura peer–to–peer, los roles de las aplicaciones son muy similares, todas tienen comportamiento tanto de cliente como de servidor (KUROSE & ROSS, 2010). Para este caso, son muy populares los programas de archivos compartidos como Ares Galaxy (Ares-Galaxy) y BitTorrent (Bit-Torrent).
También existen aplicaciones que combinan las dos arquitecturas (arquitectura híbrida). Por ejemplo, para algunas aplicaciones de mensajería instantánea, los servidores son usados para seguir la pista a las direcciones IP de los usuarios, pero los mensajes usuario–usuario con enviados directamente entre los hosts de los usuarios, sin pasar por servidores intermedios (KUROSE & ROSS, 2010).
Las diferentes aplicaciones de red necesitan servicios proporcionados por las redes de computadores y otros dispositivos que permiten realizar la comunicación. Por lo tanto, es conveniente hacer una aproximación a los conceptos básicos utilizados en las redes de computadores, aunque no sea el objetivo principal de este trabajo.
1.2 CONCEPTOS BÁSICOS DE REDES
Una red de computadores es un conjunto de computadores autónomos interconectados por algún medio (cable de cobre, fibra óptica o el aire, etc.) con el fin de compartir recursos (TANENBAUM, 2004). Hoy en día las redes no solo son conjuntos de computadores, también se pueden incluir otros dispositivos que sean capaces de ejecutar aplicaciones de red, dispositivos que reciben el nombre de hosts. Las aplicaciones que corren en hosts son en realidad quienes se podrían considerar como los usuarios de una red (CALVERT & DONAHOO, 2008).
INTRODUCCIÓN
5
Por otra parte, los enrutadores son dispositivos de propósito específico cuya tarea consiste en conectar redes distintas (COMER, 1997). Aunque se puede decir que un enrutador puede ejecutar cierto tipo de aplicaciones, las aplicaciones de red no están pensadas para su ejecución en los enrutadores (KUROSE & ROSS, 2010). La importancia principal de los enrutadores es que permiten que los hosts que pertenecen a redes distintas se puedan conectar. Es importante anotar que para los hosts, la presencia de los enrutadores es transparente (CALVERT & DONAHOO, 2008), debido a que la comunicación es realizada entre host origen y host destino.
En el proceso de intercambiar información entre una aplicación que corre en un host y otra aplicación que se ejecuta en otro host, lo que realmente se intercambia es un conjunto de paquetes (secuencias de bytes) que las aplicaciones que entienden porque implementan un protocolo particular previamente acordado, junto con cierta información de control que se necesita para que, por ejemplo, los enrutadores puedan ayudar en el proceso de enrutamiento de un paquete al host de destino (CALVERT & DONAHOO, 2008).
Un protocolo es un acuerdo acerca de la forma en la cual dos aplicaciones intercambian información y define el formato de los paquetes que se envían entre sí y de las acciones que deben realizar cuando un paquete es enviado o recibido (KUROSE & ROSS, 2010). Un protocolo establece la forma como se estructuran los paquetes, por ejemplo, especifica la forma y el lugar donde se almacena la información del destinatario o el tamaño de los datos (CALVERT & DONAHOO, 2008).
Por otra parte, las redes de computadores son sistemas complejos. Se componen de distintas clases de dispositivos que pueden ser producidos por fabricantes diferentes los cuales pueden manejar tecnologías distintas (KUROSE & ROSS, 2010) y todo debe ser articulado para que funcione correctamente. Para implementar una red que pueda ser funcional se necesita un número significativo protocolos cada uno diseñado para una tarea específica y que entre todos se puedan apoyar para la transmisión, reenvío o recepción de paquetes.
6
INTRODUCCIÓN AL DESARROLLO DE APLICACIONES DE RED EN JAVA
En el estudio de las redes de computadores, los protocolos se han organizado por capas para hacer más fácil su análisis, implementación y evolución (TANENBAUM, 2004). La pila de protocolos TCP/IP es en la actualidad el conjunto de protocolos que gobierna las comunicaciones en Internet y en la mayoría de las redes de computadores en el mundo (KUROSE & ROSS, 2010). Dentro de este conjunto de protocolos se destacan los protocolos TCP, UDP e IP (CALVERT & DONAHOO, 2008), junto con los protocolos de la capa de aplicación que utilizan los usuarios: HTTP, FTP, SMTP, entre otros.
La Figura 3 muestra la forma como se han establecido las diferentes capas de la pila de protocolos TCP/IP.
Figura 3. Pila de protocolos TCP/IP
Se puede observar que la capa física aparece como un componente de la capa de enlace de datos. Lo que ocurre es que no hay una aceptación universal sobre la existencia de los diferentes medios de acceso a la red como una capa independiente dentro de la pila de protocolos TCP/IP, la cual debería estar encargada de la interpretación de los bits en los diferentes medios de transmisión de datos así como de la propagación de la señal entre los equipos que conecta. Las tareas desarrolladas por las demás capas de la pila de protocolos TCP/IP se resumen a continuación (CALVERT & DONAHOO, 2008), (KUROSE & ROSS, 2010) y (TANENBAUM, 2004):
INTRODUCCIÓN
7
La capa de enlace de datos permite la transferencia de información (frames) entre nodos vecinos usando protocolos como Ethernet en las redes alambradas, o para las redes inalámbricas 802.11 (conocido como WiFi). Para algunos autores, es llamada capa de host a red.
La capa de red se encarga del enrutamiento de paquetes (datagramas) desde el host origen hasta el host destino usando el protocolo IP y algunos protocolos de enrutamiento. El protocolo IP proporciona un servicio de datagramas donde cada paquete se intenta entregar en forma independiente. Por lo tanto, cada paquete IP necesita debe llevar la dirección IP de destino. Durante la transmisión puede ocurrir que se produzcan paquetes perdidos, dañados o duplicados. El protocolo IP hace lo posible por entregar cada paquete en el destino, pero no puede garantizar nada, por lo que IP se considera un protocolo de mejor esfuerzo.
En la capa de transporte se realiza la transferencia de datos (segmentos) de una aplicación a otra usando los protocolos TCP o UDP. Los dos protocolos están construidos sobre el servicio que presta IP, pero con diferentes características y también con algunas similitudes. Por ejemplo, los dos tienen la función de identificar las aplicaciones que envían o deben recibir un mensaje, usando números de puerto. TCP está diseñado ofrecer confiabilidad, lo que significa que puede detectar errores y recuperarse de ellos. TCP es un protocolo orientado a conexión, lo que significa que antes de enviar información al destinatario, los programas deben establecer una conexión, la cual consiste en crear y mantener un conjunto de variables que van a ayudar a controlar todo lo que va pasando con la transferencia de información del origen al destino. Por su parte, el protocolo UDP no intenta recuperarse de los errores que pudieran presentarse en la transmisión; simplemente extiende el servicio de datagramas de mejor esfuerzo que ofrece IP para que sean las aplicaciones y no los hosts los que utilicen el servicio de datagramas.
Finalmente, la capa de aplicación define los protocolos que le dan soporte a las aplicaciones de Internet, tales como el protocolo HTTP, el protocolo FTP, el protocolo
8
INTRODUCCIÓN AL DESARROLLO DE APLICACIONES DE RED EN JAVA
SMTP, entre otros. Las aplicaciones de red intercambian mensajes que son encapsulados en las diferentes capas del modelo de referencia TCP/IP (KUROSE & ROSS, 2010).
1.2.1 Encapsulación
Cuando un host envía información a otro, lo hace creando un mensaje en la capa de aplicación. Este mensaje es pasado a la capa de transporte donde se crea un segmento. El segmento tiene información de control (encabezado) y el mensaje de la capa de aplicación en el campo de datos (KUROSE & ROSS, 2010).
El segmento es pasado entonces a la capa de red donde se crea un datagrama formado por el encabezado del datagrama con información de control propia de la capa de red y el campo de datos que contiene el segmento de la capa de transporte. El datagrama se pasa a la capa de enlace de datos donde se crea un frame que de igual manera lleva el datagrama de capa de red en el campo de datos junto con información de encabezado (KUROSE & ROSS, 2010).
El frame se pasa luego a la capa física para que inicie la transmisión que finalmente lo llevará al host de destino, pasando a través de enrutadores intermedios, si el host destino está en una red distinta que el host origen (KUROSE & ROSS, 2010).
En el destino los bits se convierten en un frame de la capa de enlace de datos y luego de realizar el procesamiento correspondiente, se extrae el datagrama y se pasa a la capa de red. En la capa de red se realiza el procesamiento propio de la capa de red para obtener el segmento de capa de transporte de donde se obtiene el mensaje de capa de aplicación que finalmente es entregado en el lado receptor para que sea recibido y procesado (KUROSE & ROSS, 2010).
La capa de aplicación entonces envía un mensaje que solo deberá ser interpretado por la capa de aplicación del lado receptor. Igual ocurre con la capa de transporte en el
INTRODUCCIÓN
9
lado transmisor donde se envía un segmento que solo la capa de transporte del destinatario debe interpretar (KUROSE & ROSS, 2010). En el caso de los datagramas, el destinatario de un datagrama es la capa de red del lado destino. Sin embargo, la capa de red de cada enrutador tiene como función revisar la información de encabezado del datagrama para poder reenviarlo por el camino adecuado. En la capa de enlace de datos, la información va dirigida a un nodo vecino donde se podrá analizar y procesar la información de encabezado que viene dentro del frame (KUROSE & ROSS, 2010). La Figura 4 muestra una idea del concepto de encapsulación, donde un mensaje de la capa de aplicación va almacenado dentro de un segmento; un segmento de capa de transporte se almacena en un datagrama; un datagrama de capa de red va dentro de un frame y un frame de la capa de enlace de datos se convierte en bits para su transmisión.
Figura 4. Encapsulación Entonces, la encapsulación es el proceso de producir los mensajes, segmentos, datagramas y frames en el lado transmisor de tal manera que solo la capa equivalente en el host de destino y/o en los nodos intermedios puedan entender y procesar (KUROSE & ROSS, 2010).
INTRODUCCIÓN AL DESARROLLO DE APLICACIONES DE RED EN JAVA
10
Otra forma de ver la encapsulación puede ser a través de la captura de un paquete transmitido por la red, con la ayuda de un programa analizador de protocolos como Wireshark (Wireshark).
La Figura 5 muestra un paquete de capa de enlace de datos (frame) capturado mediante el programa Wireshark. Observe que sobre el mismo paquete capturado se resaltan los valores hexadecimales del protocolo Ethernet, en la capa de enlace de datos; el protocolo IP en la capa de red y el protocolo TCP en la capa de transporte. Además, se puede ver el mensaje de capa de aplicación.
Figura 5. Encapsulación de protocolos
1.2.2 Direcciones para los hosts
Todas las interfaces de red de los diferentes equipos conectados en una red de computadores tienen una dirección física única comúnmente llamada dirección MAC. Para las tecnologías más populares, incluyendo Ethernet y 802.11, la dirección MAC se expresa como un número de 48 bits de longitud, las cuales se expresan como seis pares de dígitos hexadecimales separados por un guión (-) ó por un símbolo dos puntos (:), por ejemplo: 00:1F:E2:BE:87:49 ó 00-1F-E2-BE-87-49 (KUROSE & ROSS, 2010).
INTRODUCCIÓN
11
Por otra parte, las interfaces de red pueden ser configuradas con una o más direcciones IP. Las direcciones IP proporcionan un esquema de identificación lógica usado para enviar y recibir paquetes de datos a través de una red IP. Las direcciones IP son especificadas por el protocolo IP en el RFC 791 (POSTEL, 1986).
La comunicación entre aplicaciones requiere que el programa que desea iniciar una comunicación especifique la información acerca del destinatario. En las redes TCP/IP se debe suministrar tanto la dirección IP como en número de puerto. La dirección IP es utilizada por el protocolo IP, mientras que el número de puerto lo necesita el protocolo de capa de transporte, bien sea TCP o UDP.
Actualmente hay dos versiones del protocolo IP, la versión 4 (v4) y la versión 6 (v6). Una dirección IPv4 es un número de 32 bits que identifica de manera única una interfaz de red. Una interfaz de red es una conexión entre un host y un enlace físico conectado a la red (KUROSE & ROSS, 2010). Las direcciones IPv6 tienen 128 bits de longitud (O'FLAHERTY, 2009). A pesar de la necesidad de migrar al protocolo IPv6, todavía están vigentes las direcciones IPv4 y aún son las más comunes en todo el mundo.
Tomando los 32 bits de una dirección IPv4, se podrían identificar 232 interfaces de red distintas, es decir, más de 4.000.000.000. Sin embargo, debido a la forma como fueron asignadas, muchas de ellas fueron desperdiciadas (CALVERT & DONAHOO, 2008) y pronto van a estar completamente agotadas.
Una dirección IP usualmente se expresa en forma decimal con puntos, es decir, se forman cuatro grupos de ocho bits y cada grupo se convierte a su equivalente en decimal. Luego, cada uno de los números (entre 0 y 255) es separado del siguiente usando el símbolo “.” (COMER, 1997). Por ejemplo, la dirección IP expresada como 11000000 10101000 00001010 00000010 se pude expresar como 192.168.10.2, en el formato decimal con puntos.
12
INTRODUCCIÓN AL DESARROLLO DE APLICACIONES DE RED EN JAVA
Por lo regular, un host de escritorio tiene sólo una interfaz de red, mientras que un computador portátil viene con una interfaz para la red alambrada y otra para la red inalámbrica, es decir, viene con dos interfaces de red. Dado que una interfaz de red pertenece a un único host, se dice también que una dirección IP identifica a un host en la red. Sin embargo, al revés no es cierto, ya que un único host puede tener múltiples interfaces de red, y cada una de ellas puede tener una o más direcciones IP (CALVERT & DONAHOO, 2008).
Ciertas direcciones IP se han reservado para propósitos especiales, por lo que no pueden ser asignadas a ninguna interfaz de red. Entre ellas se encuentran las que su primer octeto es el número 127, conocidas como direcciones loopback, las cuales son utilizadas para realizar pruebas de las aplicaciones sin necesidad de enviar paquetes a la red. La dirección IP 127.0.0.1 es la más utilizada por los programadores y se encuentra en todos los hosts (COMER, 1997) y (CALVERT & DONAHOO, 2008).
Otra dirección IP reservada es la dirección 255.255.255.255, utilizada para enviar mensajes broadcast en la red local y 0.0.0.0 utilizada en procesos de arranque (TANENBAUM, 2004), por ejemplo, cuando un host solicita una dirección IP a un servidor DHCP.
Otro grupo de direcciones IP que se han reservado corresponde a las direcciones privadas, especificadas en el RFC1918 (REKHTER, MOSKOWITS, & GROOT, 1986). Las direcciones IP privadas facilitan la asignación de direcciones IP en entornos privados y permite que estas direcciones IP puedan ser utilizadas en una red sin necesidad de consultar con ninguna autoridad ni proveedor, siempre y cuando no se conecten directamente a Internet.
Las direcciones IP privadas son aquellas cuyo primer octeto es 10; aquellas que tienen 172 en el primer octeto y un número entre 16 y 31 en el segundo octeto; y las que tienen 192.168 en los dos primeros octetos. Estas direcciones IP no son alcanzables desde el exterior de la red y para poder salir a una red externa como Internet
INTRODUCCIÓN
13
necesitan de un servicio de traducción de direcciones IP (NAT) que pueda prestar algún dispositivo. Una breve descripción del servicio NAT es presentada en la sección 1.2.3.
Las direcciones de autoconfiguración o enlace local, son aquellas que sus primeros dos octetos son 169.254 (CALVERT & DONAHOO, 2008). Estas direcciones son asignadas automáticamente para completar el proceso de autoconfiguración de un host cuando no responde el servidor que preste el servicio de asignación de direcciones IP en forma dinámica (DHCP). Estas direcciones solo pueden ser usadas en la misma red local. También son conocidas con el nombre de direcciones APIPA (Automatic Private IP Addressing).
Finalmente, las direcciones que tienen su primer octeto en el rango 224 a 239 se han reservado como direcciones IP multicast y se utilizan para la comunicación en grupo (COMER, 1997).
Las direcciones IPv6 se representan en notación hexadecimal, expresando los 128 bits en ocho grupos de cuatro dígitos, por ejemplo FEDC:BA98:7654:3210:FEDC:BA98: 7654:3210. Si uno de los grupos de cuatro dígitos hexadecimales tiene ceros a la izquierda, éstos se pueden omitir, y si un grupo se compone de cuatro dígitos hexadecimales en cero, se puede poner uno solo, como en el siguiente ejemplo: 1080:0:0:0:8:800:200C:417A. De igual manera, si hay grupos consecutivos de dígitos en cero, se puede resumir una dirección IPv6 utilizando dos símbolos dos puntos consecutivos (::) simpre y cuando sea una sola vez en una dirección para que no se presente ambigüedad, por ejemplo 1080:0:0:0:8:800:200C:417A es equivalente a 1080::8:800:200C:417A (HINDEN & DEERING, 1998).
Algunas direcciones IPv6 importantes son: 0:0:0:0:0:0:0:0 ó su equivalente :: llamada dirección no especificada, y se utiliza cuando la dirección IP no ha sido configurada; 0:0:0:0:0:0:0:1 ó su equivalente ::1, llamada dirección de loopback (HINDEN & DEERING, 1998).
14
INTRODUCCIÓN AL DESARROLLO DE APLICACIONES DE RED EN JAVA
1.2.3 Números de puerto
Cuando una aplicación de red se encuentra en ejecución pasa a ser un proceso del sistema operativo y se identifica en el host en el que se encuentra con un número entero de 16 bits, llamado número de puerto. El rango completo es desde 1 hasta 65535, ya que el 0 está reservado (CALVERT & DONAHOO, 2008).
El conjunto de protocolos de red TCP/IP ofrece un servicio de transporte orientado a conexión y con transferencia confiable de datos llamado TCP y ofrece además un servicio no orientado a conexión llamado UDP el cual no ofrece ningún beneficio en el control de la transmisión pero es más rápido que TCP (KUROSE & ROSS, 2010).
Un número de puerto entonces es utilizado por los protocolos TCP o UDP para identificar los procesos origen y destino de un mensaje. Como analogía que ayude a entender el significado de número de puerto, considere una compañía que tenga un sistema telefónico interno. Para localizar a una persona específica dentro de la compañía, es necesario marcar el número de teléfono del conmutador y a continuación indicar la extensión telefónica de la persona o dependencia con quien desea hablar (CALVERT & DONAHOO, 2008). En este caso, el número telefónico del conmutador de la compañía es como la dirección IP que identifica un host en una red. El número de la extensión es como el número de puerto, que identifica en ese host al proceso destino de un mensaje.
Algunas aplicaciones populares tienen asignado números de puerto específicos. Si un proceso que se está ejecutando en el host a conoce la dirección IP del host b y el número de puerto donde se ejecuta un proceso en el host b, entonces puede enviarle mensajes.
Generalmente, los servidores hacen públicos sus números de puerto para que sean utilizados por los clientes. Si el cliente utiliza una dirección de Internet fija para referirse a un servicio, entonces ese servicio debe ejecutarse “siempre” en el mismo
INTRODUCCIÓN
15
computador y escuchando en el mismo puerto para que sean alcanzables por los clientes (COULOURIS & DOLLIMORE, 2005).
Por ejemplo, un servidor Web es identificado con el número 80, un servidor de correo electrónico que use el protocolo SMTP es identificado con el número 25. La lista completa de los números de puerto asignados para los protocolos estándar de Internet se puede encontrar en (IANA). Cuando un desarrollador crea una nueva aplicación de red, la aplicación debe tener un número de puerto asignado (KUROSE & ROSS, 2010), el cual puede ser escogido en el rango 1.024 a 65.535.
Retomando el concepto de NAT, mencionado en la sección anterior, se puede decir que NAT es servicio de traducción de direcciones IP y es utilizado principalmente para pasar de un esquema de direcciones privado a una dirección pública que sea válida en Internet.
El dispositivo que preste este servicio, debe realizar lo siguiente en todos los datagramas salientes (KUROSE & ROSS, 2010): Reemplazar la dirección IP y el número de puerto origen por la dirección IP NAT y un nuevo número de puerto disponible. Los clientes y servidores remotos responderán usando como dirección de destino la dirección IP NAT y el número de puerto especificado en el segmento recibido. La información que es usada para mapear la dirección IP y el número de puerto originales deben ser registradas en una tabla NAT, de tal manera que cuando lleguen las respuestas a las solicitudes, éstas puedan ser entregadas al proceso que las solicitó en el host correspondiente.
1.2.4 Nombres de hosts
Para las personas es más fácil utilizar nombres de hosts y no direcciones IP debido a que son más fáciles de recordar. Sin embargo, es importante tener presente que para poder realizar una comunicación entre hosts, es necesario contar con la dirección IP,
16
INTRODUCCIÓN AL DESARROLLO DE APLICACIONES DE RED EN JAVA
por lo que el sistema debe realizar un trabajo adicional para resolver el nombre, utilizando el servicio de resolución de nombres de dominio DNS.
Cuando se desarrollan aplicaciones que se ejecutan en ambientes privados, es posible utilizar direcciones IP privadas, pero si se pretende construir aplicaciones que puedan funcionar en cualquier host que pertenezca a Internet, es necesario contar con una dirección IP pública, y seguramente se tendrá a disposición un nombre de host universalmente alcanzable.
El DNS es un servicio que mapea nombres de host a direcciones IP y permite realizar consultas que realizan otras aplicaciones que requieren la traducción de un nombre para poder establecer una conexión o enviar información (KUROSE & ROSS, 2010). Aunque es posible configurar un servidor DNS que pueda resolver nombres de host a direcciones IP privadas, el servicio principal que presta un servidor DNS es resolver nombres de host a direcciones IP públicas.
DNS es una base de datos distribuida implementada en una jerarquía de servidores distribuidos en todo el mundo. Los servicios principales que ofrece son los siguientes: traducción de nombres de host a direcciones IP; traducción de nombres alias a nombres canónicos; y traducción de nombres de servidores de correo a nombres canónicos (KUROSE & ROSS, 2010). Un nombre canónico el nombre que en realidad identifica a un host, mientras que un nombre de alias es un nombre corto o alterno para identificar un host. Siempre que hay un nombre de alias, hay un nombre canónico, aunque el uso de alias es opcional.
Es importante tener presente que los nombres de host pueden ser utilizados para la realización de aplicaciones distribuidas siempre y cuando haya un mecanismo para resolver el nombre, bien sea dentro de un entorno local o en Internet global.
Si se quiere tener acceso a hosts remotos que no pertenezcan a una organización sino que su conexión a Internet sea a través de un servicio de banda ancha convencional, es
INTRODUCCIÓN
17
necesario utilizar un servicio que mapea un nombre de dominio a una dirección IP dinámica, tal como el que ofrece No-IP (NoIP).
1.3 COMUNICACIÓN DE PROCESOS
Un proceso es un programa en ejecución en un host. Las aplicaciones de red se comunican por medio del intercambio de mensajes a través de la red. En la Web, un navegador inicia el contacto con un servidor Web; de ahí que el browser es el programa cliente mientras que el servidor Web es el programa servidor.
En un sistema de archivos compartidos peer-to-peer, cuando el peer A solicita al peer B que envíe un archivo específico, el peer A es el cliente y el peer B es el servidor, para el contexto de esta sesión específica. Cuando no hay confusión, también se puede decir lado cliente y lado servidor (KUROSE & ROSS, 2010).
Un proceso se comunica con otro a través del intercambio de mensajes que viajan por la red. Una forma de comunicación de procesos ampliamente utilizada y sobre la cual hará énfasis este trabajo, es la comunicación sincrónica donde la operación usada para recibir bytes es bloqueante, es decir, el proceso receptor se bloquea cada vez que va a recibir bytes hasta la llegada de un nuevo mensaje (COULOURIS & DOLLIMORE, 2005).
Otra forma de comunicación de procesos es aquella donde la operación de recibir bytes no es bloqueante. En este caso, es necesario diseñar un espacio de almacenamiento temporal y un mecanismo para notificar al receptor que ha llegado un nuevo mensaje para que el proceso receptor lo pueda leer.
1.3.1 Protocolos de la capa de aplicación
Un protocolo de la capa de aplicación define cómo se estructuran los mensajes, el significado de cada campo que conforma el mensaje, y la forma como se determina el
INTRODUCCIÓN AL DESARROLLO DE APLICACIONES DE RED EN JAVA
18
momento en que un mensaje se debe enviar o responder. En general, un protocolo de la capa de aplicación define (KUROSE & ROSS, 2010):
•
El tipo de mensajes intercambiados, por ejemplo, mensajes de solicitud y mensajes de respuesta.
•
La sintaxis de los mensajes, por ejemplo, la cantidad de campos y el significado de cada uno.
•
La semántica de los campos, es decir, el significado de la información contenida en cada campo de ellos.
•
Las reglas que determinan cuándo y cómo un proceso envía y responde a los mensajes.
Algunos protocolos de la capa de aplicación están especificados en documentos RFC y por lo tanto son de dominio público. Por ejemplo, el protocolo HTTP versión 1.1 está especificado en el RFC 2616 (FIELDING, 1999). Si un desarrollador construye un navegador de Internet que siga las reglas del protocolo HTTP especificadas en este documento RFC, entonces el navegador será capaz de recuperar páginas Web de cualquier servidor Web que haya seguido las reglas del protocolo HTTP en el mismo RFC. En este tipo de aplicaciones, puede ocurrir que un equipo de desarrollo haga un programa cliente y éste pueda interoperar con un programa servidor escrito por otras personas (KUROSE & ROSS, 2010).
En el caso de las aplicaciones de red propietarias, las características de las aplicaciones y el protocolo de comunicación no están al alcance del público en general. En este caso, el mismo equipo de desarrollo debe construir tanto el programa cliente como el programa servidor. Un ejemplo de aplicaciones de red propietarias es Skype (Skype), cuyo protocolo no se ha publicado en un documento de dominio público (KUROSE & ROSS, 2010).
INTRODUCCIÓN
19
1.3.2 Servicios de la capa de transporte
El proceso de la capa de aplicación en el lado transmisor envía bytes y el protocolo de la capa de transporte tiene la responsabilidad de entregarlos al proceso receptor. Como ya se ha mencionado, Internet y en general las redes TCP/IP tienen dos protocolos disponibles para las aplicaciones de red, TCP y UDP. Cuando se desarrolla una aplicación de red, una de las primeras decisiones que deberá tomar el desarrollador es elegir el protocolo de capa de transporte que debe usar. Cada uno ofrece diferentes servicios para las aplicaciones, por lo que se debe seleccionar el que se adapte mejora las necesidades de la aplicación.
1.4 SOCKETS
Un proceso envía y recibe mensajes a través de la red, específicamente a través de un socket. Un socket es una interface entre la capa de aplicación y el servicio de transporte, dentro de un host. El proceso que envía un mensaje asume que existe una infraestructura de transporte que lleva el mensaje al otro proceso (KUROSE & ROSS, 2010). Los sockets proporcionan los puntos extremos de la comunicación entre procesos (COULOURIS & DOLLIMORE, 2005).
Para los procesos receptores de mensajes, el socket debe estar asociado a un puerto local y a una dirección IP del computador donde se ejecuta (COULOURIS & DOLLIMORE, 2005). Los procesos pueden utilizar un mismo socket tanto para enviar como para recibir mensajes por lo que la comunicación entre los procesos es bidireccional.
La Figura 6 muestra cómo un socket permite conectar un proceso en la capa de aplicación con el protocolo de la capa de transporte y la Figura 7 ilustra la comunicación de dos procesos a través de la red.
INTRODUCCIÓN AL DESARROLLO DE APLICACIONES DE RED EN JAVA
20
Figura 6. Relación entre procesos en ejecución, sockets y protocolo de capa de transporte
Figura 7. Comunicación a través de la red
El desarrollador de aplicaciones tiene todo el control de todo los que pueda ocurrir con su aplicación, pero no tiene mayor influencia en el servicio de transporte que se encarga de mover el mensaje de un proceso al otro. Una vez del desarrollador de la aplicación escoge el servicio de transporte que necesita para su aplicación, utiliza las características de ese servicio de transporte en el desarrollo de la aplicación (KUROSE & ROSS, 2010).
1.5 PROTOCOLO TCP
El modelo de servicio TCP incluye un servicio orientado a conexión y proporciona transferencia confiable de datos, control de flujo y control de congestiones. Cuando una aplicación invoca TCP como su protocolo de transporte, la aplicación recibe todos estos servicios de TCP (KUROSE & ROSS, 2010).
Los procesos que se comunican mediante el protocolo TCP establecen una conexión antes de iniciar el intercambio de información. El establecimiento de la conexión implica una petición de conexión desde el cliente al servidor, seguida de una
INTRODUCCIÓN
21
aceptación, por parte del servidor. Una vez que se ha establecido la comunicación, los dos procesos se conectan por los flujos establecidos, uno en cada dirección. Entonces, los procesos simplemente leen o escriben en el flujo sin tener que preocuparse de las direcciones IP de destino ni de los números de puerto. De este modo, cada uno de los procesos puede enviar información al otro escribiendo en el flujo de salida, y el otro puede obtener la información leyendo de su flujo de entrada (COULOURIS & DOLLIMORE, 2005).
El cliente debe iniciar el contacto con el servidor. Para que el servidor pueda reaccionar al contacto del cliente, el servidor debe estar activo, es decir, debe estar en ejecución antes que el cliente lo contacte; además, el programa servidor debe tener un socket que le de la bienvenida al cliente. El contacto inicial del cliente es como si alguien estuviera tocando la puerta de la casa (KUROSE & ROSS, 2010). Este contacto inicial recibe el nombre de three-way-handshake o acuerdo inicial en tres pasos. Ver Figura 8.
Figura 8. Three-way-handshake
Durante el three-way-handshake, el proceso cliente inicia el contacto con el servidor. Cuando el servidor escucha que el cliente está intentando conectarse, crea un nuevo socket que está reservado exclusivamente para atender a ese cliente en particular. Al final de la fase del three-way-handshake, se crea una conexión TCP entre el socket del
INTRODUCCIÓN AL DESARROLLO DE APLICACIONES DE RED EN JAVA
22
cliente y el socket del servidor. Por lo tanto, es frecuente que ese socket dedicado sea llamado socket de conexión.
Una vez que el socket ha sido creado en el cliente, el protocolo TCP en el cliente inicia un three-way-handshake y establece la conexión con el servidor, como se puede apreciar en la Figura 9 - a.
Figura 9. Conexión de sockets
Después del three-way-handshake, se ha creado una conexión, la cual, desde el punto de vista de la aplicación es como si se tuviera un “tubo virtual” entre el socket del cliente y el socket de conexión en el servidor. Ver Figura 9 - b. Entonces, el cliente puede enviar bytes por su socket y el protocolo TCP garantiza que el proceso servidor los recibirá en el mismo orden en que fueron enviados. De igual manera, el cliente también puede recibir por el mismo socket los bytes que el servidor le envíe como respuesta. De este modo, se dice que TCP garantiza un servicio de transferencia de datos confiable y orientado a conexión. El three-way-handshake es transparente tanto para el programa cliente como para el programa servidor.
Aplicaciones conocidas que utilizan TCP. Los servicios más importantes de Internet utilizan el protocolo TCP para realizar el intercambio de información entre clientes y servidores. Entre ellos se encuentran HTTP, FTP, Telnet y SMTP.
INTRODUCCIÓN
23
Como fue mencionado al inicio de esta sección, el protocolo TCP además de ser orientado a conexión, se caracteriza por la entrega confiable de datos, el control de flujo y el control de congestiones.
Entrega confiable de datos. La entrega confiable de datos está basada en la detección de paquetes dañados, perdidos o duplicados. Para los paquetes dañados, se usa una suma de comprobación la cual es calculada y enviada por el transmisor junto con los datos, luego el receptor calcula la suma de comprobación con base en los datos recibidos y compara, detectando si se ha presentado algún error en los datos. Para los paquetes perdidos y duplicados, se usa un número de secuencia que se asigna a cada paquete. Con el número de secuencia también se hace posible que el receptor reordene los datos si estos llegan en desorden, antes de pasarlos al proceso en ejecución en la capa de aplicación. El transmisor utiliza acuses de recibo con el fin de seguir el rastro de los paquetes enviados, así, puede determinar cuándo debe realizar una retransmisión de los paquetes enviados previamente. Si el transmisor no recibe un acuse de recibo dentro de un tiempo límite (timeout), entonces el transmisor debe retransmitir todos los mensajes pendientes. Si la pérdida de paquetes sobrepasa cierto límite, o la red que conecta un par de procesos en comunicación está severamente congestionada, el software TCP responsable de enviar los mensajes no recibirá acuses de recibo de los paquetes enviados y después de un tiempo declarará finalizada la conexión (KUROSE & ROSS, 2010).
Control de flujo. La información que llega a la capa de transporte del lado receptor se aloja en un buffer el cual tiene una capacidad máxima medida en bytes. En cada segmento de datos, el receptor le envía al transmisor la cantidad de espacio libre en el buffer, dentro de un acuse de recibo. De este modo, el transmisor no enviará más bytes que los que el receptor puede recibir. Cuando el transmisor tiene algo para enviar al receptor pero no hay espacio suficiente para almacenarlo, entonces el transmisor queda bloqueado esperando a que el tamaño disponible en el receptor sea suficiente (COULOURIS & DOLLIMORE, 2005) y (KUROSE & ROSS, 2010).
24
INTRODUCCIÓN AL DESARROLLO DE APLICACIONES DE RED EN JAVA
Control congestiones. El control de congestiones limita la cantidad de bytes que es enviada a la red por parte del transmisor cuando hay indicios que la red está congestionada.
1.6 PROTOCOLO UDP
El protocolo UDP es un protocolo de transporte más sencillo. No hay solicitud de conexión antes de iniciar el intercambio de mensajes, por lo que se dice que UDP es un protocolo no orientado a conexión. Además, UDP proporciona un servicio de transferencia de datos no confiable, es decir, cuando un proceso envía un mensaje, el protocolo UDP no puede garantizar la entrega al proceso receptor, tampoco puede garantizar que los mensajes van a ser entregados en orden. UDP no incluye ningún mecanismo de control de flujo o de congestión.
Cuando un proceso transmisor envía un mensaje a un proceso receptor usando datagramas, debe especificar el destinatario mediante la dirección IP y el número de puerto (KUROSE & ROSS, 2010). Algunas características de la comunicación por datagramas UDP son las siguientes (COULOURIS & DOLLIMORE, 2005):
Tamaño del mensaje. El protocolo IP en la capa subyacente permite segmentos de hasta 216 bytes, incluyendo encabezados y datos. Sin embargo, en la práctica, un tamaño de 8 KBytes es aceptado en la mayoría de las ocasiones. Por lo tanto, en el momento de enviar un datagrama, el proceso receptor debe especificar un espacio medido en bytes sobre el cual se almacenará el mensaje recibido. En el caso que una aplicación necesite enviar un mensaje más grande, deberá fragmentarlo en partes del tamaño especificado.
Bloqueo. La comunicación por datagramas UDP permite el envío de mensajes en forma no bloqueante, mientras que la recepción de los mensajes se hace en forma bloqueante. Es decir que el flujo de control de la aplicación continúa luego de dirigir el mensaje a su destino a través del socket. Cuando el proceso receptor está esperando
INTRODUCCIÓN
25
un mensaje, produce un bloqueo hasta que reciba un datagrama, a menos que se haya establecido un tiempo límite (timeout) asociado al socket. En ocasiones es útil que el proceso receptor utilice hilos (threads) para tener la posibilidad de realizar otras actividades mientras llega el mensaje que está esperando.
Tiempo máximo de espera. Algunas aplicaciones no pueden esperar indefinidamente a que llegue un mensaje. En estos casos se pueden especificar un tiempo máximo de espera en los sockets. Definir el tiempo límite no es fácil de establecer y depende de cada caso en particular.
Origen de los mensajes. Un proceso receptor recibe mensajes de cualquier proceso transmisor. Sin embargo, dado que cada segmento UDP trae la dirección IP y el puerto del proceso transmisor, el receptor puede comprobar de dónde viene el mensaje y de este modo, si lo considera conveniente, puede determinar si procesa o no los mensajes que lleguen de determinado origen.
26
INTRODUCCIÓN AL DESARROLLO DE APLICACIONES DE RED EN JAVA
API DE JAVA
27
2. API DE JAVA El kit de desarrollo de Java ofrece diferentes APIs que pueden ser utilizadas para la construcción de aplicaciones de red. Este capítulo describe las APIs principales, iniciando con las APIs que se utilizan para identificar las interfaces de red y las direcciones IP de un host, continúa con la API para sockets de flujo (utilizadas en aplicaciones con el protocolo TCP), seguida de la API para sockets de datagramas (utilizada en aplicaciones con el protocolo UDP) y termina con los sockets para comunicación en grupo.
2.1 INTERFACES DE RED Y DIRECCIONES IP
Una aplicación de red puede necesitar la identificación de las interfaces de red que están disponibles en el host, para lo cual se puede contar con la clase NetworkInterface del paquete java.net (Java Platform, Standard Edition 6 API Specification).
Por otra parte, los bytes enviados por las aplicaciones se envían en forma de segmentos TCP y UDP mediante paquetes IP. Estos paquetes IP se envían a un host de destino identificado por una dirección IP y un número de puerto dentro del host. Java proporciona una clase InetAddress del paquete java.net, la cual permite representar direcciones de Internet (Java Platform, Standard Edition 6 API Specification). Esta clase tiene dos subclases, Inet4Address e Inet6Address, las cuales representan las direcciones IP para las versiones 4 y 6 del protocolo IP.
Los usuarios de la clase InetAddress pueden referirse a los hosts por sus nombres de host en el sistema de nombres de dominio (DNS), o bien, especificando la dirección IP,
28
INTRODUCCIÓN AL DESARROLLO DE APLICACIONES DE RED EN JAVA
en el formato apropiado, de acuerdo con la versión del protocolo IP – versión 4 ó versión 6.
El Ejemplo 1 (tomado y adaptado de (CALVERT & DONAHOO, 2008)), explora todas las interfaces de red disponibles en el host y a continuación, obtiene las direcciones IP asociadas a cada una de ellas y finalmente muestra las direcciones en la consola.
2.2 EJEMPLO 1: INTERFACES DE RED EN EL HOST LOCAL
Para identificar las interfaces de red del host local se puede utilizar el método estático getNetworkInterfaces ( ) de la clase NetworkInterface. Este método devuelve una Enumeracion (java.util.Enumeration).
Enumeration < NetworkInterface > hostNics = NetworkInterface.getNetworkInterfaces ( );
En cada iteración del recorrido de la enumeración se obtiene una intefaz de red, de la cual se pueden obtener las direcciones IP asociadas, de nuevo en una enumeración. Para esto, se utiliza el método getAddresses ( ) de la clase NetworkInterface.
Enumeration < InetAddress > nicAddresses = nic.getInetAddresses ( );
En cada iteración del recorrido de la segunda enumeración de obtiene una dirección IP (un objeto de la clase InetAddress), de la cual se obtiene la dirección de host utilizando el método getHostAddress ( ). Posteriormente se imprime en la consola un mensaje con el formato correspondiente, dependiendo de la versión del protocolo IP.
System.out.println ( "( v6 ) " + address.getHostAddress ( ) );
System.out.println ( "( v4 ) " + address.getHostAddress ( ) );
A continuación el código completo del Ejemplo 1.
API DE JAVA
29
/* * Ejemplo 1: NetworkInterfacesExample * * El objetivo de este programa es mostrar el funcionamiento b谩sico de la clases * NetworkInterface, InetAddress, Inet4Address, Inet6Address. * * En este programa: * 1) Utiliza la clase NetworkInterface para obtener la lista de las interfaces * de red del host local, y * 2) Utiliza las clases InetAddress, Inet4Address e Inet6Address para obtener la * informaci贸n de las direcciones IP asociadas a cada una de las interfaces de * red del host local */ import import import import import import import
java.net.Inet4Address; java.net.Inet6Address; java.net.InetAddress; java.net.NetworkInterface; java.net.SocketException; java.util.Enumeration; java.util.Iterator;
public class NetworkInterfacesExample { public static void main ( String [ ] args ) { try { // Se obtiene la lista de interfaces de red (NIC) del host local Enumeration < NetworkInterface > hostNics = NetworkInterface.getNetworkInterfaces ( ); // Por cada una de las interfaces de red del host while ( hostNics.hasMoreElements ( ) ) { // Se obtiene su nombre y se imprime en la consola NetworkInterface nic = hostNics.nextElement ( ); System.out.println( "Interfaz " + nic.getName ( ) + ":" ); // Se obtienen las direcciones IP asociadas a la interfaz de red Enumeration < InetAddress > nicAddresses = nic.getInetAddresses ( ); // Cada direccion IP se clasifica como direccion IP v4 o v6 // y se imprime while ( nicAddresses.hasMoreElements ( ) ) { InetAddress address = nicAddresses.nextElement ( ); if ( address instanceof Inet4Address ) { System.out.println ( "( v4 ) " + address.getHostAddress ( ) ); } else { if ( address instanceof Inet6Address ) { System.out.println ( "( v6 ) " + address.getHostAddress ( ) ); } } }
INTRODUCCIÓN AL DESARROLLO DE APLICACIONES DE RED EN JAVA
30
} } catch ( SocketException e ) { e.printStackTrace ( ); } } }
Ejecución
Para realizar una prueba funcional de este programa simplemente debe tener activa al menos una interface de red y luego compilar y ejecutar el programa. La Figura 10 muestra la salida del programa en la consola.
Interfaz wlan0: ( v6 ) fe80:0:0:0:21f:e2ff:febe:8749%4 ( v4 ) 192.168.2.227 Interfaz lo: ( v6 ) 0:0:0:0:0:0:0:1%1 (v4) 127.0.0.1
Figura 10. Ejemplo 1 – Salida en consola
Es posible que el llamado al método estático getNetworkInterfaces ( ) de la clase NetworkInterface puede lanzar una SocketException, si no hay interfaces de red disponibles en el host al momento de ejecutar el programa.
Por su parte, la clase InetAddress representa una dirección IP. Dado que una dirección IP puede ser especificada tanto con el protocolo IPv4 como con el protocolo IPv6, la clase InetAddress tiene dos subclases Inet4Address e Inet6Address (Java Platform, Standard Edition 6 API Specification).
2.3 EJEMPLO 2: DIRECCIÓN MAC DEL HOST LOCAL
El Ejemplo 2 muestra una forma de obtener la dirección MAC de una interfaz de red especificada.
API DE JAVA
31
En primer lugar se obtiene una referencia a una interfaz de red conectada al host dado el nombre que la identifica. En el Ejemplo, se trata de la interfaz inalámbrica wlan0.
NetworkInterface nic = NetworkInterface.getByName ( "wlan0" );
Una vez obtenida la referencia a la interfaz de red, se utiliza el método getHardwareAddress ( ) para obtener la dirección MAC asociada a la interfaz.
byte [ ] macAddress = nic.getHardwareAddress ( );
Después de obtener la dirección MAC, se imprime en el formato que se acostumbra usar para expresar este tipo de direcciones, es decir, seis grupos de dos dígitos hexadecimales, separados por el símbolo
d
o
s
p
u
n
t
o
s
“:”.
El código completo del Ejemplo 2 se presenta a continuación:
/* * Ejemplo 2: NetworkInterfacesExample02 * * El objetivo de este programa es mostrar la direccion MAC asociada * a una interfaz de red especificada * * En este programa: * 1) Se obtiene una referencia a una interfaz de red dado el nombre de la * interfaz y se obtiene la direccion MAC de esa interfaz * 2) Se imprime la direccion MAC en formato hexadecimal separado por : */ import java.net.NetworkInterface; import java.net.SocketException; public class NetworkInterfacesExample02 { public static void main ( String args [ ] ) { try { // Nombre de la interfaz de red String name = "wlan0"; NetworkInterface nic = NetworkInterface.getByName ( name ); System.out.println ( "Interfaz " + nic.getName ( ) + ":" ); // Dirección MAC de la interfaz de red byte [ ] macAddress = nic.getHardwareAddress ( ); String m = "";
INTRODUCCIÓN AL DESARROLLO DE APLICACIONES DE RED EN JAVA
32
// Formato de la dirección MAC int i = 0; for ( ; i < macAddress.length - 1; i++ ) { m += String.format ( "%02X", macAddress [ i ] ); m += ":"; } m += String.format ( "%02X", macAddress [ i ] ); System.out.println ( "MAC Address para la interfaz " + name + ": " + m ); } catch ( SocketException e ) { e.printStackTrace ( ); } } }
Ejecución
Para realizar una prueba funcional de este programa simplemente debe tener activa al menos una interface de red y luego compilar y ejecutar el programa. Si la interfaz de red no tiene el mismo nombre que se utiliza en el código, cámbielo. Si no conoce los nombres de las interfaces de red de su computador, ejecute el ejemplo 1 de la página 30 para que los pueda obtener. La Figura 11 muestra la salida del programa en la consola.
Interfaz wlan0: MAC Address para la interfaz wlan0: 00:1F:E2:BE:87:49
Figura 11. Ejemplo 2 – Salida en consola
Observe que la dirección MAC asociada a la interfaz inalámbrica wlan0 es 00:1F:E2:BE:87:49.
Al igual que en el ejemplo 1, es posible que el llamado al método estático getNetworkInterfaces ( ) de la clase NetworkInterface puede lanzar una SocketException, si no hay interfaces de red disponibles en el host al momento de ejecutar el programa.
API DE JAVA
33
2.4 EJEMPLO 3: OTRAS APLICACIONES DE LA CLASE INETADDRESS
La clase InetAddress encapsula direcciones IP y sus nombres de dominio asociados. Permite interactuar con una dirección IP dado el nombre de un host, lo que lo hace más cómodo para el usuario (SCHILDT, 2001).
Para crear instancias de la clase InetAddress, se puede utilizar alguno de los tres métodos de fábrica disponibles, en lugar de usar un método constructor.
El método getLocalHost ( ) devuelve un objeto InetAddress con la información del host local.
InetAddress address = InetAddress.getLocalHost ( );
El método getByName ( ), devuelve un objeto InetAddress asociado al nombre de host especificado en el parámetro, en este caso, "www.uniquindio.edu.co".
address = InetAddress.getByName ( "www.uniquindio.edu.co" );
El método getAllByName ( ), retorna un arreglo de objetos InetAddress, con los nombres de host y direcciones IP asociadas al nombre de host especificado en el parámetro, en este caso, "www.nba.com". Este método constructor se utiliza cuando un mismo nombre de host tiene asociado un conjunto de direcciones IP.
InetAddress [ ] addressArray = InetAddress.getAllByName ( "www.google.com" );
Cualquiera de los anteriores métodos de fábrica para la clase InetAddress puede lanzar una excepción UnknowunHostException (excepción de host desconocido) si el nombre no puede ser resuelto por el servidor DNS asignado al host donde se ejecuta la aplicación.
A continuación se presenta el código completo del Ejemplo 3.
INTRODUCCIÓN AL DESARROLLO DE APLICACIONES DE RED EN JAVA
34
/* * Ejemplo 3: InetAddressExample * * El objetivo de este programa es mostrar el funcionamiento básico de la clase * InetAddress. * * En este programa se utiliza la clase InetAddress para obtener: * 1) Información sobre el host local, tal como el nombre de host y la dirección * IP; 2) La dirección IP principal de un host especificado y 3) Las direcciones * IP asociadas a un nombre de host especificado. */ import java.net.InetAddress; import java.net.UnknownHostException; public class InetAddressExample { public static void main ( String [ ] args ) { System.out.println ( "Información del host local" ); InetAddress address; try { // Obtener el nombre del host local y su dirección IP address = InetAddress.getLocalHost ( ); System.out.println ( address ); // Imprime la dirección IP del host local System.out.println ( "Dirección IP : " + address.getHostAddress ( ) ); // Imprime el nombre del host local System.out.println ( "Nombre de host : " + address.getHostName ( ) ); System.out.println ( ); System.out.println ( "Dirección IP de www.uniquindio.edu.co" ); // Obtiene la dirección IP dado un nombre de dominio address = InetAddress.getByName ( "www.uniquindio.edu.co" ); System.out.println ( address ); System.out.println ( ); // Obtiene un conjunto de direcciones IP dado un nombre de dominio System.out.println ( "Direcciones IP de www.google.com" ); InetAddress [ ] addressesArray; addressesArray = InetAddress.getAllByName ( "www.google.com" ); for ( int i = 0; i < addressesArray.length; i++ ) { System.out.println ( addressesArray [ i ] ); } } catch ( UnknownHostException e ) { e.printStackTrace ( ); }
API DE JAVA
35
} }
Ejecución
Para realizar una prueba funcional de este programa simplemente debe estar conectado a Internet, compilar y ejecutar el programa. La Figura 12 muestra la salida del programa en la consola.
Información del host local carlos/172.16.24.148 Dirección IP : 172.16.24.148 Nombre de host : carlos Dirección IP de www.uniquindio.edu.co www.uniquindio.edu.co/172.16.1.12 Direcciones IP de www.google.com www.google.com/74.125.159.103 www.google.com/74.125.159.99 www.google.com/74.125.159.106 www.google.com/74.125.159.105 www.google.com/74.125.159.147 www.google.com/74.125.159.104
Figura 12. Ejemplo 3 – Salida en consola
2.5 SOCKETS TCP
El protocolo TCP ofrece un modelo de servicio orientado a conexión con transferencia confiable de datos. Es decir, antes de comenzar la transferencia de datos es necesario que el proceso cliente inicie una conexión con el proceso servidor. Después de establecer la conexión, los datos van a ser enviados a través de esa conexión, conservando el orden en que son transmitidos. El protocolo TCP ofrece un servicio de transferencia confiable de datos, lo que significa que la aplicación no tiene que estar pendiente si los datos enviados son entregados en el destino o no, porque el protocolo se encarga de eso (KUROSE & ROSS, 2010).
La API de Java para el desarrollo de aplicaciones de red con el protocolo TCP hace posible la comunicación bidireccional por flujos (orientada a la conexión), entre dos
36
INTRODUCCIÓN AL DESARROLLO DE APLICACIONES DE RED EN JAVA
procesos. El rol de cliente implica la creación de un socket TCP sobre un puerto en el que debe estar escuchando el servidor y la posterior solicitud de conexión. El papel de servidor involucra la creación de un socket de contacto ligado al puerto de servicio y la espera de clientes que soliciten conexiones. Cuando un servidor acepta una conexión, crea un nuevo socket para mantener la comunicación con el cliente, mientras que se reserva el socket del puerto de servicio para escuchar las peticiones de conexión de otros clientes (COULOURIS & DOLLIMORE, 2005).
Java proporciona las clases Socket y ServerSocket del paquete java.net para implementar servicios de comunicación de aplicaciones de red con el protocolo TCP como protocolo de capa de transporte. Un socket es el punto de contacto de cada uno de los extremos de una conexión TCP. Para identificar un socket se necesita una dirección IP y un número de puerto y antes de poder iniciar el intercambio de información, se debe crear una conexión entre un programa cliente y un programa servidor. El programa cliente debe enviar una solicitud de conexión al servidor (CALVERT & DONAHOO, 2008).
2.5.1 Clase Socket
Esta clase es usada tanto por clientes como por servidores. Las operaciones básicas que puede realizar un objeto de tipo Socket son solicitar conexión con un host remoto; enviar y recibir datos; y cerrar una conexión.
Un objeto de tipo Socket se crea utilizando alguno de sus métodos constructores; luego intenta conectarse con un host remoto y si el host remoto lo permite, el host local puede obtener unos flujos de entrada y de salida a través de los cuales podrá enviar y recibir datos al mismo tiempo. Al terminar la transmisión de datos, uno de los dos hosts que se están comunicando puede cerrar la conexión.
El método constructor de la clase Socket que se utiliza con más frecuencia es el que especifica el host remoto y el número de puerto en el que ese host está escuchando. El
API DE JAVA
37
host remoto se puede especificar utilizando un String con la dirección IP ó el nombre de host.
public Socket ( String host, int port ) throws UnknownHostException, IOException
Este constructor crea un socket e intenta conectarse al host remoto. Si el nombre de dominio del host remoto no puede ser resuelto o si no está funcionando, el constructor lanza una UnknownHostException. Si el socket no puede ser abierto por alguna otra razón, el constructor lanza una IOException.
Otro método constructor, similar al anterior, permite especificar el host remoto utilizando un objeto InetAddress.
public Socket ( InetAddress host, int port ) throws IOException
A diferencia del anterior, este constructor lanza una IOException si no es posible crear el socket. No lanza una UnknownHostException si el host remoto es desconocido, debido a que esto ocurriría al intentar crear el objeto InetAddress.
Los dos métodos constructores anteriores utilizan una interfaz de red detectada automáticamente y establece un número de puerto local elegido al azar entre los disponibles en el host local.
El tercer método constructor permite especificar la interfaz de red local y el número de puerto local. Este método constructor puede ser utilizado si el host local tiene múltiples interfaces de red y se desea asegurar cuál de ellas será utilizada para realizar una conexión con un host remoto; así como especificar el puerto local que se desea utilizar.
public Socket ( String host, int port, InetAddress interface, int localPort ) throws IOException, UnknownHostException
38
INTRODUCCIÓN AL DESARROLLO DE APLICACIONES DE RED EN JAVA
Si se utiliza el 0 como número de puerto local, este constructor selecciona al azar un número de puerto disponible. Las excepciones lanzadas por este constructor son las mismas que en los dos constructores anteriores.
Por otra parte, la clase Socket proporciona el método getInputStream ( ) y el método getOutputStream ( ) para acceder a los dos flujos asociados a un socket (COULOURIS & DOLLIMORE, 2005). El método close ( ) cierra el socket.
2.5.2 Clase ServerSocket
Esta clase es usada para crear programas servidores. Las operaciones básicas que puede realizar un objeto de tipo ServerSocket son vincular un puerto; escuchar sobre un puerto vinculado y aceptar conexiones remotas.
Un objeto de tipo ServerSocket permanece escuchando un puerto que es vinculado en el momento de la creación; de modo que cuando un cliente intenta conectarse, el servidor contesta, negocia algunos parámetros de conexión y retorna un objeto de tipo Socket que será utilizado para el intercambio de información entre los hosts (ELLIOTE, 2004).
El método constructor más frecuentemente utilizado, recibe como argumento el número de puerto que será utilizado para escuchar las conexiones. Aunque sería muy extraño, es posible pasar 0 por parámetro, caso en el que se asigna un número de puerto disponible.
public ServerSocket ( int port ) throws BindException, IOException
Este método constructor puede lanza una BindException si no es posible crear el socket o vincular el puerto; y una IOException, si el puerto ya está en uso o si está intentando conectar alguno de los puertos 1 a 1023 en una máquina tipo UNIX sin privilegios de superusuario (ELLIOTE, 2004).
API DE JAVA
39
public ServerSocket ( int port, int queueLength ) throws IOException, BindException
Este método constructor, además de comportarse igual al caso anterior, establece el tamaño de la cola en la que puede almacenar conexiones entrantes pendientes de ser atendidas, antes de empezar a rechazar conexiones (Java Platform, Standard Edition 6 API Specification).
public ServerSocket ( int port, int queueLength, InetAddress bindAddress ) throws BindException, IOException
Este método constructor adicionalmente a los parámetros anteriores, también especifica la interfaz de red que puede utilizar para escuchar las conexiones entrantes. Este método constructor es especialmente útil si el host local tiene varias interfaces de red y solo se desea asignar una para escuchar las conexiones de los hosts remotos.
El método accept ( ) de la clase ServerSocket toma una solicitud de conexión, si hay, o se bloquea hasta la llegada de una solicitud. Después de ejecutar accept ( ) se crea una instancia de tipo Socket, la cual da acceso a los flujos para comunicarse con el cliente (COULOURIS & DOLLIMORE, 2005).
Es importante destacar que los servidores manejan instancias tanto de ServerSocket como de Socket, mientras que los clientes solo usan objetos de la clase Socket (CALVERT & DONAHOO, 2008).
2.6 SOCKETS UDP
El protocolo UDP ofrece un modelo de servicio de transferencia de datos no confiable y no orientado a conexión. Al ser no orientado a conexión, no es necesario establecer una conexión entre los dos hosts antes de poder intercambiar información. Además, al no ser confiable, el protocolo de capa de transporte no ofrece ninguna clase de control
40
INTRODUCCIÓN AL DESARROLLO DE APLICACIONES DE RED EN JAVA
sobre la transmisión de datos, razón por la cual es tarea del protocolo de capa de aplicación estar pendiente si los datos enviados son entregados en el destino o no, porque, a diferencia de TCP, el protocolo UDP no lo hace.
La API de Java para el desarrollo de aplicaciones de red con el protocolo UDP permite la forma más simple de comunicación entre procesos, a través de paso de mensajes autocontenidos (CALVERT & DONAHOO, 2008). En este caso, un proceso envía un mensaje al receptor usando datagramas y especificando el destino a través de un socket.
La API de Java proporciona una comunicación de datagramas por medio de dos clases: DatagramPacket y DatagramSocket.
2.6.1 Clase DatagramPacket
Una aplicación de red escrita en Java utiliza un objeto de la clase DatagramPacket tanto para enviar como para recibir un mensaje a o desde un host remoto. En un objeto DatagramPacket también se encuentran la dirección del host remoto y el número de puerto. Además, la longitud de los datos y un campo de desplazamiento (offset) los cuales describen la localización y el número de bytes del mensaje con respecto al tamaño del buffer (CALVERT & DONAHOO, 2008).
DatagramPacket permite usar diferentes constructores dependiendo si el paquete va a ser usado para enviar o recibir datos (Java Platform, Standard Edition 6 API Specification). public DatagramPacket ( byte[ ] data, int length )
Este método constructor es usado para recibir datos enviados desde un host remoto. Observe que se suministran dos parámetros, el byte [ ] que almacenará los datos, y la longitud. El byte [ ] debe estar vacío. El parámetro length especifica el número
API DE JAVA
41
máximo de bytes que serán recibidos. Este parámetro length no podrá ser mayor que la longitud del byte [ ].
public DatagramPacket ( byte[ ] data, int offset, int length )
Un segundo método constructor que se puede utilizar para recibir los datos incluye un parámetro offset, el cual indica la posición a partir de la cual será leído el byte [ ] para transferir los datos.
Para enviar datos, hay otros métodos constructores que se pueden utilizar. Se destacan:
public DatagramPacket ( byte[ ] data, int length, InetAddress remoteHost, int remotePort )
Public DatagramPacket ( byte[ ] data, int offset, int length, InetAddress remoteHost, int remotePort )
Observe que en estos dos métodos constructores, es necesario especificar además la dirección IP y el número de puerto del host remoto.
Al llegar un mensaje, tanto el mensaje como la longitud se almacenan en un DatagramPacket, junto con la dirección IP y el número de puerto del socket en el emisor. El mensaje puede recuperarse del DatagramPacket mediante el método getData ( ). Los métodos getPort ( ) y getAddress ( ) permite obtener el número de puerto y a la dirección IP, respectivamente (COULOURIS & DOLLIMORE, 2005).
2.6.2 Clase DatagramSocket
Es la clase que maneja los sockets UDP para enviar y recibir datagramas. Proporciona un constructor básico que recibe como argumento un número de puerto.
public DatagramSocket ( int port ) throws SocketException
42
INTRODUCCIÓN AL DESARROLLO DE APLICACIONES DE RED EN JAVA
Este método constructor es apropiado para los servidores que necesitan utilizar un número de puerto concreto en el cual escuchan las conexiones. Cuando no se especifica la dirección IP local, el servidor escucha por todas las interfaces de red que tenga configuradas el host local.
Un segundo método constructor puede ser utilizado especificando el número de puerto y la dirección IP local.
public DatagramSocket ( int port, Inet Address address ) throws SocketException
También es posible utilizar un método constructor sin argumentos en el cual el sistema elije un puerto entre los que estén libres, y de nuevo, escucha por todas las interfaces de red que tenga configuradas el host local.
public DatagramSocket ( ) throws SocketException
Estos métodos constructores pueden lanzar una SocketException si el puerto está en uso en el momento de la ejecución, si se especifica un número de puerto reservado (menor que 1024) y no se tienen los privilegios de superusuario, o si la dirección IP no corresponde con alguna de las interfaces configuradas, lo que impide la creación del socket.
La clase DatagramSocket proporciona varios métodos que incluyen los siguientes:
Los métodos send ( ) y receive ( ) se utilizan para transmitir y recibir datagramas entre un par de sockets. El argumento de send ( ) es un objeto de tipo DatagramPacket, el cual contiene el mensaje y especifica el destino. El argumento de receive ( ) es un DatagramPacket que tiene un byte [ ] vacío en el cual se va a almacenar el mensaje, la longitud y su origen. Los dos métodos pueden lanzar una excepción IOException.
API DE JAVA
43
El método setSoTimeout permite establecer un tiempo máximo de espera, medido en milisegundos. Cuando se fija un tiempo límite, el método receive ( ) se bloquea durante el tiempo fijado y después lanza una excepción InterruptedIOException. Este tiempo puede ser asignado tanto en el cliente como en el servidor.
2.7 SOCKETS PARA COMUNICACIÓN EN GRUPO
Los protocolos TCP y UDP son ampliamente utilizados en la solución de problemas y en el desarrollo de las aplicaciones de red. Sin embargo, existe el modelo de comunicación en grupo, donde una aplicación puede pertenecer a un grupo y comunicarse con todos los miembros del grupo de una manera sencilla (CALVERT & DONAHOO, 2008). Este modelo es llamado programación de sockets multicast. La API de Java para la comunicación en grupo (multicast) permite la difusión de datagramas a un grupo de multidifusión sobre una red IP. Algunas características de la comunicación en grupo son las siguientes (COULOURIS & DOLLIMORE, 2005):
Tolerancia a fallas y alta disponibilidad. En un grupo multicast todos los mensajes llegan a todos los miembros del grupo y si cada uno de ellos realiza la misma operación, una aplicación puede ser fácilmente tolerante a fallos y ofrecer alta disponibilidad.
Servicio de descubrimiento. En sistemas
p
e
e
r
–
t
o
–
p
e
e
r
donde no se conoce la
localización de un servidor, la programación de aplicaciones multicast puede ser útil para el descubrimiento de los miembros del grupo que actúan como servidores en un momento dado.
Orden de entrega. Es importante tener presente que no todos los datagramas enviados necesariamente van a llegar en el mismo orden en que fueron enviados, debido a que los datagramas UDP no ofrecen ese servicio.
44
INTRODUCCIÓN AL DESARROLLO DE APLICACIONES DE RED EN JAVA
Enrutadores multicast. Los mensajes que se envían a través de un grupo multicast pueden ser difundidos tanto en la red local como en Internet. Sin embargo, esta característica es dependiente de la capacidad de los enrutadores, es decir, no todos los enrutadores tienen la propiedad de propagación un datagrama multicast. Si se trata de una difusión en la red LAN, el transmisor puede especificar el número de enrutadores que un mensaje puede cruzar, especificando el tiempo de vida del datagrama.
Direcciones multicast. Las aplicaciones multicast utilizan las direcciones IP definidas como clase D, es decir aquellas cuyos primeros cuatro bits son 1110 en el protocolo IPv4. Es decir, las direcciones que están en el rango 224.0.0.0 – 239.255.255.255. Estas direcciones clase D pueden ser asignadas en forma temporal o permanente. Los grupos permanentes tienen asignadas las direcciones IP 224.2.2.1 hasta 224.0.0.255.
2.7.1 Clase MulticastSocket
La API de Java proporciona una comunicación de datagramas multicast por medio de la clase MulticastSocket, la cual es una subclase de DatagramSocket con la capacidad adicional de pertenecer a grupos multicast. La clase MulticastSocket proporciona dos métodos constructores, permitiendo crear los sockets utilizando un número de puerto local especificado o cualquier puerto local libre. Un proceso puede pertenecer a un grupo multicast con una dirección especificada mediante el llamado al método joinGroup ( ) de su socket multicast. Entonces, el socket entra a formar parte del grupo multicast en el puerto especificado y podrá recibir los datagramas enviados por todos los procesos que pertenecen al grupo en ese puerto. Si un proceso desea abandonar el grupo, debe llamar al método leaveGroup ( ) de su socket multicast [1].
Después de unirse a un grupo, el proceso que desea enviar un mensaje debe crear un objeto DatagramPacket, similar al que se utiliza para la programación de sockets con el protocolo UDP. Posteriormente se deben asignar los elementos que se necesitan
API DE JAVA
45
para conformar el paquete. A continuación, se puede enviar el paquete a través del socket multicast correspondiente, sobre el puerto elegido.
La API de Java permite especificar el tiempo de vida (campo TTL en el datagrama IP) para un socket multicast. Puede acceder a esta operación mediante el método setTimeToLive ( ). El valor por defecto es 1, permitiendo que la propagación de los mensajes sea restringida a la red local.
46
INTRODUCCIÓN AL DESARROLLO DE APLICACIONES DE RED EN JAVA
APLICACIONES DE RED CON SOCKETS TCP
47
3. APLICACIONES DE RED CON SOCKETS TCP En esta sección se presentarán cinco ejemplos prácticos de aplicaciones de red muy útiles las cuales envían y reciben datos a través del protocolo TCP. El primer ejemplo es una aplicación de eco donde se envían datos de tipo String, seguida por una aplicación en la que se envían datos de tipo int para sumar dos números enteros. Posteriormente dos aplicaciones en las que se transmiten objetos y una aplicación final en la que se transmiten archivos del servidor al cliente.
3.1 EJEMPLO 4: SERVIDOR Y CLIENTE DE ECO
Una aplicación de eco consiste en un cliente que inicia una conexión con el servidor y le envía un mensaje. El servidor recibe el mensaje y hace eco de ese mensaje enviándolo de nuevo al cliente. Se trata de un ejemplo típico tomado de (KUROSE & ROSS, 2010) con el cual se hará la introducción a la programación de sockets con TCP.
Más adelante, en este capítulo, se mostrará una variedad de ejemplos sencillos, mientras que en el capítulo siguiente se desarrollará un proyecto completo. Estos ejemplos ayudarán a entender mejor la forma de crear aplicaciones cliente servidor con el protocolo TCP.
El protocolo de capa de aplicación del servidor y cliente de eco se puede resumir de la siguiente forma:
• El cliente lee un mensaje de texto desde el teclado usando una ventana emergente y envía el mensaje por el socket de salida al servidor. • El servidor lee el mensaje a través de su socket de conexión. • El servidor envía el mensaje de vuelta al cliente por el socket de conexión.
INTRODUCCIÓN AL DESARROLLO DE APLICACIONES DE RED EN JAVA
48
• El cliente lee el mensaje a través de su socket y lo imprime en la consola. Lea cuidadosamente el código fuente en Java del ejemplo formado por las clases EchoTCPClient.java y EchoTCPServer.java.
EchoTCPClient.java
El objetivo de este programa es leer del teclado una cadena y enviarla al servidor. El servidor hace eco de esta cadena y la envía de nuevo al cliente.
/* * Ejemplo 4: EchoTCPClient * * El objetivo de este programa es leer del teclado un String y enviarlo al * servidor. El servidor hace eco de este mensaje y lo envía de nuevo al cliente. */ import import import import import import
java.io.BufferedReader; java.io.DataOutputStream; java.io.IOException; java.io.InputStreamReader; java.net.Socket; java.net.UnknownHostException;
import javax.swing.JOptionPane; public class EchoTCPClient { // Constantes para definir la dirección y el número de puerto del servidor. public static final int PORT = 3400; public static final String SERVER_LOCATION = "localhost"; private Socket clientSocket; private DataOutputStream outToServer; private BufferedReader inFromServer; // Método constructor. public EchoTCPClient ( ) { System.out.println ( "TCP CLIENT ..." ); try { // Creación del socket en el lado cliente. Debe especificar la dirección // IP o nombre de host donde está el servidor y el número de puerto en el // cual está escuchando. clientSocket = new Socket ( SERVER_LOCATION, PORT ); // Creación del flujo de salida de datos al cual se le conecta el flujo // salida del socket. Este flujo de salida de datos se utiliza para // enviar un flujo de bytes al servidor. outToServer = new DataOutputStream (
APLICACIONES DE RED CON SOCKETS TCP
clientSocket.getOutputStream ( ) ); // Creación del buffer de lectura al cual se le conecta un lector de un // flujo de entrada que a su vez se conecta con el flujo de entrada del // socket. Este flujo de entrada de datos se utiliza para leer un flujo // de bytes que viene del servidor. inFromServer = new BufferedReader ( new InputStreamReader ( clientSocket.getInputStream ( ) ) ); // Lectura del mensaje que el usuario desea enviar al servidor. String message = JOptionPane.showInputDialog ( null, "Ingrese un mensaje" ); // Envío del mensaje al servidor. No olvide el terminador "\n" para que // el servidor la pueda recuperar del buffer de entrada. outToServer.writeBytes ( message + "\n" ); // Lectura del mensaje que el servidor le envía al cliente. String receivedMessage = inFromServer.readLine ( ); // Impresión del mensaje recibido en la consola. System.out.println( "FROM SERVER: " + receivedMessage ); } // Puede lanzar una excepción de host desconocido. catch ( UnknownHostException e ) { e.printStackTrace ( ); } // Puede lanzar una excepción de entrada y salida. catch ( IOException e ) { e.printStackTrace ( ); } // Finalmente se cierran los flujos y el socket. finally { try { if ( inFromServer != null ) inFromServer.close ( ); if ( outToServer != null ) outToServer.close ( ); if ( clientSocket != null ) clientSocket.close ( ); } catch ( IOException e ) { e.printStackTrace ( ); } } } /** * Método principal utilizado para lanzar el programa cliente. */ public static void main ( String args [ ] ) { new EchoTCPClient ( ); } }
A continuación una explicación detallada de algunas instrucciones de esta clase.
49
INTRODUCCIÓN AL DESARROLLO DE APLICACIONES DE RED EN JAVA
50
clientSocket = new Socket ( SERVER_LOCATION, PORT );
Esta línea crea el objeto clientSocket de tipo Socket. También inicializa la conexión TCP entre el cliente y el servidor. Observe que el valor de la constante SERVER_LOCATION corresponde a la cadena "localhost". Esta se refiere al nombre del host donde está ubicado el servidor, por ejemplo, un nombre de dominio como “uniquindio.edu.co” o el nombre del equipo en una red local. También puede utilizar la dirección IP del servidor. El número 3400 es corresponde a un número entero entre 1 y 65535. Este número identifica el puerto en el cual está escuchando el servidor. Si el cliente usa un número de puerto diferente al que está usando el servidor, el cliente no se va a conectar con el servidor. Si se trata de un nombre de dominio en Internet, el cliente primero hará una consulta DNS para obtener la dirección IP del servidor.
Luego, se crean dos flujos, uno de salida (outToServer) y otro de entrada (inFromServer), ambos conectados al socket. Observe la Figura 13.
outToServer = new DataOutputStream ( connectionSocket.getOutputStream ( ) ); inFromServer = new BufferedReader ( new InputStreamReader (connectionSocket.getInputStream ( ) ) );
Figura 13. Comunicación entre cliente y servidor para el envío de String
Los caracteres que llegan por la red pasan del socket al flujo inFromServer. De igual manera, los caracteres que el cliente envía al servidor pasan por el flujo outToServer al socket.
APLICACIONES DE RED CON SOCKETS TCP
51
El flujo inFromServer es de tipo BufferedReader y está asociado con el flujo de entrada del socket.
String message = JOptionPane.showInputDialog ( null,
"Ingrese un mensaje" );
Esta línea solicita un mensaje del usuario y lo almacena en la cadena message.
outToServer.writeBytes ( message + "\n" );
Esta línea envía el String message junto con un carácter de fin de línea ( “\n” ) por el flujo outToServer. El mensaje pasa a través del socket del cliente dentro del tubo virtual TCP. El cliente entonces espera recibir caracteres del servidor.
String receivedMessage = inFromServer.readLine ( );
Cuando llegan los caracteres del servidor, ellos pasan a través del flujo de entrada inFromServer y son almacenados en la cadena receivedMessage. El texto se acumula en receivedMessage hasta terminar la línea con un carácter de fin de línea ( "\n" ) enviado por el servidor.
Un error muy frecuente consiste en no poner el terminador de línea al final de una cadena que el cliente envía al servidor o viceversa. Esto ocasiona que el receptor quede bloqueado esperando el carácter de fin de línea para extraer los datos del buffer.
Para finalizar, se deben cerrar los flujos y el socket. Estas líneas de código deben estar validadas para evitar que se lance la excepción de entrada y salida por intentar el cierre de un flujo o un socket cuyo valor sea null. La cláusula finally se ejecuta tanto después de ejecutar el flujo normal como después de capturar una excepción, si se ha presentado.
INTRODUCCIÓN AL DESARROLLO DE APLICACIONES DE RED EN JAVA
52
if ( inFromServer != null ) inFromServer.close ( ); if ( outToServer != null ) outToServer.close ( ); if ( clientSocket != null ) clientSocket.close ( );
EchoTCPServer.java
El objetivo de este programa es recibir una cadena por el socket y enviarla de nuevo al cliente.
/* * Ejemplo 4: EchoTCPServer * * El objetivo de este programa es leer del teclado un String y enviarlo al * servidor. El servidor hace eco de este mensaje y lo envía de nuevo al cliente. */ import import import import import import
java.io.BufferedReader; java.io.DataOutputStream; java.io.IOException; java.io.InputStreamReader; java.net.ServerSocket; java.net.Socket;
public class EchoTCPServer { // Constante arbitraria para definir el número de puerto en el que escucha el // servidor. public static final int PORT = 3400; private private private private
ServerSocket Socket DataOutputStream BufferedReader
welcomeSocket; connectionSocket; outToClient; inFromClient;
// Método constructor. public EchoTCPServer ( ) { System.out.println ( "TCP SERVER ..." ); try { // Creación del socket de servidor. Debe especificar el número de puerto // en el cual está escuchando. welcomeSocket = new ServerSocket ( PORT ); // Ciclo infinito que permite al servidor atender varios clientes, uno a // la vez. El servidor va a prestar un mismo servicio a cada cliente. while ( true ) { // El servidor queda bloqueado esperando una conexión de un cliente. // Cuando el servidor recibe el contacto de un cliente, crea un nuevo // socket, dedicado para atender ese cliente en particular. connectionSocket = welcomeSocket.accept ( ); System.out.println ( "Connection incoming ..." );
APLICACIONES DE RED CON SOCKETS TCP
53
// Creación de un flujo de lectura al cual se le conecta un lector de // un flujo de entrada que a su vez se conecta con el flujo de entrada // del socket. Este flujo de entrada se utiliza para leer un flujo de // bytes que viene del cliente. inFromClient = new BufferedReader ( new InputStreamReader ( connectionSocket.getInputStream ( ) ) ); // Creación del flujo de salida de datos al cual se le conecta un flujo // de salida del socket. Este flujo de salida de datos se utiliza para // enviar un flujo de bytes al cliente. outToClient = new DataOutputStream ( connectionSocket.getOutputStream ( ) ); // Lectura del mensaje que el cliente le envía al servidor. String clientMessage = inFromClient.readLine ( ); // Procesamiento de la información que el cliente ha enviado al // servidor. String answer = clientMessage; // Envío del mensaje al cliente. No olvide el terminador "\n" para que // el servidor la pueda recuperar del buffer de entrada. outToClient.writeBytes ( answer + "\n" ); } } // Puede lanzar una excepción de entrada y salida. catch ( IOException e ) { e.printStackTrace ( ); } // Al terminar se cierran los sockets y los flujos. finally { try { if ( outToClient != null ) outToClient.close ( ); if ( inFromClient != null ) inFromClient.close ( ); if ( connectionSocket != null ) connectionSocket.close ( ); if ( welcomeSocket != null ) welcomeSocket.close ( ); } // Puede lanzar una excepción de entrada y salida. catch ( IOException e ) { e.printStackTrace ( ); } } } /** * Método principal utilizado para lanzar el programa servidor. */ public static void main ( String args [ ] ) { new EchoTCPServer ( ); } }
EchoTCPServer tiene muchas similitudes con EchoTCPClient. A continuación se comentarán sólo las líneas diferentes.
54
INTRODUCCIÓN AL DESARROLLO DE APLICACIONES DE RED EN JAVA
welcomeSocket = new ServerSocket ( PORT );
Esta línea crea el objeto welcomeSocket de tipo ServerSocket. Este socket especial es el punto de contacto que proporciona el servidor para recibir la conexión del cliente. En este caso, se dice que el puerto es el número 3400.
while ( true )
El ciclo infinito quiere decir que el servidor estará
“ l
t
o
d
o
e
t
i
e
m
p
o
”
en función de
atender clientes y prestarles a todos el mismo servicio.
connectionSocket = welcomeSocket.accept ( );
Esta línea crea un socket llamado connectionSocket cuando algún cliente se ha conectado con el socket de contacto (welcomeSocket). Entonces, TCP establece un tubo virtual directo entre clientSocket en el cliente y connectionSocket en el servidor.
El cliente y el servidor pueden entonces enviarse datos uno al otro sobre este “tubo” (usando los flujos que están conectados con sus respectivos sockets), y todos los datos enviados deben llegar al otro lado en el mismo orden en que fueron enviados.
Después de establecido el socket de contacto, el servidor puede continuar escuchando solicitudes de otros clientes de la capa de aplicación usando el socket de contacto welcomeSocket.
String clientMessage = inFromClient.readLine ( );
Para este ejemplo, el servidor entonces lee una línea de texto enviada por el cliente.
String answer = clientMessage;
APLICACIONES DE RED CON SOCKETS TCP
55
Luego, realiza el procesamiento. En este caso, no hay procesamiento en realidad, por tratarse de un servidor de eco. La respuesta es igual al mensaje ingresado por el usuario en el lado cliente.
outToClient.writeBytes ( answer
+ "\n" );
Luego, la respuesta al mensaje de eco es enviada por la red a través del flujo.
Ejecución
Para probar esta aplicación de red, se deben compilar y ejecutar. No olvide ejecutar primero el servidor y luego el cliente. Asegúrese de establecer correctamente el nombre o la dirección IP del servidor. Si lo desea, puede probarlos en la misma máquina usando "localhost" como nombre de host o escribir la dirección IP "127.0.0.1", la cual se utiliza para este tipo de pruebas. También puede utilizar las direcciones IPv6 si el equipo en el que se va a ejecutar tiene el soporte para estas direcciones. Recuerde que la dirección "::1" es la dirección de loopback para realizar las pruebas.
Observe que el servidor inicia su ejecución y entra en modo de espera hasta que recibe el contacto del cliente. Luego, simplemente en el cliente ingrese el mensaje en el cuadro de diálogo que se puede apreciar en la Figura 14 y presione la tecla Enter o haga clic en el botón Aceptar.
Figura 14. Ejemplo 4 – Entrada cliente
INTRODUCCIÓN AL DESARROLLO DE APLICACIONES DE RED EN JAVA
56
Luego de presionar el botón Aceptar, se inicia la transmisión de datos y la salida en las consolas del cliente y servidor son como se puede ver en la Figura 15.
Consola del servidor TCP SERVER ... Connection incoming ...
Consola del cliente TCP CLIENT ... FROM SERVER: mensaje de prueba
Figura 15. Ejemplo 4 – Salida en consola
A continuación se presentan otros ejemplos de aplicaciones Cliente – Servidor donde el protocolo TCP es utilizado como protocolo de capa de transporte.
3.2 EJEMPLO 5: SERVIDOR Y CLIENTE PARA LA SUMA DE DOS ENTEROS
Los programas cliente y servidor para la suma de dos enteros cumplen el siguiente protocolo:
•
El cliente lee dos números en forma de String, desde el teclado usando dos ventanas emergentes. Luego los conveirte a entero y los envía por el socket de salida al servidor.
•
El servidor recibe a través de su socket de conexión los dos números enteros.
•
El servidor calcula la suma.
•
El servidor envía el resultado de la suma en forma de númeo entero (int) al cliente por el socket de conexión.
•
El cliente recibe el resultado a través de su socket y lo imprime en la consola.
SumTCPClient.java
El objetivo de este programa es leer del teclado dos números enteros y enviarlos al servidor. El servidor calcula la suma y envía el resultado al cliente.
/* * Ejemplo 5: SumTCPClient *
APLICACIONES DE RED CON SOCKETS TCP
* El objetivo de este programa es leer del teclado dos números enteros y * enviarlos al servidor. El servidor realiza el cálculo de la suma de los dos * números y envía el resultado al cliente. */ import import import import import
java.io.DataInputStream; java.io.DataOutputStream; java.io.IOException; java.net.Socket; java.net.UnknownHostException;
import javax.swing.JOptionPane; public class SumTCPClient { public static final int PORT = 3400; public static final String SERVER_LOCATION = "localhost"; private Socket clientSocket; private DataOutputStream outToServer; private DataInputStream inFromServer; // Método constructor. public SumTCPClient ( ) { System.out.println ( "TCP CLIENT ..." ); try { // Localización del servidor. clientSocket = new Socket ( SERVER_LOCATION, PORT ); // Creación del flujo de salida hacia el servidor. outToServer = new DataOutputStream ( clientSocket.getOutputStream ( ) ); // Creación del flujo de entrada desde el servidor. inFromServer = new DataInputStream ( clientSocket.getInputStream
( ) );
// Lectura de los números en forma de String y conversión a enteros. String a = JOptionPane.showInputDialog ( null, "Primer número" ); String a = JOptionPane.showInputDialog ( null, "Primer número" ); int n1 = Integer.parseInt ( a ); String b = JOptionPane.showInputDialog ( null, int n2 = Integer.parseInt ( b );
"Segundo número" );
// Envío de los dos enteros al servidor. Observe que los datos // enviados son de tipo int. Observe que los dos números // enteros son enviados en forma independiente. outToServer.writeInt ( n1 ); outToServer.writeInt ( n2 ); // Lectura de un entero enviado por el servidor. int result = inFromServer.readInt ( ); System.out.println ( "FROM SERVER: " + result ); } // Puede lanzar una excepción de host desconocido. catch ( UnknownHostException e ) { e.printStackTrace ( ); } // Puede lanzar una excepción de entrada y salida.
57
INTRODUCCIÓN AL DESARROLLO DE APLICACIONES DE RED EN JAVA
58
catch ( IOException e ) { e.printStackTrace ( ); } // Finalmente se cierran los flujos y el socket. finally { try { if ( inFromServer != null ) inFromServer.close ( ); if ( outToServer != null ) outToServer.close ( ); if ( clientSocket != null ) clientSocket.close ( ); } catch ( IOException e ) { e.printStackTrace ( ); } } } /** * Método principal utilizado para lanzar el programa cliente. */ public static void main ( String args [ ] ) { new SumTCPClient ( ); } }
De manera similar al ejemplo de servidor de eco, solo serán explicadas las líneas de código que aparecen en este ejemplo.
inFromServer = new DataInputStream ( clientSocket.getInputStream
( ) );
Para esta aplicación, el flujo de entrada en el cliente corresponde a un flujo de entrada de datos, a diferencia del ejemplo anterior. Esto se debe a que en el ejemplo de la suma de dos números enteros se identifica cada elemento recibido con su tipo de datos. Para este ejemplo, se trata de dos números enteros. Observe las diferencias entre la Figura 16 y la Figura 13.
APLICACIONES DE RED CON SOCKETS TCP
59
Figura 16. Comunicación entre cliente y servidor para el envío de enteros
outToServer.writeInt ( n1 );
A través del flujo de salida se envía un dato de tipo entero (int).
int result = inFromServer.readInt ( );
Y de forma similar se puede recibir un entero del flujo de entrada.
SumTCPServer.java
El objetivo de este programa es recibir dos números enteros por el socket y enviar el resultado de la suma al cliente.
/* * Ejemplo 5: SumTCPServer * * El objetivo de este programa es leer del teclado dos números enteros y * enviarlos al servidor. El servidor realiza el cálculo de la suma de los dos * números y envía el resultado al cliente. */ import import import import import
java.io.DataInputStream; java.io.DataOutputStream; java.io.IOException; java.net.ServerSocket; java.net.Socket;
public class SumTCPServer { public static final int PORT = 3400; private private private private
ServerSocket Socket DataOutputStream DataInputStream
welcomeSocket; connectionSocket; outToClient; inFromClient;
INTRODUCCIÓN AL DESARROLLO DE APLICACIONES DE RED EN JAVA
60
// Método constructor public SumTCPServer ( ) { System.out.println ( "TCP SERVER ..." ); try { // Creación del socket de servidor vinculado a un puerto welcomeSocket = new ServerSocket ( PORT ); while ( true ) { // Aceptación de la conexión entrante y creación del socket para // atender esa conexión connectionSocket = welcomeSocket.accept ( ); System.out.println ( "Connection incoming ..." ); // Creación del flujo de salida hacia el cliente. outToClient = new DataOutputStream ( connectionSocket.getOutputStream ( ) ); // Creación del flujo de entrada desde el cliente. inFromClient = new DataInputStream ( connectionSocket.getInputStream ( ) ); // Lectura de los números enteros enviados por el cliente. int n1 = inFromClient.readInt ( ); int n2 = inFromClient.readInt ( ); // Procesamiento de la información que el cliente ha enviado al // servidor. En este caso realiza la suma. int result = n1 + n2; // Envío del resultado al cliente. outToClient.writeInt ( result ); } } // Puede lanzar una excepción de entrada y salida. catch ( IOException e ) { e.printStackTrace ( ); } // Al terminar se cierran los sockets y los flujos. finally { try { if ( outToClient != null ) outToClient.close ( ); if ( inFromClient != null ) inFromClient.close ( ); if ( connectionSocket != null ) connectionSocket.close ( ); if ( welcomeSocket != null ) welcomeSocket.close ( ); } // Puede lanzar una excepción de entrada y salida. catch ( IOException e ) { e.printStackTrace ( ); } } }
APLICACIONES DE RED CON SOCKETS TCP
61
/** * Método principal utilizado para lanzar el programa servidor. */ public static void main ( String args [ ] ) { new SumTCPServer ( ); } }
Debido a que en el servidor de la aplicación para la suma de dos enteros no se presentan instrucciones que no hayan sido explicadas, a continuación se muestra la ejecución de la aplicación.
Ejecución
Ejecute el servidor y luego el cliente. Ingrese en una ventana emergente el primer número y luego, en otra ventana emergente, ingrese el segundo número. Ver Figuras 17 y 18.
Figura 17. Ejemplo 5 – Entrada cliente
Consola del servidor TCP SERVER ... Connection incoming ...
Consola del cliente TCP CLIENT ... FROM SERVER: 13
Figura 18. Ejemplo 5 – Salida en consola
3.3 EJEMPLO 6: SERVIDOR Y CLIENTE PARA LA TRANSMISIÓN DE OBJETOS – 1
Los programas cliente y servidor para la transmisión de objetos (serializados por defecto) cumplen el siguiente protocolo:
INTRODUCCIÓN AL DESARROLLO DE APLICACIONES DE RED EN JAVA
62
• El cliente crea un Vector de String de cinco elementos y lo envía por el socket de salida al servidor. • El servidor recibe el objeto. • El servidor imprime en su consola el contenido del objeto recibido. • El servidor envía como resultado un mensaje “OK”. • El cliente recibe el resultado y lo imprime en la consola. ObjectTCPClient01.java
El objetivo de este programa es crear un objeto en el cliente y enviarlo al servidor. El servidor lo recibe e imprime su contenido. El objeto corresponde a un Vector de String.
/* * Ejemplo 6: ObjectTCPClient01 * * El objetivo de este programa es crear un Vector de String y enviarlo como * objeto al servidor. El servidor lo recibe y envía un mensaje de confirmación * al cliente. */ import import import import import import
java.io.IOException; java.io.ObjectInputStream; java.io.ObjectOutputStream; java.net.Socket; java.net.UnknownHostException; java.util.Vector;
public class ObjectTCPClient01 { public static final int PORT = 20000; public static final String SERVER_LOCATION = "localhost"; private Socket clientSocket; private ObjectOutputStream outToServer; private ObjectInputStream inFromServer; // Método constructor. public ObjectTCPClient01 ( ) { System.out.println ( "Client" ); try { clientSocket = new Socket ( SERVER_LOCATION, PORT ); crearFlujos ( ); // Creación del Vector de String.
APLICACIONES DE RED CON SOCKETS TCP
Vector < String > names = new Vector < String > ( ); names.add names.add names.add names.add names.add
( ( ( ( (
"Ana" ); "Bibiana" ); "Carolina" ); "Diana" ); "Elena" );
System.out.println( names ); // Envío del Vector de String al servidor, en forma de objeto. send ( names ); // Lectura del mensaje de respuesta enviado por el servidor. String answer = ( String ) receive ( ); System.out.println ( answer ); } // Puede lanzar una excepción de host desconocido. catch ( UnknownHostException e ) { e.printStackTrace ( ); } // Puede lanzar una excepción de entrada y salida. catch ( IOException e ) { e.printStackTrace ( ); } // Puede lanzar una excepción por clase no encontrada. catch ( ClassNotFoundException e ) { e.printStackTrace ( ); } // Finalmente se cierran los flujos y el socket. finally { try { if ( inFromServer != null ) inFromServer.close ( ); if ( outToServer != null ) outToServer.close ( ); if ( clientSocket != null ) clientSocket.close ( ); } catch ( IOException e ) { e.printStackTrace ( ); } } } /** * Este método permite crear los flujos de entrada y salida necesarios para * comunicar el cliente y el servidor. * @throws IOException */ private void crearFlujos ( ) throws IOException { // Creación del flujo de salida hacia el servidor. outToServer = new ObjectOutputStream ( clientSocket.getOutputStream ( ) ); // Creación del flujo de entrada desde el servidor. inFromServer = new ObjectInputStream ( clientSocket.getInputStream ( ) ); }
63
64
INTRODUCCIÓN AL DESARROLLO DE APLICACIONES DE RED EN JAVA
/** * Este método permite enviar un objeto al servidor. * @param o Recibe por parámetro el objeto que desea enviar. * @throws IOException */ private void send ( Object o ) throws IOException { outToServer.writeObject ( o ); outToServer.flush ( ); } /** * Este método permite recibir un objeto enviado por el servidor. * @throws IOException * @throws ClassNotFoundException */ private Object receive ( ) throws IOException, ClassNotFoundException { return inFromServer.readObject ( ); } /** * Método principal utilizado para lanzar el programa cliente. */ public static void main ( String args [ ] ) throws Exception { new ObjectTCPClient01 ( ); } }
En este ejemplo, se usa un flujo de salida de objetos (ObjectOutputStream) y un flujo de entrada de objetos (ObjectOutputStream). El objeto que se crea para transferir entre cliente y servidor corresponde a un Vector de String.
Para enviarlo del cliente al servidor, ese objeto se pasa por parámetro al método send ( ). El método send ( ) es encargado de escribir el objeto por el flujo de salida de objetos outToServer y luego se hace uso del método flush ( ) del flujo de salida de objetos para asegurar que el objeto será escrito correctamente en el flujo de datos.
De manera similar, el cliente recibirá una respuesta por parte del servidor, la cual será obtenida a través del método receive ( ). Este método recibe datos de tipo Object, razón por la cual es necesario especificar el tipo de dato que realmente el cliente está esperando, en este caso, espera una respuesta de tipo String.
APLICACIONES DE RED CON SOCKETS TCP
65
ObjectTCPServer01.java El objetivo de este programa es recibir un objeto por parte del cliente e imprimirlo en la consola. Luego, el servidor envía una respuesta al cliente quien la imprimirá en su consola.
/* * Ejemplo 6: ObjectTCPServer01 * * El objetivo de este programa es crear un Vector de String y enviarlo como * objeto al servidor. El servidor lo recibe y envía un mensaje de confirmación * al cliente. */ import import import import import import
java.io.IOException; java.io.ObjectInputStream; java.io.ObjectOutputStream; java.net.ServerSocket; java.net.Socket; java.util.Vector;
public class ObjectTCPServer01 { public static final int PORT = 20000; private private private private
ServerSocket welcomeSocket; Socket connectionSocket; ObjectOutputStream outToClient; ObjectInputStream inFromClient;
// Método constructor. public ObjectTCPServer01 ( ) { System.out.println ( "Server" ); try { welcomeSocket = new ServerSocket ( PORT ); while ( true ) { connectionSocket = welcomeSocket.accept ( ); crearFlujos ( ); // Lectura del objeto enviado por el cliente. Observe que se debe // recibir haciendo cast al tipo de dato que el cliente está enviando. Vector < String > names = ( Vector < String > ) receive( ); System.out.println ( names ); // Procesamiento de la información que el cliente ha enviado al // servidor. En este caso, la respuesta es el String OK. String answer = "OK"; // Envío de la respuesta del servidor al cliente. send ( answer ); }
INTRODUCCIÓN AL DESARROLLO DE APLICACIONES DE RED EN JAVA
66
} // Puede lanzar una excepción de entrada y salida. catch ( IOException e ) { e.printStackTrace ( ); } // Puede lanzar una excepción por clase no encontrada. catch ( ClassNotFoundException e ) { e.printStackTrace ( ); } // Al terminar se cierran los sockets y los flujos. finally { try { if ( outToClient != null ) outToClient.close ( ); if ( inFromClient != null ) inFromClient.close ( ); if ( connectionSocket != null ) connectionSocket.close ( ); if ( welcomeSocket != null ) welcomeSocket.close ( ); } // Puede lanzar una excepción de entrada y salida. catch ( IOException e ) { e.printStackTrace ( ); } } } /** * Este método permite crear los flujos de entrada y salida necesarios para * comunicar el cliente y el servidor. * @throws IOException */ private void crearFlujos ( ) throws IOException { // Creación del flujo de salida hacia el cliente. outToClient = new ObjectOutputStream ( connectionSocket.getOutputStream ( ) ); // Creación del flujo de entrada desde el cliente. inFromClient = new ObjectInputStream ( connectionSocket.getInputStream ( ) ); } /** * Este método permite enviar un objeto al cliente. * @param o Recibe por parámetro el objeto que desea enviar. * @throws IOException */ private void send ( Object o ) throws IOException { outToClient.writeObject ( o ); outToClient.flush ( ); } /** * Este método permite recibir un objeto enviado por el cliente. * @throws IOException * @throws ClassNotFoundException */ private Object receive ( ) throws IOException, ClassNotFoundException {
APLICACIONES DE RED CON SOCKETS TCP
67
return inFromClient.readObject ( ); } /** * Método principal utilizado para lanzar el programa servidor. */ public static void main ( String args [ ] ) throws Exception { new ObjectTCPServer01 ( ); } }
Ejecución
Ejecute el servidor y luego el cliente. En esta aplicación no hay interacción con el usuario, por lo que la ejecución siempre mostrará el mismo resultado. Ver Figura 19.
Consola del servidor Server [Ana, Bibiana, Carolina, Diana, Elena] Consola del cliente Client [Ana, Bibiana, Carolina, Diana, Elena] OK
Figura 19. Ejemplo 6 – Salida en consola
3.4 EJEMPLO 7: SERVIDOR Y CLIENTE PARA LA TRANSMISIÓN DE OBJETOS – 2
Los programas cliente y servidor para la transmisión de objetos (definidos por el usuario) cumplen el siguiente protocolo:
• El cliente crea un objeto de tipo Person, el cual corresponde a una clase definida por el usuario. • El servidor recibe el objeto. • El servidor imprime en su consola el contenido del objeto recibido. • El servidor envía como resultado un mensaje “OK”. • El cliente recibe el resultado y lo imprime en la consola.
INTRODUCCIÓN AL DESARROLLO DE APLICACIONES DE RED EN JAVA
68
ObjectTCPClient02.java
/* * Ejemplo 7: ObjectTCPClient02 * * El objetivo de este programa es crear un objeto especificado por el usuario y * enviarlo al servidor. En este caso se trata de un objeto de tipo Person. El * servidor lo recibe y envía un mensaje de confirmación al cliente. Finalmente * el servidor envía de respuesta al cliente la cadena OK. */ import import import import import
java.io.IOException; java.io.ObjectInputStream; java.io.ObjectOutputStream; java.net.Socket; java.net.UnknownHostException;
public class ObjectTCPClient02 { public static final int PORT = 20000; public static final String SERVER_LOCATION = "localhost"; private Socket clientSocket; private ObjectOutputStream outToServer; private ObjectInputStream inFromServer; // Método constructor. public ObjectTCPClient02 ( ) { System.out.println ( "Client" ); try { clientSocket = new Socket ( SERVER_LOCATION, PORT ); crearFlujos ( ); // Creación del objeto Person. Person person = new Person ( "Martin", 7, 1.32 ); System.out.println( person ); // Envío del objeto al servidor. send ( person ); // Lectura del mensaje de respuesta enviado por el servidor. String answer = ( String ) receive ( ); System.out.println ( answer ); } // Puede lanzar una excepción de host desconocido. catch ( UnknownHostException e ) { e.printStackTrace ( ); } // Puede lanzar una excepción de entrada y salida. catch ( IOException e ) { e.printStackTrace ( ); } // Puede lanzar una excepción por clase no encontrada.
APLICACIONES DE RED CON SOCKETS TCP
catch ( ClassNotFoundException e ) { e.printStackTrace ( ); } // Finalmente se cierran los flujos y el socket. finally { try { if ( inFromServer != null ) inFromServer.close ( ); if ( outToServer != null ) outToServer.close ( ); if ( clientSocket != null ) clientSocket.close ( ); } catch ( IOException e ) { e.printStackTrace ( ); } } } /** * Este método permite crear los flujos de entrada y salida necesarios para * comunicar el cliente y el servidor. * @throws IOException */ private void crearFlujos ( ) throws IOException { // Creación del flujo de salida hacia el servidor. outToServer = new ObjectOutputStream ( clientSocket.getOutputStream ( ) ); // Creación del flujo de entrada desde el servidor. inFromServer = new ObjectInputStream ( clientSocket.getInputStream ( ) ); } /** * Este método permite enviar un objeto al servidor. * @param o Recibe por parámetro el objeto que desea enviar. * @throws IOException */ private void send ( Object o ) throws IOException { outToServer.writeObject ( o ); outToServer.flush ( ); } /** * Este método permite recibir un objeto enviado por el servidor. * @throws IOException * @throws ClassNotFoundException */ private Object receive ( ) throws IOException, ClassNotFoundException { return inFromServer.readObject ( ); }
/** * Método principal utilizado para lanzar el programa cliente. */ public static void main ( String args [ ] ) throws Exception { new ObjectTCPClient02 ( );
69
INTRODUCCIÓN AL DESARROLLO DE APLICACIONES DE RED EN JAVA
70
} }
Para el envío y recepción de objetos se utilizan flujos de salida de objetos y flujos de entrada de objetos los cuales son asociados al socket en ambos lados de la comunicación. Luego, simplemente al usar el método readObject ( ) y el método writeObject ( ) sobre los flujos se pueden leer y escribir los objetos en el socket. ObjectTCPServer02.java
/* * Ejemplo 7: ObjectTCPServer02 * * El objetivo de este programa es crear un objeto especificado por el usuario y * enviarlo al servidor. En este caso se trata de un objeto de tipo Person. El * servidor lo recibe y envía un mensaje de confirmación al cliente. Finalmente * el servidor envía de respuesta al cliente la cadena OK. */ import import import import import
java.io.IOException; java.io.ObjectInputStream; java.io.ObjectOutputStream; java.net.ServerSocket; java.net.Socket;
public class ObjectTCPServer02 { public static final int PORT = 20000; private private private private
ServerSocket Socket ObjectOutputStream ObjectInputStream
welcomeSocket; connectionSocket; outToClient; inFromClient;
// Método constructor. public ObjectTCPServer02 ( ) { System.out.println ( "Server" ); try { welcomeSocket = new ServerSocket ( PORT ); while ( true ) { connectionSocket = welcomeSocket.accept ( ); crearFlujos ( ); // Lectura del objeto enviado por el cliente. Observe que se debe // recibir haciendo cast al tipo de dato que el cliente está enviando. Person person = ( Person ) receive ( ); System.out.println ( person ); // Procesamiento de la información que el cliente ha enviado al
APLICACIONES DE RED CON SOCKETS TCP
// servidor. En este caso, la respuesta es el String OK. String answer = "OK"; // Envío de la respuesta del servidor al cliente. send ( answer ); } } // Puede lanzar una excepción de entrada y salida. catch ( IOException e ) { e.printStackTrace ( ); } // Puede lanzar una excepción por clase no encontrada. catch ( ClassNotFoundException e ) { e.printStackTrace ( ); } // Al terminar se cierran los sockets y los flujos. finally { try { if ( outToClient != null ) outToClient.close ( ); if ( inFromClient != null ) inFromClient.close ( ); if ( connectionSocket != null ) connectionSocket.close ( ); if ( welcomeSocket != null ) welcomeSocket.close ( ); } // Puede lanzar una excepción de entrada y salida. catch ( IOException e ) { e.printStackTrace ( ); } } } /** * Este método permite crear los flujos de entrada y salida necesarios para * comunicar el cliente y el servidor. * @throws IOException */ private void crearFlujos ( ) throws IOException { // Creación del flujo de salida hacia el cliente. outToClient = new ObjectOutputStream ( connectionSocket.getOutputStream ( ) ); // Creación del flujo de entrada desde el cliente. inFromClient = new ObjectInputStream ( connectionSocket.getInputStream ( ) ); } /** * Este método permite enviar un objeto al cliente. * @param o Recibe por parámetro el objeto que desea enviar. * @throws IOException */ private void send ( Object o ) throws IOException { outToClient.writeObject ( o ); outToClient.flush ( ); }
71
INTRODUCCIÓN AL DESARROLLO DE APLICACIONES DE RED EN JAVA
72
/** * Este método permite recibir un objeto enviado por el cliente. * @throws IOException * @throws ClassNotFoundException */ private Object receive ( ) throws IOException, ClassNotFoundException { return inFromClient.readObject ( ); } /** * Método principal utilizado para lanzar el programa servidor. */ public static void main ( String args [ ] ) throws Exception { new ObjectTCPServer02 ( ); } }
Person.java
/* * Ejemplo 7: Person * * Clase Person * Esta clase corresponde a un tipo de datos que representa personas del mundo * real. */ import java.io.Serializable; public class Person implements Serializable { // Observe que este tipo de clases permiten crear objetos que serán // transportados por la red, deben implementar la interface Serializable. String name; int age; double height; // metodo constructor. Recibe el nombre, la edad y la estatura de la persona. public Person ( String name, int age, double height ) { this.name = name; this.age = age; this.height = height; } /* * Este método retorna el nombre de la persona. */ public String getName ( ) { return name; } /* * Este método retorna la edad de la persona. */ public int getAge ( )
APLICACIONES DE RED CON SOCKETS TCP
73
{ return age; } /* * Este método retorna la estatura de la persona. */ public double getHeight ( ) { return height; } /* * Este método especifica la forma de imprimir el valor del objeto. */ public String toString ( ) { return name + ":\t" + age + " - " + height; } }
Las APIs de Java proporcionan muchas clases serializables, es decir, aquellas que pueden ser enviadas por la red. Sin embargo, cuando se trata de clases definidas por el usuario, éstas deben ser explícitamente definidas como serializables para que puedan ser enviadas por la red. Omitir esta condición es un error frecuente y a veces difícil de encontrar.
Una clase se define como serializable si implementa la interface Serializable como en el caso de la clase Person.
public class Person implements Serializable
Para poder implementar la interface Serializable, es necesario importarla del paquete java.io.Serializable.
import java.io.Serializable;
Ejecución
Ejecute el servidor y luego el cliente. En esta aplicación no hay interacción con el usuario, por lo que la ejecución siempre mostrará el mismo resultado. Ver Figura 20.
INTRODUCCIÓN AL DESARROLLO DE APLICACIONES DE RED EN JAVA
74
Consola del servidor Server Martin:
7 - 1.32 Consola del cliente
Client OK
Figura 20. Ejemplo 7 – Salida en consola
3.5 EJEMPLO 8: SERVIDOR Y CLIENTE PARA LA TRANSMISIÓN DE ARCHIVOS
Es posible enviar y recibir archivos de diferentes formas. En este trabajo, se ha definido que los programas cliente y servidor para la transmisión de archivos cumplen el siguiente protocolo:
• El cliente solicita el nombre de un archivo a través de una ventana emergente. • El cliente envía el nombre del archivo solicitado al servidor. • El servidor envía el archivo solicitado al cliente. • El cliente recibe el archivo y lo almacena en el disco local. Los archivos que el servidor puede enviar al cliente están almacenados en una carpeta llamada Shared.
FileTCPClient.java
/* * Ejemplo 8: FileTCPClient * * El objetivo de este programa es transferir un archivo desde el servidor al * cliente. El nombre del archivo es solicitado al usuario para que lo ingrese * por teclado. El cliente envía el nombre del archivo solicitado al servidor y * el servidor envía el archivo por fragmentos, si se hace necesario. El servidor * busca el archivo en la carpeta Shared. El cliente crea el archivo dentro de la * carpeta Download. */ import import import import
java.io.File; java.io.FileNotFoundException; java.io.FileOutputStream; java.io.IOException;
APLICACIONES DE RED CON SOCKETS TCP
import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.net.Socket; import javax.swing.JOptionPane; public class FileTCPClient { public static final int PORT = 20000; public static final String SERVER_LOCATION = "localhost"; private private private private private
Socket ObjectOutputStream ObjectInputStream FileOutputStream File
clientSocket; outToServer; inFromServer; out; file;
// Método constructor. public FileTCPClient ( ) { System.out.println ( "File Transfer Client" ); try { clientSocket = new Socket ( SERVER_LOCATION, PORT ); crearFlujos ( ); // Solicita al usuario el nombre del archivo. String fileName = JOptionPane.showInputDialog ( null, "Nombre del archivo:" ); // Envía el nombre del archivo al servidor. send ( fileName ); // Recibe el archivo. receiveFile ( fileName ); } // Puede lanzar una excepción de entrada y salida catch ( IOException e ) { e.printStackTrace ( ); } // Finalmente se cierran los flujos y el socket. finally { try { if ( inFromServer != null ) inFromServer.close ( ); if ( outToServer != null ) outToServer.close ( ); if ( clientSocket != null ) clientSocket.close ( ); } catch ( IOException e ) { e.printStackTrace ( ); } } System.out.println ( "OK" ); } /** * Este método recibe un archivo enviado por el servidor.
75
INTRODUCCIÓN AL DESARROLLO DE APLICACIONES DE RED EN JAVA
76
* @throws IOException */ private void receiveFile ( String fileName ) throws IOException { try { // Se crea el archivo con el nombre especificado en la carpeta Download. // Observe que esta carpeta debe existir en el host el cliente. file = new File ( "Download" + File.separator + fileName ); out = new FileOutputStream ( file ); // El cliente recibe el número de bloques que compone el archivo. int numberOfBlocks = ( ( Integer ) receive ( ) ).intValue ( ); // Se reciben uno a uno los bloques que conforman el archivo y se // almacenan en el archivo. for ( int i = 0; i < numberOfBlocks; i++ ) { byte [ ] buffer = ( byte [ ] ) receive ( ); out.write ( buffer, 0, buffer.length ); } } // Puede lanzar una excepción por clase no encontrada. catch ( ClassNotFoundException e ) { e.printStackTrace ( ); } // Puede lanzar una excepción por un archivo no encontrado. catch ( FileNotFoundException e ) { e.printStackTrace ( ); } // Puede lanzar una excepción de entrada y salida. catch ( IOException e ) { e.printStackTrace ( ); } // Finalmente se cierra el archivo. finally { if ( out != null ) out.close ( ); } } /** * Este método permite crear los flujos de entrada y salida necesarios para * comunicar el cliente y el servidor. * @throws IOException */ private void crearFlujos ( ) throws IOException { // Creación del flujo de salida hacia el servidor. outToServer = new ObjectOutputStream ( clientSocket.getOutputStream ( ) ); // Creación del flujo de entrada desde el servidor. inFromServer = new ObjectInputStream ( clientSocket.getInputStream ( ) ); } /** * Este método permite enviar un objeto al servidor. * @param o Recibe por parámetro el objeto que desea enviar. * @throws IOException */
APLICACIONES DE RED CON SOCKETS TCP
77
private void send ( Object o ) throws IOException { outToServer.writeObject ( o ); outToServer.flush ( ); } /** * Este método permite recibir un objeto enviado por el servidor. * @throws IOException * @throws ClassNotFoundException */ private Object receive ( ) throws IOException, ClassNotFoundException { return inFromServer.readObject ( ); } /** * Método principal utilizado para lanzar el programa cliente. */ public static void main ( String args [ ] ) throws Exception { new FileTCPClient ( ); } }
Una vez que el cliente se conecta con el servidor envía el nombre del archivo que desea descargar. Este nombre lo ha solicitado previamente a través de una ventana emergente. A continuación recibe el archivo. El método receiveFile ( ) hace lo siguiente:
Crea el archivo en la carpeta Download. Luego recibe el número de bloques que compone el archivo. Cada bloque es de 1024 bytes como está definido en la parte inicial de la aplicación FileTCPServer.java. Entonces, recibe uno por uno los bloques y los escribe en el archivo destino. Finalmente cierra el archivo en la máquina donde se ejecuta el cliente.
FileTCPServer.java /* * Ejemplo 8: FileTCPServer * * El objetivo de este programa es transferir un archivo desde el servidor al * cliente. El nombre del archivo es solicitado al usuario para que lo ingrese * por teclado. El cliente envía el nombre del archivo solicitado al servidor * y el servidor envía el archivo por fragmentos, si se hace necesario. El * servidor busca el archivo en la carpeta Shared. El cliente crea el archivo * dentro de la carpeta Download. */
INTRODUCCIÓN AL DESARROLLO DE APLICACIONES DE RED EN JAVA
78
import import import import import import import import
java.io.File; java.io.FileInputStream; java.io.FileNotFoundException; java.io.IOException; java.io.ObjectInputStream; java.io.ObjectOutputStream; java.net.ServerSocket; java.net.Socket;
public class FileTCPServer { public final static int PORT = 20000; // Constante que define el tamaño de un fragmento. Un archivo que mida mas que // el tamaño del fragmento, deberá ser enviado por partes. public final static int BUFFER_SIZE = 1024; private private private private private
ServerSocket Socket ObjectInputStream ObjectOutputStream File
welcomeSocket; connectionSocket; inFromClient; outToClient; file;
// Método constructor. public FileTCPServer ( ) { System.out.println ( "File Transfer Server ..." ); try { welcomeSocket = new ServerSocket ( PORT ); while ( true ) { connectionSocket = welcomeSocket.accept ( ); crearFlujos ( ); // Se recibe el nombre del archivo que el cliente desea que el servidor // le envíe y se imprime en la consola. String fileName; fileName = ( String ) receive ( ); System.out.println ( fileName ); // Se envía el archivo. sendFile ( fileName ); } } // Puede lanzar una excepción de entrada y salida. catch ( IOException e ) { e.printStackTrace ( ); } // Puede lanzar una excepción por clase no encontrada. catch ( ClassNotFoundException e ) { e.printStackTrace ( ); } // Al terminar se cierran los sockets y los flujos. finally
APLICACIONES DE RED CON SOCKETS TCP
{ try { if ( outToClient != null if ( inFromClient != null if ( connectionSocket != null if ( welcomeSocket != null } // Puede lanzar una excepción de catch ( IOException e ) { e.printStackTrace ( ); }
) ) ) )
outToClient.close ( ); inFromClient.close ( ); connectionSocket.close ( ); welcomeSocket.close ( );
entrada y salida.
} } /** * Este método envía un archivo al cliente. * @param filename Es el nombre del archivo solicitado a enviar. * @throws IOException */ private void sendFile ( String fileName ) throws IOException { FileInputStream fileIn = null; try { // Los archivos compartidos se almacenan en la carpeta Shared. Observe // que esta carpeta debe existir en el host el servidor y que en ella // debe existir el archivo a enviar. file = new File ( "Shared" + File.separator + fileName ); // Abre el archivo solicitado. fileIn = new FileInputStream ( file ); // Se obtiene el tamaño del archivo y se imprime en la consola. long size = file.length ( ); System.out.println ( "Size: " + size ); // Se calcula el número de bloques y el tamaño del ultimo bloque. int numberOfBlocks = ( int ) ( size / BUFFER_SIZE ); int sizeOfLastBlock = ( int ) ( size % BUFFER_SIZE ); // Si el archivo no se puede partir en bloques de igual tamaño queda un // bloque adicional, más pequeño. if ( sizeOfLastBlock > 0 ) { numberOfBlocks++; } // Se imprimen en la consola el número de bloques y el tamaño del último // bloque. System.out.println ( "Number of blocks: " + numberOfBlocks ); System.out.println ( "Size of last block: " + sizeOfLastBlock ); // Se envía el número de bloques al cliente. send ( new Integer ( numberOfBlocks ) ); // Si todos los bloques son de igual tamaño, no hay un bloque al final, // más pequeño. if ( sizeOfLastBlock == 0 ) { // Se envían todos los bloques.
79
INTRODUCCIÓN AL DESARROLLO DE APLICACIONES DE RED EN JAVA
80
for ( int i = 0; i < numberOfBlocks; i++ ) { byte [ ] buffer = new byte [ BUFFER_SIZE ]; fileIn.read ( buffer ); send ( buffer ); } } else { // Si queda un bloque más pequeño al final, se envían todos los bloques // excepto el último. for ( int i = 0; i < numberOfBlocks - 1; i++ ) { byte [ ] buffer = new byte [ BUFFER_SIZE ]; fileIn.read ( buffer ); send ( buffer ); } // El bloque restante se envía a continuación. byte [ ] lastBuffer = new byte [ sizeOfLastBlock ]; fileIn.read ( lastBuffer); send ( lastBuffer ); } } // Puede lanzar una excepción por un archivo no encontrado. catch ( FileNotFoundException e ) { e.printStackTrace ( ); } // Puede lanzar una excepción de entrada y salida. catch ( IOException e ) { e.printStackTrace ( ); } finally { if ( fileName != null ) fileIn.close ( ); } } /** * Este método permite crear los flujos de entrada y salida necesarios para * comunicar el cliente y el servidor. * @throws IOException */ private void crearFlujos ( ) throws IOException { // Creación del flujo de salida hacia el cliente. outToClient = new ObjectOutputStream ( connectionSocket.getOutputStream ( ) ); // Creación del flujo de entrada desde el cliente. inFromClient = new ObjectInputStream ( connectionSocket.getInputStream ( ) ); } /** * Este método permite enviar un objeto al cliente. * @param o Recibe por parámetro el objeto que desea enviar. * @throws IOException */ private void send ( Object o ) throws IOException {
APLICACIONES DE RED CON SOCKETS TCP
81
outToClient.writeObject ( o ); outToClient.flush ( ); } /** * Este método permite recibir un objeto enviado por el cliente. * @throws IOException * @throws ClassNotFoundException */ private Object receive ( ) throws IOException, ClassNotFoundException { return inFromClient.readObject ( ); } /** * Método principal utilizado para lanzar el programa servidor. */ public static void main ( String args [ ] ) throws Exception { new FileTCPServer ( ); } }
Una vez que el servidor recibe la conexión del cliente y el nombre del archivo que desea descargar, el servidor envía el archivo sin realizar ninguna verificación.
El método sendFile ( ) hace lo siguiente:
Lee el archivo de la carpeta Shared. Luego calcula el número de bloques de 1024 bytes que compone el archivo y envía este número al cliente. El último bloque puede tener un tamaño diferente de todos los demás, entonces, el servidor primero lee del archivo y envía todos los bloques excepto el último. A continuación lee del archivo y envía el último bloque y luego cierra el archivo.
Ejecución
Ejecute el servidor y luego el cliente. Cuando se ejecute el cliente, el usuario debe ingresar el nombre del archivo que desea descargar, en una ventana emergente. Ver Figuras 21 y 22.
82
INTRODUCCIÓN AL DESARROLLO DE APLICACIONES DE RED EN JAVA
Figura 21. Ejemplo 8 – Entrada cliente
A continuación, observe lo que sale en las consolas, tanto del cliente como del servidor.
Consola del servidor File Transfer Server ... 3com 4500.pdf Size: 755865 Number of blocks: 739 Size of last block: 153 Consola del cliente File Transfer Client OK
Figura 22. Ejemplo 8 – Salida en consola
3.6 EJEMPLO 9: SERVIDOR MULTIHILO Y CLIENTE PARA LA TRANSMISIÓN DE ARCHIVOS
Este programa tiene exactamente la misma funcionalidad del anterior, con la diferencia que el servidor en este caso es multihilo (multithreaded), para hacer posible que el servidor atienda varios clientes a la vez.
Para entender el código de este programa, se van a explicar los cambios con respecto al ejemplo 8, sección 3.5 página 70. De este modo no se duplica el código.
En primer lugar, la clase FileTCPServer02.java implementa la interface Runnable. Esto se hace adicionando implements Runnable en la declaración de la clase.
APLICACIONES DE RED CON SOCKETS TCP
83
public class FileTCPServer02 implements Runnable, Cloneable
Implementar la interface Runnable es una forma más sencilla de crear hilos (threads) en las aplicaciones. Esta interface obliga la creación de un método llamado run ( ) en la clase clase que la implementa. En este caso, en la clase FileTCPServer02.java se debe escribir el contenido del método run ( ). Por otra parte, implementar la interface Cloneable permite obtener clones de los objetos de esta clase. Esto será utilizado para que cada usuario sea atendido por un hilo diferente sin que se presenten conflictos por el manejo de las variables de cada cliente que se conecte al servidor.
A continuación, dentro del ciclo infinito del servidor, observe que tan pronto el ServerSocket (el objeto welcomeSocket) acepta la conexión del cliente, se crea el hilo y se inicia su ejecución. Iniciar la ejecución del hilo significa que se ejecuta el contenido del método run ( ).
while ( true ) { connectionSocket = welcomeSocket.accept ( ); FileTCPServer02 server = ( FileTCPServer02 ) clone ( ); // Creación del thread que atiende el mensaje que acaba de llegar. Thread thread = new Thread ( server ); thread.start ( ); }
El método run ( ) contiene las mismas instrucciones que estaban dentro del ciclo infinito en el ejemplo 8. Observe que se envía un clon del objeto para permitir que cada instancia del servidor maneje sus propios objetos sin que se presente alguna confusión entre las distintas instancias que manejan cada uno de los clientes.
crearFlujos ( ); // Se recibe el nombre del archivo que el cliente desea que el servidor le envíe // y se imprime en la consola. String fileName; fileName = ( String ) receive ( ); System.out.println ( fileName ); // Se envía el archivo.
INTRODUCCIÓN AL DESARROLLO DE APLICACIONES DE RED EN JAVA
84
sendFile ( fileName );
Los demás métodos : crearFlujos ( ), send ( ), sendFile ( ), receive ( ) y main ( ) son exactamente los mismos que en el ejemplo 8.
Para el cliente, se puede utilizar el mismo programa cliente del ejemplo 8, y por lo tanto, la ejecución permite ver la misma interfaz de usuario.
3.7 EJERCICIOS
1. En cada ejercicio, escriba una aplicación cliente–servidor de acuerdo con el protocolo especificado:
a. El cliente envía dos objetos de la clase Punto. La clase Punto es una abstracción de un punto en el plano cartesiano de dos dimensiones, con valores (x ,y). El servidor regresa un objeto de la clase Punto, el cual corresponde al punto medio.
b. El cliente envía dos objetos de la clase Punto. El servidor regresa un dato de tipo Double correspondiente a la distancia entre los dos puntos.
c. El cliente ingresa tres números enteros en una interfaz gráfica de usuario. Cuando hace clic en el botón Aceptar, los envía un objeto que agrupe los tres números. El servidor recibe el objeto, obtiene los tres números y retorna al cliente el mayor de ellos, el número del medio y el menor, en forma String. El cliente recibe el mensaje y lo muestra en la ventana. El formato del mensaje de respuesta es: "Mayor: mr, Medio: md, Menor: mn", donde mr, md y mn son el número mayor, el número del medio y el número menor, respectivamente.
d. Haga una variación del protocolo anterior en la cual el servidor registre el mayor de todos los números que haya recibido de cualquier cliente. En este
APLICACIONES DE RED CON SOCKETS TCP
85
caso, el servidor deberá retornar dos mensajes, el que ya está especificado en el ejercicio anterior, y un segundo mensaje con el mayor número enviado por cualquier cliente al servidor. El cliente recibe los dos mensajes y los muestra en la ventana.
2. Escriba una aplicación cliente–servidor que cumpla el protocolo especificado a continuación.
El programa cliente:
Solicita el nombre al usuario.
Envía el siguiente mensaje: “LOGIN” seguido por el nombre del usuario,
separados por un espacio en blanco. Recibe un mensaje de respuesta del servidor y lo imprime en la consola. El programa servidor:
• Recibe un mensaje del cliente que debe traer la palabra reservada “LOGIN” y un nombre de usuario. • Del mensaje recibido, debe extraer el nombre. • El servidor registra los nombres de todos los clientes en un vector. Si el usuario es nuevo (no está en el vector), el servidor le envía al cliente el mensaje: “BIENVENIDO” seguido del nombre del usuario seguido de USTED EL ES USUARIO No. n, usando espacios en blanco para la separación respectiva. Si el usuario no es nuevo (ya está en el vector), el servidor envía al cliente el mensaje: “ACCESO NEGADO”. 3. Construya una GUI sencilla que pueda ser utilizada para enviar archivos desde el cliente hasta el servidor. La GUI debe tener un campo de texto en el cual se va a escribir la ruta completa del archivo que se desea enviar al servidor, seguido de un botón para explorar el sistema de archivos del computador. Adicionalmente, se debe tener un botón para que el usuario inicie el envío del archivo. Se espera una confirmación del servidor cuando el archivo sea completamente recibido y después
86
INTRODUCCIÓN AL DESARROLLO DE APLICACIONES DE RED EN JAVA
que el cliente recibe esta confirmación, debe mostrarlo dentro de la GUI con un mensaje.
4. Escriba una aplicación de archivos compartidos que cumpla el protocolo especificado a continuación.
Cada uno de los clientes tendrá también comportamiento de servidor. Los clientes escucharán en un puerto para recibir conexiones de otros clientes para descargar archivos compartidos en una carpeta. Además, existe un servidor central al cual se conectarán los clientes y enviarán la siguiente información: dirección IP, número de puerto y la lista de archivos compartidos en una carpeta.
El servidor central debe almacenar en una estructura de datos una lista con los nombres de los archivos y la información necesaria para que puedan ser descargados. Por otra parte, el servidor central ofrece el servicio de consulta para que cualquier cliente se conecte y pueda consultar por un archivo, caso en el que el servidor central responde con la información necesaria para que el cliente que hace la solicitud pueda conectarse directamente con el cliente que tiene el archivo.
Entonces, el servidor central ofrece los siguientes servicios: 1) Recibe y procesa un mensaje de un nuevo nodo que se conecta; y 2) Recibe y procesa un mensaje de consulta por un archivo.
Un nodo cualquiera, actuando como servidor ofrece un servicio: Recibe y procesa una solicitiud por un archivo y lo envía.
Un nodo cualquiera, actuando como cliente realiza las siguientes acciones: 1) Enviar un mensaje de registro al servidor central cuando inicia; 2) Envía un mensaje de consulta por un archivo al servidor central; y 3) Envía un mensaje de solicitud por un archivo a otro nodo.
PROYECTO FILETRANSFER
87
4. PROYECTO FILETRANSFER El objetivo este proyecto es crear una aplicación cliente–servidor de transferencia de archivos, dando al usuario la posibilidad de interactuar con los archivos almacenados en el servidor. En este caso, el usuario de la aplicación cliente no necesita conocer de antemano los nombres de los archivos disponibles en el servidor. En su interfaz de usuario son mostrados una vez la conexión sea aceptada por el servidor.
La Figura 23 presenta la colaboración entre las clases que conforman el proyecto FileTransfer.
Figura 23. Proyecto FileTransfer – Diagrama de colaboración
El servidor carece de interfaz gráfica de usuario. La apariencia del cliente se puede apreciar en la Figura 24.
88
INTRODUCCIÓN AL DESARROLLO DE APLICACIONES DE RED EN JAVA
Figura 24. Proyecto FileTransfer – Interfaz gráfica de usuario
Cuando el usuario establece los parámetros de conexión (nombre o dirección del servidor y el puerto en el cual el servidor está escuchando por una conexión del cliente), aparece la lista de archivos que tiene el servidor en una carpeta compartida, llamada Shared, tal como lo muestra la Figura 25.
Figura 25. Proyecto FileTransfer – Lista de archivos disponibles en el servidor
PROYECTO FILETRANSFER
89
El usuario elige un archivo de la lista y hace clic en el botón Download file.
Cuando finalice la descarga, el archivo se almacena en una carpeta preestablecida en el host del cliente llamada Download. A continuación un ícono muestra que la descarga ha terminado. Ver Figura 26.
Figura 26. Proyecto FileTransfer – Fin de la descarga de un archivo
4.1 PASO 1: APLICACIÓN CLIENTE
Escriba el encabezado de la clase FileTCPClientGui.java. Esta clase corresponde a una GUI, por lo que extiende de JFrame.
public class FileTCPClientGui extends JFrame {
Determine los componentes gráficos de la GUI. No olvide importar los paquetes correspondientes a cada componente.
private DefaultTableModel model; private JTable table; private JScrollPane scroll;
INTRODUCCIÓN AL DESARROLLO DE APLICACIONES DE RED EN JAVA
90
private private private private private private private private private
JButton JButton JButton JLabel JTextField JLabel JTextField JLabel ImageIcon
connect; download; exit; labelServer; textServer; labelPort; textPort; result; resultImage;
Escriba el método constructor de la clase FileTCPClientGui. En este método se están asignando las propiedades a los componentes gráficos. La Figura 27 muestra los nombres de los componentes gráficos para que se vaya familiarizando con ellos. No olvide importar los paquetes correspondientes a cada componente.
Figura 27. Proyecto FileTransfer – Componentes gráficos de la GUI del cliente
Observe que el servidor por defecto se encuentra ubicado en la dirección del host local “localhost” y el puerto preestablecido es el número 20000.
public FileTCPClientGui ( ) { labelServer = new JLabel ( "Servidor: " ); labelServer.setBounds ( 10, 10, 60, 20 ); textServer = new JTextField ( "localhost" ); textServer.setBounds ( 60, 10, 150, 20 ); labelPort = new JLabel ( "Puerto: " );
PROYECTO FILETRANSFER
91
labelPort.setBounds ( 10, 35, 60, 20 ); textPort = new JTextField ( "20000" ); textPort.setBounds ( 60, 35, 150, 20 ); result = new JLabel ( ); result.setBounds ( 10, 245, 140, 140 ); connect = new JButton ( "Connect" ); connect.setBounds ( 10, 60, 200, 20 ); download = new JButton ( "Download file" ); download.setBounds ( 60, 290, 150, 20 ); exit = new JButton ( "Exit" ); exit.setBounds ( 60, 315, 150, 20 ); download.setEnabled ( false ); exit.setEnabled ( false ); // pendiente 1 setLayout ( add add add add add add add add
( ( ( ( ( ( ( (
null );
connect ); download ); exit ); result ); labelServer labelPort textServer textPort
); ); ); );
setSize ( 230, 380 ); setLocationRelativeTo ( null ); setResizable ( false ); setVisible ( true ); }
Adicione la siguiente línea al encabezado de la clase. Esto indica que serán capturados los eventos del mouse. No olvide importar los paquetes correspondientes a los sensores y eventos del mouse.
implements MouseListener
Los siguientes métodos son obligatorios debido a la implementación de la clase MouseListener.
public void mouseClicked
( MouseEvent event ) { }
public void mousePressed
( MouseEvent event ) { }
INTRODUCCIÓN AL DESARROLLO DE APLICACIONES DE RED EN JAVA
92
public void mouseReleased ( MouseEvent event ) { } public void mouseEntered
( MouseEvent event ) { }
public void mouseExited
( MouseEvent event ) { }
Adicione los sensores (listeners) a los botones en el método constructor. En este caso, los métodos que acaba de crear serán encontrados en esta (this) clase.
connect.addMouseListener ( this ); download.addMouseListener ( this ); exit.addMouseListener ( this );
Agregue title y list como atributos a la clase. El atributo title será utilizado para almacenar los títulos de las columnas en la tabla. En este caso, la tabla solo tiene una columna “Archivos en el servidor”. El atributo list almacena la lista de archivos que el servidor tiene disponibles en la carpeta Shared, para que los clientes los puedan descargar.
private String [ ] title; private String [ ] list;
Escriba dentro del método constructor, justo después de la etiqueta pendiente 1, las propiedades de la tabla en la que se mostrará la lista de archivos disponibles para descargar. En este fragmento de código se asocia a la tabla con el modelo y se establece que la información de la tabla es únicamente de lectura. A continuación, se asocia la tabla con un scroll para su ubicación y se establecen las coordenadas para su ubicación (10, 85), junto con el ancho (200) y la altura (200). Las cuatro medidas anteriores están medidas en pixeles. De igual manera, se está configurando la tabla para la selección de una fila únicamente. No olvide importar los paquetes correspondientes a los nuevos componentes. Preste especial atención que la segunda línea a continuación no lleva el símbolo punto y coma (;) al final. Finalizar esta línea de código con este símbolo es un error bastante frecuente. El método isCellEditable ( ) a continuación establece que la tabla va a contener información de solo lectura.
PROYECTO FILETRANSFER
93
title = new String [ ] { "Archivos en el servidor" }; model = new DefaultTableModel ( title, 0 ) { public boolean isCellEditable ( int fila, int columna ) { return false; } }; table = new JTable ( model ); scroll = new JScrollPane ( table ); table.setSelectionMode ( ListSelectionModel.SINGLE_SELECTION ); scroll.setBounds ( 10, 85, 200, 200 ); add ( scroll );
Agregue la constante PORT y los componentes de conexión como atributos a la clase. El puerto 20000 será usado como valor por omisión para la conexión con el servidor. Si el servidor eventualmente estuviera escuchando por otro puerto, sería posible modificarlo en la GUI cuando el cliente esté en ejecución. No olvide importar los paquetes correspondientes a cada componente.
private final static int PORT = 20000; private Socket clientSocket; private ObjectOutputStream outToServer; private ObjectInputStream inFromServer;
A continuación, adicione el método connectProcess ( ). Este método realiza todas las acciones necesarias para establecer la conexión con el servidor. Envía un mensaje “HELLO” al servidor y queda en modo de espera para recibir la lista de archivos que el servidor tiene disponibles para compartir con el cliente. Luego construye la información que va a ser mostrada en la tabla (la lista de archivos) y deshabilita los botones que ya no se pueden usar durante la ejecución del programa.
private void connectProcess ( ) { try { clientSocket = new Socket ( textServer.getText ( ), Integer.parseInt ( textPort.getText ( ) ) ); outToServer = new ObjectOutputStream ( clientSocket.getOutputStream ( ) );
INTRODUCCIÓN AL DESARROLLO DE APLICACIONES DE RED EN JAVA
94
inFromServer = new ObjectInputStream ( clientSocket.getInputStream ( ) ); send ( "HELLO" ); list = ( String [ ] ) receive ( ); for ( int i = 0; i < list.length; i++ ) { String [ ] file = new String [ 1 ]; file [ 0 ] = list [ i ]; model.addRow ( file ); } System.out.println ( ); } // Puede lanzar una excepción catch ( Exception e ) { e.printStackTrace ( ); } connect.setEnabled ( false ); download.setEnabled ( true ); exit.setEnabled ( true ); }
Adicione el método send ( ). Este método es usado par enviar los mensajes al servidor.
private void send ( Object o ) throws Exception { outToServer.writeObject ( o ); outToServer.reset ( ); }
Adicione el método receive ( ), el cual se utiliza par recibir los objetos que envía el servidor.
private Object receive ( ) throws Exception { return inFromServer.readObject ( ); }
El proceso de descarga de un archivo es realizado en el método downloadProcess ( ). Este método ha sido creado por partes para facilitar la escritura y entenderlo mejor. En una primera división del código, se han creado tres partes que están dentro del bloque try { }, que maneja las excepciones.
PROYECTO FILETRANSFER
95
private void downloadProcess ( ) { try { // parte 1 // parte 2 // parte 3 } // Puede lanzar una excepción catch ( Exception e ) { e.printStackTrace ( ); } }
A continuación, se deben incluir las tres partes de este método. Inicie con la parte 1. En esta primera parte, se obtiene la fila seleccionada y con esta información se puede determinar el nombre del archivo que se desea descargar del servidor. Se crea un objeto de tipo File y se verifica la existencia del archivo en la máquina del cliente.
Parte 1: int x = table.getSelectedRow ( ); String fileName = list [ x ]; File file = new File ( "Download" + File.separator + fileName ); int answer = -1; boolean localExists = file.exists ( );
Continúe con la parte 2. En este caso, si el archivo existe en la máquina del cliente, sale en la pantalla una ventana emergente de confirmación, preguntándole al usuario si desea reemplazar el archivo.
Parte 2: if ( localExists == true ) { answer = JOptionPane.showConfirmDialog ( null, "El archivo existe. ¿Desea reemplazar?" ); // 0 Si desea reemplazar, 1 si no desea reemplazar }
96
INTRODUCCIÓN AL DESARROLLO DE APLICACIONES DE RED EN JAVA
Termine el método downloadProcess ( ) con la parte 3. Si el archivo no existe y se desea reemplazar, o el archivo no existe, el procedimiento a seguir está especificado en las partes 4 y 5.
Parte 3: if ( answer == 0 || localExists == false ) { // parte 4 // parte 5 } else { send ( "EXIT" ); }
En la parte 4, cliente envía al servidor dos mensajes: en el primero va el comando GET. En el segundo mensaje, va el nombre del archivo que el cliente desea descargar. El servidor enviará un valor boolean que indica si el archivo solicitado existe en la carpeta compartida del servidor.
Parte 4: send ( "GET" ); send ( fileName ); boolean exists = ( ( Boolean ) receive ( ) ).booleanValue ( );
En la parte 5, hay una condición acerca de la existencia o no del archivo en el servidor. Si el archivo existe, recibe del servidor el número de bloques y se crea el archivo en el disco del cliente, para luego continuar con el código especificado en la parte 6.
Parte 5: if ( exists == true ) { int numeroDeBloques = ( ( Integer ) receive ( ) ).intValue ( ); FileOutputStream fileOut = new FileOutputStream ( file ); // parte 6 } else { System.out.println ( "El archivo no existe." ); }
PROYECTO FILETRANSFER
97
Continúe con la parte 6. Este fragmento de código es responsable de recibir cada bloque que hace parte del archivo y de escribirlo en el disco. Este procedimiento se repite para todos los bloques que conforman el archivo. Luego, se cierra el archivo y muestra la imagen que indica que el archivo ha sido descargado.
Parte 6: for ( int i = 0; i < numeroDeBloques; i++ ) { byte [ ] buffer = ( byte [ ] ) receive ( ); fileOut.write ( buffer, 0, buffer.length ); } fileOut.close ( ); resultImage = new ImageIcon ( "OK.GIF" ); result.setIcon ( resultImage );
Ahora, continúe con el contenido del método mouseClicked ( ). Este método determina lo que debe hacer la aplicación dependiendo del botón presionado. Si se presiona el botón coonect, entonces se ejecuta el método connectProcess ( ). Si el botón presionado es download, se ejecuta el método downloadProcess ( ). Finalmente, si el botón presionado es exit, (se determina por descarte), entonces el cliente envía el mensaje EXIT y el programa cliente termina.
public void mouseClicked ( MouseEvent event ) { try { if ( textServer.getText ( ).equals ( "" ) == false && textPort. getText ( ).equals ( "" ) == false ) { if ( event.getSource ( ) == connect ) { connectProcess ( ); } else { if ( event.getSource ( ) == download ) { downloadProcess ( ); } else { send ( "EXIT" ); System.exit ( 1 );
INTRODUCCIÓN AL DESARROLLO DE APLICACIONES DE RED EN JAVA
98
} } } } // Puede lanzar una excepción. catch ( Exception e ) { e.printStackTrace ( ); } }
Adicione el método mouseEntered ( ). Cuando el mouse ingresa a uno de los botones que tiene activado el sensor (listener) de mouse, entonces la imagen asociada al resultado de la descarga es ocultada. Esto se puede apreciar mejor cuando haya descargado el primer archivo del servidor y desee hacer una o más descargas adicionales.
public void mouseEntered ( MouseEvent event ) { result.setIcon ( null ); }
Escriba ahora el método look ( ). Este método permite capturar la apariencia del sistema operativo para una mejor presentación de las interfaces gráficas de usuario.
private void look ( ) { try { UIManager.setLookAndFeel ( UIManager.getSystemLookAndFeelClassName ( ) ); } // Puede lanzar una excepción. catch ( Exception e ) { e.printStackTrace ( ); } }
Haga un llamado al método look ( ) en la primera línea del método constructor.
look ( );
PROYECTO FILETRANSFER
99
Finalmente, cree el método main ( ) para esta clase. En este método es suficiente con lanzar el método constructor de la clase FileTCPClientGUI.
public static void main ( String args [ ] ) { new FileTCPClientGui ( ); }
4.2 PASO 2: APLICACIÓN SERVIDOR
Escriba el encabezado de la clase FileTCPServer.java. No olvide importar los paquetes correspondientes a cada componente.
public class FileTCPServer {
Defina los atributos y constantes de la clase. Observe que el puerto en el que el servidor va a escuchar las peticiones de los clientes es el 20000, es decir, el mismo número de puerto que el cliente tiene asignado por defecto en la interfaz gráfica de usuario. Observe también que el tamaño del buffer es 1024. Este valor corresponde al tamaño de cada bloque en los que será dividido el archivo (medido en bytes), si su tamaño supera este valor.
private final static int PORT = 20000; private final static int BUFFER_SIZE = 1024; private private private private private private private
ServerSocket Socket ObjectOutputStream ObjectInputStream long int int
welcomeSocket; connectionSocket; outToClient; inFromClient; size; numberOfBlocks; sizeOfLastBlock;
A continuación, escriba el método constructor del servidor. Luego de crear el socket de bienvenida e imprimir un mensaje en la consola, el servidor ingresa a un ciclo infinito. Este ciclo indica que va a atender clientes hasta que el mensaje sea terminado a la fuerza. El código que está dentro del ciclo infinito se ha dividido en dos partes.
INTRODUCCIÓN AL DESARROLLO DE APLICACIONES DE RED EN JAVA
100
public FileTCPServer ( ) throws Exception { welcomeSocket = new ServerSocket ( PORT ); System.out.println ( "File Server" ); while ( true ) { // parte 1 // parte 2 } }
A continuación, escriba el código de la parte 1. En la parte 1, el servidor espera una solicitud de conexión de un cliente en forma bloqueante. Cuando llegue una conexión del cliente, se crean los flujos de entrada y salida. El servidor recibe un mensaje de saludo que envía el cliente y luego obtiene la lista de archivos que el servidor tiene en la carpeta compartida (llamada Shared) y la envía al cliente.
Parte 1: try { connectionSocket = welcomeSocket.accept ( ); outToClient = new ObjectOutputStream ( connectionSocket.getOutputStream ( ) ); inFromClient = new ObjectInputStream ( connectionSocket.getInputStream ( ) ); String message = ( String ) receive ( ); System.out.println ( message ); File folder = new File ( "Shared" + File.separator ); String list [ ] = folder.list( ); send ( list ); } // Puede lanzar una excepción. catch ( Exception e ) { e.printStackTrace ( ); } System.out.println ("File Server" );
La parte 2 es un ciclo do – while en el que se van a procesar todas las solicitudes de un cliente, dependiendo de los comandos enviados. Si el comando es “GET”, se recibe a
PROYECTO FILETRANSFER
101
continuación el nombre del archivo solicitado y se envía el archivo. Si comando es “EXIT”, entonces se sale del ciclo infinito, lo que significa que el servidor va a esperar la llegada de otro cliente.
Parte 2: do { String message = ( String ) receive ( ); if ( message.equals ( "GET" ) ) { String fileName = ( String ) receive ( ); System.out.println ( fileName ); sendFile ( fileName ); } else { if ( message.equals ( "EXIT" ) ) { break; } } } while ( true );
A continuación escriba el método sendFile ( ). Este método es utilizado para enviar un archivo al cliente. El procedimiento consiste en verificar primero si el archivo solicitado existe y dependiendo de eso, se envía un valor booleano true o false al cliente. El procedimiento a seguir cuando el archivo existe e se va a enviar al cliente se ha dividido en tres partes.
private void sendFile ( String fileName ) throws Exception { File file = new File ( "Shared" + File.separator + fileName ); boolean exists = file.exists ( ); System.out.println( "El archivo existe: " + exists ); if ( exists == true ) { send ( new Boolean ( true ) ); FileInputStream fileIn = new FileInputStream ( file ); try { // Parte 1 // Parte 2
INTRODUCCIÓN AL DESARROLLO DE APLICACIONES DE RED EN JAVA
102
// Parte 3 } // Puede lanzar una excepción por un archivo no encontrado. catch ( FileNotFoundException e ) { e.printStackTrace ( ); } // Puede lanzar una excepción de entrada y salida. catch ( IOException e ) { e.printStackTrace ( ); } // Puede lanzar una excepción catch ( Exception e ) { e.printStackTrace ( ); } } else { send ( new Boolean ( false ) ); } }
En la parte 1, se abre el archivo y se determina el tamaño. Con el tamaño del archivo y con el tamaño del buffer, se calcula el número de bloques completos y el tamaño del último bloque. Esto porque el tamaño del archivo no necesariamente será múltiplo de 1024 bytes (tamaño del buffer). Observe que el número de bloques se obtiene con la división entera entre el tamaño del archivo y el tamaño del buffer. De igual manera, el tamaño del último bloque se obtiene con la operación módulo (residuo de la división entera) entre el tamaño del archivo y el tamaño del buffer.
Parte 1: fileIn = new FileInputStream
( file );
size = file.length ( ); System.out.println ( "size: " + size ); numberOfBlocks = ( int ) ( size / BUFFER_SIZE ); sizeOfLastBlock = ( int ) ( size % BUFFER_SIZE );
En la parte 2, se puede apreciar que si el tamaño del ultimo bloque es mayor que cero, el número de bloques debe ser aumentado en uno, porque puede darse el caso que el tamaño del archivo sea múltiplo de 1024 bytes (todos los bloques van a medir 1024
PROYECTO FILETRANSFER
103
bytes) y no habrá un tamaño diferente para el último bloque. El número de bloques entonces es enviado al cliente.
Parte 2: if ( sizeOfLastBlock > 0 ) { numberOfBlocks++; } System.out.println ( "numero de bloques: " + numberOfBlocks ); System.out.println ( "Tamaño del ultimo bloque: " + sizeOfLastBlock ); send ( new Integer ( numberOfBlocks
) );
En la parte 3, se envía el archivo. Si la primera condición es verdadera, es decir, todos los bloques son del mismo tamaño (el tamaño del archivo es múltiplo de 1024 bytes), entonces se leen y envían numberOfBlocks bloques de 1024 bytes al cliente. En caso contrario, se envían numberOfBlocks – 1 bloques de 1024 bytes seguido de un bloque de sizeOfLastBlock bytes. Al terminar, se cierra el archivo.
if ( sizeOfLastBlock == 0 ) { for ( int i = 0; i < numberOfBlocks; i++ ) { byte [ ] buffer = new byte [ BUFFER_SIZE ]; fileIn.read ( buffer ); send ( buffer ); } } else { for ( int i = 0; i < numberOfBlocks - 1; i++ ) { byte [ ] buffer = new byte [ BUFFER_SIZE ]; fileIn.read(buffer ); send ( buffer ); } if ( sizeOfLastBlock > 0 ) { byte [ ] lastBuffer = new byte [ sizeOfLastBlock ]; fileIn.read ( lastBuffer ); send ( lastBuffer ); } } fileIn.close ( );
INTRODUCCIÓN AL DESARROLLO DE APLICACIONES DE RED EN JAVA
104
Continúe con el método send ( ), el cual es usado para enviar objetos al cliente.
private void send ( Object o ) throws Exception { outToClient.writeObject ( o ); outToClient.flush ( ); }
A continuación, escriba el método receive ( ). Este método se utiliza para recibir objetos del cliente.
private Object receive ( ) throws Exception { return inFromClient.readObject ( ); }
Finalmente, escriba el método main ( ). Este programa se pone en marcha sólo con lanzar el método constructor de la clase FileTCPServer.
public static void main ( String args [ ] ) throws Exception { new FileTCPServer ( ); }
4.3 EJERCICIOS
1. Responda el siguiente cuestionario acerca del proyecto File Transfer. La idea es que lea el código y se familiarice para que pueda hacer los ejercicios propuestos adicionales.
1. ¿Por qué motivo el cliente puede ejecutarse antes que el servidor? 2. ¿Qué mensaje sale en la consola si se ejecuta el cliente y se hace clic en el botón connect, pero el servidor no se ha ejecutado? 3. En la GUI del cliente hay tres botones. Indique el nombre de cada uno de ellos, haciendo distinción entre letras mayúsculas y minúsculas.
PROYECTO FILETRANSFER
Nombre del objeto
105
Etiqueta del botón
4. En la GUI del cliente hay dos arreglos de String. ¿Cuáles son sus nombres?
5. En la GUI del cliente hay un objeto llamado result. ¿Qué tipo de dato es?
6. ¿Cuál es el número de puerto por omisión que aparece en la GUI del cliente para conectarse con el servidor?
7. ¿Cuál es la función que desempeña el método apariencia ( ) en la GUI del cliente? 8. ¿Cuántos elementos tiene el arreglo de String llamado title en la GUI del cliente? Explique
9. ¿Cuál es la instrucción del lenguaje Java que permite que un botón inicie deshabilitado en la GUI del cliente?
10.En palabras, explique lo que hace el método connectProcess ( ) en la GUI del cliente.
INTRODUCCIÓN AL DESARROLLO DE APLICACIONES DE RED EN JAVA
106
11.¿Qué hacen los métodos receive ( ) y send ( ) en la GUI del cliente? 12.¿Por qué motivo en la GUI del cliente se recibe un objeto de tipo String [ ] String? o
b
j
e
t
o
t
i
p
y
u
n
o
13.¿Qué hace el método addRow ( ) de la clase DefaultTableModel? 14.¿Qué hace el método getSelectedRow ( ) de la clase JTable ( )? 15.¿Qué hace la instrucción boolean localExists = file.exists ( ); que se utiliza en el método downloadProcess ( ) en la GUI del cliente? 16.¿Cuál es el tipo de datos de la variable answer que se utiliza en el método downloadProcess ( ) en la GUI del cliente? 17.Explique los mensajes HELLO, GET, y EXIT que envía el cliente al servidor. 18.¿Qué hace la variable númeroDeBloques que se utiliza en el método downloadProcess ( ) en la GUI del cliente? 19.¿Qué hace la variable fileOut que se utiliza en el método downloadProcess ( ) en la GUI del cliente? 20.¿De qué tamaño son los bloques que utiliza el servidor para enviar un archivo al cliente? 21.¿Qué hace el atributo sizeOfLastBlock en el servidor? 22.¿Qué tipo de dato es la variable folder en el servidor? 23.¿Qué hace la instrucción String list [ ] = folder.list( ); usada en el método constructor del servidor? 24.¿Qué hace la instrucción send ( list ); usada en el método constructor del servidor?
PROYECTO FILETRANSFER
107
25.¿Qué función desempeña la variable message en el método constructor del servidor? 26.¿Qué hace la instrucción size = file.length ( ); usada en el método constructor del servidor? 27.¿Cómo calcula el servidor el número de bloques que debe enviar al cliente para la transferencia del archivo? ¿Cómo calcula el tamaño del último bloque? 28.Haga un diagrama en el que ilustre el protocolo de este proyecto. 2. Tome como punto de partida el Proyecto FileTransfer y haga las modificaciones necesarias para cumplir con los requisitos planteados.
1. Modifique la interfaz gráfica de usuario en el cliente para que pueda incluir dos campos de texto: login y password. Además, la interfaz debe tener un nuevo botón para registrar un usuario nuevo, llamado New user.
2. Si el usuario hace clic en el botón New user, se debe validar que los campos de login y password no estén vacíos, y debe salir una ventana emergente para confirmar el password. Además, debe validarse que el login de un usuario no incluya ningún símbolo ni el espacio en blanco.
3. Si los passwords coinciden, entonces se envían al servidor: un mensaje “NEW”, el login, y el password, bien sea en una cadena de texto utilizando algún separador, o en mensajes independientes.
4. El servidor entonces verifica si se trata efectivamente de un usuario nuevo o si es un usuario que ya existe en el sistema.
5. Si es un usuario nuevo, entonces lo almacena en una tabla hash. A continuación crea una carpeta en el servidor con el nombre del usuario y a continuación envía al cliente un mensaje “OK” seguido de la lista de archivos.
INTRODUCCIÓN AL DESARROLLO DE APLICACIONES DE RED EN JAVA
108
6. Si el usuario ya existe, entonces envía al cliente un mensaje “ERROR – USUARIO YA REGISTRADO” y no acepta la conexión.
7. Si el usuario hace clic en el botón Connect, se envían al servidor: un mensaje “HELLO”, el login, y el password, bien sea en una cadena de texto utilizando algún separador, o en mensajes independientes.
8. El servidor entonces verifica que el usuario exista en el sistema y que el password corresponda.
9. Si el acceso del usuario es correcto, el servidor envía al cliente un mensaje “OK” seguido de la lista de archivos.
10. Si el usuario no existe, entonces el servidor envía al cliente un mensaje “ERROR – USUARIO NO EXISTE” y no acepta la conexión.
11. Si el usuario existe pero el password no corresponde, entonces el servidor envía al cliente un mensaje “ERROR – PASSWORD INCORRECTO” y no acepta la conexión.
3. Tome como punto de partida el ejercicio anterior (numeral 2). Modifique la interfaz gráfica de usuario en el cliente para que pueda incluir un nuevo botón llamado SendFile a través del cual el usuario podrá enviar un archivo al servidor (elegido mediante una ventana de selección de archivos). Para este ejercicio, debe especificar un nuevo comando al protocolo llamado PUT. Los archivos enviados desde el cliente al servidor deben quedar almaceandos en la carpeta que tiene el nombre del usuario.
4. Tome como punto de partida el Proyecto FileTransfer y haga las modificaciones necesarias en las clases cliente y servidor de tal manera que cuando el cliente se conecte al servidor, reciba la lista de archivos que el servidor tiene almacenados en la
PROYECTO FILETRANSFER
109
carpeta Shared. Esta lista de archivos deberá contener el nombre del archivo y la cantidad de bytes de cada archivo, tal como se muestra a continuación.
Ej.jpg 352768 HelloWorld.pdf 904 Logo.gif 1474 5. Tome como punto de partida el Proyecto FileTransfer y haga las modificaciones necesarias en la interfaz gráfica de usuario para incluir un nuevo campo de texto que deberá ser llenado por el usuario antes de establecer la conexión. Este campo de texto indica la cantidad máxima de archivos que pueden ser descargados durante una conexión. El valor por omisión es 5. La GUI deberá tener una etiqueta en la que se indique la cantidad de archivos descargados y así como la cantidad de bytes que suman. Cuando el usuario llegue a la cantidad de archivos especificada, el cliente debe enviar el comando EXIT para que se cierre la conexión.
6. Tome como punto de partida el ejercicio del numeral 2 y haga las modificaciones necesarias en la interfaz gráfica de usuario para incluir un nuevo botón llamado Private connect. Al hacer clic sobre este botón, el servidor verifica el login y password enviados por el usuario y si son correctos, envía la lista de archivos que hay en la carpeta privada del usuario. De igual manera, la GUI deberá tener otro botón llamado Delete, el cual debe permitir al usuario eliminar el archivo seleccionado de la carpeta privada del usuario en el servidor. Al hacer clic en el botón Delete, deberá salir un cuadro diálogo para confirmar si efectivamente se desea enviar la orden de eliminar el archivo. El mensaje que se le enviará al servidor será DELETE seguido de otro mensaje con el nombre del archivo. Al borrar el archivo, deberá actualizarse la lista de archivos en el cliente.
7. Tome como referencia el proyecto FileTransfer y conviértalo en un servidor multihilo.
110
INTRODUCCIÓN AL DESARROLLO DE APLICACIONES DE RED EN JAVA
APLICACIONES DE RED CON SOCKETS UDP
111
5. APLICACIONES DE RED CON SOCKETS UDP En esta sección se presentarán cuatro ejemplos prácticos de aplicaciones de red muy útiles las cuales envían y reciben datos a través del protocolo UDP. El primer ejemplo es una aplicación de eco donde se envían datos de tipo String, seguida por dos aplicaciones en las que se transmiten objetos y finaliza con un ejemplo de una aplicación que simula un chat cliente – servidor donde el servidor es multihilo.
5.1 EJEMPLO 10: SERVIDOR Y CLIENTE DE ECO
En esta sección se ilustra la programación de sockets utilizando la misma aplicación explicada con el protocolo TCP (capítulo 3), pero esta vez, realizada sobre el protocolo UDP. Las principales diferencias entre las dos aplicaciones son las siguientes: 1) No se necesita socket de contacto porque no hay establecimiento de conexión; 2) No hay flujos asociados a los sockets; 3) El host que envía crea los paquetes los cuales contienen la dirección y el número de puerto para cada uno de ellos; y 4) El proceso receptor debe abrir el paquete recibido para obtener la información que ha enviado el transmisor.
Recuerde que el protocolo de esta aplicación consiste en:
•
El cliente lee un mensaje de texto desde el teclado usando una ventana emergente y envía el mensaje por el socket de salida al servidor.
•
El servidor lee el mensaje de su socket.
•
El servidor envía el mensaje de vuelta al cliente por le socket.
•
El cliente lee el mensaje de su socket y lo imprime en la consola.
INTRODUCCIÓN AL DESARROLLO DE APLICACIONES DE RED EN JAVA
112
Lea cuidadosamente el código fuente en Java del ejemplo formado por las clases EchoUDPClient.java y EchoUDPServer.java.
EchoUDPClient.java
Este programa cliente envía y recibe cadenas de caracteres (String). El cliente envía al servidor un mensaje y el servidor lo recibe y lo imprime en la consola. A continuación reenvía al cliente quien lo recibe y lo imprime en su consola, por eso se dice que el servidor hace eco del mensaje recibido del cliente.
/* * Ejemplo 10: EchoUDPClient * * El objetivo de este programa es leer del teclado un String y enviarlo al * servidor. El servidor hace eco de este mensaje y lo envía de nuevo al cliente. */ import import import import import import
java.io.IOException; java.net.DatagramPacket; java.net.DatagramSocket; java.net.InetAddress; java.net.SocketException; java.net.UnknownHostException;
import javax.swing.JOptionPane; public class EchoUDPClient { public static final int PORT = 3500; public static final String SERVER_LOCATION = "localhost"; private DatagramSocket clientSocket; private InetAddress private int
serverIPAddress; serverPort;
private DatagramPacket packetToSend; private DatagramPacket packetToReceive; private byte [ ]
bufferToReceive;
// Método constructor. public EchoUDPClient ( ) { // Espacio de memoria utilizado para recibir datos. bufferToReceive = new byte [ 1024 ]; System.out.println ( "UDP Client: " ); try { // Creación del socket de datagrama en el lado cliente. Observe que en
APLICACIONES DE RED CON SOCKETS UDP
113
// este caso no es necesario identificar el servidor en el momento de // crear el socket. clientSocket = new DatagramSocket ( ); // Identificación del servidor serverIPAddress = InetAddress.getByName ( SERVER_LOCATION ); String message = JOptionPane.showInputDialog ( null, "Ingrese un mensaje:" ); System.out.println ( "Enviado: " + message ); // Envío del mensaje al servidor. send ( message ); // Recepción del mensaje de respuesta enviado por el servidor. String response = ( String ) receive ( ); System.out.println ( "Recibido: " + response ); clientSocket.close ( ); } // Puede lanzar una excepción de socket. catch ( SocketException e ) { e.printStackTrace ( ); } // Puede lanzar una excepción de host desconocido. catch ( UnknownHostException e ) { e.printStackTrace ( ); } // Puede lanzar una excepción de entrada y salida. catch ( IOException e ) { e.printStackTrace ( ); } } /** * Este método permite enviar un mensaje al servidor. * @param messageToSend Recibe por parámetro el mensaje que desea enviar. * @throws IOException */ private void send ( String messageToSend ) throws IOException { // Convierte el String en un byte [ ] byte [ ] ba = messageToSend.getBytes ( ); // Crea el paquete a enviar con el mensaje en forme de byte [ ], la // dirección IP del servidor y el número de puerto. packetToSend = new DatagramPacket ( ba, ba.length, serverIPAddress, PORT ); // Envía el paquete por el socket. clientSocket.send ( packetToSend ); } /** * Este método permite recibir un mensaje enviado por el servidor. * @throws IOException */ private String receive ( ) throws IOException {
INTRODUCCIÓN AL DESARROLLO DE APLICACIONES DE RED EN JAVA
114
// Crea el paquete para recibir el mensaje de respuesta del servidor. // Observe que solo tiene el buffer para recibir. packetToReceive = new DatagramPacket ( bufferToReceive, bufferToReceive.length ); // Recibe el paquete enviado por el servidor. clientSocket.receive ( packetToReceive ); // Convierte a String el contenido del campo de datos del paquete recibido. String receivedMessage = new String ( packetToReceive.getData ( ), 0, packetToReceive.getLength ( ) ); return receivedMessage; } /** * Método principal utilizado para lanzar el programa cliente. */ public static void main ( String args [ ] ) { new EchoUDPClient ( ); } }
El programa EchoUDPClient.java construye un socket de tipo DatagramSocket llamado clientSocket. Note que UDP en el cliente usa una clase de socket diferente que el usado en TCP. En particular, el cliente UDP usa un socket de la clase DatagramSocket, mientras que el cliente TCP usa un socket de la clase Socket.
Al igual que en el ejemplo de sockets TCP, la entrada del mensaje de prueba es a través de una ventana emergente. A diferencia del programa que usa el protocolo TCP, cuando se usa UDP no se crean flujos que se conectan con los sockets, ni para la entrada ni para la salida. En su lugar, UDP pone los bytes en paquetes que se pasan al DatagramSocket.
A continuación, se detallarán algunas de las instrucciones que difieren de las usadas en el programa que usó TCP para la transmisión de datos.
clientSocket = new DatagramSocket ( );
Esta línea crea el objeto clientSocket de tipo DatagramSocket. A diferencia con TCP, no hay inicialización de conexión. En particular, el host cliente no contacta al servidor con la ejecución de esta línea de código. Por esta razón, el método constructor de la
APLICACIONES DE RED CON SOCKETS UDP
115
clase DatagramSocket no recibe como parámetro el nombre del host o la dirección IP, ni el número de puerto. Es como crear una salida hacia el socket pero no crea el tubo que los va a unir.
serverIPAddress = InetAddress.getByName ( "localhost" );
Para enviar bytes al proceso destino, se necesita la dirección del host donde está en ejecución el proceso servidor. La línea anterior indica que se ha establecido "localhost" como nombre de host. Este nombre puede cambiarse por un nombre de dominio de Internet para ser buscado por el servidor DNS y así obtener la dirección IP del host donde se ejecuta el servidor. Cuando el servidor DNS ha encontrado la dirección asociada al nombre del host, la asigna en la variable serverIPAddress de tipo InetAddress.
private void send ( String messageToSend ) throws Exception { byte [ ] ba = messageToSend.getBytes ( ); packetToSend = new DatagramPacket ( ba, ba.length, serverIPAddress, PORT ); clientSocket.send ( packetToSend ); }
El método send ( ) es usado para encapsular todo el procedimiento de envío de un mensaje de tipo String. Este método recibe por parámetro un String y lo convierte a arreglo de bytes (byte [ ]) utilizando el método getBytes ( ) de la clase String. Luego, crea un paquete especial de datagrama. Este paquete necesita el arreglo de bytes que acaba de ser creado, la longitud de ese arreglo, la dirección IP del servidor y el número de puerto. Luego, el paquete es enviado por el socket.
private String receive ( ) throws Exception { packetToReceive = new DatagramPacket ( bufferToReceive, bufferToReceive.length ); clientSocket.receive ( packetToReceive ); String receivedMessage = new String ( packetToReceive.getData ( ), 0, packetToReceive.getLength ( ) );
INTRODUCCIÓN AL DESARROLLO DE APLICACIONES DE RED EN JAVA
116
return receivedMessage; }
El método receive ( ) es usado para retornar un String con el mensaje recibido, igual que en el caso anterior, encapsulando el proceso. Primero, se crea un paquete de datagrama vacío. Luego, se recibe en ese paquete el datagrama que llega por el socket. A continuación, se genera el String a partir de los datos contenidos en el paquete (usando el método getData ( ) del paquete y la posiciones inicial del String dentro del arreglo de bytes y la longitud del arreglo de bytes). Finalmente la cadena es retornada.
EchoUDPServer.java
Este programa servido recibe y envía cadenas de caracteres (String). El cliente envía al servidor un mensaje y el servidor lo recibe y lo imprime en la consola. A continuación reenvía al cliente quien lo recibe y lo imprime en su consola, por eso se dice que el servidor hace eco del mensaje recibido del cliente.
/* * Ejemplo 10: EchoUDPServer * * El objetivo de este programa es leer del teclado un String y enviarlo al * servidor. El servidor hace eco de este mensaje y lo envía de nuevo al cliente. */ import import import import import
java.io.IOException; java.net.DatagramPacket; java.net.DatagramSocket; java.net.InetAddress; java.net.SocketException;
public class EchoUDPServer { public static final int PORT = 3500; private DatagramSocket serverSocket; private InetAddress private int
clientIPAddress; clientPort;
private DatagramPacket packetToSend; private DatagramPacket packetToReceive; private byte [ ]
bufferToReceive;
// Método constructor. public EchoUDPServer ( )
APLICACIONES DE RED CON SOCKETS UDP
117
{ System.out.println ( "Run UDP Server..." ); try { // Espacio de memoria utilizado para recibir datos. bufferToReceive = new byte [ 1024 ]; // Creación del socket de datagrama en el lado servidor. Observe que // especifica el número de puerto en el cual está escuchando. serverSocket = new DatagramSocket ( PORT ); // Ciclo infinito que permite al servidor atender varios clientes, uno a // la vez. El servidor va a prestar un mismo servicio a cada cliente. while ( true ) { // Recepción del mensaje de respuesta enviado por el cliente. String message = ( String ) receive ( ); System.out.println( "Recibido: " + message ); // Procesamiento de la información que el cliente ha enviado al // servidor. String echoMessage = message; // Envío del mensaje al cliente. send ( echoMessage ); System.out.println ( "Enviado: " + echoMessage ); } } // Puede lanzar una excepción de socket. catch ( SocketException e ) { e.printStackTrace ( ); } // Puede lanzar una excepción de entrada y salida. catch ( IOException e ) { e.printStackTrace ( ); } } /** * Este método permite enviar un mensaje al servidor. * @param messageToSend Recibe por parámetro el mensaje que desea * enviar. * @throws IOException */ private void send ( String messageToSend ) throws IOException { byte [ ] ba = messageToSend.getBytes ( ); packetToSend = new DatagramPacket ( ba, ba.length, clientIPAddress, clientPort ); serverSocket.send ( packetToSend ); } /* * Este método permite recibir un mensaje enviado por el cliente. */ private String receive ( ) throws IOException { packetToReceive = new DatagramPacket
INTRODUCCIÓN AL DESARROLLO DE APLICACIONES DE RED EN JAVA
118
( bufferToReceive, bufferToReceive.length ); serverSocket.receive ( packetToReceive ); clientIPAddress = packetToReceive.getAddress ( ); clientPort = packetToReceive.getPort ( ); String receivedMessage = new String ( packetToReceive.getData ( ), 0, packetToReceive.getLength ( ) ); return receivedMessage; } /** * Método principal utilizado para lanzar el programa servidor. */ public static void main ( String args [ ] ) { new EchoUDPServer ( ); } }
El programa EchoUDPServer.java construye un socket. Este socket es llamado serverSocket. Es un objeto de tipo DatagramSocket, similar al creado en la aplicación cliente, sin flujos conectados a él.
serverSocket = new DatagramSocket ( PORT );
Observe que este socket está escuchando el puerto especificado en la constante PORT, en este caso, el puerto número 3500. Todos los datos recibidos y enviados pasarán por este socket. Debido a que UDP es no orientado a conexión, no es necesario crear un socket nuevo para continuar escuchando por nuevas solicitudes de clientes. Si llegan múltiples solicitudes de clientes, todas llegarán por el mismo socket, por el mismo puerto.
El método send ( ) del servidor es igual que el equivalente en el cliente. La única diferencia es que en el servidor el paquete que se crea va destinado a la dirección del cliente y con el número de puerto del cliente, mientras que en el método send ( ) del cliente, el paquete va con dirección de destino y número de puerto del servidor. Los dos métodos son funcionalmente idénticos.
APLICACIONES DE RED CON SOCKETS UDP
119
private String receive ( ) throws Exception { packetToReceive = new DatagramPacket ( bufferToReceive, bufferToReceive.length ); serverSocket.receive ( packetToReceive ); clientIPAddress = packetToReceive.getAddress ( ); clientPort = packetToReceive.getPort
( );
String receivedMessage = new String ( packetToReceive.getData ( ), 0, packetToReceive.getLength ( ) ); return receivedMessage; }
El método receive ( ) es un poco diferente, porque además de obtener la información para retornar la cadena que viene en forma de arreglo de bytes, se obtiene del paquete la dirección IP del cliente y el número de puerto creado para el envío del mensaje. Tanto la dirección IP como el número de puerto del cliente permiten al cliente recibir la respuesta a la petición enviada al servidor.
Ejecución
Ahora, para probar la aplicación, de nuevo compile y ejecute los dos programas. Recuerde iniciar primero el servidor y luego el cliente. Luego, en la ventana de diálogo que aparece, ingrese el mensaje para probar la aplicación. Ver Figuras 28 y 29.
Figura 28. Ejemplo 10 – Entrada cliente
Luego de presionar el botón Aceptar, se inicia la transmisión de datos y la salida en las consolas del cliente y servidor son como se puede ver en la Figura 29.
INTRODUCCIÓN AL DESARROLLO DE APLICACIONES DE RED EN JAVA
120
Consola del servidor Run UDP Server... Recibido: mensaje de prueba Enviado: mensaje de prueba
Consola del cliente UDP Client: Enviado: mensaje de prueba Recibido: mensaje de prueba
Figura 29. Ejemplo 10 – Salida en consola
5.2 EJEMPLO 11: SERVIDOR Y CLIENTE PARA LA TRANSMISIÓN DE OBJETOS – 1
Los programas cliente y servidor para la transmisión de objetos (serializados por defecto) cumplen con el siguiente protocolo:
•
El cliente crea un Vector de String de cinco elementos y lo envía por el socket de salida al servidor.
•
El servidor recibe el objeto.
•
El servidor imprime en su consola el contenido del objeto recibido.
•
El servidor envía como resultado un mensaje "OK".
•
El cliente recibe el resultado y lo imprime en la consola.
ObjectUDPClient01.java
Este programa permite el envío y recibo de objetos entre cliente y servidor. El objeto consiste en un Vector de String que el cliente envía al servidor, el servidor lo recibe y lo imprime en la consola. Finalmente el servidor envía de respuesta al cliente la cadena OK.
/* * Ejemplo 11: ObjectUDPClient01 * * El objetivo de este programa es crear un Vector de String y enviarlo como * objeto al servidor. El servidor lo recibe y envía un mensaje de confirmación * al cliente. */ import import import import import import
java.io.IOException; java.net.DatagramPacket; java.net.DatagramSocket; java.net.InetAddress; java.net.SocketException; java.net.UnknownHostException;
APLICACIONES DE RED CON SOCKETS UDP
import java.util.Vector; public class ObjectUDPClient01 { private DatagramSocket clientSocket; private InetAddress private int
serverIPAddress; serverPort;
private DatagramPacket packetToSend; private DatagramPacket packetToReceive; private byte [ ]
bufferToReceive;
// Método constructor. public ObjectUDPClient01 ( String address, int serverPort ) { // Inicializa el buffer de recepción. bufferToReceive = new byte [ 1024 ]; System.out.println ( "UDP Client: " ); try { // Crea el socket en el lado cliente. clientSocket = new DatagramSocket ( ); // Localización del servidor. serverIPAddress = InetAddress.getByName ( address ); // Inicializa el número de puerto en el servidor. this.serverPort = serverPort; // Crea el objeto que se desea enviar. Vector < String > names = new Vector < String > ( ); names.add names.add names.add names.add names.add
( ( ( ( (
"Ana" ); "Bibiana" ); "Carolina" ); "Diana" ); "Elena" );
System.out.println( names ); // Envío del objeto. send ( names ); // Recepción de la respuesta que viene del servidor. String answer = ( String ) receive ( ); System.out.println ( answer ); clientSocket.close ( ); } // Puede lanzar una excepción de socket. catch ( SocketException e ) { e.printStackTrace ( ); } // Puede lanzar una excepción de host desconocido. catch ( UnknownHostException e ) { e.printStackTrace ( );
121
INTRODUCCIÓN AL DESARROLLO DE APLICACIONES DE RED EN JAVA
122
} // Puede lanzar una excepción de entrada y salida. catch ( IOException e ) { e.printStackTrace ( ); } // Puede lanzar una excepción por una clase no encontrada. catch ( ClassNotFoundException e ) { e.printStackTrace ( ); } } /** * Este método permite enviar un objeto al servidor. * @param o Recibe por parámetro el objeto que desea enviar. * @throws IOException */ private void send ( Object o ) throws IOException { // Serializa el objeto en un byte array. byte [ ] ba; ba = Util.objectToByteArray ( o ); // Crea el paquete para enviar, usando el byte array que acaba de crear. packetToSend = new DatagramPacket ( ba, ba.length, serverIPAddress, serverPort ); // Envía el paquete a través del socket. clientSocket.send ( packetToSend ); } /** * Este método permite recibir un objeto enviado por el servidor. * @throws IOException * @throws ClassNotFoundException */ private Object receive ( ) throws IOException, ClassNotFoundException { // Crea el paquete para recibir un objeto. packetToReceive = new DatagramPacket ( bufferToReceive, bufferToReceive.length ); // Recibe un paquete que llega por el socket. clientSocket.receive ( packetToReceive ); // Deserializa el byte array en un objeto. Object o = Util.byteArrayToObject ( bufferToReceive ); // Retorna el objeto ya deserializado. return o; } /** * Método principal utilizado para lanzar el programa cliente. */ public static void main ( String args [ ] ) { new ObjectUDPClient01 ( "localhost", 3500 ); } }
APLICACIONES DE RED CON SOCKETS UDP
123
ObjectUDPServer01.java
Este programa permite el envío y recibo de objetos entre cliente y servidor. El objeto consiste en un Vector de String que el cliente envía al servidor, el servidor lo recibe y lo imprime en la consola. Finalmente el servidor envía de respuesta al cliente la cadena OK.
/* * Ejemplo 11: ObjectUDPServer01 * * El objetivo de este programa es crear un Vector de String y enviarlo como * objeto al servidor. El servidor lo recibe y envía un mensaje de confirmación * confirmación al cliente. */ import import import import import import
java.io.IOException; java.net.DatagramPacket; java.net.DatagramSocket; java.net.InetAddress; java.net.SocketException; java.util.Vector;
public class ObjectUDPServer01 { private DatagramSocket serverSocket; private InetAddress private int
clientIPAddress; clientPort;
private DatagramPacket packetToSend; private DatagramPacket packetToReceive; private byte [ ]
bufferToReceive;
// Método constructor. public ObjectUDPServer01 ( ) { try { // Inicializa el buffer de recepción. bufferToReceive = new byte [ 1024 ]; System.out.println ( "Run UDP Server..." ); // Crea el socket en el lado servidor. serverSocket = new DatagramSocket ( 3500 ); while ( true ) { // Recibe el objeto Vector < String > names = ( Vector < String > ) receive ( ); System.out.println ( names ); // Crea la respuesta
INTRODUCCIÓN AL DESARROLLO DE APLICACIONES DE RED EN JAVA
124
String answer = new String ( "OK" ); // Envía la respuesta send ( answer ); } } // Puede lanzar una excepción de socket. catch ( SocketException e ) { e.printStackTrace ( ); } // Puede lanzar una excepción de entrada y salida. catch ( IOException e ) { e.printStackTrace ( ); } // Puede lanzar una excepción por una clase no encontrada. catch ( ClassNotFoundException e ) { e.printStackTrace ( ); } } /** * Este método permite enviar un objeto al cliente. * @param o Recibe por parámetro el objeto que desea enviar. * @throws IOException */ private void send ( Object o ) throws IOException { // Serializa el objeto en un byte array. byte [ ] ba = Util.objectToByteArray ( o ); // Crea el paquete para enviar, usando el byte array que acaba de crear. packetToSend = new DatagramPacket ( ba, ba.length, clientIPAddress, clientPort ); // Envía el paquete a través del socket. serverSocket.send ( packetToSend ); } /** * Este método permite recibir un objeto enviado por el cliente. * @throws IOException * @throws ClassNotFoundException */ private Object receive ( ) throws IOException, ClassNotFoundException { // Crea el paquete para recibir packetToReceive = new DatagramPacket ( bufferToReceive, bufferToReceive.length ); // Recibe un paquete que llega por el socket. serverSocket.receive ( packetToReceive ); // Obtiene la dirección IP del cliente clientIPAddress = packetToReceive.getAddress ( ); // Obtiene el número de puerto del cliente clientPort = packetToReceive.getPort ( ); // Deserializa el byte array en un objeto. Object o = Util.byteArrayToObject ( bufferToReceive );
APLICACIONES DE RED CON SOCKETS UDP
125
// Retorna el objeto ya deserializado. return o; } /** * Método principal utilizado para lanzar el programa servidor. */ public static void main ( String args [ ] ) { new ObjectUDPServer01 ( ); } }
Util.java
/** * Ejemplo 11: Clase Util * Esta clase contiene métodos que pueden ser de utilidad para el desarrollo de * diferentes aplicaciones. */ import import import import import
java.io.ByteArrayInputStream; java.io.ByteArrayOutputStream; java.io.IOException; java.io.ObjectInputStream; java.io.ObjectOutputStream;
public class Util { /** * Este método serializa un objeto cualquiera en un arreglo de bytes. Es decir, * pasa de Object a byte [ ]. Es necesario que el objeto que se va a * serializar implemente la interface Serializable. * @param o El objeto que desea serializar * @throws IOException */ public static byte [ ] objectToByteArray ( Object o ) throws IOException { ByteArrayOutputStream bos = new ByteArrayOutputStream ( ); ObjectOutputStream out = new ObjectOutputStream ( bos ) ; out.writeObject ( o ); out.close ( ); byte [ ] buffer = bos.toByteArray ( ); return buffer; } /** * Este método deserializa un objeto. Es decir, pasa de byte [ ] a Object. * @param byteArray El byte [ ] que desea desserializar * @throws IOException * @throws ClassNotFoundException */ public static Object byteArrayToObject ( byte [ ] byteArray ) throws IOException, ClassNotFoundException {
INTRODUCCIÓN AL DESARROLLO DE APLICACIONES DE RED EN JAVA
126
ObjectInputStream in = new ObjectInputStream ( new ByteArrayInputStream ( byteArray ) ); Object o = in.readObject ( ); in.close ( ); return o; } }
Ejecución
Ahora, para probar la aplicación, de nuevo compile y ejecute los dos programas. Recuerde iniciar primero el servidor y luego el cliente. En esta aplicación no hay interacción con el usuario por lo que la aplicación siempre mostrará el mismo resultado. Observe la Figura 30.
Consola del servidor Run UDP Server... [Ana, Bibiana, Carolina, Diana, Elena] Consola del cliente UDP Client: [Ana, Bibiana, Carolina, Diana, Elena] OK
Figura 30. Ejemplo 11 – Salida en consola
5.3 EJEMPLO 12: SERVIDOR Y CLIENTE PARA LA TRANSMISIÓN DE OBJETOS – 2
Los programas cliente y servidor para la transmisión de objetos (definidos por el usuario) cumplen con el siguiente protocolo:
•
El cliente crea objeto de tipo Person el cual corresponde a una clase definida por el usuario.
•
El servidor recibe el objeto.
•
El servidor imprime en su consola el contenido del objeto recibido.
•
El servidor envía como resultado un mensaje "OK".
•
El cliente recibe el resultado y lo imprime en la consola.
APLICACIONES DE RED CON SOCKETS UDP
127
ObjectUDPClient02.java
Este programa permite el envío y recibo de objetos entre cliente y servidor. El objeto es un conjunto de campos asociados en una clase Person que el cliente envía al servidor, el servidor lo recibe y lo imprime en la consola. Finalmente el servidor envía de respuesta al cliente la cadena OK.
/* * Ejemplo 12: * * El objetivo * enviarlo al * servidor lo */ import import import import import import
ObjectUDPClient02 de este programa es crear un objeto especificado por el usuario y servidor. En este caso se trata de un objeto de tipo Person. El recibe y envía un mensaje de confirmación al cliente.
java.io.IOException; java.net.DatagramPacket; java.net.DatagramSocket; java.net.InetAddress; java.net.SocketException; java.net.UnknownHostException;
public class ObjectUDPClient02 { private DatagramSocket clientSocket; private InetAddress private int
serverIPAddress; serverPort;
private DatagramPacket packetToSend; private DatagramPacket packetToReceive; private byte [ ]
bufferToReceive;
// Método constructor. public ObjectUDPClient02 ( String address, int serverPort ) { // Inicializa el buffer de recepción. bufferToReceive = new byte [ 1024 ]; System.out.println ( "UDP Client: " ); try { // Crea el socket en el lado cliente. clientSocket = new DatagramSocket ( ); // Inicializa la dirección IP del servidor. serverIPAddress = InetAddress.getByName ( address ); // Inicializa el número de puerto en el servidor. this.serverPort = serverPort; // Crea el objeto que se desea enviar. Person person = new Person ( "Martin", 7, 1.32 );
INTRODUCCIÓN AL DESARROLLO DE APLICACIONES DE RED EN JAVA
128
System.out.println ( person ); // Envía el objeto. send ( person ); // Recibe la respuesta que viene del servidor. String answer = ( String ) receive ( ); System.out.println ( answer ); // Cierra el socket. clientSocket.close ( ); } // Puede lanzar una excepción de socket. catch ( SocketException e ) { e.printStackTrace ( ); } // Puede lanzar una excepción de host desconocido. catch ( UnknownHostException e ) { e.printStackTrace ( ); } // Puede lanzar una excepción de entrada y salida. catch ( IOException e ) { e.printStackTrace ( ); } // Puede lanzar una excepción por una clase no encontrada. catch ( ClassNotFoundException e ) { e.printStackTrace ( ); } } /** * Este método permite enviar un objeto al servidor. * @param o Recibe por parámetro el objeto que desea enviar. * @throws IOException */ private void send ( Object o ) throws IOException { // Serializa el objeto en un byte array. byte [ ] ba; ba = Util.objectToByteArray ( o ); // Crea el paquete para enviar, usando el byte array que acaba de crear. packetToSend = new DatagramPacket ( ba, ba.length, serverIPAddress, serverPort ); // Envía el paquete a través del socket. clientSocket.send ( packetToSend ); } /** * Este método permite recibir un objeto enviado por el servidor. * @throws IOException * @throws ClassNotFoundException */ private Object receive ( ) throws IOException, ClassNotFoundException { // Crea el paquete para recibir un objeto.
APLICACIONES DE RED CON SOCKETS UDP
129
packetToReceive = new DatagramPacket ( bufferToReceive, bufferToReceive.length ); // Recibe un paquete que llega por el socket. clientSocket.receive ( packetToReceive ); // Deserializa el byte array en un objeto. Object o = Util.byteArrayToObject ( bufferToReceive ); // Retorna el objeto ya deserializado. return o; } /** * Método principal utilizado para lanzar el programa cliente. */ public static void main ( String args [ ] ) { new ObjectUDPClient02 ( "localhost", 3500 ); } }
ObjectUDPServer02.java
Este programa permite el envío y recibo de objetos entre cliente y servidor. El objeto consiste en un Vector de String que el cliente envía al servidor, el servidor lo recibe y lo imprime en la consola. Finalmente el servidor envía de respuesta al cliente la cadena OK.
/* * Ejemplo 12: * * El objetivo * enviarlo al * servidor lo */ import import import import import
ObjectUDPServer02 de este programa es crear un objeto especificado por el usuario y servidor. En este caso se trata de un objeto de tipo Person. El recibe y envía un mensaje de confirmación al cliente.
java.io.IOException; java.net.DatagramPacket; java.net.DatagramSocket; java.net.InetAddress; java.net.SocketException;
public class ObjectUDPServer02 { private DatagramSocket serverSocket; private InetAddress private int
clientIPAddress; clientPort;
private DatagramPacket packetToSend; private DatagramPacket packetToReceive; private byte [ ]
bufferToReceive;
INTRODUCCIÓN AL DESARROLLO DE APLICACIONES DE RED EN JAVA
130
// Método constructor. public ObjectUDPServer02 ( ) { try { // Inicializa el buffer de recepción. bufferToReceive = new byte [ 1024 ]; System.out.println ( "Run UDP Server..." ); // Crea el socket en el lado servidor. serverSocket = new DatagramSocket ( 3500 ); while ( true ) { // Crea el paquete para recibir packetToReceive = new DatagramPacket ( bufferToReceive, bufferToReceive.length ); // Recibe el objeto Person person = ( Person ) receive ( ); System.out.println ( person ); // Crea la respuesta String answer = new String ( "OK" ); // Obtiene la dirección IP del cliente clientIPAddress = packetToReceive.getAddress ( ); // Obtiene el número de puerto del cliente clientPort = packetToReceive.getPort ( ); // Envía la respuesta send ( answer ); } } // Puede lanzar una excepción de socket. catch ( SocketException e ) { e.printStackTrace ( ); } // Puede lanzar una excepción de entrada y salida. catch ( IOException e ) { e.printStackTrace ( ); } // Puede lanzar una excepción por una clase no encontrada. catch ( ClassNotFoundException e ) { e.printStackTrace ( ); } } /** * Este método permite enviar un objeto al cliente. * @param o Recibe por parámetro el objeto que desea enviar. * @throws IOException */ private void send ( Object o ) throws IOException {
APLICACIONES DE RED CON SOCKETS UDP
131
// Serializa el objeto en un byte array. byte [ ] ba = Util.objectToByteArray ( o ); // Crea el paquete para enviar, usando el byte array que acaba de crear. packetToSend = new DatagramPacket ( ba, ba.length, clientIPAddress, clientPort ); // Envía el paquete a través del socket. serverSocket.send ( packetToSend ); } /** * Este método permite recibir un objeto enviado por el cliente. * @throws IOException * @throws ClassNotFoundException */ private Object receive ( ) throws IOException, ClassNotFoundException { // Crea el paquete para recibir packetToReceive = new DatagramPacket ( bufferToReceive, bufferToReceive.length ); // Recibe un paquete que llega por el socket. serverSocket.receive ( packetToReceive ); // Obtiene la dirección IP del cliente clientIPAddress = packetToReceive.getAddress ( ); // Obtiene el número de puerto del cliente clientPort = packetToReceive.getPort ( ); // Deserializa el byte array en un objeto. Object o = Util.byteArrayToObject ( bufferToReceive ); // Retorna el objeto ya deserializado. return o; } /** * Método principal utilizado para lanzar el programa servidor. */ public static void main ( String args [ ] ) { new ObjectUDPServer02 ( ); } }
Person.java y Util.java
Para no duplicar código, encuentre la clase Person.java en el ejemplo 7, Sección 3.4 y la clase Util.java en el ejemplo 11, Sección 5.2.
INTRODUCCIÓN AL DESARROLLO DE APLICACIONES DE RED EN JAVA
132
Ejecución
Ahora, para probar la aplicación, de nuevo compile y ejecute los dos programas. Recuerde iniciar primero el servidor y luego el cliente. En esta aplicación no hay interacción con el usuario por lo que la aplicación siempre mostrará el mismo resultado. Observe la Figura 31.
Consola del servidor Run UDP Server... Martin: 7 - 1.32 Consola del cliente UDP Client: Martin: 7 - 1.32 OK
Figura 31. Ejemplo 12 – Salida en consola
5.4 EJEMPLO 13: SERVIDOR MULTIHILO Y CLIENTE PARA UN CHAT SENCILLO
El objetivo de este programa es la creación de un chat para usuarios que están ubicados en computadores distintos de tal manera que puedan enviarse mensajes entre todos utilizando el protocolo UDP, es decir, las aplicaciones cliente no permanecen conectadas con el servidor. Todo mensaje enviado por un cliente es recibido por el servidor quien reenvía este mensaje a todos los usuarios del chat. En este caso, tanto el cliente como el servidor manejan dos hilos distintos.
Al iniciar, el cliente envía un mensaje de texto “NAME:” seguido del nombre del usuario. El servidor recibe el mensaje, extrae el nombre del usuario, la dirección IP del cliente y el número de puerto en el cliente y almacena esta información en un objeto de tipo User. Con los datos de cada usuario crea una lista que va a utilizar cada vez que llega un mensaje de conversación. La clase User también hace parte de este programa.
APLICACIONES DE RED CON SOCKETS UDP
133
Cuando un usuario va a enviar un mensaje de conversación, cada mensaje inicia con el prefijo “MSG:” y a continuación va el mensaje que el usuario va a enviar a todos los usuarios del chat. El servidor recibe el mensaje, elimina la parte inicial y envía el mensaje a todos los usuarios. Cada usuario recibe el mensaje y lo imprime en la consola.
En la interfaz de usuario (en el cliente), se utilizan un hilo (thread) adicional utilizado para recibir la respuesta, mientras que el hilo principal se encarga de los mensajes salientes del usuario. En el servidor, también se utilizan dos hilos, el hilo principal recibe los mensajes y el hilo alterno es encargado de procesar cada mensaje recibido.
ChatUDPClient.java
El objetivo de esta clase es crear un programa cliente del chat UDP que pueda enviar y recibir mensajes de otros usuarios a través de un servidor.
/* * Ejemplo 13: ChatUDPClient * * El objetivo de este programa es crear un chat para permitir a los clientes * chatear enviándose mensajes a través del servidor. */ import import import import import import
java.io.IOException; java.net.DatagramPacket; java.net.DatagramSocket; java.net.InetAddress; java.net.SocketException; java.net.UnknownHostException;
import javax.swing.JOptionPane; // Esta clase implementa la interface Runnable para permitir el uso de threads. // El thread principal es usado para la ejecución del cliente y la salida de // mensajes, mientras que un nuevo thread realiza todas las operaciones // necesarias con los mensajes que le llegan al cliente. public class ChatUDPClient implements Runnable { public static final int PORT = 6800; public static final String SERVER_LOCATION = "localhost"; private DatagramSocket clientSocket; private InetAddress
serverIPAddress;
private DatagramPacket packetToSend;
INTRODUCCIÓN AL DESARROLLO DE APLICACIONES DE RED EN JAVA
134
private DatagramPacket packetToReceive; private byte [ ]
bufferToReceive;
// Método constructor. public ChatUDPClient ( ) { // Espacio de memoria utilizado para recibir datos. bufferToReceive = new byte [ 1024 ]; System.out.println ( "UDP Client: " ); try { // Creación del socket de datagrama en el lado cliente. Observe que en // este caso no es necesario identificar el servidor en el momento de // crear el socket. clientSocket = new DatagramSocket ( ); // Identificación del servidor serverIPAddress = InetAddress.getByName ( SERVER_LOCATION ); String userName = JOptionPane.showInputDialog ( null, "Ingrese su nombre:" ); // Envío del mensaje al servidor. send ( "NAME:" + userName ); Thread thread = new Thread ( this ); thread.start ( ); while ( true ) { String message = JOptionPane.showInputDialog ( null, "Ingrese un mensaje:" ); // Envío del mensaje al servidor. send ( "MSG:" + userName + " dice " + message ); } } // Puede lanzar una excepción de socket. catch ( SocketException e ) { e.printStackTrace ( ); } // Puede lanzar una excepción de host desconocido. catch ( UnknownHostException e ) { e.printStackTrace ( ); } // Puede lanzar una excepción de entrada y salida. catch ( IOException e ) { e.printStackTrace ( ); } } public void run ( ) { while ( true ) { // Recepción del mensaje de respuesta enviado por el servidor.
APLICACIONES DE RED CON SOCKETS UDP
135
String response; try { response = ( String ) receive ( ); System.out.println ( response ); } // Puede lanzar una excepción de entrada y salida. catch ( IOException e ) { e.printStackTrace( ) ; } } } /** * Este método permite enviar un mensaje al servidor. * @param messageToSend Recibe por parámetro el mensaje que desea enviar. * @throws IOException */ private void send ( String messageToSend ) throws IOException { // Convierte el String en un byte [ ] byte [ ] ba = messageToSend.getBytes ( ); // Crea el paquete a enviar con el mensaje en forme de byte [ ], la // dirección IP del servidor y el número de puerto. packetToSend = new DatagramPacket ( ba, ba.length, serverIPAddress, PORT ); // Envía el paquete por el socket. clientSocket.send ( packetToSend ); } /** * Este método permite recibir un mensaje enviado por el servidor. * @throws IOException */ private String receive ( ) throws IOException { // Crea el paquete para recibir el mensaje de respuesta del servidor. // Observe que solo tiene el buffer para recibir. packetToReceive = new DatagramPacket ( bufferToReceive, bufferToReceive.length ); // Recibe el paquete enviado por el servidor. clientSocket.receive ( packetToReceive ); // Convierte a String el contenido del campo de datos del paquete recibido. String receivedMessage = new String ( packetToReceive.getData ( ), 0, packetToReceive.getLength ( ) ); return receivedMessage; } /** * Método principal utilizado para lanzar el programa cliente. */ public static void main ( String args [ ] ) { new ChatUDPClient ( ); } }
136
INTRODUCCIÓN AL DESARROLLO DE APLICACIONES DE RED EN JAVA
En el método constructor de la clase ChatUDPClient.java se lee el nombre del usuario y se envía al servidor, utilizando el protocolo establecido, es decir, el mensaje es el texto “NAME:” seguido del nombre del usuario.
String userName = JOptionPane.showInputDialog ( null, "Ingrese su nombre:" ); // Envío del mensaje al servidor. send ( "NAME:" + userName );
A continuación se crea e inicia la ejecución del hilo. Luego, dentro de un ciclo infinito se solicita al usuario que ingrese un mensaje en una ventana emergente y se envía el mensaje al servidor.
Thread thread = new Thread ( this ); thread.start ( );
Al crear el objeto Thread se está especificando la clase que contiene el método run ( ), es decir, “esta clase” (this) contiene el método run ( ). Al iniciar el hilo, se ejecuta el método run ( ). Observe que en el caso del cliente no hay variables compartidas que puedan presentar conflicto, por lo tanto el hilo puede ser manejado en la misma instancia.
En el método run ( ) se leen los mensajes que llegan, dentro de un ciclo infinito. Cada mensaje que llega se imprime en la consola. Dentro de este ejemplo, no se ha especificado ningún mecanismo para salir del chat, por lo que se propone como ejercicio.
Los métodos send ( ) y receive ( ), son exactamente iguales a los de la clase EchoUDPClient.java, en el ejemplo 10.
Para ejecutar el cliente del chat UDP, es necesario el método main ( ).
APLICACIONES DE RED CON SOCKETS UDP
137
/** * Método principal utilizado para lanzar el programa cliente. */ public static void main ( String args [ ] ) { new ChatUDPClient ( ); }
ChatUDPServer.java
El objetivo de esta clase es crear un programa servidor del chat UDP que pueda servir de intermediario entre los usuarios del chat.
/* * Ejemplo 13: ChatUDPServer * * El objetivo de este programa es crear un chat para permitir a los clientes * chatear enviándose mensajes a través del servidor. */ import import import import import import
java.io.IOException; java.net.DatagramPacket; java.net.DatagramSocket; java.net.InetAddress; java.net.SocketException; java.util.Vector;
// Esta clase implementa la interface Runnable para permitir el uso de threads. // El thread principal es usado para la ejecución del servidor, mientras que un // nuevo thread realiza todas las operaciones necesarias con los mensajes que le // llegan al servidor. public class ChatUDPServer implements Runnable, Cloneable { public static final int PORT = 6800; private DatagramSocket serverSocket; private InetAddress private int
clientIPAddress; clientPort;
private DatagramPacket packetToSend; private DatagramPacket packetToReceive; private byte [ ] bufferToReceive; private static Vector < User > users; private String input; // Método constructor. public ChatUDPServer ( ) { users = new Vector < User > ( ); System.out.println ( "Run UDP Server..." );
INTRODUCCIÓN AL DESARROLLO DE APLICACIONES DE RED EN JAVA
138
try { // Espacio de memoria utilizado para recibir datos. bufferToReceive = new byte [ 1024 ]; // Creación del socket de datagrama en el lado servidor. Observe que // especifica el número de puerto en el cual está escuchando. serverSocket = new DatagramSocket ( PORT ); // Ciclo infinito que permite al servidor atender varios clientes, uno a // la vez. El servidor va a prestar un mismo servicio a cada cliente. while ( true ) { // Recepción del mensaje de respuesta enviado por un cliente. input = ( String ) receive ( ); ChatUDPServer server = ( ChatUDPServer ) clone ( ); // Creación del thread que atiende el mensaje que acaba de llegar. Thread thread = new Thread ( server ); thread.start ( ); } } // Puede lanzar una excepción de socket. catch ( SocketException e ) { e.printStackTrace ( ); } // Puede lanzar una excepción de entrada y salida. catch ( IOException e ) { e.printStackTrace ( ); } catch ( CloneNotSupportedException e ) { e.printStackTrace ( ); } } // Método run. Este método se ejecuta cuando se inicia la ejecución del thread. public void run ( ) { try { // Se crea un String con el mismo contenido de la entrada. String message = new String ( input ); // Si el mensaje incicia con NAME, se trata del mensaje que trae el // nombre del usuario del chat. if ( message.startsWith ( "NAME" ) ) { // Se obtiene el nombre del usuario, el cual se almacena en la segunda // posición del array luego de dividir el mensaje de entrada donde // esté el caracter ":". String [ ] array = message.split ( ":" ); String name = array [ 1 ]; // Se crea un objeto de tipo User con el nombre del usuario, la // dirección IP y el número de puerto. User newUser = new User ( name, clientIPAddress, clientPort ); // Se adiciona el nuevo usuario al vector de usuarios. users.add ( newUser );
APLICACIONES DE RED CON SOCKETS UDP
139
// Se envía un mensaje de bienvenida al nuevo usuario. send ( "Bienvenido!!", clientIPAddress, clientPort ); System.out.println ( users ); } else { // Si el mensaje no inicia con "NAME", pero inicia con "MSG" if ( message.startsWith ( "MSG" ) ) { // Se obtiene el mensaje que está enviando el usuario del chat. // Se obtiene el texto como la subcadena a partir de la posición 4 // del mensaje recibido, debido a que se desea que el mensaje que // se reenvía a los usuarios del chat no tenga el prefijo MSG: String text = message.substring ( 4, message.length ( ) ); sendAll ( text ); } } } // Puede lanzar una excepción de entrada y salida. catch ( IOException e ) { e.printStackTrace ( ); } } /** * Este método envía el texto recibido por parámetro a todos los usuarios * del chat. * @param text Recibe por parámetro el mensaje que desea enviar. * @throws IOException */ private void sendAll ( String text ) throws IOException { // Se envía el mensaje que acaba de llegar a todos los usuarios del // chat, incluyendo a quien lo envió. for ( int i = 0; i < users.size ( ); i++ ) { // Se recupera la información de un usuario del chat. User temp = users.get( i ); InetAddress destinationAddress = temp.getAddress ( ); int destinationPort = temp.getPort ( ); // Se envía el texto del mensaje a cada destinatario. send ( text, destinationAddress, destinationPort ); // Se imprime el mensaje en la consola del servidor. System.out.println ( "Enviado a: " + temp.getName ( ) + "> " + text ); } } /** * Este método permite enviar un mensaje al servidor. * @param messageToSend Recibe por parámetro el mensaje que desea enviar. * @param destinationAddress Recibe por parámetro la dirección IP del host del * usuario donde se ejecuta el chat. * @param destinationPort Recibe por parámetro el número de puerto donde el * host del usuario está en ejecución. * @throws IOException
INTRODUCCIÓN AL DESARROLLO DE APLICACIONES DE RED EN JAVA
140
*/ private void send ( String messageToSend, InetAddress destinationAddress, int destinationPort ) throws IOException { byte [ ] ba = messageToSend.getBytes ( ); packetToSend = new DatagramPacket ( ba, ba.length, destinationAddress, destinationPort ); serverSocket.send ( packetToSend ); } /* * Este método permite recibir un mensaje enviado por el cliente. */ private String receive ( ) throws IOException { packetToReceive = new DatagramPacket ( bufferToReceive, bufferToReceive.length ); serverSocket.receive ( packetToReceive ); clientIPAddress = packetToReceive.getAddress ( ); clientPort = packetToReceive.getPort ( ); String receivedMessage = new String ( packetToReceive.getData ( ), 0, packetToReceive.getLength ( ) ); return receivedMessage; } /** * Método principal utilizado para lanzar el programa servidor. */ public static void main ( String args [ ] ) { new ChatUDPServer ( ); } }
En el método constructor de la clase ChatUDPServer.java se recibe un mensaje enviado por un cliente, luego se clona la instancia del servidor y se crea el hilo al que se le pasa por parámetro el clon del servidor. A continuación se inicia su ejecución.
input = ( String ) receive ( ); ChatUDPServer server = ( ChatUDPServer ) clone ( ); // Creación del thread que atiende el mensaje que acaba de llegar. Thread thread = new Thread ( server ); thread.start ( );
APLICACIONES DE RED CON SOCKETS UDP
141
En el método run ( ) se procesa cada mensaje recibido. Primero, se determina el tipo de mensaje, es decir, si el mensaje incluye el nombre de un usuario del chat o si se trata de un mensaje de conversación.
Si es un mensaje que lleva el nombre del usuario, se obtiene el nombre, la dirección IP y el número de puerto y con esta información se crea un objeto de tipo User y lo adiciona a la lista de usuarios. Luego el servidor envía un mensaje de bienvenida al usuario. Es de anotar que el servidor almacena la información de todos los usuarios que están en el chat en una lista de objetos de tipo User y que la dirección y número de puerto se obtienen al momento de leer cada uno de los mensajes recibidos.
String [ ] array = message.split ( ":" ); String name = array [ 1 ]; User newUser = new User ( name, clientIPAddress, clientPort ); users.add ( newUser ); send ( "Bienvenido!!", clientIPAddress, clientPort
);
System.out.println ( users );
Si se trata de un mensaje de conversación, el mensaje debe ser enviado a todos los usuarios del chat, pero es necesario eliminar los primeros cuatro caracteres recibidos, debido a que cada mensaje de conversación recibido inicia con “MSG:”. Para enviar el mensaje a todos los usuarios del chat, es necesario obtener de la lista de usuarios la dirección IP y el número de puerto de cada uno de ellos.
String text = message.substring ( 4, message.length ( ) ); for ( int i = 0; i < users.size ( ); i++ ) { User temp = users.get( i ); InetAddress destinationAddress = temp.getAddress ( ); int destinationPort = temp.getPort ( ); send ( text, destinationAddress, destinationPort
);
System.out.println ( "Enviado a: " + temp.getName ( ) + " " + text ) : }
INTRODUCCIÓN AL DESARROLLO DE APLICACIONES DE RED EN JAVA
142
Los métodos send ( ) y receive ( ), son exactamente iguales a los de la clase EchoUDPServer.java, en el ejemplo 10.
Para ejecutar el servidor del chat UDP, es necesario el método main ( ).
/** * Método principal utilizado para lanzar el programa servidor. */ public static void main ( String args [ ] ) { new ChatUDPServer ( ); }
User.java
La clase User.java se utiliza para agrupar los datos de un usuario que son relevantes para el funcionamiento del chat.
/* * Ejemplo 13: User * * Clase User * Esta clase corresponde a un tipo de datos que representa usuarios del chat. * Encapsula el nombre, la dirección IP y el número de puerto en el que está * escuchando un usuario del chat. */ import java.io.Serializable; import java.net.InetAddress; public class User implements Serializable { // Observe que este tipo de clases permiten crear objetos que serán // transportados por la red, deben implementar la interface Serializable. String name; private InetAddress address; private int port; // metodo constructor. Recibe el nombre, la dirección IP y el número de puerto // de un usuario del chat. public User ( String name, InetAddress address, int port ) { this.name = name; this.address = address; this.port = port; } /* * Este método retorna el nombre del usuario del chat. */ public String getName ( )
APLICACIONES DE RED CON SOCKETS UDP
143
{ return name; } /* * Este método retorna la dirección IP del host donde se ejecuta * el chat. */ public InetAddress getAddress ( ) { return address; } /* * Este método retorna el número de puerto donde está escuchando el chat. */ public int getPort ( ) { return port; } /* * Este método especifica la forma de imprimir el valor del objeto. En este * caso, retorna un String con el nombre del usuario. */ public String toString ( ) { return name; } }
5.5 EJERCICIOS
1. Escriba un programa cliente-servidor con datagramas UDP que cumpla el siguiente protocolo:
• El usuario ingresa una fecha inicial. La fecha es almacenada en un objeto de tipo Fecha, el cual tiene los campos dia, mes y año. • El usuario ingresa una fecha final. La fecha es almacenada en un objeto de tipo Fecha. • El cliente envía los dos objetos de tipo Fecha al servidor. • El servidor contesta: o ERROR, si la fecha final es anterior a la fecha inicial. o La diferencia medida en dias, meses y años, en caso contrario. o Esta diferencia también se envía en un objeto Fecha. Para el cálculo de la diferencia, considere meses los meses con su duración habitual y los años bisiestos.
INTRODUCCIÓN AL DESARROLLO DE APLICACIONES DE RED EN JAVA
144
2. Escriba el contenido de la clase Hora, la cual permite representar una marca de tiempo especificada en horas y minutos, de acuerdo con las siguientes consideraciones: • • •
El método constructor debe recibir los valores necesarios para especificar una marca de tiempo. Deben especificarse los métodos getters y el método toString ( ) para mostrar la hora en formato hh:mm. Escriba el método main ( ) de manera que sea posible escribir dos marcas de tiempo distintas, especificadas por el usuario a través de ventanas emergentes.
Escriba una clase llamada Duración la cual tiene como atributos dos objetos de tipo Hora, de acuerdo con las siguientes consideraciones: • •
•
El método constructor recibe por parámetro los dos objetos de tipo Hora. Debe escribir el método getTiempo ( ) el cual retorna la diferencia entre la segunda hora y la primera. Por ejemplo, suponga que la primera hora es: 9:19 y la segunda hora es: 10:23. En este caso la diferencia es 1:04 (una hora, cuatro minutos). Escriba el método main ( ) necesario para probar esta clase.
3. Desarrolle una aplicación cliente-servidor con datagramas UDP para la gestión de libros en una biblioteca con los siguientes requerimientos:
• • • • •
Ingreso de libros. Préstamo de libros. Devolución de libros. Listado de libros disponibles. Listado de libros prestados.
Consideraciones a tener en cuenta en el programa cliente:
• •
Para el ingreso de libros, el cliente debe enviar un mensaje con la siguiente sintaxis: INGRESO seguido de un espacio en blanco seguido del nombre del libro. Para el préstamo de libros, el cliente debe enviar un mensaje con la siguiente sintaxis: PRESTAMO seguido de un espacio en blanco seguido del nombre del libro.
APLICACIONES DE RED CON SOCKETS UDP
•
• •
145
Para la devolución de libros, el cliente debe enviar un mensaje con la siguiente sintaxis: DEVOLUCION seguido de un espacio en blanco seguido del nombre del libro. Para solicitar el listado de libros disponibles, el cliente debe enviar un mensaje: DISPONIBLES. Para solicitar el listado de libros prestados, el cliente debe enviar un mensaje: PRESTADOS.
Si lo desea, no es necesario construir una GUI, puede manejar la entrada por ventanas emergentes y el cliente puede ejecutarse para un comando a la vez.
Consideraciones a tener en cuenta en el programa servidor:
•
•
•
•
•
Para el ingreso de libros, el servidor debe recibir y procesar el mensaje para obtener el nombre del libro y luego almacenarlo en un vector de libros existentes. Debe responderle al cliente con un mensaje OK. Para el préstamo de libros, el servidor debe recibir y procesar el mensaje para obtener el nombre del libro y luego eliminarlo del vector de libros existentes y almacenarlo en el vector de libros prestados. Debe responderle al cliente con un mensaje OK. Para la devolución de libros, el servidor debe recibir y procesar el mensaje para obtener el nombre del libro y luego eliminarlo del vector de libros prestados y almacenarlo en el vector de libros existentes. Debe responderle al cliente con un mensaje OK. Para solicitar el listado de libros disponibles, el servidor debe verificar el mensaje y contestar con un mensaje en el que envía el resultado del método toString ( ) del vector de libros disponibles. Para solicitar el listado de libros prestados, el servidor debe verificar el mensaje y contestar con un mensaje en el que envía el resultado del método toString ( ) del vector de libros prestados.
4. Desarrolle una aplicación cliente-servidor con datagramas UDP para la gestión de frases célebres con los siguientes requerimientos:
•
El cliente puede enviar una frase célebre al servidor. En este caso, debe enviar un texto que inicia con el prefijo PUT: seguido de la frase. El servidor debe recibir el mensaje, retirar el prefijo y almacenar la frase en una lista.
•
El cliente puede obtener una frase célebre del servidor, enviando un mensaje con el texto GET. El servidor debe calcular un número aleatorio entre 0 y el
INTRODUCCIÓN AL DESARROLLO DE APLICACIONES DE RED EN JAVA
146
número de frases almacenadas. A continuación debe enviar la frase que esté almacenada en esa posición en la lista. Si el servidor no tiene almacenada ninguna frase célebre, el servidor enviará un mensaje ERROR, NO HAY NINGUNA FRASE CÉLEBRE DISPONIBLE.
5. Desarrolle una aplicación cliente-servidor con datagramas UDP para obtener la fecha actual desde el servidor.
6. Tome como punto de partida el chat del ejemplo 13. Incluya en la aplicación los siguientes requerimientos:
•
Si un usuario del chat envía la palabra EXIT en uno de sus mensajes, el cliente debe terminar su ejecución y el servidor debe eliminarlo de la lista de usuarios.
•
El servidor debe validar que dos usuarios no tengan el mismo login.
•
Un usuario puede consultar la cantidad de usuarios del chat, en este caso envía un mensaje con el texto GET COUNT al servidor. El servidor le debe responder únicamente a quien hizo la consulta.
PROYECTO TRANSACTIONMANAGER
147
6. PROYECTO TRANSACTIONMANAGER El objetivo este proyecto es crear una aplicación cliente/servidor para el manejo de transacciones bajo el protocolo UDP. Se trata de comercializadora en la cual se pueden abrir cuentas, realizar depósitos y retiros, pagos por compras y consultar la cuenta de un usuario y de la comercializadora. Este proyecto pretende ilustrar la comunicación cliente–servidor a través de datagramas, sin considerar la confiabilidad que podría ser necesaria en este tipo de aplicaciones. El envío de mensajes entre cliente y servidor se hace utilizando cadenas de texto que son interpretadas en el destino.
La Figura 32 presenta la colaboración entre las clases que conforman el proyecto TransactionManager.
Figura 32. Proyecto TransactionManager – Diagrama de colaboración
La apariencia del cliente se puede apreciar en la Figura 33.
INTRODUCCIÓN AL DESARROLLO DE APLICACIONES DE RED EN JAVA
148
Figura 33. Proyecto TransactionManager – Interfaz gráfica de usuario
Para la realización de este proyecto se necesitan el programa cliente y el programa servidor, suministrados a continuación.
6.1 PASO 1: APLICACIÓN CLIENTE Escriba el encabezado de la clase TransactionManagerClient.java. Esta clase corresponde a una GUI, por lo que extiende de JFrame.
public class TransactionManagerClient extends JFrame {
Determine los componentes gráficos de la GUI. No olvide importar los paquetes correspondientes a cada componente.
private private private private private private private private private private private private private private private
JLabel JLabel JLabel JLabel JTextField JTextField JLabel JButton JButton JButton JButton JButton JButton Font Font
titulo; labelId; labelValor; labelResultado; textId; textValor; resultado; buttonAbrir; buttonPagar; buttonConsultar; buttonDepositar; buttonRetirar; buttonSaldo; fuente; fuenteResultado;
PROYECTO TRANSACTIONMANAGER
149
La Figura 34 muestra los nombres de los componentes gráficos para que se vaya familiarizando con ellos.
Figura 34. Proyecto TransactionManager – Componentes gráficos de la GUI
Escriba el método constructor de la clase TransactionManagerClient. En este método se están asignando las propiedades a los componentes gráficos. No olvide importar los paquetes correspondientes a cada componente.
public TransactionManagerClient ( ) { fuente = new Font ( "Times new Roman", Font.BOLD, 16 ); fuenteResultado = new Font ( "Times new Roman", Font.PLAIN+Font.ITALIC, 12 ); titulo = new JLabel ( ); labelId = new JLabel ( ); labelValor = new JLabel ( ); labelResultado = new JLabel ( ); textId = new JTextField ( ); textValor = new JTextField ( ); resultado = new JLabel ( ); buttonAbrir = new JButton ( ); buttonPagar = new JButton ( ); buttonConsultar = new JButton ( ); buttonDepositar = new JButton ( ); buttonRetirar = new JButton ( ); buttonSaldo = new JButton ( ); setLayout ( null ); titulo.setText labelId.setText labelValor.setText labelResultado.setText titulo.setBounds
( ( ( (
"Transferencia electrónica de fondos" ); "Id: " ); "Valor: " ); "Resultado:" ); (
5,
10, 310, 20 );
INTRODUCCIÓN AL DESARROLLO DE APLICACIONES DE RED EN JAVA
150
labelId.setBounds textId.setBounds labelValor.setBounds textValor.setBounds labelResultado.setBounds resultado.setBounds
( ( ( ( ( (
110, 170, 110, 170, 110, 170,
80, 80, 20 ); 80, 145, 20 ); 110, 80, 20 ); 110, 145, 20 ); 140, 80, 20 ); 140, 145, 20 );
resultado.setFont (fuenteResultado); titulo.setFont ( fuente ); titulo.setHorizontalAlignment ( SwingConstants.CENTER ); buttonAbrir.setBounds ( 5, 50, 100, 20 ); buttonAbrir.setText ( "Abrir cuenta" ); buttonPagar.setBounds ( 5, 80, 100, 20 ); buttonPagar.setText ( "Pagar" ); buttonConsultar.setBounds ( 5, 110, 100, 20 ); buttonConsultar.setText ( "Consultar" ); buttonDepositar.setBounds ( 5, 140, 100, 20 ); buttonDepositar.setText ( "Depositar" ); buttonRetirar.setBounds ( 5, 170, 100, 20 ); buttonRetirar.setText ( "Retirar" ); buttonSaldo.setBounds ( 5, 200, 100, 20 ); buttonSaldo.setText ( "Saldo almacén" ); add add add add add add add add add add add add add add
( ( ( ( ( ( ( ( ( ( ( ( ( (
titulo ); labelId ); textId ); labelValor ); textValor ); labelResultado ); resultado ); buttonAbrir ); buttonPagar ); buttonConsultar ); buttonDepositar ); buttonRetirar ); buttonAbrir ); buttonSaldo );
setTitle ( "Cliente - Transferencia electrónica" ); setSize ( 330, 270 ); setLocationRelativeTo ( null ); setVisible ( true ); setDefaultCloseOperation ( WindowConstants.EXIT_ON_CLOSE ); }
Adicione la siguiente línea al encabezado de la clase. Esto indica que serán capturados los eventos de acción del mouse. No olvide importar los paquetes correspondientes a los sensores y eventos de acción.
implements ActionListener
PROYECTO TRANSACTIONMANAGER
151
El siguiente método es obligatorio debido a la implementación de la clase ActionListener.
public void actionPerformed ( ActionEvent event ) {}
Adicione los sensores (listeners) a los botones en el método constructor.
buttonAbrir.addActionListener buttonPagar.addActionListener buttonConsultar.addActionListener buttonDepositar.addActionListener buttonRetirar.addActionListener buttonSaldo.addActionListener
( ( ( ( ( (
this this this this this this
); ); ); ); ); );
Agregue los componentes de conexión como atributos a la clase.
private DatagramSocket clientSocket; private InetAddress private int
serverIPAddress; serverPort;
private DatagramPacket packetToSend; private DatagramPacket packetToReceive; private byte [ ]
bufferToReceive;
A continuación, inicialice el buffer de recepción de datos al inicio del método constructor.
bufferToReceive = new byte [ 1024 ];
Adicione el método send ( ). Este método es usado para enviar los mensajes al servidor. Recibe por parámetro un mensaje de tipo String y no retorna ningún dato.
private void send ( String messageToSend ) throws Exception { // Convierte la cadena en un byte array. byte [ ] ba = messageToSend.getBytes ( ); // Crea el paquete para enviar, usando el byte array que acaba de // crear. packetToSend = new DatagramPacket ( ba, ba.length, serverIPAddress, serverPort );
INTRODUCCIÓN AL DESARROLLO DE APLICACIONES DE RED EN JAVA
152
// Envía el paquete a través del socket. clientSocket.send ( packetToSend ); }
Adicione el método receive ( ). Este método es usado para recibir los mensajes que envía el servidor. No recibe ningún parámetro y retorna le mensaje recibido en forma de cadena de caracteres ( String ).
private String receive ( ) throws Exception { // Crea el paquete para recibir un objeto. packetToReceive = new DatagramPacket ( bufferToReceive, bufferToReceive.length ); // Recibe un paquete que llega por el socket. clientSocket.receive ( packetToReceive ); // Crea el mensaje de respuesta. String receivedMessage = new String ( packetToReceive.getData ( ), 0, packetToReceive.getLength ( ) ); // Retorna el mensaje. return receivedMessage; }
Cree la infraestructura de comunicaciones al final del método constructor. En este caso se están creando los sockets de datagramas y la ubicación del servidor se está realizando hacia el host local, en el puerto 3500.
// comunicacion try { clientSocket = new DatagramSocket ( ); // Inicializa la dirección IP del servidor. serverIPAddress = InetAddress.getByName ( "localhost" ); // Inicializa el número de puerto en el servidor. this.serverPort = 3500; } // Puede lanzar una excepción de socket. catch ( SocketException e ) { e.printStackTrace ( ); } // Puede lanzar una excepción de host desconocido. catch ( UnknownHostException e ) { e.printStackTrace ( ); }
PROYECTO TRANSACTIONMANAGER
153
A continuación, escriba el código del método actionPerformed ( ). En este método se determina cuál fue el botón que el usuario seleccionó y se encuentra también el llamado correspondiente a cada acción.
if ( event.getSource ( ) == buttonAbrir ) { open ( ); } else { if ( event.getSource ( ) == buttonPagar ) { pay ( ); } else { if ( event.getSource ( ) == buttonConsultar ) { ask ( ); } else { if ( event.getSource ( ) == buttonDepositar ) { deposit ( ); } else { if ( event.getSource ( ) == buttonRetirar ) { withdraw ( ); } else { balance ( ); } } } } }
A continuación, cada uno de los métodos que son llamados cuando se presiona un botón en la GUI del cliente. Escriba el código del método open ( ). El cliente envía el comando OPEN seguido del identificador del cliente, el comando VALUE y el valor ingresado en la GUI, todo separado por espacios en blanco. Si la respuesta que envía el servidor inicia con la palabra ERROR, entonces el mensaje de respuesta lo escribe en color rojo, en caso contrario lo escribe en color azul.
INTRODUCCIÓN AL DESARROLLO DE APLICACIONES DE RED EN JAVA
154
private void open ( ) { // enviar mensaje OPEN con los datos para abrir una cuenta try { int id = Integer.parseInt ( textId.getText ( ) ); int value = Integer.parseInt ( textValor.getText ( ) ); this.send ( "OPEN " + id + " VALUE " + value ); // Recibe la respuesta que viene del servidor. String response = ( String ) receive ( ); if ( response.startsWith ( "Error" ) ) { resultado.setForeground ( Color.RED ); } else { resultado.setForeground ( Color.BLUE ); } resultado.setText ( response ); } // Puede lanzar una excepción. catch ( Exception e ) { e.printStackTrace ( ); } }
A continuación, escriba el método pay ( ). El cliente envía el comando PAY seguido del identificador del cliente, el comando VALUE y el valor ingresado en la GUI, todo separado por espacios en blanco.
private void pay ( ) { // enviar mensaje PAY con los datos para hacer un pago try { int id = Integer.parseInt ( textId.getText ( ) ); int value = Integer.parseInt ( textValor.getText ( ) ); this.send ( "PAY " + id + " VALUE " + value ); // Recibe la respuesta que viene del servidor. String response = ( String ) receive ( ); if ( response.startsWith ( "Error" ) ) { resultado.setForeground ( Color.RED ); } else { resultado.setForeground ( Color.BLUE ); } resultado.setText ( response ); }
PROYECTO TRANSACTIONMANAGER
155
// Puede lanzar una excepción. catch ( Exception e ) { e.printStackTrace ( ); } }
Luego, escriba el método ask ( ). El cliente envía el comando ASQ seguido del identificador del cliente separados por un espacio en blanco. Observe que en este caso no se envía un valor de transacción.
private void ask ( ) { // enviar mensaje ASK con los datos para hacer una consulta try { int id = Integer.parseInt ( textId.getText ( ) ); this.send ( "ASK " + id ); // Recibe la respuesta que viene del servidor. String response = ( String ) receive ( ); if ( response.startsWith ( "Error" ) ) { resultado.setForeground ( Color.RED ); } else { resultado.setForeground ( Color.BLUE ); } resultado.setText ( response ); } // Puede lanzar una excepción. catch ( Exception e ) { e.printStackTrace ( ); } }
Escriba el método deposit ( ). El cliente envía el comando DEPOSIT seguido del identificador del cliente, el comando VALUE y el valor ingresado en la GUI, todo separado por espacios en blanco.
private void deposit ( ) { // enviar mensaje DEPOSIT con los datos para hacer un depósito try { int id = Integer.parseInt ( textId.getText ( ) ); int value = Integer.parseInt ( textValor.getText ( ) );
INTRODUCCIÓN AL DESARROLLO DE APLICACIONES DE RED EN JAVA
156
this.send ( "DEPOSIT " + id + " VALUE " + value ); // Recibe la respuesta que viene del servidor. String response = ( String ) receive ( ); if ( response.startsWith ( "Error" ) ) { resultado.setForeground ( Color.RED ); } else { resultado.setForeground ( Color.BLUE ); } resultado.setText ( response ); } // Puede lanzar una excepción. catch ( Exception e ) { e.printStackTrace ( ); } }
Siga con el método withdraw ( ). El cliente envía el comando WITHDRAW seguido del identificador del cliente, el comando VALUE y el valor ingresado en la GUI, todo separado por espacios en blanco.
private void withdraw ( ) { // enviar mensaje WITHDRAW con los datos para hacer un retiro try { int id = Integer.parseInt ( textId.getText ( ) ); int value = Integer.parseInt ( textValor.getText ( ) ); this.send ( "WITHDRAW " + id + " VALUE " + value ); // Recibe la respuesta que viene del servidor. String response = ( String ) receive ( ); if ( response.startsWith ( "Error" ) ) { resultado.setForeground ( Color.RED ); } else { resultado.setForeground ( Color.BLUE ); } resultado.setText ( response ); } // Puede lanzar una excepción. catch ( Exception e ) { e.printStackTrace ( ); } }
PROYECTO TRANSACTIONMANAGER
157
Termine el protocolo con el método balance ( ). El cliente envía únicamente el comando BALANCE.
private void balance ( ) { // enviar mensaje BALANCE try { this.send ( "BALANCE" ); // Recibe la respuesta que viene del servidor. String response = ( String ) receive ( ); resultado.setForeground ( Color.BLUE ); resultado.setText ( response ); } // Puede lanzar una excepción. catch ( Exception e ) { e.printStackTrace ( ); } }
Finalice el programa cliente con el método main ( ).
public static void main ( String [ ] args ) throws Exception { // captura la presentación del sistema operativo UIManager.setLookAndFeel ( UIManager.getSystemLookAndFeelClassName ( ) ); new TransactionManagerClient ( ); }
6.2 PASO 2: APLICACIÓN SERVIDOR
Escriba el encabezado de la clase TransactionManagerServer.java. El servidor no tiene interfaz gráfica de usuario.
Escriba el encabezado de la clase. El servidor no tiene interfaz gráfica de usuario.
public class TransactionManagerServer {
Especifique los atributos de la clase.
INTRODUCCIÓN AL DESARROLLO DE APLICACIONES DE RED EN JAVA
158
private DatagramSocket serverSocket; private InetAddress private int
clientIPAddress; clientPort;
private DatagramPacket packetToSend; private DatagramPacket packetToReceive; private byte [ ]
bufferToReceive;
private Hashtable < Integer, Integer > table; private int storeBalance;
Escriba el método constructor. Este método ha sido creado por partes para facilitar la escritura y entenderlo mejor.
public TransactionManagerServer ( ) { try { // Parte 1 while ( true ) { // Parte 2 } } // Puede lanzar una excepción. catch ( Exception e ) { e.printStackTrace ( ); } }
Ahora escriba el código de la parte 1, donde se inicializan una tabla hash para almacenar los datos de los clientes, el saldo del almacén en cero y el buffer para la recepción de mensajes. También se crea el socket en el cual el programa servidor espera los mensajes de los clientes.
Parte 1: // Inicializa la tabla hash table = new Hashtable < Integer, Integer > ( ); storeBalance = 0; // Inicializa el buffer de recepción.
PROYECTO TRANSACTIONMANAGER
159
bufferToReceive = new byte [ 1024 ]; // Crea el socket en el lado servidor. serverSocket = new DatagramSocket ( 3500 ); System.out.println ( "Run UDP Server..." );
Continúe con el código de la parte 2. En este método, dentro del ciclo infinito del servidor, se recibe el mensaje que envía el cliente y se hace el análisis para determinar el comando que ha sido enviado y poder realizar las acciones pertinentes. Observe que al finalizar la parte 2, se debe incluir la parte 3.
Parte 2: // Recibe el mensaje String message = ( String ) receive ( ); String tokens [ ] = message.split ( " " ); int value = 0; int id = 0; if ( tokens.length > 1 ) { id = Integer.parseInt ( tokens [ 1 ] ); if ( tokens.length > 2 ) { value = Integer.parseInt ( tokens [ 3 ] ); } } // Parte 3
Ahora escriba el código de la parte 3. Esta parte del código es autoexplicativa. En este caso, se determina la acción a seguir de acuerdo con el comando que el cliente envió. Se pasan por parámetros los valores recibidos en el mensaje, después del análisis que se realizó en la parte 2.
Parte 3: if ( message.startsWith ( "OPEN" ) ) { openProcess ( id, value ); } else { if ( message.startsWith ( "PAY" ) ) {
INTRODUCCIÓN AL DESARROLLO DE APLICACIONES DE RED EN JAVA
160
payProcess ( id, value ); } else { if ( message.startsWith ( "ASK" ) ) { askProcess ( id ); } else { if ( message.startsWith ( "DEPOSIT" ) ) { depositProcess ( id, value ); } else { if ( message.startsWith ( "WITHDRAW" ) ) { withdrawProcess ( id, value ); } else { balanceProcess ( ); } } } } }
A continuación, los métodos que procesan los diferentes comandos que han sido enviados por el cliente. Son siete métodos.
Método openProcess ( ). Este método abre una cuenta si no existe. Si la cuenta existe, envía como respuesta un mensaje de error.
private void openProcess ( int id, int value ) throws Exception { String answer = ""; if ( table.containsKey ( id ) == false ) { // adicionar un nuevo elemento a la tabla. table.put ( id, value ); // Crea el mensaje de respuesta answer = "OK."; } else { // error. La cuenta existe y no puede ser creada. // Crea el mensaje de respuesta answer = "Error. La cuenta ya existe."; } // Envía el mensaje .
PROYECTO TRANSACTIONMANAGER
161
send ( answer ); }
Método payProcess ( ). Este método realiza un pago. Debe verificar si la cuenta existe y si tiene saldo suficiente para realizar el retiro correspondiente del saldo del usuario y acreditar ese dinero en la cuenta del almacén. Si algo sale mal, envía como respuesta un mensaje de error.
private void payProcess ( int id, int value ) throws Exception { String answer = ""; if ( table.containsKey ( id ) ) { // se obtiene el saldo de la cuenta Integer saldo = table.get ( id ); if ( saldo >= value ) { saldo -= value; table.put ( id, saldo ); storeBalance += value; // Crea el mensaje de respuesta answer = "OK."; } else { // error. El saldo es insuficiente y no puede ser realizado el // pago. Crea el mensaje de respuesta answer = "Error. Saldo insuficiente."; } } else { // error. La cuenta no existe y no puede ser realizado el pago. // Crea el mensaje de respuesta answer = "Error. Cuenta inexistente."; } // Envía el mensaje .
send ( answer ); }
Método askProcess ( ). Este método consulta el saldo de la cuenta del usuario. Si la cuenta existe, envía como respuesta un mensaje de error.
private void askProcess ( int id ) throws Exception {
INTRODUCCIÓN AL DESARROLLO DE APLICACIONES DE RED EN JAVA
162
String answer = ""; if ( table.containsKey ( id ) ) { // se obtiene el saldo de la cuenta Integer saldo = table.get ( id ); // Crea el mensaje de respuesta answer = "Saldo: " + saldo; } else { // error. La cuenta no existe y no puede ser realizado el pago. // Crea el mensaje de respuesta answer = "Error. Cuenta inexistente."; } // Envía el mensaje . send ( answer ); }
Método depositProcess ( ). Este método permite realizar un depósito en la cuenta del usuario. Si la cuenta no existe, envía como respuesta un mensaje de error.
private void depositProcess ( int id, int value ) throws Exception { String answer = ""; if ( table.containsKey ( id ) ) { // se obtiene el saldo de la cuenta Integer saldo = table.get ( id ); saldo += value; table.put ( id, saldo ); // Crea el mensaje de respuesta answer = "OK."; } else { // error. La cuenta no existe y no puede ser realizado el // deposito. Crea el mensaje de respuesta answer = "Error. Cuenta inexistente."; } // Envía el mensaje . send ( answer ); }
Método withdrawProcess ( ). Este método permite realizar un retiro de la cuenta del usuario. Si la cuenta no existe, envía como respuesta un mensaje de error.
PROYECTO TRANSACTIONMANAGER
163
private void withdrawProcess ( int id, int value ) throws Exception { String answer = ""; if ( table.containsKey ( id ) ) { // se obtiene el saldo de la cuenta Integer saldo = table.get ( id ); if ( saldo >= value ) { saldo -= value; table.put ( id, saldo ); // Crea el mensaje de respuesta answer = "OK."; } else { // error. El saldo es insuficiente y no puede ser realizado el // retiro. Crea el mensaje de respuesta answer = "Error. Saldo insuficiente."; } } else { // error. La cuenta no existe y no puede ser realizado el retiro. // Crea el mensaje de respuesta answer = "Error. Cuenta inexistente."; } // Envía el mensaje. send ( answer ); }
Método balanceProcess ( ). Este método permite consultar el saldo de la cuenta del almacén.
private void balanceProcess ( ) throws Exception { String answer = ""; answer = "Saldo almacén: " + storeBalance; // Envía el mensaje . send ( answer ); }
Continúe con el método send ( ). Este método se utiliza para enviar mensajes de texto (String) del servidor al cliente.
private void send ( String messageToSend ) throws Exception {
INTRODUCCIÓN AL DESARROLLO DE APLICACIONES DE RED EN JAVA
164
// Convierte la cadena en un byte array. byte [ ] ba = messageToSend.getBytes ( ); // Crea el paquete para enviar, usando el byte array que acaba de // crear. packetToSend = new DatagramPacket ( ba, ba.length, clientIPAddress, clientPort ); // Envía el paquete a través del socket. serverSocket.send ( packetToSend ); }
Siga con el método receive ( ). Este método se utiliza par recibir mensajes de texto (String) enviados por el cliente.
private String receive ( ) throws Exception { // Crea el paquete para recibir un objeto. packetToReceive = new DatagramPacket ( bufferToReceive, bufferToReceive.length ); // Recibe un paquete que llega por el socket. serverSocket.receive ( packetToReceive ); // Obtiene la dirección IP del cliente clientIPAddress = packetToReceive.getAddress ( ); // Obtiene el número de puerto del cliente clientPort = packetToReceive.getPort ( ); // Deserializa el byte array en un String. String receivedMessage = new String ( packetToReceive.getData ( ), 0, packetToReceive.getLength ( ) ); // Retorna la cadena. return receivedMessage; }
Finalice con el método main ( ).
public static void main ( String args [ ] ) { new TransactionManagerServer ( ); }
PROYECTO TRANSACTIONMANAGER
165
6.3 EJERCICIOS
1. Responda el siguiente cuestionario acerca del proyecto Transaction Manager. La idea es que lea el código y se familiarice para que pueda hacer los ejercicios propuestos adicionales.
1. ¿Cuál es la función que desempeña el componente gráfico resultado, de tipo JLabel? 2. ¿Cuáles manipulaciones con los tipos de letra se realizan en la interfaz gráfica de usuario del programa cliente? 3. ¿A qué tipo de dato corresponde el identificador del usuario? 4. ¿Cuál es el número de puerto en el cual escucha el servidor? 5. Explique el proceso que realiza el servidor para analizar el mensaje que viene del cliente. 2. En los ejercicios a continuación, tome como punto de partida el proyecto TransactionManager y haga las modificaciones necesarias para cumplir con los requisitos planteados a continuación.
1. Modifique la interfaz gráfica de usuario para que pueda incluir dos campos de texto: servidor y puerto, de tal manera que puedan ser modificadas durante la ejecución del programa cliente sin necesidad de estar cambiando el código del programa. Debe incluir un botón para establecer la conexión. 2. Adicione un botón en la GUI del cliente para enviar un mensaje de consulta del número de transacciones realizadas por el servidor. Esto implica que el servidor deberá llevar esa cuenta para poder responder a la consulta. 3. Adicione un botón en la GUI del cliente para enviar un mensaje de consulta del promedio de los valores de las compras realizadas por los usuarios. Esto implica que el servidor deberá llevar esa cuenta para poder responder a la consulta.
166
INTRODUCCIÓN AL DESARROLLO DE APLICACIONES DE RED EN JAVA
4. Adicione un botón en la GUI del cliente para enviar un mensaje de consulta del número de retiros que no se pueden realizar por saldo insuficiente. Esto implica que el servidor deberá llevar esa cuenta para poder responder a la consulta. 5. Adicione un botón en la GUI del cliente para enviar un mensaje de consulta del mayor valor de un retiro realizado por alguno de los usuarios. Esto implica que el servidor deberá llevar esa cuenta para poder responder a la consulta.
APLICACIONES DE RED CON SOCKETS MULTICAST
167
7. APLICACIONES DE RED CON SOCKETS MULTICAST A diferencia de las aplicaciones que se construyen utilizando el protocolo TCP o el protocolo UDP, donde se escribe código tanto para el cliente como para el servidor, en las aplicaciones multicast una aplicación usualmente es a la vez cliente y servidor. Sin embargo, en los ejemplos a continuación, el primero será exclusivamente receptor de mensajes y el segundo estará orientado hacia la trasmisión de mensajes.
7.1 EJEMPLO 14: RECEPTOR MULTICAST
En este programa se inscribe la aplicación a un grupo multicast utilizando una dirección y número de puerto especificados. Luego escucha un mensaje que haya sido escrito por otra aplicación perteneciente al grupo.
/** * Ejemplo 14: MulticastReceiver * * El objetivo de este programa es crear un receptor de mensajes Multicast. */ import import import import import
java.io.IOException; java.net.DatagramPacket; java.net.InetAddress; java.net.MulticastSocket; java.net.UnknownHostException;
public class MulticastReceiver { public static void main ( String args [ ] ) { System.out.println ( "Multicast Receiver. Waiting messages ..." ); try { // Identificación del grupo multicast. Observe que corresponde a una // dirección IPv4 clase D, la cual puede estar en el rango 224.x.x.x // hasta 239.x.x.x. InetAddress group; group = InetAddress.getByName ( "239.1.2.3" ); // Número de puerto del grupo. int puerto = 7200;
INTRODUCCIÓN AL DESARROLLO DE APLICACIONES DE RED EN JAVA
168
// Creación del socket multicast. MulticastSocket socket = new MulticastSocket ( puerto ); // Unión al grupo. socket.joinGroup ( group ); byte [ ] buffer = new byte [ 1000 ]; // Creación del paquete que va a ser utilizado para recibir datos. DatagramPacket packet = new DatagramPacket ( buffer, buffer.length ); // Recepción de datos enviados por usuarios del grupo. socket.receive ( packet ); // Creación de un mensaje a partir de los datos recibidos. String message = new String ( packet.getData ( ), 0, packet.getLength ( ) ); System.out.println ( "Received: " + message ); // Salida del grupo. socket.leaveGroup ( group ); } // Puede lanzar una excepción de host desconocido. catch ( UnknownHostException e ) { e.printStackTrace ( ); } // Puede lanzar una excepción entrada y salida. catch ( IOException e ) { e.printStackTrace ( ); } } }
A continuación una explicación detallada de algunas instrucciones de esta clase.
InetAddress group = InetAddress.getByName ( "239.1.2.3" );
La clase InetAddress representa una dirección IP. En este caso, se trata de una dirección para un grupo multicast. Un grupo multicast se especifica mediante una dirección IP clase D y un número de puerto estándar UDP.
MulticastSocket socket = new MulticastSocket ( puerto );
APLICACIONES DE RED CON SOCKETS MULTICAST
169
Esta línea de código crea un socket multicast. Un socket multicast es un socket no orientado a conexión, con capacidad para unirse a grupos de múltiples hosts en Internet. Una dirección IP clase D está en el rango 224.0.0.1 a 239.255.255.255.
socket.joinGroup ( group );
Para unirse a un grupo multicast, se utiliza el método joinGroup ( ) del socket y se le pasa como parámetro la dirección IP del grupo.
byte [ ] buffer = new byte [ 1000 ]; DatagramPacket packet = new DatagramPacket ( buffer, buffer.length );
Se debe crear un paquete de tipo DatagramPacket para poder recibir el mensaje. El paquete se recibe en un buffer (byte [ ]), de un tamaño especificado.
socket.receive ( packet ); String mensaje = new String ( packet.getData ( ), 0, packet.getLength ( ) );
El método receive ( ) es usado para recibir el paquete que viene de algún miembro del grupo multicast. Recibe por parámetro el paquete de tipo DatagramPacket. Después de recibir el paquete, extrae los bytes que vienen en el paquete y con ellos construye un String del tamaño apropiado.
socket.leaveGroup ( group );
Para abandonar un grupo multicast, se utiliza el método leaveGroup ( ) del socket y se le pasa como parámetro la dirección IP del grupo.
INTRODUCCIÓN AL DESARROLLO DE APLICACIONES DE RED EN JAVA
170
7.2 EJEMPLO 15: TRANSMISOR MULTICAST
En este programa se inscribe la aplicación a un grupo multicast utilizando una dirección y número de puerto especificados. Luego envía un un mensaje al grupo.
MulticastSender.java
/** * Ejemplo 15: MulticastSerder * * El objetivo de este programa es crear un transmisor de mensajes Multicast. */ import import import import import
java.io.IOException; java.net.DatagramPacket; java.net.InetAddress; java.net.MulticastSocket; java.net.UnknownHostException;
import javax.swing.JOptionPane; public class MulticastSender { public static void main ( String args [ ] ) { System.out.println ( "Multicast Sender." ); try { // Identificación del grupo multicast. InetAddress group = InetAddress.getByName ( "239.1.2.3" ); // Número de puerto del grupo. int port = 7200; // Creación del socket multicast. MulticastSocket socket = new MulticastSocket ( port ); // Unión al grupo. socket.joinGroup ( group ); String message = JOptionPane.showInputDialog ( null, "Input a message:" ); byte[] m = message.getBytes ( ); // Creación del paquete que va a ser utilizado para enviar datos. Observe // que en este caso debe identificar el grupo al que desea enviar los // datos ye especificar el número de puerto. DatagramPacket packet = new DatagramPacket ( m, m.length, group, port ); // Envío del paquete al grupo multicast. socket.send ( packet ); System.out.println ( "Sent: " + message ); // Salida del grupo.
APLICACIONES DE RED CON SOCKETS MULTICAST
171
socket.leaveGroup ( group ); } // Puede lanzar una excepción de host desconocido. catch ( UnknownHostException e ) { e.printStackTrace ( ); } // Puede lanzar una excepción entrada y salida. catch ( IOException e ) { e.printStackTrace ( ); } } }
A continuación una explicación detallada de algunas instrucciones de esta clase.
String message = JOptionPane.showInputDialog ( null, "Input a message :" ); byte [ ] m = message.getBytes ( ); DatagramPacket packet = new DatagramPacket ( m, m.length, group, port );
Se debe crear un paquete de tipo DatagramPacket para poder enviar el mensaje. El paquete tiene un tamaño especificado por la longitud del mensaje que ingresa el usuario en una ventana emergente. El paquete debe llevar la dirección IP del grupo y el número de puerto.
socket.send ( packet );
El método send ( ) es usado para enviar el paquete al grupo multicast. Recibe por parámetro el paquete de tipo DatagramPacket.
Ejecución
Las Figuras 35 y 36 muestran la ejecución de las aplicaciones multicast para el envío y recepción de mensajes.
INTRODUCCIÓN AL DESARROLLO DE APLICACIONES DE RED EN JAVA
172
Figura 35. Ejemplo 15 – Entrada
Consola de MulticastSender Mensaje enviado: mensaje de prueba
Consola de MulticastReceiver Esperando un mensaje. Recibido: mensaje de prueba
Figura 36. Ejemplos 14 y 15 – Salida en consola
PROYECTO MULTICASTCHAT
173
8. PROYECTO MULTICASTCHAT El objetivo de este proyecto es crear un chat para grupos multicast en una interfaz gráfica de usuario. Utiliza la dirección de grupo: 239.1.2.3 y el puerto número 7200. En la interfaz se necesitan dos threads (hilos de ejecución), uno para actualizar permanentemente la GUI y otro para que esté pendiente de los paquetes que llegan por el puerto. Es decir, ninguna de las dos tareas bloque la ejecución del programa mientras se realiza.
La Figura 37 presenta la colaboración entre las clases que conforman el proyecto MulticastChat.
Figura 37. Proyecto MulticastChat – Diagrama de colaboración
Al iniciar la ejecución del chat, aparece una ventana en la que el usuario ingresa el nombre que lo va a identificar en el sistema, tal como se puede apreciar en la Figura 38.
INTRODUCCIÓN AL DESARROLLO DE APLICACIONES DE RED EN JAVA
174
Figura 38. Proyecto MulticastChat – Entrada del nombre del usuario
La apariencia de la interfaz gráfica de usuario para este proyecto se puede apreciar en la Figura 39.
Figura 39. Proyecto MulticastChat – Apariencia de la GUI
8.1 PASO 1: APLICACIÓN GRÁFICA
Escriba el encabezado de la clase. Esta clase corresponde a una GUI, por lo que extiende de JFrame. Debe importar la clase JFrame del paquete javax.swing.
public class MulticastChat extends JFrame {
Especifique los componentes gráficos de la GUI. No olvide importar los paquetes correspondientes a cada componente. En algunas líneas de código podrá ver un comentario que dice Pendiente paso 2 parte 1. Es importante que escriba este comentario en el sitio indicado para evitar alguna confusión porque en las secciones siguientes se deben adicionar líneas de código en lugares específicos. La Figura 40 muestra los nombres de los componentes gráficos para que se vaya familiarizando con ellos.
PROYECTO MULTICASTCHAT
175
Figura 40. Proyecto MulticastChat – Componentes gráficos de la GUI
private private private private
JLabel JTextArea JScrollPane JTextField
title; area; scroll; inputText;
// Pendiente paso 2 parte1 // Pendiente paso 7 parte 2
Escriba el método constructor de la clase MulticastChat.java. En este método se están asignando las propiedades a los componentes gráficos. Esta GUI se lanza automáticamente y queda a disposición del usuario. El manejo de excepciones queda para el método main ( ), que es el método que crea el objeto de este tipo.
public MulticastChat ( ) throws Exception { // Pendiente paso 1 // Pendiente paso 4 // establece el administrador de componentes gráficos. El administrador null, // indica que todos los componentes deberán tener especificado el tamaño y la // ubicación en la ventana. setLayout ( null ); // se crean todos los componentes gráficos. Observe que en el constructor del // JScrollPane se pasa por parámetro el objeto de tipo JTextArea. Lo que quiere // decir que el área de mensajes estará dentro de un scroll. En el constructor // de los botones, se ha especificado el texto que se puede apreciar en cada // uno de ellos. title = new JLabel ( ); area = new JTextArea ( ); scroll = new JScrollPane ( area ); inputText = new JTextField ( );
INTRODUCCIÓN AL DESARROLLO DE APLICACIONES DE RED EN JAVA
176
// Pendiente paso 7 parte 3 // configuración de los componentes gráficos. // configuración de la etiqueta en la cual está el título title.setText ( "Multicast Chat" ); title.setBounds ( 10, 10, 180, 20 ); // configuración del scroll. scroll.setBounds ( 10, 40, 260, 100 ); // configuración del campo de texto. inputText.setBounds ( 10, 150, 260, 20 ); // Pendiente paso 7 parte 4 // adición de los componentes al contenedor. add ( title ); add ( scroll ); add ( inputText ); // Pendiente paso 7 parte 5 // configuración de la ventana. setTitle ( "MulticastChat" ); setSize ( 290, 250 ); // centrar la ventana con respecto a la pantalla. setLocationRelativeTo ( null ); // la ventana se hace visible dentro del proceso de construcción. setVisible ( true ); // configuración del área de texto. area.setEditable ( false ); area.setFont ( new Font ( "Courier new", Font.PLAIN, 12 ) ); // Pendiente paso 7 parte 1 // esta aplicación se cierra si se cierra la ventana. setDefaultCloseOperation ( WindowConstants.EXIT_ON_CLOSE ); // Pendiente paso 5 parte 1 // Pendiente paso 7 parte 6 // Pendiente paso 2 parte 2 // Pendiente paso 3 parte 1 }
Escriba el código del método look ( ), el cual se utiliza para adquirir la apariencia de la interfaz gráfica del sistema operativo. Llame este método al iniciar el constructor, donde está el comentario Pendiente paso 1.
private void look ( ) { try {
PROYECTO MULTICASTCHAT
177
UIManager.setLookAndFeel ( UIManager.getSystemLookAndFeelClassName ( ) ); } // Puede lanzar una excepción. catch ( Exception e ) { JOptionPane.showMessageDialog ( null, e.getStackTrace ( ), "Error", JOptionPane.ERROR_MESSAGE ); } }
8.2 PASO 2: CREACIÓN DEL CANAL DE SALIDA Y UNIÓN AL GRUPO MULTICAST
Agregue los siguientes atributos a la clase, en la parte superior, justo a continuación de los otros atributos, antes del método constructor, donde está el comentario Pendiente paso 2 parte 1. No olvide importar los nuevos paquetes que son necesarios. Parte 1: // componentes de comunicación private InetAddress groupAddress; private MulticastSocket socket; private int port; private String user;
A continuación, agregue las propiedades del grupo, en la última línea del método constructor, donde está el comentario Pendiente paso 2 parte 2. Parte 2: // configuración de los componentes de comunicación. // número de puerto. port = 7200; // dirección del grupo multcast. Observe que la dirección debe estar dentro del // rango 224.0.0.0 hasta 239.255.255.255. groupAddress = InetAddress.getByName ( "239.1.2.3" ); socket = new MulticastSocket ( port ); // esta aplicación se une al grupo multicast. socket.joinGroup ( groupAddress );
8.3 PASO 3: CREACIÓN DEL CANAL DE ENTRADA PARA LEER LOS MENSAJES
Esta aplicación necesita hacer dos actividades en paralelo. Una debe estar encargada de la interacción del usuario con la GUI, mientras que la otra está encargada de leer lo que llegue por el socket. Por lo tanto, es necesario crear un Thread adicional.
INTRODUCCIÓN AL DESARROLLO DE APLICACIONES DE RED EN JAVA
178
Incluya en la línea de encabezado de la clase la indicación que esta clase va a implementar la interface Runnable. public class MulticastChat extends JFrame implements Runnable
Esto hace necesario crear un método en la clase con el nombre run ( ). Este nombre no puede modificarse, porque es consecuencia de implementar la interface Runnable. // este método se ejecuta como segundo thread para estar pendiente de la llegada // de un paquete por la red. public void run ( ) { // componentes del paquete que se crea para recibir lo que llegue // por la red. byte [ ] message = new byte [ 1000 ]; int size = message.length; // queda permanentemente en modo escuchando. while ( true ) { try { // crea el paquete para recibir los datos que llegan. DatagramPacket packet = new DatagramPacket ( message, size ); // recibe el paquete. socket.receive ( packet ); // obtiene una cadena con los datos recibidos. String receivedMessage = new String ( packet.getData ( ), 0, packet.getLength ( ) ); // adiciona el mensaje recibido al área de texto para verlo en la // ventana. area.append ( receivedMessage ); // posiciona el cursos al final del área de texto en la ventana. area.setCaretPosition ( area.getText ( ).length ( ) ); } // Puede lanzar una excepción. catch ( Exception e ) { e.printStackTrace ( ); } } }
Incluya las siguientes líneas, justo después de que se solicite la unión al grupo en la parte final del método constructor, donde está el comentario Pendiente paso 3 parte 1. Parte 1: // se crea el objeto Thread. El método run, el cual es obligatoria su creación se // encuentra en este (this) objeto. Thread thread = new Thread ( this ); thread.start ( );
PROYECTO MULTICASTCHAT
179
8.4 PASO 4: SOLICITUD DEL NOMBRE DEL USUARIO PARA EL CHAT
Ahora, solicite a través de una ventana emergente el ingreso del nombre del usuario. Esto debe ser realizado en la parte inicial de la aplicación, luego de llamar al método look ( ) en el método constructor de la aplicación, donde está el comentario Pendiente paso 4. // solicita un nombre de usuario para el chat. Este nombre de usuario es // ingresado en una ventana emergente. user = JOptionPane.showInputDialog ( null, "Ingrese su nombre de usuario:" );
8.5 PASO 5: MANEJO DE LOS MENSAJES DE SALIDA
Para el manejo de los mensajes de salida, se va a digitar el mensaje en el campo de texto. Al presionar la tecla Enter, el mensaje será enviado por el socket, luego el mensaje será borrado del campo de texto y el cursor quedará posicionado al comienzo del campo de texto, disponible para que el usuario pueda ingresar un nuevo mensaje. Finalmente, cuando llegue un mensaje por el socket, será recibido y se mostrará al final del área de mensajes. Para identificar que el usuario ha presionado la tecla Enter, es necesario manejar eventos de teclado. Incluya en la línea de encabezado de la clase la indicación que esta clase va a implementar la interface KeyListener. El encabezado de la clase entonces debe quedar así: public class MulticastChat extends JFrame implements KeyListener, Runnable
Para poder capturar los eventos del teclado en el campo de texto, se necesita asociar un sensor (listener) de teclado al campo de texto. Debe incluir la línea a continuación, antes de la creación de los objetos que manejan los mensajes de salida, donde está el comentario Pendiente paso 5 parte 1. Parte 1: inputText.addKeyListener ( this );
También es necesario que implemente los siguientes métodos: keyPressed ( ), keyReleased ( ) y keyTyped ( ), los cuales serán explicados a continuación. Es obligatorio crear el método keyPressed ( ) porque esta clase implementa la interface KeyListener. Controla las acciones que deben ejecutarse luego de presionar alguna tecla, en este caso, la tecla Enter. // método que es obligatorio crear porque esta clase implementa
INTRODUCCIÓN AL DESARROLLO DE APLICACIONES DE RED EN JAVA
180
// KeyListener. Controla el uso de las teclas presionadas, en este caso, solo // controla la tecla <Enter>. public void keyPressed ( KeyEvent event ) { if ( event.getKeyCode ( ) == KeyEvent.VK_ENTER ) { this.send ( ); } }
Igualmente, es obligatorio crear los métodos keyReleased ( ) y keyTyped ( ) aunque en este caso, no desempeñen ninguna acción. public void keyReleased ( KeyEvent event) { } public void keyTyped ( KeyEvent event) { }
Observe que en la implementación del método keyPressed ( ), si la tecla presionada es Enter, se debe ejecutar el método send ( ), el cual se detalla a continuación. // este método se ejecuta cuando se hace clic en el botón Enviar o cuando se // presiona la tecla <Enter>. private void send ( ) { // primero, se verifica que la cadena ingresada no sea vacía. Si el mensaje a // enviar es vacío, no hace nada. if ( inputText.getText ( ).equals ( "" ) == false ) { // se adiciona un prefijo con el nombre del usuario y la palabra "dice:" // luego, se saca el mensaje del campo de texto. String messageToSend = user + " dice: " + inputText.getText ( ) + "\n"; // se convierte a byte [ ] el mensaje. byte [ ] message = messageToSend.getBytes ( ); // se calcula la longitud del mensaje. int size = message.length; try { // se crea el paquete DatagramPacket packet = new DatagramPacket ( message, size, groupAddress, port ); // se envía por el socket. socket.send ( packet ); } // Puede lanzar una excepción. catch ( Exception e ) { e.printStackTrace ( ); } // se borra el campo de texto y se deja el cursor en él. inputText.setText ( "" ); inputText.grabFocus ( ); } }
PROYECTO MULTICASTCHAT
181
8.6 PASO 6: EJECUTE LA APLICACIÓN
Haga una primera ejecución de la aplicación. Escriba el método main ( ) de la clase MulticastChat.
// método main que crea un objeto de tipo MulticastChat. public static void main ( String args [ ] ) throws Exception { new MulticastChat ( ); }
8.7 PASO 7: PRESENTACIÓN
Haga que el campo de texto tenga el cursor al iniciar la aplicación. Escriba las siguientes líneas en la parte final del método constructor, antes de escribir las líneas de código que manejan los mensajes de salida, donde está el comentario Pendiente paso 7 parte 1. Parte 1: // establecer la posición inicial del cursor directamente dentro del campo de // texto. inputText.grabFocus ( );
Adicione unos botones que permitan manejar la aplicación. Escriba las siguientes líneas de código en la parte superior de la clase, antes de iniciar el método constructor, donde está el comentario Pendiente paso 7 parte 2. Parte 2: private JButton sendButton; private JButton deleteButton; private JButton exitButton;
Cree los nuevos botones. Esto debe hacerlo dentro del método constructor en las propiedades de los componentes gráficos, donde está el comentario Pendiente paso 7 parte 3. Parte 3: sendButton = new JButton ( "Enviar" ); deleteButton = new JButton ( "Borrar" ); exitButton = new JButton ( "Salir" );
182
INTRODUCCIÓN AL DESARROLLO DE APLICACIONES DE RED EN JAVA
Configure los botones que acaba de crear. Esto debe hacerlo dentro del método constructor en las propiedades de los componentes gráficos, donde está el comentario Pendiente paso 7 parte 4. Parte 4: // configuracion de los botones sendButton.setBounds ( 10, 180, 80, 20 ); deleteButton.setBounds ( 100, 180, 80, 20 ); exitButton.setBounds ( 190, 180, 80, 20 );
Ahora debe indicar a la clase que implemente los eventos de acción. Para esto, debe cambiar el encabezado de la clase agregando la interface ActionListener a la lista de interfaces implementadas. El encabezado de la clase quedará así: public class MulticastChat extends JFrame implements ActionListener, KeyListener, Runnable
Adicione los botones al contenedor. Puede escribirlas a continuación del sensor de teclado, donde está el comentario Pendiente paso 7 parte 5. Parte 5: add ( sendButton ); add ( deleteButton ); add ( exitButton );
Para que la aplicación pueda reaccionar frente a los eventos de acción sobre los botones, es necesario que se asignen sensores a los botones. Incluya las siguientes líneas de código en la parte final del método constructor. Puede escribirlas a continuación del sensor de teclado, donde está el comentario Pendiente paso 7 parte 6. Parte 6: sendButton.addActionListener ( this ); deleteButton.addActionListener ( this ); exitButton.addActionListener ( this );
En esta clase es necesario crear el método actionPerformed ( ), debido a que se definió que la clase implemente la interface ActionListener. // método que es obligatorio crear porque esta clase implementa ActionListener. // Controla el uso de los botones. public void actionPerformed ( ActionEvent event ) { // si el botón presionado fue sendButton, el programa ejecuta el método enviar // que se encuentra en esta (this) clase. if ( event.getSource ( ) == sendButton ) { this.send ( ); } else {
PROYECTO MULTICASTCHAT
183
// si el botón presionado fue deleteButton, se borra el mensaje que estaba // en edición. El cursor queda en el campo de texto. if ( event.getSource ( ) == deleteButton ) { inputText.setText ( "" ); inputText.grabFocus ( ); } else { // por descarte, el botón presionado fue exitButton. En este caso, el // usuario deja (abandona) el grupo y esta aplicación se cierra. try { socket.leaveGroup ( groupAddress ); } // Puede lanzar una excepción. catch ( Exception e ) { e.printStackTrace ( ); } System.exit ( 0 ); } } }
8.8 EJERCICIOS
1. Haga una modificación al proyecto de tal manera que los mensajes se manejen utilizando el siguiente protocolo:
Cada que un usuario se inscribe en el grupo multicast, envía un mensaje JOIN junto con el nombre del usuario y luego envía un mensaje SYN nuevamente con el nombre del usuario.
El mensaje JOIN es un mensaje de unión al grupo. El mensaje SYN es un mensaje de solicitud de sincronización de la lista de usuarios. Cada usuario que recibe un mensaje SYN que no haya sido el que lo origina, envía un mensaje ACK con la lista de los usuarios. Todos los usuarios sincronizan la lista de usuarios. Sincronizar la lista de usuarios consiste en revisar la lista y para verificar si aparecen nuevos usuarios que otro usuario del chat no los tenga en su lista. Si hay nuevos usuarios, son incluidos en la lista la cual finalmente es ordenada.
184
INTRODUCCIÓN AL DESARROLLO DE APLICACIONES DE RED EN JAVA
Un mensaje normal dentro de la conversación, inicia con MSG. Finalmente, cuando un usuario abandona el grupo, envía un mensaje LEAVE junto con el nombre de usuario. Luego, cada uno los usuarios que reciba el mensaje de salida lo retira de su lista de usuarios.
A continuación, un resumen de los comandos que son enviados y la forma en que son procesados.
Mensaje JOIN:
Este mensaje es enviado al iniciar la sesión de chat, seguido de un mensaje SYN. En ambos casos se envía el mensaje y el nombre del usuario separados por un espacio en blanco. Cada usuario obtiene el nombre del usuario. Luego muestra en el área de texto el mensaje que indica que el usuario se ha unido a la conversación. Entonces lo adiciona a la lista de usuarios.
Mensaje SYN:
Cada usuario obtiene el nombre del usuario. Entonces se hace una verificación para determinar si el usuario que ha enviado esa solicitud de sincronización de la lista de usuarios es un usuario distinto a él mismo. Si es un usuario distinto, entonces envía el mensaje ACK junto con la lista de usuarios en un String donde los nombres de los usuarios van separados por espacios en blanco.
Mensaje ACK:
Este mensaje se envía como respuesta a un mensaje SYN, cuando se trata de otro usuario. Quien recibe este mensaje obtiene la lista de usuarios y verifica si hay algún usuario que no tenga en la lista y lo adiciona a la lista de usuarios.
PROYECTO MULTICASTCHAT
185
Mensaje MSG:
Este es el mensaje que envía un usuario que usa el chat en condiciones normales. Cuando un usuario recibe este mensaje lo procesa para mostrar el mensaje correspondiente en el área de texto.
Mensaje LEAVE:
Este mensaje es enviado cuando un usuario hace clic en el botón Salir. Cuando un usuario recibe este mensaje identifica el nombre del usuario que abandona el chat, lo elimina de la lista de usuarios y muestra en la ventana del chat un mensaje que indica que el usuario ha salido de la conversacion.
186
INTRODUCCIÓN AL DESARROLLO DE APLICACIONES DE RED EN JAVA
REFERENCIAS BIBLIOGRテ:ICAS
9.
Ares-Galaxy.
187
REFERENCIAS BIBLIOGRテ:ICAS
(s.f.).
.
l
A
r
G
s
e
a
a
x
y
(Ares
Galaxy)
Recuperado
el
2010,
de
Torrent)
Recuperado
el
2010,
de
http://aresgalaxy.sourceforge.net/ Bit-Torrent.
(s.f.).
B
i
T
t
o
r
r
n
e
.
t
(Bit
http://www.bittorrent.com/ CALVERT, K., & DONAHOO, M. (2008). P
g
o
r
a
r
m
e
m
,
s
r
S
e
c
o
n
E
d
COMER, D. (1997).
d
R
t
i
i
d
e
o
s
e
C
P
/
I
P
S
c
o
k
t
e
s
J
n
i
a
v
a
:
P
a
r
c
t
c
i
a
G
u
d
i
f
e
o
r
Morgan Kaufmann Publishers.
.
n
l
T
d
c
e
o
p
m
u
a
t
d
o
a
r
,
s
I
n
t
e
n
r
t
e
I
e
n
t
e
r
r
d
e
Prentice-Hall
.
s
e
Interamericana. COULOURIS, G., & DOLLIMORE, J. y. (2005). F
o
u
h
t
r
E
d
t
i
i
o
.
n
D
s
i
t
r
u
b
i
t
S
d
e
s
y
t
e
:
s
m
C
o
c
n
p
e
t
a
s
n
d
d
s
e
g
i
,
n
Addison Wesley.
ELLIOTE, H. (2004).
J
a
v
FIELDING, R. (1999).
a
N
w
t
e
o
k
r
P
g
o
r
a
r
m
m
g
n
i
,
T
h
i
E
d
r
d
t
i
o
i
O'Reilly.
.
n
-
l
Recuperado el
F
H
p
y
e
t
r
x
e
T
t
a
r
n
f
s
e
P
r
o
r
t
c
o
H
o
T
T
P
/
1
.
1
R
C
2
6
1
6
.
2010, de http://www.ietf.org/rfc/rfc2616.txt HINDEN, R., & DEERING, y. S. (1998).
F
I
P
V
e
s
r
o
i
6
n
A
d
d
r
e
s
s
i
g
n
A
c
r
h
i
t
c
e
t
u
r
R
e
C
2
3
7
3
.
Recuperado el 2010, de http://www.ietf.org/rfc/rfc2373.txt I
A
N
A
(s.f.). Recuperado el 2010, de http://www.iana.org/assignments/port-numbers.
.
(s.f.). Recuperado el 2010, de
l
J
a
v
a
P
a
t
f
o
r
m
,
S
t
a
n
d
a
r
E
d
d
t
i
o
i
6
n
A
P
I
S
p
c
e
i
f
c
i
a
t
o
i
.
n
http://java.sun.com/javase/6/docs/api/index.html -
KUROSE, J., & ROSS, K. (2010). E
d
t
i
i
o
n
.
F
C
o
p
m
u
t
e
r
N
e
t
w
o
r
k
i
n
g
:
A
T
o
p
D
o
w
n
A
p
p
r
o
a
c
l
I
P
i
f
t
h
. (Mozilla Firefox) Recuperado el 2010, de
l
F
M
o
z
a
i
i
r
e
f
x
o
http://www.mozilla.com/ o
,
Addison Wesley.
Mozilla-Firefox. (s.f.).
N
h
. (s.f.). Recuperado el 2010, de http://www.noip.com/
O'FLAHERTY, C. y. (2009).
I
P
v
6
p
a
r
a
t
o
d
o
s
.
Internet Society Capテュtulo Argentina.
INTRODUCCIÓN AL DESARROLLO DE APLICACIONES DE RED EN JAVA
188
POSTEL, J. (1986).
l
l
F
I
n
t
e
n
r
P
t
e
o
r
t
c
o
:
o
D
A
R
P
A
I
n
t
e
n
r
P
t
e
o
r
t
c
o
S
o
p
c
e
f
i
i
c
a
t
o
i
R
n
C
7
1
9
.
Recuperado el 2010, de http://www.ietf.org/rfc/rfc791.txt REKHTER, Y., MOSKOWITS, B., & GROOT, D. K. (1986). F
I
n
t
e
n
r
t
e
R
s
C
1
9
1
8
.
l
A
d
d
r
s
e
l
A
s
c
o
a
t
i
o
n
f
o
P
r
r
i
v
a
t
e
Recuperado el 2010, de http://www.ietf.org/rfc/rfc1918.txt
SCHILDT, H. (2001).
McGraw-Hill
l
J
a
v
a
2
.
M
a
n
a
u
d
e
r
f
e
e
r
c
n
e
i
a
C
a
u
t
r
a
E
d
i
c
i
ó
.
n
Interamericana. S
k
p
y
. (s.f.). Recuperado el 2010, de http://www.skype.com/
e
Sun Microsystems, Inc. (1997). TANENBAUM, A. (2004).
J
a
v
a
C
o
d
C
e
o
v
n
n
e
t
o
i
.
n
Prentice-Hall.
F
C
o
p
m
u
t
e
r
N
w
t
e
o
The Apache Software Fundation. (s.f.).
k
r
A
,
s
a
p
o
c
h
u
W
e
E
t
r
d
S
b
e
t
i
o
i
e
r
.
n
v
e
. Recuperado el 2010, de
r
http://apache.org/ WAI, A. y. (2007). l
T
W
c
e
i
h
r
n
e
o
s
h
g
o
a
r
i
k
e
s
.
-
P
e
e
r
-
t
o
l
p
e
e
r
c
o
m
p
u
t
i
n
g
:
B
u
i
d
i
n
g
S
u
p
e
r
c
o
m
p
u
t
e
r
s
The Computer Communications and Network Series: Springer.
. (s.f.). Recuperado el 2010, de http://www.wireshark.org/
w
i
t
h
W
e
b