Está en la página 1de 87

Comunicaciones en Redes

1
1. Introducción a las redes
1.1. Concepto

Las redes interconectan computadoras con distintos sistemas operativos, ya sea dentro de una
empresa u organización (LANs) o por todo el mundo (WANs, Internet).

Anteriormente se utilizaban básicamente para compartir los recursos de las computadoras


conectadas. Hoy, las redes son medios de comunicación internacional a través de los cuales se
intercambian grandes volúmenes de datos.

Las razones más usuales para decidir la instalación de una red son:

• Compartición de programas, archivos e impresora.


• Posibilidad de utilizar software de red.
• Creación de grupos de trabajo.
• Gestión centralizada.
• Seguridad.
• Acceso a otros sistemas operativos.
• Compartir recursos.

Un ejemplo de red muy sencilla se ve en la figura:

1.2. Estructura Cliente-Servidor

En las redes basadas en estructuras cliente-servidor, los servidores ponen a disposición de sus
clientes recursos, servicios y aplicaciones.

Dependiendo de qué recursos ofrece el servidor y cuáles se mantienen en los clientes se pueden
hacer distinciones entre distintas estructuras cliente-servidor.

En estas estructuras se diferencia:

• Donde se encuentran los datos.


• Donde se encuentran los programas de aplicación.
• Donde se presentan los datos.

2
A continuación, se presentarán brevemente los distintos conceptos.

1. Sistema centralizado basado en el host (anfitrión).

Aquí, los datos, los programas de aplicación y la presentación se encuentran en el


servidor. La imagen final se transmite a los terminales de los usuarios. Desde los
terminales tontos, las cadenas de caracteres de las entradas de los usuarios se reenvían al
host. Este concepto es el que sirve de base para las computadoras mainframes.

2. Pc cliente y servidor host.

Los datos de aplicación se conservan de forma centralizada en el servidor. Con


programas clientes de las aplicaciones, éstas se presentan en cada estación de trabajo. El
lugar de trabajo suele ser una PC ejecutando, por ejemplo, Windows.

3. Estación de trabajo cliente y servidor de archivo.

Los datos se encuentran en el servidor (generalmente en una base de datos). Con un


programa de aplicación de cliente se accede a esos datos desde cualquier computadora.
En el cliente se procesan los datos utilizando la inteligencia del cliente. Cada
computadora contiene aplicaciones con las que se puede procesar los datos. Es la misma
estructura a la del punto 2, lo que cambia es el tipo de datos que almacena el servidor.

Esta estructura se conoce como aplicación Cliente – Servidor de 2 capas.

4. Pc cliente y servidor de aplicaciones.

En esta red se dispone al menos de dos servidores distintos. Uno de ellos actúa
meramente como servidor de base de datos y el resto como servidor de aplicaciones. Los
servidores de aplicaciones de esta red también son los responsables de acceso a las bases
de datos. En las estaciones de trabajo funcionan los clientes de los programas de
aplicación correspondientes.

Esta estructura se conoce como aplicación Cliente – Servidor de 3 capas.

5. Sistema cliente-servidor cooperativo descentralizado.

Las bases de datos están repartidas en distintos servidores o incluso clientes. Las
aplicaciones funcionan igualmente en distintos servidores o en parte también en clientes.
A este sistema se le conoce como Aplicaciones Distribuidas.

Esta estructura se conoce como aplicación Cliente – Servidor de n capas.

1.3. Topologías Lógicas y Topologías Físicas

Hay varias maneras de conectar dos o más computadoras en red.

3
Para ellos se utilizan cuatro elementos fundamentales: servidores de archivos, estaciones de
trabajo, tarjetas de red y cables.

A ellos se les suman los elementos propios de cada cableado, así como los manuales y el
software de red, a efectos de la instalación y mantenimiento.

Los cables son generalmente de tres tipos: UTP par trenzado, coaxial y fibra óptica.

La manera en que están conectadas las computadoras no es arbitraria, sino que siguen estándares
físicos llamados topologías.

Dependiendo de la topología será la distribución física de la red y dispositivos conectados a la


misma, así como también las características de ciertos aspectos de la red como: velocidad de
transmisión de datos y confiabilidad del conexionado.

Las topologías Lógicas y Físicas, se describen a continuación:

• TOPOLOGÍA FÍSICAS: Es la forma que adopta un plano esquemático del cableado o


estructura física de la red, también hablamos de métodos de control.

• TOPOLOGÍA LÓGICAS: Es la forma de cómo la red reconoce a cada conexión de


estación de trabajo.

Las topologías lógicas se clasifican en:

1. TOPOLOGÍA LINEAL O BUS

Consiste en un solo cable al cual se le conectan todas las estaciones de trabajo.

En este sistema una sola computadora por vez puede mandar datos los cuales son
escuchados por todas las computadoras que integran el bus, pero solo el receptor designado
los utiliza.

Ventajas:

• Es la más barata.
• Apta para oficinas medianas y chicas.

Desventajas:

• Si se tienen demasiadas computadoras conectadas a la vez, la eficiencia baja


notablemente.
• Es posible que dos computadoras intenten transmitir al mismo tiempo provocando lo que
se denomina “colisión”, y por lo tanto se produce un reintento de transmisión.
• Un corte en cualquier punto del cable interrumpe la red

4
2. TOPOLOGÍA ESTRELLA

En este esquema todas las estaciones están conectadas a un concentrador (Hub o Switche),
actualmente se utiliza un Switche, con cable por red o inalámbrico.

Para futuras ampliaciones pueden colocarse otros Switches en cascada dando lugar a la
estrella jerárquica.

Por ejemplo, en la estructura Cliente-Servidor: el servidor está conectado al Switche activo,


de este a los pasivos y finalmente a las estaciones de trabajo.

Ventajas:

• La ausencia de colisiones en la transmisión y dialogo directo de cada estación con el


servidor.
• La caída de una estación no anula la red.

Desventajas:

• Baja transmisión de datos.

3. TOPOLOGÍA ANILLO (TOKEN RING)

Es un desarrollo de IBM que consiste en conectar cada estación con otras dos formando un
anillo.

Los servidores pueden estar en cualquier lugar del anillo y la información es pasada en un
único sentido de una a otra estación hasta que alcanza su destino.

Cada estación que recibe el TOKEN regenera la señal y la transmite a la siguiente.

Por ejemplo, en esta topología envía una señal por toda la red, si la terminal quiere
transmitir pide el TOKEN y hasta que lo tiene puede transmitir.

5
Si no está la señal la pasa a la siguiente en el anillo y sigue circulando hasta que alguna
terminal pide permiso para transmitir.

Ventajas:

• No existen colisiones, pues cada paquete tiene una cabecera o TOKEN que identifica al
destino.

Desventajas:

• La caída de una estación interrumpe toda la red.


• Es cara, llegando a costar una placa de red lo que cuesta una estación de trabajo.

Por último, actualmente no hay conexiones físicas entre estaciones, sino que existen
centrales de cableado o MAU que implementa la lógica de anillo sin que estén conectadas
entre sí; evitando las caídas.

4. TOPOLOGÍA ÁRBOL

En esta topología que es una generalización del tipo bus, el árbol tiene su primer nodo en la raíz
y se expande hacia afuera utilizando ramas, en donde se conectan las demás terminales.

Esta topología permite que la red se expanda y al mismo tiempo asegura que nada más existe una
ruta de datos entre dos terminales cualesquiera.

Las desventajas son las mismas de la topología lineal o bus, para cada rama, y la ventaja es de la
expandir redes de bus ya existentes.
6
5. TOPOLOGÍA MESH.

Es una combinación de más de una topología, como podría ser un bus combinado con una
estrella.

Este tipo de topología es común en lugares en donde tenían una red bus y luego la fueron
expandiendo en estrella.

Son complicadas para detectar su conexión por parte del servicio técnico para su reparación.

Dentro de estas topologías encontramos:

a) TOPOLOGÍA ANILLO EN ESTRELLA. Se utilizan con el fin de facilitar la


administración de la red. Físicamente la red es una estrella centralizada en un
concentrador o HUB, mientras que a nivel lógico la red es un anillo.

b) TOPOLOGÍA BUS EN ESTRELLA. El fin es igual al anterior. En este caso la red es


un bus que se cablea físicamente como una estrella mediante el uso de concentradores.

c) TOPOLOGÍA ESTRELLA JERÁRQUICA. Esta estructura se utiliza en la mayor


parte de las redes locales actuales. Por medio de concentradores dispuestos en cascadas
para formar una red jerárquica.

1.4. Concentradores

Son equipos que permiten estructurar el cableado de las redes, la variedad de tipos y
características de estos equipos es muy grande. Cada vez disponen de mayor número de
capacidades como aislamiento de tramos de red, capacidad de conmutación de las salidas para
aumentar la capacidad de la red, gestión remonta, etc.; por lo que se tiende a incorporar más
funciones en el concentrador.

Tenemos dos tipos de concentradores:

1. HUB. Un Hub o concentrador es el dispositivo que permite centralizar el cableado de


una red de computadoras, para luego poder ampliarla. Trabaja en la capa física (capa 1)
del modelo OSI o la capa de acceso al medio en el modelo TCP/IP. Esto significa que
dicho dispositivo recibe una señal y repite esta señal emitiéndola por sus diferentes
puertos (repetidor).

7
En la actualidad, la tarea de los concentradores la realizan, con frecuencia, los
conmutadores (switches).

2. SWITCHE. Un Switche o conmutador es un dispositivo digital de lógica de


interconexión de redes de computadores que opera en la capa de enlace de datos del
modelo OSI. Su función es interconectar dos o más segmentos de red, de manera similar
a los puentes de red, pasando datos de un segmento a otro de acuerdo con la dirección
MAC de destino de las tramas en la red.

Un conmutador es el centro de una red en estrella.

Los conmutadores (switch) se utilizan cuando se desea conectar múltiples redes,


fusionándolas en una sola. Al igual que los puentes, dado que funcionan como un filtro en
la red, mejoran el rendimiento y la seguridad de las redes de área local

1.5. Métodos De Acceso

En las topologías anteriores se comparte el medio, por parte de más de una PC, con lo que puede
ocurrir que 2 o más PC intenten acceder al medio al mismo tiempo produciéndose una colisión
que provocaría errores en los datos enviados a través de medio.

Para evitar estas situaciones o corregirlas se dispone de varios mecanismos de acceso al medio
de forma controlada que se basan en la secuencia de bits que habilita el permiso para transmitir
por el medio físico.

1. Si existe una estación de trabajo "jefe" que centralice el paso de la señal, los métodos se
llaman:
o POLLING. Si la topología usada es bus.
o LUP CENTRAL. Si la topología usada es de tipo anillo.
2. Si no existe esa estación jefe que controle el paso de la señal o TESTIGO, tenemos los
métodos:
o PASO DEL TESTIGO EN ANILLO. Usa la topología en anillo.
o TESTIGO EN BUS. Usa la topología en bus.
3. Si no utilizamos ningún método de control sobre el medio para habilitar permisos de
transmisión de las estaciones tenemos:
o TÉCNICAS DE ACCESO SORDAS: se transmiten sin consultar el medio
previamente, para ver si está libre.
o TÉCNICAS CON ESCUCHAS DEL MEDIO. Dan lugar a un control del tipo
aleatorio.
✓ PARA TOPOLOGÍA BUS. Esta técnica se conoce como CSMA/CD
técnica de acceso al medio con escuchas y detección de colisiones.
✓ PARA TOPOLOGÍA ANILLO. Esta técnica se la conoce como
INSERCIÓN DE REGISTROS.

1.6. Tipos De Redes

Se clasifican según su Extensión y Topología.

Según su extensión tenemos redes LAN, MAN y WAN.


8
1. LAN (Local Area Network - Red de Área Local)

Las redes de área local (LAN por las siglas de Local Area Network) son las de uso más
frecuente. Son conjuntos de máquinas interconectadas, ubicadas en extensiones
relativamente pequeñas. Desde nuestros hogares hasta grandes edificios de oficinas, pasando
por entidades gubernamentales e instituciones académicas. Es decir, son redes de propiedad
privada dentro de un solo edificio de hasta unos cuantos kilómetros de extensión.

Este tipo de redes son las más comunes. En todos los lugares de trabajo del mundo, con
más de una computadora interconectada, existe seguramente una LAN activa.

Las LAN permiten la interacción entre múltiples equipos para compartir datos y recursos.
Muchas computadoras accediendo a la misma impresora, al mismo servidor, a la misma
conexión a Internet. Todas ellas compartiendo datos a gran velocidad.

En las redes de área local la distancia entre una máquina y otra no suele ser muy grande. Por
debajo de los 100 metros es lo normal. Sin embargo, con configuraciones especiales, pueden
existir redes LAN con computadoras a 5 km de distancia entre sí.

Por otro lado, la velocidad de transmisión de datos en este tipo de redes es muy alta. El
protocolo de interconexión más común es Ethernet. Por cable entrelazado puede llegar a los
100 Mbps. Por fibra óptica podría alcanzar los 1000 Mbps.

Teóricamente no existe un límite de computadoras que se puedan conectar a una LAN. Sin
embargo, con el uso de muy buenos equipos y excelente organización de la red, a partir de
los 400 ó 500 equipos se percibe degradación en el rendimiento de la red.

En la siguiente figura se presenta un ejemplo de una red de área Local LAN:

Las LAN se distinguen de otro tipo de redes por las siguientes tres características: tamaño,
tecnología de transmisión y topología:

9
• Las LAN están restringidas en tamaño. Las computadoras se distribuyen dentro de la
LAN para obtener mayor velocidad en las comunicaciones dentro de un edificio o un
conjunto de edificios, lo cual significa que el tiempo de transmisión del peor caso está
limitado y se conoce de antemano.

Conocer este límite hace posible usar ciertos tipos de diseños que de otra manera no
serían prácticos y también simplifica la administración de la red.

• Las LAN a menudo usan una tecnología de transmisión. Que consiste en un cable sencillo
al cual están conectadas todas las máquinas.

Las LAN tradicionales operan a velocidades de 10 a 100 Mbps, Las más nuevas pueden
operar a velocidades muy altas, de hasta cientos de megabits/seg.

• Las LAN pueden tener diversas topologías. La topología o la forma de conexión de la


red, depende de algunos aspectos como la distancia entre las computadoras y el medio de
comunicación entre ellas ya que este determina la velocidad del sistema.

Básicamente existen tres topologías de red: estrella (Star), canal (Bus) y anillo (Ring).
Pero actualmente se ha divulgado la topología estrella.

WLAN (Wireless Local Area Network - Red Inalámbrica de Área Local)

Una LAN con sus nodos interconectados con tecnología WiFi se conoce como red
inalámbrica de área local.

Con una WLAN no hay que tender engorrosos cables en la oficina para lograr la
interconexión. Esta se realiza mediante ondas de radio de alta frecuencia. Una desventaja es
que estas redes son menos seguras que sus versiones conectadas físicamente. La señal
podría ser interceptada y desencriptada por personas indeseadas.

Para evitar problemas de rendimiento pueden interconectarse varias LAN entre sí, sin
importar la distancia. Si estas redes están muy separadas unas de otras, pasan a llamarse
redes de área metropolitana o MAN. Y aún más, si la distribución abarca zonas geográficas
todavía mayores se les conoce como redes de área amplia o WAN.

En la siguiente figura se presenta un ejemplo de red wireless WLAN:

10
2. MAN (Redes de Área Metropolitana)

Una red de área metropolitana (MAN por las siglas en inglés de Metropolitan Area
Network) consiste en computadoras compartiendo recursos entre sí en áreas de cobertura de
mayor tamaño que una LAN, pero menor que una WAN. Funcionan de forma muy
parecida a una red de área local, pero cumplen estándares tecnológicos diferentes. Estas
mejoras son necesarias para subsanar los problemas de latencia (retardo en la entrega de
información) y pérdida de calidad de la señal en interconexiones que abarcan largas
distancias.

Una MAN podría abarcar una serie de oficinas cercanas o en una ciudad, puede ser pública o
privada.

La principal razón para distinguir las MAN como una categoría especial es que se ha
adoptado un estándar para ellas, y este se llama DQDB (Distributed-queue dual-bus - Bus
dual de cola distribuida).

Bus Dual de Cola Distribuida, es un sistema de comunicación informático de red multi-


acceso, que se apoya en las comunicaciones integradas utilizando un bus dual y organizando
la información solicitada mediante una cola distribuida, proporcionando de esta manera, el
acceso a las redes de área local (LAN) o área metropolitana (MAN).

Se apoya en las transferencias de datos con estado sin conexión, en las transferencias de
datos orientadas a conexión, y en comunicaciones isócronas tales como la comunicación por
voz. Un ejemplo de red que proporciona métodos de acceso DQDB es la que sigue el
estándar IEEE 802.6.

Generalmente usan un bus doble, ida y vuelta, con fibra óptica, para interconectar las
diferentes LAN a la red. También se consiguen redes MAN usando pares de cobre o
microondas. Por la mayor estabilidad y menor latencia que ofrecen, son ideales para
ofrecer servicios multimedia y videovigilancia en grandes ciudades, entre otras ventajas.
Es decir, una MAN puede manejar datos y voz, e incluso podría estar relacionada con una
red de televisión por cable local.

11
Como el resto de las redes cableadas, tiene su versión inalámbrica llamada WMAN
(Wireless Metropolitan Area Network). Esta red utiliza tecnologías de telefonía celular
como LTE y WiMax para interconectar sus miembros.

Velocidad de transmisión: Las redes MAN bucle ofrecen velocidades de 10 Mbps, 20 Mbps, 16
gbps y 10 gbps mediante fibra óptica.

En la siguiente figura se presenta un ejemplo de una red de área Local LAN:

3. WAN (Wide Area Network - Redes de Área Amplia)

Las redes de área amplia (WAN por las siglas de Wide Area Network), son redes
informáticas LAN y MAN interconectadas entre sí. Sus nodos están separados por
distancias que pueden abarcar continentes enteros. Los integrantes de esas redes no
necesariamente están conectados físicamente. Hacen uso de servicios de cables
submarinos, microondas y satelitales para integrar sus diferentes nodos.

Son muy usadas por grandes empresas que abarcan mucho territorio. Generalmente
necesitan usar redes privadas virtuales (VPN) para conseguir la privacidad necesaria en el
intercambio de datos. Otro uso muy frecuente es para ofrecer conexión web a clientes de
grandes proveedores de Internet (ISP – Internet Service Provider).

Debido a la amplitud de su cobertura necesitan atravesar redes públicas, como las


telefónicas, así como rentar servicios de transporte a otras redes privadas y usar conexiones
satelitales para poder llevar la información de un lado a otro.

Su versión inalámbrica es una WWAN. Esta interconecta al resto de los nodos mediante el
uso de redes de telefonía celular con tecnología LTE, WiMax, GSM, CDMA2000, UMTS,
entre otras.

12
WAN contiene una colección de máquinas dedicadas a ejecutar programas de usuario
(aplicaciones), estas máquinas se llaman Hosts.

Los Hosts están conectados por una subred de comunicación. El trabajo de una subred es
conducir mensajes de un Host a otro.

La separación entre los aspectos exclusivamente de comunicación de la red (la subred) y los
aspectos de aplicación (Hosts), simplifica enormemente el diseño total de la red.

En muchas redes de área amplia, la subred tiene dos componentes distintos: las líneas de
transmisión y los elementos de conmutación.

• Las líneas de transmisión (también llamadas circuitos o canales) mueven los bits de una
máquina a otra.
• Los elementos de conmutación son computadoras especializadas que conectan dos o más
líneas de transmisión. Por ejemplo, los Routers.

Cuando los datos llegan por una línea de entrada, el elemento de conmutación debe escoger
una línea de salida para enviarlos.

Como término genérico para las computadoras de conmutación, les llamaremos enrutadores
(routers).

La velocidad de transmisión se encuentra entre 1 Mbps y 1 Gbps, aunque este último límite
puede cambiar drásticamente con los avances tecnológicos.

En la siguiente figura se presenta un ejemplo de una red de área Local WAN:

Otros tipos de redes

• PAN (Personal Area Network): Se denomina red de área personal la que abarca los
diferentes dispositivos de uso cercano de un usuario. Teléfono celular, laptop, cámaras
13
de fotos, tabletas, etc, son los más comunes. Permite el intercambio de archivos de
manera sencilla entre los aparatos. Su versión inalámbrica hace uso de la red WiFi, el
Bluetooth o los rayos infrarrojos para intercambiar información.

• VLAN (Virtual Local Area Network): Funciona como una VPN dentro de una red
local. Permite la creación de una conexión privada entre dos o más nodos dentro del
universo de una LAN. Ideal, por ejemplo, para separar el acceso a la red de diferentes
departamentos de una empresa. Son creadas vía software, por lo que sus nodos no
necesitan estar interconectados directamente entre sí.

• SAN (Storage Area Network): Las redes de área de almacenamiento (SAN) son una
tecnología usada para enlazar unidades de almacenamiento (básicamente discos
duros) a una red local, de manera de compartir su uso en todas las áreas de una
empresa. Este sistema puede crecer casi ilimitadamente sin afectar el rendimiento de la
red ya que el tráfico de almacenamiento se mantiene separado del tráfico de los usuarios.

1.7. Estándar Ethernet

Ethernet es una tecnología desarrollada para las redes LAN que permite transmitir información
entre computadoras a velocidades de 10 y 100 millones de bits por segundo (100 Mbps).

Ethernet es un estándar, por lo tanto se trata de un sistema independiente de las empresas


fabricantes de hardware de red.

Si bien Ethernet es el sistema más popular, existen otras tecnologías como Token Ring, 100 VG.

Se usa en redes que no superan las 30 máquinas, de exceder este número conviene usar Token
Ring.

Un sistema Ethernet consiste de tres elementos básicos:

1. Un medio físico utilizado para transportar señales entre dos computadoras (adaptadores
de red y cableado).
2. Un juego de reglas o normas de acceso al medio (al cable, por ejemplo) que le permita a
las computadoras poder arbitrar o regular el acceso al sistema Ethernet (recordar que el
medio está compartido por todas las computadoras integrantes de la red).
3. Un estándar o patrón llamado trama o frame que consiste en un juego determinado de
bits, usados para transportar datos a través del sistema.

Cada computadora equipada con Ethernet opera en forma independiente de las otras estaciones
de la red, es decir que no hay una controladora central.

Todas las estaciones conectadas vía Ethernet se conectan a un sistema compartido de señales,
llamado medio.

Las señales Ethernet se transmiten en serie, un bit por vez, a través del canal Ethernet (llamado
de señal compartida) a cada una de las estaciones integrantes de la red Ethernet.

El preámbulo de un paquete Ethernet se genera mediante el hardware (la placa de red).


14
El software es responsable de establecer la dirección de origen y de destino y de los datos.

La información sobre la secuencia de los paquetes en general es tarea del hardware.

Un paquete Ethernet está compuesto esencialmente por las siguientes partes:

• El preámbulo: es una serie de unos y ceros, que serán utilizados por la computadora
destino (receptor) para conseguir la sincronización de la transmisión.
• Separador de la trama: son dos bits consecutivos utilizados para lograr alineación de
los bytes de datos. Son dos bits que no pertenecen a los datos, simplemente están a modo
de separador entre el preámbulo y el resto del paquete.
• Dirección de destino: es la dirección de la computadora a la que se le envía el paquete.
La dirección de difusión o broadcast (se le envía a todos los equipos) está compuesta por
uno solamente (son todos unos).
• Dirección de origen: es la dirección de la computadora que envía los datos.
• Longitud o tipo de datos: es el número de bytes de datos o el tipo de los mismos. Los
códigos de tipos de datos son mayores que 1500, ya que 1500 bytes es la máxima
longitud de los datos en Ethernet. Entonces, si este campo es menor que 1500 se estará
refiriendo a la longitud de los datos y si es mayor, se referirá al tipo de datos. El tipo de
datos tendrá un código distinto, por ejemplo para Ethernet que para Fast Ethernet.
• Datos: su longitud mínima es de 46 bytes y su largo máximo de 1500 bytes como dijimos
en el ítem anterior.
• Secuencia de chequeo de la trama: se trata de un chequeo de errores (CRC) que utiliza
32 bits. Este campo se genera generalmente por el hardware (placa de red).

Basándose en lo visto, sin contar preámbulo, separadores y CRC, la longitud de los paquetes
Ethernet serán:

El más corto: 6 + 6 + 2 + 46 = 60 bytes.

El más largo: 6 + 6 + 2 + 1500 = 1514 bytes.

1.8. TOKEN RING

La red Token-Ring es una implementación del standard IEEE 802.5, en el cual se distingue más
por su método de transmitir la información que por la forma en que se conectan las
computadoras.

A diferencia del Ethernet, aquí un Token (Ficha Virtual) es pasado de computadora a


computadora como si fuera una papa caliente.

Cuando una computadora desea mandar información debe de esperar a que le llegue el Token
vacío, cuando le llega utiliza el Token para mandar la información a otra computadora, entonces
cuando la otra computadora recibe la información regresa el Token a la computadora que envió
con el mensaje de que fue recibida la información.

Así se libera el Token para volver a ser usado por cualquiera otra computadora.

15
Aquí debido a que una computadora requiere el Token para enviar información no hay
colisiones, el problema reside en el tiempo que debe esperar una computadora para obtener el
Token sin utilizar.

Los datos en Token-Ring se transmiten a 4 ó 16mbps, depende de la implementación que se


haga.

Todas las estaciones se deben de configurar con la misma velocidad para que funcione la red.

Cada computadora se conecta a través de cable Par Trenzado ya sea blindado o no a un


concentrador llamado MAU(Media Access Unit), y aunque la red queda físicamente en forma de
estrella, lógicamente funciona en forma de anillo por el cual da vueltas el Token.

En realidad es el MAU el que contiene internamente el anillo y si falla una conexión


automáticamente la ignora para mantener cerrado el anillo.

El Token-Ring es eficiente para mover datos a través de la red.

En redes grandes con tráfico de datos pesado el Token Ring es más eficiente que Ethernet.

"Por lo tanto es conveniente usar Token ring en redes que superan las 30 máquinas."

1.9. ELEMENTOS INVOLUCRADOS EN UN CABLEADO DE REDES

A continuación, trataremos los componentes más importantes de una instalación física de redes a
saber:

1. ADAPTADORES O TARJETAS DE RED.


2. MEDIOS FÍSICO DE CONEXIÓN: CABLES Y CONECTORES.
3. CONCENTRADORES (HUBS) O SWITCHES.

1. Adaptadores de red:

Si bien hasta ahora hablamos de las topologías o formas de conexión de computadoras entre sí
por intermedio de cables, todavía no se dijo nada sobre los tipos de cables existentes y sobre
cómo se conectan los cables a las computadoras.

Una tarjeta de red no es más que una placa o adaptador físico de red que permite establecer la
comunicación entre diversas computadoras de la red.

16
2. Medios físicos de conexión (medios de transmisión y conectores):

Los medios físicos para la transmisión de datos son los siguientes:

Cable coaxil Cable UTP (Par Trenzado)

Fibra Óptica. Microondas, usadas en redes inalámbricas

Los elementos físicos para la conexión para cable COAXIAL son los siguientes conectores:

• Conectores BNC ( Macho Y Hembra).


• T BNC.
• Terminadores BNC.

17
Otros elementos físicos para la conexión para cable UTP son los siguientes:

• Conector RJ 45 macho (PLUG).


• Conector RJ 45 hembra (JACK).

3. Concentradores (Hubs) o Switches

Hub

Switche

1.10. Cableado estructurado

Si bien la palabra estructurado no es común que figure en los diccionarios que no sean técnicos,
sabemos que proviene de estructura.

La definición literal de estructura es la siguiente: "Distribución en forma ordenada de las partes


que componen un todo".

Si traducimos esta definición al área que nos respecta, podemos empezar diciendo que el
cableado estructurado deberá respetar a ciertas normas de distribución, no solo de los cables en
sí, sino también de todos los dispositivos involucrados, como ser los conectores de lo que
hablamos anteriormente.

Cuando nos referimos a distribución, hablamos de la disposición física de los cables y los demás
accesorios.

Para dar un ejemplo práctico, no podemos llamar cableado estructurado a un cableado UTP de la
instalación de la red, en el cual los cables estén tendidos de cualquier manera.

Al habla de orden, hablamos por un lado de la prolijidad de una instalación, pero también
estamos diciendo que las instalaciones no podrán llevarse a cabo como se les ocurra a los
instaladores, sino que deberán cumplir ciertas normas técnicas, como la norma EIA/TIA 586 A.

18
Otra de las características del Cableado Estructurado es que debe brindar flexibilidad de
conexión; esto significa que no tendremos que cambiar todo el cableado o hacer complejas
extensiones, cuando necesitemos agregar una computadora a la res o mudar un equipo de una
oficina a otra.

Ventajas Del Cableado Estructurado

• Permite realizar instalaciones de cables para datos y telefonía utilizando la misma


estructura, es decir usando el cable, los mismos conectores, herramientas, etc.

Si una empresa necesita realizar el cableado para la red (para datos) y para telefonía va a
optar por una solución que le ofrezca un cableado unificado, que sirva para ambos
servicios.

• Otra ventaja adicional está dada por la flexibilidad del cableado estructurado, que
veremos con un ejemplo: si por una reconfiguración de la oficina, necesitamos conectar
un teléfono donde había un puesto de computación, podremos hacerlo mediante una
operación sencilla, sin tener que instalar nuevos cables, no agujerear paredes.

Esta operación, consiste solamente en desconectar un cable y reconectarlo en otro lado.

Pasos para la Instalación de una Red con Cableado Estructurado

Los pasos a principales que debería seguir un instalador son los siguientes:

1. Revisar los componentes de hardware de la red.


2. Determinar el mapa del cableado.
3. Establecer en base a lo anterior, los materiales necesarios.
4. Realizar el cableado propiamente dicho, y la colocación de accesorios.
5. Probar el funcionamiento del cableado.

1- Revisar Los Componentes De Hardware De La Red.

Un buen instalador debe consultar con el administrador de la red o con el servicio que mantiene
el hardware de la empresa, si el equipamiento que poseen va a servir para ser conectado al
cableado a realizar.

Es decir que debemos relevar que elementos posee la empresa y ver cuáles sirven y cuales no
para que podamos utilizar los elementos seleccionados en la instalación de la red.

2- Determinar El Mapa Del Cableado

Este paso es la determinación del mapa o plano del cableado.

Esta etapa se basa principalmente en el relevamiento lugar en el que se realizará la instalación


del cableado estructurado.

19
Consiste en varias tareas en donde la complejidad dependerá del edificio en que se va a instalar
la red.

Estas tareas involucran la medición de las distancias de los distintos ambientes, la cantidad de
agujeros que se deben realizar en las paredes, el tipo de pared con las que nos encontraremos, es
decir si se pueden agujerearse con facilidad o no), la determinacion de por dónde y cómo van a
pasar los cables y además es ideal poseer un plano de la planta para poder guiarse mejor y armar
sobre el mismo el mapa de la instalación.

A continuación, podemos ver un mapa de la planta que nos será de gran utilidad

Es importante también concluir la instalación en el tiempo acordado, de lo contrario le estaremos


restando tiempo a otra obra que ya estaba prevista.

3- Materiales Necesarios Para El Cableado

Un buen cálculo en la compra de los materiales podrá ahorrar tiempo y dinero.

Es común que, por errores en el relevamiento previo, nos demos cuenta que faltan materiales y
haya que salir corriendo de "apuro" a conseguirlos en algún proveedor cercano a la obra.

En el siguiente esquema vemos los pasos primordiales para poder armar un presupuesto de
cableado sin pasar sorpresas inesperadas.

• Relevamiento previo del edificio.


• Cálculo de materiales necesarios.
• Tiempo estimado de ejecución (costo de la mano de obra).
• Presupuesto final.

4- Realización Del Cableado

Esta etapa se realiza a través de:

La colocación de alojamientos para los cables ya sean, canaletas, zócalos, caños, bandejas, etc.

20
Una vez fijados los alojamientos para sostener los cables, se procede al tendido de los cables
sobre los mismos.

Y por último la colocación en las paredes los conectores (Plugs y Jaks RJ45) y san la
terminación final del trabajo como veremos en la siguiente figura:

5- Prueba Del Cableado

En general la prueba del cableado se realiza, en general, fuera del horario de trabajo de la
empresa y consiste en la conexión final de los equipos y la prueba de acceso de los mismos a los
recursos de la red y la velocidad de transmisión.

Componentes De Un Cableado Estructurado


Si bien conocemos los componentes principales, como ser el cable UTP y los conectores RJ45
(plug y jack), desarrollaremos a continuación el resto de los elementos involucrados en este tipo
de cableado.

Lista De Componentes Utilizados:

1. Cable UTP.
2. Jack RJ45.
3. Plug RJ45.
4. Elementos para el alojamiento de cables (canaletas de cable, bandejas, caños, zócalos).
5. Rosetas.
6. Racks.
7. Patch Panels (patchetas).
8. Patch Cords.

21
2. Cliente (informática)
El cliente es una aplicación informática que se utiliza para acceder a los servicios que ofrece un
servidor, normalmente a través de una red de telecomunicaciones.

El término se usó inicialmente para los llamados terminales tontos, dispositivos que no eran
capaces de ejecutar programas por sí mismos, pero podían conectarse a un ordenador central y
dejar que éste realizase todas las operaciones requeridas, mostrando luego los resultados al
usuario. Se utilizaban sobre todo porque su costo en esos momentos era mucho menor que el de
un ordenador.

Muchas redes utilizan un terminal tonto en lugar de puestos de trabajo para la entrada de datos.
En estos sólo se exhiben datos o se introducen. Este tipo de terminales, trabajan contra un
servidor, que es quien realmente procesa los datos y envía pantallas de datos a los terminales.

Actualmente se suelen utilizar para referirse a programas que requieren específicamente una
conexión a otro programa, al que se denomina servidor y que suele estar en otra máquina. Ya no
se utilizan por criterios de coste, sino para obtener datos externos (por ejemplo páginas web,
información bursatil o bases de datos), interactuar con otros usuarios a través de un gestor central
(como por ejemplo los protocolos bittorrent o IRC), compartir información con otros usuarios
(servidores de archivos y otras aplicaciones Groupware) o utilizar recursos de los que no se
dispone en la máquina local (por ejemplo impresión)

Uno de los clientes más utilizados, sobre todo por su versatilidad, es el navegador web. Muchos
servidores son capaces de ofrecer sus servicios a través de un navegador web en lugar de requerir
la instalación de un programa específico.

2.1. Tipos de Clientes

Existen varios tipos de clientes, dependiendo de la cantidad de tareas que realice el cliente en
comparación con el servidor.

Almacenamiento Proceso de
Tipo de Cliente
de datos local datos local
Cliente pesado Sí Sí
Cliente híbrido No Sí
Cliente liviano No No

1. Cliente pesado

Un cliente pesado tiene capacidad de almacenar los datos y procesarlos, pero sigue necesitando
las capacidades del servidor para una parte importante de sus funciones. Un cliente de correo
electrónico suele ser un cliente pesado. Puede almacenar los mensajes de correo electrónico del
usuario, trabajar con ellos y redactar nuevos mensajes, pero sigue necesitando una conexión al
servidor para enviar y recibir los mensajes.

22
2. Cliente híbrido

Un cliente híbrido no tiene almacenados los datos con los que trabaja, pero sí es capaz de
procesar datos que le envía el servidor. Muchos programas de colaboración almacenan
remotamente los datos para que todos los usuarios trabajen con la misma información, y utilizan
clientes híbridos para acceder a esa información.

3. Cliente liviano

Un cliente liviano no tiene capacidad de procesamiento y su única función es recoger los datos
del usuario, dárselos al servidor, y mostrar su respuesta. Los primeros navegadores web eran
clientes livianos, simplemente mostraban las páginas web que solicitaba el usuario. Actualmente,
el uso de lenguajes de script, programas Java y otras funciones de DHTML dan una capacidad de
procesamiento a los navegadores, por lo que se consideran clientes Híbridos.

En la siguiente figura se muestra la comparación en tamaño entre un cliente liviano y un cliente


pesado. El cliente híbrido pudiera ser de cualquier tamaño entre estos dos.

23
3. Servidor
En informática, un servidor es una computadora que, formando parte de una red, provee
servicios a otros denominados clientes.

También se suele denominar con la palabra servidor a:

• Una aplicación informática o programa que realiza algunas tareas en beneficio de otras
aplicaciones llamadas clientes. Algunos servicios habituales son los servicios de
archivos, que permiten a los usuarios almacenar y acceder a los archivos de una
computadora y los servicios de aplicaciones, que realizan tareas en beneficio directo del
usuario final. Este es el significado original del término. Es posible que un ordenador
cumpla simultáneamente las funciones de cliente y de servidor.

• Una computadora en la que se ejecuta un programa que realiza alguna tarea en beneficio
de otras aplicaciones llamadas clientes, tanto si se trata de un ordenador central
(mainframe), un miniordenador, un ordenador personal, una PDA o un sistema integrado;
sin embargo, hay computadoras destinadas únicamente a proveer los servicios de estos
programas: estos son los servidores por antonomasia.

Un servidor no es necesariamente una máquina de última generación grande y monstruosa, no es


necesariamente un superordenador; un servidor puede ser desde una computadora vieja, hasta
una máquina sumamente potente (ej.: servidores web, bases de datos grandes, etc. Procesadores
especiales y hasta varios gigabytes de memoria). Todo esto depende del uso que se le dé al
servidor. Si usted lo desea, puede convertir al equipo desde el cual usted está leyendo ésto en un
servidor instalando un programa que trabaje por la red y a la que los usuarios de su red ingresen
a través de un programa de servidor web como Apache.

A lo cual podemos llegar a la conclusión de que un servidor también puede ser un proceso que
entrega información o sirve a otro proceso. El modelo Cliente-servidor no necesariamente
implica tener dos ordenadores, ya que un proceso cliente puede solicitar algo como una
impresión a un proceso servidor en un mismo ordenador.

24
3.1. Tipos de servidores

En las siguientes listas, hay algunos tipos comunes de servidores y de su propósito.

• Servidor de archivo: almacena varios tipos de archivos y los distribuye a otros clientes
en la red.

• Servidor de impresiones: controla una o más impresoras y acepta trabajos de impresión


de otros clientes de la red, poniendo en cola los trabajos de impresión (aunque también
puede cambiar la prioridad de las diferentes impresiones), y realizando la mayoría o todas
las otras funciones que en un sitio de trabajo se realizaría para lograr una tarea de
impresión si la impresora fuera conectada directamente con el puerto de impresora del
sitio de trabajo.

• Servidor de correo: almacena, envía, recibe, enruta y realiza otras operaciones


relacionadas con email para los clientes de la red.

• Servidor de fax: almacena, envía, recibe, enruta y realiza otras funciones necesarias para
la transmisión, la recepción y la distribución apropiada del fax.

• Servidor de la telefonía: realiza funciones relacionadas con la telefonía, como es la de


contestador automático, realizando las funciones de un sistema interactivo para la
respuesta de la voz, almacenando los mensajes de voz, encaminando las llamadas y
controlando también la red o el Internet, p. ej., la entrada excesiva del IP de la voz
(VoIP), etc.

• Servidor proxy: realiza un cierto tipo de funciones a nombre de otros clientes en la red
para aumentar el funcionamiento de ciertas operaciones (p. ej., prefetching y depositar
documentos u otros datos que se soliciten muy frecuentemente), también sirve seguridad,
esto es, tiene un Firewall. Permite administrar el acceso a internet en una Red de
computadoras permitiendo o negando el acceso a diferentes sitios Web.

• Servidor del acceso remoto (RAS): controla las líneas de módem de los monitores u
otros canales de comunicación de la red para que las peticiones conecten con la red de
una posición remota, responden llamadas telefónicas entrantes o reconocen la petición de
la red y realizan los chequeos necesarios de seguridad y otros procedimientos necesarios
para registrar a un usuario en la red.

• Servidor de uso: realiza la parte lógica de la informática o del negocio de un uso del
cliente, aceptando las instrucciones para que se realicen las operaciones de un sitio de
trabajo y sirviendo los resultados a su vez al sitio de trabajo, mientras que el sitio de
trabajo realiza el interfaz operador o la porción del GUI del proceso (es decir, la lógica de
la presentación) que se requiere para trabajar correctamente.

• Servidor web: almacena documentos HTML, imágenes, archivos de texto, escrituras, y


demás material Web compuesto por datos (conocidos colectivamente como contenido), y
distribuye este contenido a clientes que la piden en la red.

25
• Servidor de Base de Datos: (database server) provee servicios de base de datos a otros
programas u otras computadoras, como es definido por el modelo cliente-servidor.
También puede hacer referencia a aquellas computadoras (servidores) dedicadas a
ejecutar esos programas, prestando el servicio.

• Servidor de reserva: tiene el software de reserva de la red instalado y tiene cantidades


grandes de almacenamiento de la red en discos duros u otras formas del almacenamiento
(cinta, etc.) disponibles para que se utilice con el fin de asegurarse de que la pérdida de
un servidor principal no afecte a la red. Esta técnica también es denominada clustering.

• Servidor de nombres de dominio: un Servidor de Nombres de Dominio (DNS) o


“servidor de nombres” es un servidor que mapea o conecta un nombre de dominio con
una dirección de IP específica. En definitiva, indica el dominio (y todo el tráfico del
dominio) al que acceder en Internet.

26
4. Comunicaciones en Red

4.1. Introducción

A continuación, se presenta una amplia introducción al tema de las comunicaciones TCP/IP, que
son la base del modelo sobre el que tiene sus fundamentos de Internet.

Una red de ordenadores es un conjunto de máquinas o dispositivos que están interconectados a


través de algún medio que les permite intercambiar datos. Cada uno de los dispositivos de la red
puede considerarse como un nodo, y cada uno de estos nodos está identificado mediante una
dirección única. La dirección de cada dispositivo debe ser única, para permitir distinguir a ese
dispositivo de cualquier otro. Las direcciones son cifras con las cuales los ordenadores trabajan
con facilidad, pero son muy difíciles de recordar para los humanos; por lo tanto, algunas redes
también proporcionan nombres, que son más fáciles de recordar.

Las redes actuales utilizan para la transferencia de datos un concepto conocido como packet
switching. Los datos son encapsulados en paquetes que son transferidos desde el origen hasta el
destino, en donde los datos se van extrayendo de uno o más paquetes para reconstruir el mensaje
original.

En una red se trata de pasar la información de una máquina a otra, o viceversa; decidiendo cada
máquina qué es lo que quiere hacer con la información que le llega. Una de las principales
características de Java, es precisamente su tratamiento de la red, donde Java abstrae todos los
detalles de manejo a bajo nivel de la red, dejándole ese trabajo a la Máquina Virtual Java.

El modelo de programación que se utiliza es el de archivos; de hecho, se puede comparar una


conexión de red (un socket) con un canal. Además, la capacidad de manejo de múltiples hilos de
ejecución que proporciona Java, es muy cómoda a la hora de poder manejar múltiples
conexiones a la vez.

Lo más utilizado en redes de ordenadores es la aplicación cliente – servidor. Servidor es aquel


que escucha y está siempre a la espera de que el Cliente se conecte y comenzar la conversación
entre ambos.

4.2. Conceptos Básicos de Redes

Protocolo de Comunicaciones

Para que dos o más ordenadores puedan conectarse a través de una red y ser capaces de
intercambiar datos de una forma ordenada, deben seguir un protocolo de comunicaciones que sea
aceptado por todos ellos.

27
El protocolo define las reglas que se deben seguir en la comunicación. Hay muchos protocolos
disponibles para ser utilizados; por ejemplo, el protocolo HTTP define como se van a comunicar
los servidores y navegadores Web y el protocolo SMTP define la forma de transferencia del
correo electrónico. Estos protocolos, son protocolos de aplicación que actúan al nivel de
superficie, pero también hay otros protocolos de bajo nivel que actúan por debajo del nivel de
aplicación y que son más complicados, aunque, afortunadamente, como programadores Java, no
será necesario tener excesivo conocimiento de los protocolos de bajo nivel; nada que vaya más
allá del conocimiento de su existencia.

Capas de Red

Las redes están separadas lógicamente en capas, o niveles, o layers; desde el nivel de aplicación
en la parte más alta hasta el nivel físico en la parte más baja. Porque al presentarse un problema
de tamaño considerable, la solución más óptima comienza por dividirlo en pequeñas secciones,
para posteriormente proceder a solventar cada una de ellas independientemente.

La gente de JavaSoft se ha encargado de ocultar toda la parafernalia que involucra el manejo de


los protocolos de redes de bajo nivel y las capas de más bajo nivel del modelo de
comunicaciones. La única capa interesante para el usuario y el programador es el Nivel de
Aplicación, que es el que se encarga de coger los datos en una máquina desde esta capa y
soltarlos en la otra máquina en esta misma capa, los pasos intermedios y los saltos de capas que
se hayan producido por el camino, no resultan de interés, ya que su uso está oculto en el lenguaje
Java.

El modelo OSI
El estándar internacional para Sistemas Abiertos de Interconexión, OSI (por su sigla en inglés:
Open Systems Interconnection), se define en el documento ISO/IEC 7498-1, emanado de la
International Standards Organization y la International Electrotechnical Comission. El estándar
completo está disponible como publicación “ISO/IEC 7498-1: 1994”, en
http://standards.iso.org/ittf/PubliclyAvailableStandards/x

El modelo OSI divide el tráfico de la red en una cantidad de capas. Cada capa es independiente
de las capas que la rodean y cada una se apoya en los servicios prestados por la capa inferior
mientras que proporciona sus servicios a la capa superior. La separación entre capas hace que sea
fácil diseñar una pila de protocolos (protocol stack) muy elaborada y confiable, tal como la
difundida pila TCP/IP. Una pila de protocolos es una implementación real de un marco de
comunicaciones estratificado. El modelo OSI no define los protocolos que van a usarse en una
red en particular, sino que simplemente delega cada “trabajo” de comunicaciones a una sola capa
dentro de una jerarquía bien definida.

Mientras que la especificación ISO/IEC 7498-1 determina cómo deberían interactuar las capas,
los detalles de la implementación real se dejan al fabricante. Cada capa puede implementarse en
el hardware (es más común para las capas inferiores), o en el software. Siempre y cuando la
interfaz entre capas se adhiera al estándar, los instaladores son libres de usar cualquier medio a
su disposición para construir su pila de protocolos. Esto quiere decir que cualquier capa de un
28
fabricante A puede operar con la misma capa de un fabricante B (suponiendo que las
especificaciones relevantes se implementen e interpreten correctamente).

A continuación, se presenta un breve bosquejo del modelo de redes OSI de siete capas.
Capa Nombre Descripción
7 Aplicación La Capa de Aplicación es la capa con la que la mayoría de los usuarios
tiene contacto, y es el nivel en el que ocurre la comunicación humana. Por
ejemplo, HTTP, FTP, y SMTP son protocolos de la capa de aplicación. El
usuario se ubica por encima de esta capa, interactuando con la aplicación.

6 Presentación La Capa de Presentación tiene que ver con representación de datos,


antes de que lleguen a la aplicación. Esto incluye codificación MIME
(Multipurpose Internet Mail Extensions), compresión de datos,
comprobación del formato, ordenación de los bytes, etc.

5 Sesión La Capa de Sesión maneja la sesión de comunicación lógica entre


aplicaciones. NetBIOS y RPC son dos ejemplos de protocolos de la capa 5.

4 Transporte La Capa de Transporte provee un método para obtener un servicio


particular en un nodo de red específico. Algunos ejemplos de protocolos
que operan en esta capa son TCP y UDP. Algunos protocolos de la capa de
transporte (como TCP), garantizan que todos los datos lleguen a destino y
se reorganicen y entreguen a la próxima capa en el orden apropiado. UDP
es un protocolo “no orientado a conexión” comúnmente usado para señales
de video y audio de flujo continuo.

3 Red IP (el protocolo de Internet) es el más común de la Capa de Red. Esta es


la capa donde ocurre el enrutamiento. Se encarga de transferir los paquetes
desde la capa de enlace local a la de otras redes. Los enrutadores cumplen
esta función en una red por medio de, al menos, dos interfaces de red, una
en cada una de las redes que se van a interconectar. Cada nodo en Internet
tiene una dirección IP exclusiva. Otro protocolo crítico de Capa de Red es
ICMP, que es un protocolo especial que proporciona varios mensajes
necesarios para la adecuada operación de IP. Esta capa a menudo se
denomina la Capa de Internet.

2 Enlace de datos Cada vez que dos o más nodos comparten el mismo medio físico (por
ejemplo, varios computadores conectados a un concentrador (hub), o una
habitación llena de dispositivos inalámbricos que usan el mismo canal de
radio), usan la Capa de Enlace de Datos para comunicarse. Los
ejemplos más comunes de protocolos de enlace de datos son Ethernet,
Token Ring, ATM, y los protocolos de redes inalámbricas (802.11a/b/g).
La comunicación en esta capa se define como de enlace-local porque todos
los nodos conectados a esta capa se comunican directamente entre sí. Esta
capa también se conoce como capa de Control de Acceso al Medio (MAC
en inglés). En redes modeladas de acuerdo con Ethernet, los nodos se
identifican por su dirección MAC. Esta es un número exclusivo de 48 bits
asignado de fábrica a todo dispositivo de red.

1 Física La Capa Física es la capa más baja en el modelo OSI, y se refiere al


medio físico real en el que ocurre la comunicación. Este puede ser un cable
CAT5 de cobre, un par de fibras ópticas, ondas de radio, o cualquier otro
medio capaz de transmitir señales. Cables cortados, fibras partidas, e
interferencia de RF constituyen, todos, problemas de capa física.

Las capas de este modelo están numeradas del 1 al 7, con el 7 en el tope. Esto se hace para
reforzar la idea de que cada capa está basada y depende de la capa de abajo. Imagine el modelo
OSI como un edificio, con sus bases en la capa 1. Las próximas capas, como los pisos sucesivos,

29
y el techo, como la capa 7. Si se remueve una sola capa, el edificio no se sostiene. De manera
semejante, si se incendia el piso 4, nadie podría atravesarlo en ninguna de las dos direcciones.

Las primeras tres capas (Física, Enlace de Datos y Red) ocurren todas “en la red”. Es decir, la
actividad en estas capas va a estar determinada por la configuración de los cables, conmutadores,
enrutadores y otros dispositivos semejantes. Un conmutador (switch) de red puede distribuir
paquetes usando sólo direcciones MAC, así que necesita implementar sólo las capas 1 y 2. Un
enrutador sencillo puede enrutar paquetes usando sólo sus direcciones IP, así que necesita
implementar sólo las capas 1 a 3. Un servidor web o un computador portátil (laptop) ejecutan
aplicaciones, así que deben implementar las siete capas. Algunos enrutadores avanzados pueden
implementar desde la capa 4 en adelante lo que les permite tomar decisiones basadas en la
información de alto nivel contenida en un paquete, como el nombre de un sitio web, o los
adjuntos de un correo electrónico.

El modelo OSI es internacionalmente reconocido, y es considerado como el modelo de red


definitivo y completo. Proporciona un esquema para los fabricantes e implementadores de
protocolos de red que puede ser usado para construir dispositivos inter-operacionales en
cualquier parte del mundo. Desde la perspectiva de un ingeniero de redes, o una persona que
trate de localizar una falla, el modelo OSI puede parecer innecesariamente complejo. En
particular, la gente que construye o localiza fallas en redes TCP/IP rara vez se encuentra con
problemas en las capas de Sesión o Presentación. Para la mayor parte de las implementaciones
de redes, el modelo OSI puede ser simplificado en un conjunto menor de cinco capas.

OSI (International Standards Organisation), Organización de Estándares Internacional, creó un


modelo de interconexión de sistemas abiertos, este modelo divide en siete capas el proceso de
transmisión de información entre equipos informáticos, desde el hardware físico, hasta las
aplicaciones de red que maneja el usuario. Estas capas son: física, de enlace de datos, de red, de
transporte, de sesión, de presentación y, de aplicación. Cada nuevo protocolo de red que se
define se suele asociar a uno (o a varios) niveles del estándar OSI.

Internet dispone de un modelo más sencillo; no define nada en cuanto al aspecto físico de los
enlaces, o a la topología o clase de red de sus subredes y, por lo tanto, dentro del modelo OSI,
sólo existe una correlación con los niveles superiores.

La figura siguiente muestra la correlación existente entre el modelo teórico de capas o niveles de
red propuestos por OSI, y el modelo empleado por las redes TCP/IP.

30
Las aplicaciones que trabajan a un cierto nivel o capa, sólo se comunican con sus iguales en los
sistemas remotos, es decir, a nivel de aplicación, un navegador sólo se entiende con un servidor
Web, sin importarle para nada cómo le llega la información. Este mismo principio es el que se
emplea para el resto de las capas.

De todo esto, se tiene tres ideas fundamentales:

1. En primer lugar, que TCP/IP opera sólo en los niveles superiores de red, resultándole
indiferente el conjunto de protocolos que se entienden con los adaptadores de red Token
Ring, Ethernet, ATM, etc., que se encuentren por debajo.
2. En segundo lugar, que IP es un protocolo de datagramas que proporciona un interfaz
estándar a protocolos superiores. Y,
3. En tercer lugar, que dentro de estos protocolos superiores se incluyen TCP y UDP, los
cuales ofrecen prestaciones adicionales que ciertas aplicaciones de red necesitan.

Internet Protocol (IP)

El protocolo de Internet se utiliza por debajo del Nivel de Aplicación y es el encargado de mover
datos en forma de paquetes entre un origen (servidor) y un destino (cliente) y que, como bien
indica su nombre, es el protocolo que normalmente se utiliza en Internet.

IP es un protocolo simple, fácilmente implementable, de pequeñas unidades de datos o


datagramas, que proporciona un interfaz estándar a partir del cual el resto de los protocolos y
servicios pueden ser construidos, sin tener que preocuparse de las diferencias que existan entre
las distintas subredes por la cuales circulen los datos.

Todo dispositivo conectado a Internet o a cualquier red basada en TCP/IP, posee al menos una
dirección IP, un identificador que define unívocamente al dispositivo que lo tiene asignado en la
red.

Un datagrama IP se encuentra dividido en dos partes: cabecera y datos. Dentro de la cabecera se


encuentran, entre otros campos, la dirección IP del equipo origen y la del destino, el tamaño y un
número de orden.

IP opera entre un sistema local conectado a Internet y su router o encaminador más próximo, así
como entre los distintos encaminadores que forman la red. Cuando un datagrama llega a un
encaminador, éste determina, a partir de su dirección IP de destino, hacia cuál de sus conexiones
de salida ha de dirigir el datagrama que acaba de recibir. Por desgracia, en cuanto al transporte,
IP provee un servicio que intenta entregar los datos al equipo destino, pero no puede garantizar
la integridad, e incluso la recepción de esos datos. Por ello, la mayoría de las aplicaciones hacen
uso de un protocolo de más alto nivel que ofrezca el grado de fiabilidad necesario.

Cada datagrama IP es independiente del resto, por lo que cada uno de ellos es llevado a su
destino por separado. La longitud del datagrama es variable, pudiendo almacenar hasta 65
31
Kbytes de datos; si el paquete de datos (TCP o UDP) sobrepasa ese límite, o el tamaño de la
unidad de datos de la red que se encuentra por debajo es más pequeño que el datagrama IP, el
mismo protocolo IP lo fragmenta, asignándole un número de orden, y distribuye empleando el
número de datagramas que sea necesario.

Transmission Control Protocol (TCP)

El protocolo TCP se incorporó al protocolo IP para proporcionar a éste la posibilidad de dar


reconocimiento de la recepción de paquetes y poder pedir la retransmisión de los paquetes que
hubiesen llegado mal o se hubiesen perdido. Además, TCP hace posible que todos los paquetes
lleguen al destinatario, juntos y en el mismo orden en que fueron enviados.

Por lo tanto, es habitual la utilización de los dos acrónimos juntos, TCP/IP, ya que los dos
protocolos constituyen un método más fiable de encapsular un mensaje en paquetes, de enviar
los paquetes a un destinatario, y de reconstruir el mensaje original a partir de los paquetes
recibidos.

TCP, en resumen, ofrece un servicio de transporte de datos fiable, que garantiza la integridad y
entrega de los datos entre dos procesos o aplicaciones de máquinas remotas. Es un protocolo
orientado a la conexión, en primer lugar, el equipo local solicita al remoto el establecimiento de
un canal de comunicación; y solamente cuando ese canal ha sido creado, y ambas máquinas
están preparadas para la transmisión, empieza la transferencia de datos real.

User Datagram Protocol (UDP)

UDP es un protocolo menos fiable que el TCP, ya que no garantiza que una serie de paquetes
lleguen en el orden correcto, e incluso no garantiza que todos esos paquetes lleguen a su destino.
Los procesos que hagan uso de UDP han de implementar, si es necesario, sus propias rutinas de
verificación de envío y sincronización. Esto porque hay ocasiones en las que no se quiere
incurrir en una sobrecarga del sistema o en la introducción de retrasos por causa de cumplir esas
garantías.

Por ejemplo, si un ordenador está enviando la fecha y la hora a otro ordenador cada 100
milisegundos para que la presente en un reloj digital, es preferible que cada paquete llegue lo
más rápidamente posible, incluso aunque ello signifique la pérdida de algunos de los paquetes.

Como programador Java, el lector tiene en sus manos la elección del protocolo que va a utilizar
en sus comunicaciones, en función de las características de velocidad y seguridad que requiera la
comunicación que desea establecer.

Dirección IP

Cada ordenador o dispositivo conectado a una red TCP/IP dispone de una dirección IP única de 4
bytes (32 bits) para IPv4 (6 bytes (48 bits) para IPv6), en donde, según la clase de red que se
32
tenga y la máscara, parte de los 4 bytes representan a la red, parte a la subred (donde proceda) y
parte al dispositivo final o nodo específico de la red. Por ejemplo, la figura siguiente muestra la
representación de los distintos números de una dirección IP de un nodo perteneciente a una
subred de clase B (máscara 255.255.0.0) para IPv4.

Por razones administrativas, en los primeros tiempos del desarrollo del protocolo IP, se
establecieron cinco rangos de direcciones, dentro del rango total de 32 bits de direcciones IP
disponibles para IPv4, denominando a esos subrangos, clases. Cuando una determinada
organización requiere conectarse a Internet, solicita una clase, de acuerdo al número de nodos
que precise tener conectados a la Red. La administración referente a la cesión de rangos la
efectúa InterNIC (Internet Network Information Center), aunque existen autoridades que, según
las zonas, gestionan dominios locales.

Los subrangos se definen en orden ascendente de direcciones IP, por lo cual, a partir de una
dirección IP es fácil averiguar el tipo de clase de Internet con la que se ha conectado. El tipo de
clase bajo la que se encuentra una dirección IP concreta viene determinado por el valor del
primer byte de los cuatro que la componen o, lo que es igual, el primer número que aparece en la
dirección IP. Las clases toman nombre de la A a la E, aunque las más conocidas son las A, B y
C.

En Internet, las clases de las redes se describen a continuación:

• Las redes clase A. Son las que comienzan con un número entre el 1 y el 126, que permiten
otorgar el mayor número de direcciones IP (16,7 millones), por lo que se asignan a grandes
instituciones educativas o gubernamentales.
• Las redes clases B. (65536 direcciones por clase), suelen concederse a grandes empresas o
corporaciones y, en general, a cualquier organización que precise un importante número de
nodos.
• Las redes clase C. (256 direcciones) son las más comunes y habitualmente se asignan sin
demasiados problemas a cualquier empresa u organización que lo solicite.
• La red clase D. Se reserva a la transmisión de mensajes de difusión múltiple (multicast).
• La red clase E. Es la destinada a investigación y desarrollo.

La tabla siguiente resume estos datos:

33
Todo lo dicho antes solamente implica a la asignación de direcciones dentro de Internet. Si se
diseña una red TCP/IP que no vaya a estar conectada a la Red, se puede hacer uso de cualquier
conjunto de direcciones IP. Solamente existen cuatro limitaciones, intrínsecas al protocolo, a la
hora de escoger direcciones IP, pero que reducen en cierta medida el número de nodos
disponibles por clase que se indicaban en la tabla anterior. Estas 4 limitaciones son:

1. La primera es que no se pueden asignar direcciones que comiencen por 0; dichas direcciones
hacen referencia a nodos dentro de la red actual.
2. La segunda es que la red 127 se reserva para los procesos de resolución de problemas y
diagnosis de la red; de especial interés resulta la dirección 127.0.0.1, bucle interno
(loopback) de la estación de trabajo local.
3. La tercera consiste en que las direcciones IP de nodos no pueden terminar en 0, o en
cualquier otro valor base del rango de una subred; porque es así como concluyen las redes.
4. La cuarta, cuando se asignan direcciones a nodos, no se pueden emplear el valor 255, o
cualquier otro valor final del rango de una subred. Este valor se utiliza para enviar mensajes a
todos los elementos de una red (broadcast); por ejemplo, si se envía un mensaje a la dirección
192.168.37.255, se estaría enviando en realidad a todos los nodos de la red de clase C
192.168.37.xx.

Ahora bien, si se quiere que una red local tenga acceso exterior, hay una serie de restricciones
adicionales, por lo que hay una serie de direcciones reservadas que, a fin de que pudiesen ser
usadas en la confección de redes locales, fueron excluidas de Internet. Estas direcciones se
muestran en la siguiente tabla:

Para la creación de una intranet, se debería escoger direcciones IP para la red dentro de alguno
de los rangos reservados de la tabla anterior, y emplear un servidor proxy, o cualquier otro
mecanismo que enmascare las direcciones IP de esa intranet, de forma que todos los puestos de
la red local utilicen una única dirección IP a la hora de salir a la Red.

Actualmente, se intenta expandir el número de direcciones únicas a un número mucho mayor,


utilizando 128 bits. E.R. Harold, en su libro Java Network Programming, dice que el número de
direcciones únicas que se podría alcanzar representando las direcciones con 128 bits es
1.6043703E32.

34
Dominios

El dominio, está constituido por una cadena de caracteres, que es mucho más fácil de recordar
para los humanos, que un número de dirección IP. Así, el dominio para la dirección IP
204.160.241.98 es java.sun.com.

El Sistema de Nombres de Dominio (DNS, Domain Name System) fue desarrollado para realizar
la conversión entre los dominios y las direcciones IP. De este modo, cuando el lector entra en
Internet a través de su navegador e intenta conectarse con un dominio determinado, el navegador
se comunica en primer lugar con un servidor DNS para conocer la dirección IP numérica
correspondiente a ese dominio. Esta dirección numérica IP, y no el nombre del dominio, es la
que va encapsulada en los paquetes y es la que utiliza el protocolo Internet para enrutar estos
paquetes desde el ordenador del lector hasta su destino.

Java proporciona clases para la manipulación y conocimiento de direcciones y dominios,


concretamente la clase InetAddress permite encontrar un nombre de dominio a partir de su
dirección IP; y viceversa, encontrar la dirección IP que corresponde a un dominio determinado.

Cada proveedor de acceso a Internet dispone de un bloque de direcciones reservadas; cuando se


conecta con él y se accede a Internet, el proveedor asigna una dirección de ese bloque, que
durará solamente el tiempo que dure la conexión. Si el lector desconecta y vuelve a conectar,
casi seguro que la dirección IP para esta nueva sesión será diferente de la utilizada en la anterior
conexión; lo que el lector no tendrá un dominio específico.

WWW es una sigla que significa: World Wide Web (Red informática mundial, es un sistema de
distribución de documentos de hipertexto o hipermedia interconectados y accesibles vía Internet
– Web Mundo Amplio). El prefijo WWW al comienzo de las direcciones web debido a la
costumbre de nombrar a los host de Internet (los servidores) con los servicios que proporcionan.
De esa forma, por ejemplo, el nombre de host para un servidor web normalmente es "WWW",
para un servidor FTP se suele usar "ftp", y para un servidor de noticias, USENET, "news" o
"nntp" (en relación al protocolo de noticias NNTP). Estos nombres de host aparecen como
subdominio de DNS, como en "www.example.com".

El uso de estos prefijos no está impuesto por ningún estándar, de hecho, el primer servidor web
se encontraba en "nxoc01.cern.ch"29 e incluso hoy en día existen muchos sitios Web que no
tienen el prefijo "www". Este prefijo no tiene ninguna relación con la forma en que se muestra el
sitio web principal. El prefijo "www" es simplemente una elección para el nombre de
subdominio del sitio web.

Servicios y Puertos

Un servicio es una facilidad que proporciona el sistema, y cada uno de estos servicios está
asociado a un puerto.

Un puerto es una dirección numérica a través de la cual se procesa el servicio, es decir es una
dirección lógica proporcionada por el sistema operativo para poder responder.
35
Sobre un sistema Unix, por ejemplo, los servicios que proporciona ese sistema y los puertos
asociados por los cuales responde a cada uno de esos servicios, se indican en el archivo
/etc/services, y algunos de ellos son:

Puerto/Protocolo que
Nombre del servicio Alias del servicio
está asociado al servicio
daytime 13/udp
ftp 21/tcp
telnet 23/tcp telnet
smtp 25/tcp mail
http 80/tcp

Las comunicaciones de información relacionada con Web tienen lugar a través del puerto 80
mediante protocolo TCP. Para emular esto en Java, se utiliza la clase Socket. La fecha (daytime),
sin embargo, el servicio que coge la fecha y la hora del sistema, está ligado al puerto 13
utilizando el protocolo UDP. Un servidor que lo emule en Java usaría un objeto
DatagramSocket.

Teóricamente hay 65535 puertos disponibles, aunque antiguamente los puertos del 1 al 1023 (en
la actualidad también sobrepasan a 1023) están reservados al uso de servicios estándar
proporcionados por el sistema, quedando el resto libre para utilización por las aplicaciones de
usuario.

De no existir los puertos, solamente se podría ofrecer un servicio por máquina. Nótese que el
protocolo IP no sabe nada al respecto de los números de puerto, al igual que TCP y UDP no se
preocupan en absoluto por las direcciones IP.

Se puede decir que IP pone en contacto las máquinas, TCP y UDP establecen un canal de
comunicación entre determinados procesos que se ejecutan en tales equipos y, los números de
puerto se pueden entender como números de oficinas dentro de un gran edificio. El edificio
(equipo), tendrá una única dirección IP, pero dentro de él, cada tipo de negocio, en este caso
HTTP, FTP, etc., dispone de una oficina individual.

Servidores Proxy

Un servidor proxy actúa como interfaz entre los ordenadores de la red interna de una empresa e
Internet. Frecuentemente, el servidor proxy tiene posibilidad de ir almacenando un cierto número
de páginas web temporalmente en caché, para un acceso más rápido. Esto reduce en gran medida
el tiempo de espera por la descarga de la página y el tráfico, tanto dentro como fuera de la
empresa.

Uniform Resource Locator (URL)

Una URL , o dirección, es en realidad un puntero a un determinado recurso de un determinado


sitio de Internet.
36
La sintaxis general, para una dirección URL, sería:

protocolo://nombre_servidor[:puerto]/directorio/archivo#referencia

En donde:

• El protocolo utilizado para acceder al servidor (http, por ejemplo)


• El nombre_servidor o dominio
• El puerto de conexión (opcional)
• El directorio, y el nombre de un archivo determinado en el servidor (opcional a veces)
• Un punto de referencia dentro del archivo (opcional)

El puerto es opcional y normalmente no es necesario especificarlo si se está accediendo a un


servidor que proporcione sus servicios a través de los puertos estándar; tanto el navegador como
cualquier otra herramienta que se utilice en la conexión conocen perfectamente los puertos por
los cuales se proporciona cada uno de los servicios e intentan conectarse directamente a ellos por
defecto.

A veces el nombre del archivo se puede omitir, ya que el navegador incorporará


automáticamente el nombre de archivo index.html, index.php, index.jsp, etc.; cuando no se
indique ninguno, e intentará descargar ese archivo. Por ejemplo, si se quiere acceder a la
siguiente página principal, se puede utilizar cualquiera de las dos URL siguientes:

http://members.es.tripod.de/froufe/index.html
http://members.es.tripod.de/froufe/

Además de indicar el archivo o página a la que se desea acceder, también es posible indicar una
referencia, que se haya establecido dentro de esa página.

37
4.3. Sockets

Java proporciona dos formas diferentes de abordar la programación de comunicaciones a través


de red, al menos en lo que a la comunicación web concierne:

1. Las clases Socket, DatagramSocket y ServerSocket,


2. Las clases URL, URLEncoder y URLConnection.

Los sockets son puntos finales de enlaces de comunicaciones entre procesos. Los procesos los
tratan como descriptores de archivos, de forma que se pueden intercambiar datos con otros
procesos transmitiendo y recibiendo a través de sockets.

El tipo de sockets describe la forma en la que se transfiere información a través de ese socket, a
continuación, se describe cada uno:

1. Sockets Stream (TCP)

Es un servicio orientado a conexión, donde los datos se transfieren sin encuadrarlos en registros
o bloques. Si se rompe la conexión entre los procesos, éstos serán informados de tal suceso para
que tomen las medidas oportunas.

El protocolo de comunicaciones con streams es un protocolo orientado a conexión, ya que para


establecer una comunicación utilizando el protocolo TCP, hay que establecer en primer lugar una
conexión entre un par de sockets. Mientras uno de los sockets atiende peticiones de conexión
(servidor), el otro solicita una conexión (cliente). Una vez que los dos sockets estén conectados,
se pueden utilizar para transmitir datos en ambas direcciones.

2. Sockets Datagrama (UDP)

Es un servicio de transporte sin conexión, más eficientes que TCP, pero en su utilización no está
garantizada la fiabilidad. Los datos se envían y reciben en paquetes, cuya entrega no está
garantizada. Los paquetes pueden ser duplicados, perdidos o llegar en un orden diferente al que
se envió.

El protocolo de comunicaciones con datagramas es un protocolo sin conexión, es decir, cada vez
que se envíen datagramas es necesario enviar el descriptor del socket local y la dirección del
socket que debe recibir el datagrama. Como se puede ver, hay que enviar datos adicionales cada
vez que se realice una comunicación.

La ventaja es que se pueden acceder direcciones globales y el mismo mensaje llegará a muchas
máquinas a la vez.

38
3. Sockets Raw

Son sockets que dan acceso directo a la capa de software de red subyacente o a protocolos de
más bajo nivel. Se utilizan sobre todo para la depuración del código de los protocolos.

Diferencias entre Sockets Stream y Datagrama

La decisión de escoger un protocolo depende de la aplicación cliente/servidor que se esté


escribiendo; hay algunas diferencias entre los protocolos UDP o TCP, a continuación se presenta
una ayuda para tomar una decisión y decantar la utilización de sockets de un tipo:

1. En UDP, cada vez que se envía un datagrama, hay que enviar también el descriptor del
socket local y la dirección del socket que va a recibir el datagrama, luego los mensajes
son más grandes que los TCP. Como el protocolo TCP está orientado a conexión, hay que
establecer esta conexión entre los dos sockets antes de nada, lo que implica un cierto
tiempo empleado en el establecimiento de la conexión, que no es necesario emplear en
UDP.
2. En UDP hay un límite de tamaño de los datagramas, establecido en 64 kilobytes, que se
pueden enviar a una localización determinada, mientras que TCP no tiene límite; una vez
que se ha establecido la conexión, el par de sockets funciona como los streams: todos los
datos se leen inmediatamente, en el mismo orden en que se van recibiendo.
3. UDP es un protocolo desordenado, no garantiza que los datagramas que se hayan enviado
sean recibidos en el mismo orden por el socket de recepción. Al contrario, TCP es un
protocolo ordenado, garantiza que todos los paquetes que se envíen serán recibidos en el
socket destino en el mismo orden en que se han enviado.
4. Los datagramas son bloques de información del tipo lanzar y olvidar. Para la mayoría de
los programas que utilicen la red, el usar un flujo TCP en vez de un datagrama UDP es
más sencillo y hay menos posibilidades de tener problemas. Sin embargo, cuando se
requiere un rendimiento óptimo, y está justificado el tiempo adicional que supone realizar
la verificación de los datos, la comunicación a través de sockets TCP es un mecanismo
realmente útil.
5. En resumen, TCP parece más indicado para la implementación de servicios de red como
un control remoto (rlogin, telnet) y transmisión de archivos (ftp); que necesitan transmitir
datos de longitud indefinida. UDP es menos complejo y tiene una menor sobrecarga
sobre la conexión; esto hace que sea el indicado en la implementación de aplicaciones
cliente/servidor en sistemas distribuidos montados sobre redes de área local.

A continuación, se muestra un cuadro de las diferencias entre TCP Y UDP:

39
CARACTERÍSTICAS TCP UDP
Conexión de transmisión SI NO
Transferencia ordenada de los datagramas SI NO
Confiabilidad de transferencia de los datagramas SI NO
Más sencillo y menos posibilidad de tener problemas SI NO
Rendimiento óptimo y verificación de datos SI NO
Servidores de red SI NO
Límite de tamaño de los datagramas NO SI

4.4. Dominios de Comunicaciones

El mecanismo de sockets está diseñado para ser todo lo genérico posible. El socket por sí mismo
no contiene información suficiente para describir la comunicación que se establece entre
procesos. Los sockets operan dentro de dominios de comunicación, entre ellos se define si los
dos procesos que se comunican se encuentran en el mismo sistema o en sistemas diferentes y
cómo pueden ser direccionados.

Dominio Internet

Las comunicaciones intersistemas proporcionan acceso a TCP, ejecutado sobre IP (TCP/IP). De


la misma forma que el dominio Unix, el dominio Internet permite tanto sockets stream como
datagrama, pero además permite sockets de tipo Raw.

1. Los sockets stream permiten a los procesos comunicarse a través de TCP. Una vez
establecidas las conexiones, los datos se pueden leer y escribir a/desde los sockets como
un flujo (stream) de bytes. Algunas aplicaciones de servicios TCP son:

 File Tranfer Protocol, FTP


 Simple Mail Transfer Protocol, SMTP
 TELNET, servicio de conexión de terminal remoto

2. Los sockets datagrama permiten a los procesos utilizar el protocolo UDP para
comunicarse hacia, y desde, esos sockets por medio de bloques. UDP es un protocolo no
fiable y la entrega de los paquetes no está garantizada. Algunos de los servicios UDP son:

 Simple Network Management Protocol, SNMP


 Trivial File Transfer Protocol, TFTP (versión de FTP sin conexión)
 Versatile Message Transaction Protocol, VMTP (servicio fiable de entrega punto a
punto de datagramas independiente de TCP)

3. Los sockets raw proporcionan acceso al Internet Control Message Protocol, ICMP, y se
utiliza para comunicarse entre varias entidades IP.

40
4.5. Las clases para la programación

Para poder ejecutar los próximos programas es necesario estar conectado en una red, o a Internet,
para que funcionen correctamente, aunque es posible que también pueda hacerse sobre el mismo
ordenador si el lector usa un sistema operativo que lo soporte, Linux por ejemplo; en cualquier
otro caso, el resultado de la ejecución del ejemplo será una triste UnknownHostException.

localhost se utiliza para describir el ordenador local, la máquina en la que se está ejecutando la
aplicación, servidor. Cuando se conecta a una red IP, el ordenador local debe tener una dirección
IP, que se la puede conseguir de diferentes formas:

1. La clase InetAddress

La clase InetAddress proporciona objetos que se pueden utilizar para manipular tanto
direcciones IP como nombres de dominio; sin embargo, no se pueden instanciar estos objetos
directamente. La clase proporciona varios métodos estáticos que devuelven un objeto de tipo
InetAddress, a continuación se indican algunos.

• El método estático getByName() devuelve un objeto InetAddress representando el host


que se le pasa como parámetro. Este método se puede utilizar para determinar la
dirección IP de un host, conociendo su nombre; entendiendo por nombre del host el
nombre de la máquina, como "java.sun.com", o la representación como cadena de su
dirección IP, como "206.26.48.100". El método getByName() puede aceptar como
parámetro de entrada tanto el nombre del host, como su dirección IP en forma de cadena
• El método getAllByName() devuelve un array de objetos InetAddress, y se puede utilizar
para determinar todas las direcciones IP asignadas a un host.
• El método getLocalHost() devuelve un objeto InetAddress representando el ordenador
local sobre el que se ejecuta la aplicación. El método getLocalHost() se puede utilizar
para obtener un objeto de tipo InetAddress que represente al ordenador en el cual se está
ejecutando la aplicación.
• El método getHostName(), obtiene el nombre de la máquina.
• El método getAddress() obtiene un array de bytes conteniendo la dirección IP de la
máquina.

En resumen, la clase InetAddress obtiene la dirección IP real de la red en que se está ejecutando
una aplicación, también se pueden realizar llamadas a los métodos getLocalHost() y
getAddress(). El método getLocalHost() devuelve un objeto InetAddress, que si se usa con
getAddress() generará un array con la dirección IP.

2. La clase URL

La programación de URL se produce a un nivel más alto que la programación de sockets y, al


menos en teoría, resulta una idea muy poderosa. Esa teoría dice que, utilizando la clase URL, se

41
puede establecer una conexión con cualquier recurso que se encuentre en Internet, especificando
un objeto URL y simplemente invocando el método getContent() sobre ese objeto URL.

La clase URL también proporciona una forma alternativa de conectar un ordenador con otro y
compartir datos, basándose en streams.

La clase URL contiene constructores y métodos para la manipulación de URL (Universal


Resource Locator): un objeto o servicio en Internet. El protocolo TCP, necesita dos tipos de
información: la dirección IP y el número de puerto.

Java permite los siguientes cuatro constructores para la clase URL:

• public URL( String spec )


• public URL( String protocol, String host, int port, String file )
• public URL( String protocol, String host, String file ) throws MalformedURLException;
• public URL( URL context, String spec )

Así que se podrían especificar todos los componentes de una dirección URL como en:

URL( "http","www.yahoo.com","80","index.html" );

o dejar que los sistemas utilicen todos los valores por defecto que tienen definidos, como en:

URL( "http://www.yahoo.com" );

y en los dos casos se obtendría la visualización de la página principal de Yahoo en el navegador


desde el cual se ha invocado.

3. La clase URLConnection

Es una clase abstracta que puede ser extendida, con un constructor protegido que admite un
objeto URL como parámetro. Tiene unas ocho variables que contienen información muy útil
sobre la conexión que se haya establecido y cerca de cuarenta métodos que se pueden utilizar
para examinar y manipular el objeto que se crea con la instanciación de la clase.

Una forma habitual de conseguir un objeto de tipo URLConnection es invocar un método sobre
un objeto URL que devuelva un objeto de una subclase de la clase URLConnection, que es lo
que muestra el ejemplo.

URL url = new URL( "http://members.es.tripod.de/froufe/parte20/prueba01.html" );

La siguiente sentencia crea un objeto de tipo URLConnection invocando al método


openConnection() del objeto que se ha instanciado antes.

URLConnection conexion = url.openConnection();

42
Y, ya solamente queda como interesante el fragmento de código que se usa para invocar a

A continuación se indica tres de los métodos de la clase URLConnection, sobre el objeto


instanciado, para obtener la información de alto nivel que muestra la dirección URL.

 conexion.getURL(), obtiene la dirección del URL


 conexion.getLastModified(), la última fecha que se ha modificado
 conexion.getContentType(), el tipo de contenido del archivo al que apunta esa dirección
URL

4. Las clases Socket, ServerSocket, DatagramPacket y DatagramSocket

Estas clases se analizarán en los siguientes subcapítulos

5. Cliente y Servidor con Sockets


La programación utilizando sockets involucra a dos clases principalmente: Socket y
DatagramSocket, y una tercera no tan empleada, ServerSocket:

• Socket. Se puede usar para crear tanto clientes como servidores, representando
comunicaciones TCP
• DatagramSocket. Se puede usar para crear tanto clientes como servidore, representando
comunicaciones UDP
• ServerSocket, No es tan empleada y solamente se utiliza para implementar servidores.

La programación con sockets es una aproximación de bastante bajo nivel para la comunicación
entre dos ordenadores que van a intercambiar datos. Uno de ellos será el cliente y el otro el
servidor.

Aunque la distinción entre cliente y servidor se va haciendo menos clara cada día, en Java hay
una clara diferencia que es inherente al lenguaje:

• El cliente siempre inicia conversaciones con servidores, y


• Los servidores siempre están esperando a que un cliente quiera establecer una
conversación.

Ya después, a nivel de aplicación, se determinará lo que sucede después de que se establezca la


conexión y se inicie la conversación.

El hecho de que dos ordenadores puedan conectarse no significa que puedan comunicarse, es
decir, que además de establecerse la conexión, las dos máquinas deben utilizar un protocolo
entendible por ambas para poder entenderse.

43
La programación de sockets en el mundo Unix viene de muy antiguo, Java simplemente
encapsula mucha de la complejidad de su uso en clases, permitiendo un acercamiento a esa
programación mucho más orientado a objetos de lo que podía hacerse antes.

Básicamente, la programación de sockets hace posible que el flujo de datos se establezca en las
dos direcciones entre cliente y servidor, por ello lo de comentar que la diferencia entre cliente y
servidor, una vez establecida la conexión, se diluye. El flujo de datos que se intercambian cliente
y servidor se puede considerar de la misma forma que cuando se guardan y recuperan datos de
un disco: como un conjunto de bytes a través de un canal. Y como en todo proceso en el que
intervienen datos, el sistema es responsable de llevar esos datos desde su punto de origen al
destinatario, y es responsabilidad del programador el asignar significado a esos datos.

Entre las responsabilidades del programador es la implementación de un protocolo de


comunicaciones que sea mutuamente aceptable entre las dos máquinas a nivel de aplicación, para
hacer que los datos fluyan de forma ordenada.

Un protocolo a nivel de aplicación es un conjunto de reglas a través de las cuales los programas
que se ejecutan en los dos ordenadores pueden establecer una conversación e intercambiarse
datos.

Es decir, la programación de sockets proporciona un mecanismo de muy bajo nivel para la


comunicación e intercambio de datos entre dos ordenadores, uno considerado como cliente, que
es el que inicia la conexión con el otro, servidor, que está a la espera de conexiones de clientes.
La clase Socket permite este tipo de intercambio de datos entre dos ordenadores.

El protocolo de comunicación entre ambos, determinará lo que suceda tras el establecimiento de


la conexión. Para que las dos máquinas puedan entenderse, ambas deben implementar un
protocolo conocido por la dos. En la programación de sockets, la comunicación es full-duplex,
en ambos sentidos a la vez, entre cliente y servidor; siendo responsabilidad del sistema el llevar
los datos de una máquina a otra, dejando al programador el proporcionar significado a esos
datos. Parte de la información que fluye entre las dos máquinas es, pues, para implementar el
protocolo, y el resto son los propios datos que se quieren transferir.

Es muy sencilla la utilización de sockets para establecer la comunicación entre cliente y servidor;
en realidad, no es más complicada que lo que pueda serlo el escribir datos en un archivo. Enviar
y recoger los datos que se intercambian es la parte fácil del asunto; porque más allá de esto ya se
encuentra el protocolo de comunicación que debe ser entendido por cliente y servidor, que en
caso de ser necesario implementarlo, se convierte ya en algo mucho más complicado.

5.1. Los clientes con Sockets

A continuación, se presenta la programación de sockets, de algunos ejemplos de clientes


concretos utilizando los conceptos indicados anteriormente:

44
1. Cliente Eco
2. Cliente Fecha
3. Cliente HTTP
4. Cliente SMTP

1. Cliente Eco

El ejemplo de programación de sockets que se presenta en java2005.java implementa un cliente


que realiza una prueba de eco con un servidor, enviándole una línea de texto por el puerto 7 del
servidor, que es el puerto de ECO, destinado a estos menesteres.

El programa comienza instanciando un objeto de tipo String que contiene el nombre del servidor
que se va a utilizar, seguido por la declaración e inicialización de la variable que guarda el
número del puerto, que en el caso del servidor estándar de echo es el 7. A continuación ya se
abren los canales de entrada y salida desde el socket que se mapean en las clases Reader y
Writer. Una vez que se ha establecido la conexión y los canales de entrada y salida están listos
para usarlos, el programa envía una línea de texto al puerto de eco del servidor. Esto hace que el
servidor reenvíe esa misma línea de vuelta al cliente, y el programa la leerá y presentará en
pantalla.

Este programa es muy simple, y solamente trata de enviar una línea de texto al puerto 7, que es el
puerto de ECO, al servidor con el que se establece la conexión. El programa declara las variables
necesarias, luego establece la conexión con el puerto 7 del servidor y abre los canales de entrada
y salida a través de los cuales se va a comunicar. Una vez que estén listos envía la línea de texto
al servidor, que la devuelve al cliente, y lo que se hace aquí es presentar la respuesta del servidor
en la pantalla.

import java.net.*;
import java.io.*;
import java.util.*;

class java2005 {
public static void main( String[] args ) {
String servidor = "localhost";
int puerto = 7; // puerto echo

try {
// Abrimos un socket conectado al servidor y al
// puerto estandar de echo
Socket socket = new Socket( servidor,puerto );

// Conseguimos el canal de entrada


BufferedReader entrada =
new BufferedReader( new InputStreamReader(
socket.getInputStream() ) );

// Conseguimos el canal de salida


PrintWriter salida =
new PrintWriter( new OutputStreamWriter(
socket.getOutputStream() ),true );

// Enviamos una linea de texto al servidor


salida.println( "Prueba de Eco…" );

45
// Recogemos la linea devuelta por el servidor y la
// presentamos en pantalla
System.out.println( entrada.readLine() );

// Cerramos el socket
socket.close();
} catch( UnknownHostException e ) {
e.printStackTrace();
System.out.println(
"Debes estar conectado para que esto funcione bien." );
} catch( IOException e ) {
e.printStackTrace();
}
}
}

Explicación del programa

En caso de que se seleccione un servidor que no tenga soporte para el protocolo echo en el
puerto 7, la salida que generará el programa será diferente que la que cabría esperar. Por
ejemplo, si el lector intenta ejecutar este programa contra el servidor "www.whitehouse.net", la
respuesta que obtendrá será un rechazo de la conexión, algo así como "connection refused.".

Como se puede ver en el listado, el método main() es el que contiene todo el código de la clase,
porque es muy sencillo y no merece la pena desglosarlo. Las dos primeras líneas que hay que ver
del ejemplo son las que contienen las variables a las que se asigna el nombre del servidor al que
se va a conectar y el número del puerto de ese servidor que se va a atacar.

String servidor = "localhost";


int puerto = 7; // puerto echo

El resto del programa se encuentra englobado en un bloque try/catch para tratar las excepciones.
La línea siguiente es la clave del programa, en ella se establece una conexión con el puerto
indicado del servidor que se ha designado para instanciar un nuevo objeto de tipo Socket

Socket socket = new Socket( server,port );

Una vez que este objeto existe, ya es posible realizar la comunicación con el servidor utilizando
el protocolo que tenga predefinido para el servido que proporciona a través de ese puerto. Lo
siguiente destacar en el código del ejemplo es el uso de las clases Reader y Writer, que se
incorporaron en el JDK 1.1.

// Conseguimos el canal de entrada


BufferedReader entrada =
new BufferedReader( new InputStreamReader(
socket.getInputStream() ) );
// Conseguimos el canal de salida
PrintWriter salida =
new PrintWriter( new OutputStreamWriter(
socket.getOutputStream() ),true );

46
El parámetro true de la última línea hace que el canal de salida realice una descarga o limpieza
de su contenido automáticamente. La realización de esta descarga o vaciado automático de los
canales, es algo de vital importancia a la hora de la programación con sockets.

Y ya lo que resta del código del ejemplo es solamente el uso del canal de salida para enviar a
línea de texto al servidor de eco, la recogida de su respuesta a través del canal de entrada y el
cierre del socket, una vez presentada la cadena devuelta por el servidor.

// Enviamos una línea de texto al servidor


salida.println( "Prueba de Eco" );
// Recogemos la línea devuelta por el servidor y la
// presentamos en pantalla
System.out.println( entrada.readLine() );
// Cerramos el socket
socket.close();

Y esto es todo lo que hay sobre sockets desde el punto de vista de la parte cliente, no piense el
lector que la complejidad es mucho mayor, se haga lo que se haga. Ahora bien, sí que la
programación de sockets se puede complicar, pero todos los problemas van a venir asociados a la
necesidad de implementar un protocolo a nivel de aplicación para comunicarse correctamente
con el servidor, no del uso en sí de los sockets.

La salida que se obtiene, por ejemplo, es:

Hola que tal Prueba de Eco...

2. Cliente Fecha

El ejemplo java2006.java, también implementa un cliente, pero en este caso para acceder el
puerto que proporciona el día y la hora sobre un servidor que dé soporte a este protocolo.

El ejemplo es incluso más sencillo que el ejemplo java2005.java, ya que no es necesario enviar
nada al servidor para obtener respuesta, todo lo que hay que hacer es establecer la conexión. El
código por tanto es bien simple, tal como se muestra en el listado completo que sigue.

Este ejemplo muestra la fecha y hora del servidor local, en comparación con la hora actual de la
máquina en que se esté ejecutando, en este caso también la máquina local. La secuencia del
programa es semejante al ejemplo anterior, solamente que en este caso se conecta al puerto 13
del servidor localhost, que es el puerto estándar para el servicio de fecha. Una vez que se
establece la conexión, no es necesario preguntar nada al servidor, éste, por el mero hecho de
haber establecido la conexión en ese puerto, envía la hora automáticamente. Finalmente se
imprime la fecha y hora de la máquina utilizando la clase Date.

import java.net.*;
import java.io.*;
import java.util.*;

class java2006 {
public static void main( String[] args ) {
String servidor = "localhost";
47
int puerto = 13; // puerto de daytime

try {
// Abrimos un socket conectado al servidor y al
// puerto estandar de daytime
Socket socket = new Socket( servidor,puerto );
System.out.println( "Socket Abierto." );

// Conseguimos el canal de entrada


BufferedReader entrada = new BufferedReader(
new InputStreamReader( socket.getInputStream() ) );

System.out.println( "Hora actual en www.espe.edu.ec:" );


System.out.println( "\t"+entrada.readLine() );
System.out.println( "Hora Actual en Quito, Ecuador:" );
System.out.println( "\t"+new Date() );

// Cerramos el socket
socket.close();
} catch( UnknownHostException e ) {
System.out.println( e );
System.out.println(
"Debes estar conectado para que esto funcione bien." );
} catch( IOException e ) {
System.out.println( e );
}
}
}

Explicación del programa

El programa recoge y presenta la hora del servidor "www.espe.edu.ec", y luego presenta la fecha
y hora donde reside el autor (o donde el lector esté ejecutando el programa), por motivos de
comparación solamente.

La salida que se obtiene, por ejemplo, puede ser:

Socket Abierto.
Hora actual en www.espe.edu.ec:
Mon Nov 19 15:21:08 COT 2018
Hora Actual en Quito, Ecuador:
Mon Nov 19 15:21:08 COT 2018

Como se puede comprobar, hay diferencia en el día debido a las zonas en que se está obteniendo
la fecha. También se puede ver que hay una diferencia de un segundo entre una hora y otra, el
autor sincronizó el reloj de su ordenador con el servidor de tiempo de bernina.ethz.ch, así que
esa diferencia solamente puede ser atribuible a la duración de la transmisión entre el servidor y el
cliente.

La filosofía del programa es muy similar a la del ejemplo del cliente de eco. Se comienza con la
instanciación de un objeto String para contener el nombre del servidor que va a servir la fecha, y
la declaración e inicialización de la variable que va a contener el número del puerto de ese
servidor que se va a atacar, en este caso el puerto 13, que es el puerto estándar para el servicio
daytime.
48
Luego el programa se agencia un canal de entrada a través de la conexión socket que ha
establecido previamente y ya está listo, porque en este caso, la sola conexión es suficiente para
que el servidor envíe la fecha y la hora. Así que, todo lo que hay que hacer ya es leer la línea de
entrada que contiene esa fecha y hora enviada por el servidor. El programa la presenta y también
presenta la fecha y hora actuales de la máquina en que se está ejecutando el programa, para
establecer una comparación con la que se ha llegado a través del socket.

Como no hay diferencias significativas con el ejemplo java2005.java que vayan más allá del uso
del método System.out.println(), no merece la pena detenerse a contar nada del programa, es
suficiente con que el lector lea los comentarios del código.

3. Cliente HTTP

El ejemplo que se va a ver ahora, java2007.java es un navegador extremadamente simple; o más


correctamente, el programa es un cliente HTTP muy simple, implementado mediante sockets. El
programa implementa el suficiente protocolo HTTP para poder recoger un archivo desde
cualquier servidor Web. Si el lector desea realizar un navegador más útil, también tendrá que
invertir mucho más tiempo, pero para ver el funcionamiento, es suficiente con lo que se muestra
en este ejemplo, cuyo código fuente se muestra a continuación.

Este ejemplo es un navegador muy simple implementado mediante sockets. Evidentemente las
páginas que se pueden recibir han de ser de lo más sencillas, no obstante, se implementa lo
suficiente del protocolo http para que la recepción sea decente. Hay que estar conectado para que
el programa pueda funcionar, si no se obtiene una excepción de tipo UnknownHostException. La
mayor parte del programa está encerrada en el bloque try/catch que recoge las excepciones. El
programa abre un socket con el servidor y el puerto 80, luego utiliza la clase BufferedReader
junto con la clase InputStreamReader para abrir un canal de entrada desde el socket. Luego se
utiliza la clase PrintWriter junto con la clase OutputStreamWriter para abrir un canal de salida al
socket. La salida se auto libera, lo cual es crítico, ya que si esa salida no se libera, el servidor no
responde, probablemente lo que ocurra es que el servidor no contesta hasta que tenga la
información de la petición completa. El programa, actuando como navegador, cliente http, envía
un comando GET al servidor indicando un camino y un archivo determinados, para que éste
intente encontrarlos y envíeselos de vuelta al cliente. Cuando ya no haya más líneas que leer, se
recibirá un null, lo cual hará que el cliente salga del bucle de entrada y cierre el socket.
import java.net.*;
import java.io.*;

class java2007 {
public static void main( String[] args ) {
String servidor = "www.deee.espe.edu.ec"; // servidor
int puerto = 80; // puerto

try {
// Crea un socket, conectado al servidor y al puerto
// que se indica
Socket socket = new Socket( servidor,puerto );
// Crea el canal de entrada desde el socket
BufferedReader entrada = new BufferedReader(
new InputStreamReader( socket.getInputStream() ) );
// Crea el canal de salida
49
PrintWriter salida = new PrintWriter(
new OutputStreamWriter( socket.getOutputStream() ),true );

// Envia un comando GET al servidor


salida.println(
"GET /index.html" );

// En esta cadena se va a ir leyendo el archivo


String linea = null;
// Bucle para leer y presentar la linea hasta que se
// reciba un null
while( (linea = entrada.readLine()) != null )
System.out.println( linea );

// Se cierra el socket
socket.close();
} catch( UnknownHostException e ) {
e.printStackTrace();
System.out.println(
"Debes estar conectado para que esto funcione bien." );
} catch( IOException e ) {
e.printStackTrace();
}
}
}

Explicación del programa

El programa se inicia definiendo el servidor y el puerto con el que se va a establecer la conexión,


abriendo a continuación un socket sobre ese puerto. Al igual que en los ejemplos anteriores, el
programa crea canales de entrada y salida para la transferencia de información entre la parte del
cliente y la parte del servidor.

El programa envía un comando GET, actuando como cliente, al servidor, indicándole el archivo
que desea recibir. Este comando es parte del protocolo HTTP, que provoca la búsqueda en el
servidor del archivo indicado y su envío al cliente. Si el servidor está montado sobre el puerto
con el que se ha establecido la conexión, siempre enviará algo, si encuentra el archivo, enviará su
contenido, y si no lo encuentra o hay algún otro problema, siempre envía un mensaje de error
indicando la circunstancia por la cual el archivo no ha podido ser transferido al cliente.

El programa lee el texto que recibe por el canal de entrada y lo presenta en el dispositivo
estándar de salida, por lo que se verá el código HTML que forma la página, es decir, se verá
como texto normal.

El código que compone la página es el que se reproduce a continuación.

<!doctype html public "-//w3c//dtd html 4.0 transitional//en">


<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
<meta name="GENERATOR" content="Mozilla/4.5 [en] (WinNT; I) [Netscape]">
</head>
<body>
50
<center>
<h3>Archivo de Pruebas</h3></center>
Este es un programa de prueba utilizado en el cap&iacute;tulo de
comunicaciones por red, del Tutorial de Java
<p><b><i>Agust&iacute;n Froufe,</i></b><br>
<i><a href="mailto:froufe@arrakis.es">froufe@arrakis.es</a>,
<a href="http://members.es.tripod.de/froufe/">
http://members.es.tripod.de/froufe/</a></i><br>
&nbsp;
</body>
</html>

Esta página que se descarga es una página de ejemplo, que no tiene mayor importancia, pero si el
lector tiene curiosidad por saber cómo se ve en un navegador, la imagen siguiente reproduce
exactamente esa página,

Si el lector revisa el código del programa Java, recordará de ejemplos anteriores todo la parte
inicial de declaración de variables para determinar el servidor y puerto al que conectarse y la
apertura de sockets para los canales de entrada y salida. Así que la primera parte de código que
realmente merece la pena revisar es el envío de comandos HTTP al servidor.

// Envía un comando GET al servidor


salida.println( "GET /froufe/parte20/prueba01.html" );

Observe el lector, que el árbol de directorios que se indica al servidor para que consiga localizar
el archivo, es relativo a su pseudo-raiz, es decir, relativo al nivel inicial en que se encuentran los
documentos HTML. La petición se realiza simplemente escribiendo el comando en el canal de
salida.

Si el comando GET ha sido enviado correctamente, hay que esperar siempre una respuesta del
servidor, aunque sea un mensaje de error. El siguiente fragmento de código va leyendo texto del
canal de entrada, presentándolo a continuación en la pantalla, cerrando el socket cuando ya no
hay más datos que leer.

51
// En esta cadena se va a ir leyendo el archivo
String linea = null;
// Bucle para leer y presentar la líneas hasta que se
// reciba un null
while( (linea = entrada.readLine()) != null )
System.out.println( linea );
// Se cierra el socket
socket.close();

El resto del código es ya la parte conocida de control de las excepciones. Si el lector desea
incorporar más características a este ejemplo, a fin de convertirlo en un navegador útil; se le
sugiere que revise las especificaciones HTML y escriba un método de presentación al estilo que
normalmente se asocia a la página web. E incluso puede englobar todo el ejemplo en AWT,
proporcionando un interfaz gráfico mediante un área de texto, TextArea, para la parte de
presentación de la página y un campo de texto, TextField, para especificar la dirección del
archivo del servidor que se desea visualizar.

Otra idea de mejora que puede realizar el lector es crear un robot, que comience en una dirección
determinada, descargue los archivos y siga los enlaces de éstos hasta que alcance el nivel más
inferior; aunque para empezar, un programa más seguro sería un robot que siguiese los enlaces
pero no grabase en disco los archivos, sino que de cada uno de ellos, tras analizarlos, descartase
todo aquello que no fuesen enlaces.

4. Cliente SMTP

Ahora se propone otro ejemplo, java2008.java, en este caso para implementar un cliente SMTP
que sea capaz de enviar correo electrónico a cualquier persona de la que se conozca su cuenta de
correo y el servidor en que está localizada.

El programa comienza instanciando un objeto de tipo String que contiene el nombre del servidor
SMTP. Aunque el autor ha probado el programa con su propio servidor, se ha eliminado esta
asignación del código, para evitar el envío de correo no deseado cuando el lector ejecute el
ejemplo. Seguro que resultará muy sencillo al lector obtener la identificación de su propio
servidor y probar el ejemplo enviándose correo a sí mismo. El puerto estándar para el protocolo
SMTP, de correo electrónico, es el 25, que es el valor de inicialización de la variable declarada
para ello.

El protocolo SMTP consiste en la transmisión de líneas de texto simples al servidor y en la


lectura de la respuesta de éste. En el programa, una vez que se han establecido los canales de
entrada y salida, comienza la conversación con el servidor de correo a través de la versión
abreviada que se ha implementado del protocolo SMTP. El texto que recibe el cliente se
reproduce en la salida estándar, y el resultado de una ejecución de este ejemplo contra el servidor
del autor, es lo que se reproduce las siguientes líneas.

% java java2008.java
220 landsraad.net ESMTP Sendmail 8.9.1a/8.9.1;
Sun, 3 Jan 1999 10:37:21 +0100 (MET)
52
250 froufe@arrakis.es... Sender ok
250 froufe@arrakis.es... Recipient ok
354 Enter mail, end with "." on a line by itself

El código completo del ejemplo es el que se muestra, aunque si el lector desea utilizarlo deberá
especificar adecuadamente su cuenta de correo y el servidor en donde resida para que la
ejecución del programa sea correcta.

En este ejemplo se muestra como se construye un cliente de correo (protocolo SMTP),


implementándolo con sockets. El programa es capaz de enviar un mensaje e-mail a cualquier
servidor, siempre que se está conectado. Debes asegurarte de que la cuenta de correo y el
servidor sean los que estás utilizando normalmente. Tras estas declaraciones, el programa abre
una conexión a través de socket con el puerto 25, que es el estándar utilizado por SMTP. Luego
se establecen los canales de entrada y salida y comienza la conversación del programa con el
servidor de correo, siguiendo el protocolo SMTP. Este consiste en la transmisión de una serie de
líneas de texto al servidor y escuchar lo que éste devuelve. El texto que devuelve el servidor se
presenta en la consola. Una vez que ha enviado el mensaje, el programa cierra el socket y
termina.

import java.net.*;
import java.io.*;
import java.util.*;

class java2008 {
public static void main( String[] args ) {
String servidor = "mail.espe.edu.ec"; // !!!!!
String usuario = "eagranizo@espe.edu.ec"; // !!!!!
int puerto = 25; // puerto SMTP

try {
// Abrimos un socket conectado al servidor y al
// puerto del protocolo SMTP
Socket socket = new Socket( servidor,puerto );

// Conseguimos el canal de entrada


BufferedReader entrada = new BufferedReader(
new InputStreamReader( socket.getInputStream() ) );
// Conseguimos el canal de salida
PrintWriter salida = new PrintWriter(
new OutputStreamWriter( socket.getOutputStream() ),true );

// Comenzamos la conversacion con el servidor de


// correo electronico
salida.println( "mail from: "+ usuario );
System.out.println( entrada.readLine() );

salida.println( "rcpt to: "+usuario );


System.out.println( entrada.readLine() );

salida.println( "data" );
System.out.println( entrada.readLine() );

String timeStamp = (new Date()).toString();


salida.println( "Mensaje de prueba: "+timeStamp );
salida.println( "." );
System.out.println(entrada.readLine());

// Cerramos el socket
socket.close();
53
} catch( UnknownHostException e ) {
e.printStackTrace();
System.out.println(
"Debes estar conectado para que esto funcione bien." );
} catch( IOException e ) {
e.printStackTrace();
}
}
}

Si se revisan las partes más interesantes del código, observará el lector que las líneas iniciales
son las más importantes, ya que establecen el servidor de correo electrónico al que se va a enviar
el mensaje y el nombre del usuario en ese servidor, a cuya cuenta se va a enviar el mensaje. El
texto que muestran las líneas siguientes debe ser sustituido por los valores correctos del servidor
y cuenta de correo del lector.

String servidor = "pon aqui el nombre de tu servidor"; // !!!!!


String usuario = "pon aquí tu nombre email"; // !!!!!
int puerto = 25; // puerto SMTP

El propósito del programa es enviar un mensaje de correo electrónico a alguien, preferiblemente


al propio lector, así que, tras la apertura de los canales de entrada y salida para la transferencia de
información entre cliente y servidor, se inicia la conversación enviando al servidor la línea de
texto que identifica el remitente del mensaje, luego se lee y presenta la respuesta del servidor.

salida.println( "mail from: "+usuario );


System.out.println( entrada.readLine() );

Luego se envía la línea de texto en la que se indica al servidor la cuenta de correo a la que va
destinado el mensaje. De nuevo, se espera la respuesta del servidor y se presenta en la pantalla.

salida.println( "rcpt to: "+usuario );


System.out.println( entrada.readLine() );

Todo esto sucede suponiendo que no se produce ningún error, y todo funciona como es de
esperar. Si el lector desea escribir un programa más serio, debería incorporar al código la
comprobación de las respuestas que proporciona el servidor, e implementar las acciones
correctivas necesarias en caso de que surgiese algún problema; pero esto requiere un profundo
estudio del protocolo que se escapa a todas luces del alcance de este Tutorial. Se envía la cadena
"data" al servidor y cuando se recibe la respuesta de que está listo para recibir datos, se construye
un mensaje de prueba que se envía, y se concluye con un punto, que es el que indica al servidor
que se ha llegado al final del mensaje.

salida.println( "data" );
System.out.println( entrada.readLine() );
String timeStamp = (new Date()).toString();
salida.println( "Mensaje de prueba "+timeStamp );
salida.println( "." );
System.out.println(entrada.readLine());

Tras esto ya se cierra el socket. El resto del código vuelve a ser el control de las excepciones. Si
el lector se decir a completar el ejemplo, ha de tener en cuenta que la especificación debe seguir
54
escrupulosamente, y que debe examinar cada uno de los mensajes de respuesta del servidor para
confirmar que todo el trasiego de información se está realizando correctamente.

5.2. El servidor con Sockets

La clase ServerSocket es la que se utiliza a la hora de crear servidores, al igual que, como ya se
ha visto, la clase Socket se utilizaba para crear clientes.

El ejemplo java2009.java es solamente ilustrativo que implementa tres servidores operando


sobre tres puertos diferentes del ordenador que está ejecutando el programa, a continuación se
indica estos tres servidores:

• Uno de los servidores es un servidor "echo" que está implementado a través de un hilo de
ejecución que monitoriza el puerto 7. Este servidor devuelve la cadena que reciba por el
puerto 7 al mismo cliente que la ha enviado.
• El otro servidor es un servidor HTTP abreviado, que se ha implementado a través de un
hilo de ejecución que monitoriza el puerto 80, que es el puerto estándar para el protocolo
HTTP. En este servidor se ha implementado la respuesta al comando GET, que devuelve
al cliente el contenido del archivo que haya solicitado, o un mensaje de error en caso de
no encontrarlo.
• El tercer servidor es un servidor Fecha, que se ha implementado a través de un hilo de
ejecución que monitoriza el puerto 13, solamente responde la clase Date, cualquier otro
comando será ignorado y el cliente obtendrá como respuesta un mensaje de error.

Si el lector ejecuta los ejemplos en entorno Windows, es suficiente con que arranque los
ejemplos como cualquier otro programa Java, antes de lanzar la parte cliente, o al menos antes de
que la parte cliente solicite algo al servidor. Sin embargo, si la ejecución se realiza en entornos
Solaris, Linux, etc. probablemente se encuentre con que no hay permiso para utilizar los puertos
que se asignan en los ejemplos. Esto, como ya se ha indicado, es debido a que los puertos por
debajo del 1024 están reservados para uso del sistema; no obstante, el lector puede cambiar el
número de puerto tanto en el servidor como en el cliente y ejecutarlos.

La parte principal de la aplicación se encuentra en el método main(), que contiene las tres
sentencias que permiten el uso de las clases que implementan el objetivo concreto de la creación
de los servidores.

import java.net.*;
import java.io.*;
import java.util.*;
public class java2009 {
public static void main( String[] args ) {
// Se instancia un objeto servidor para escuchar el puerto 7
ServidorEco servidorEcho = new ServidorEco();
// Se instancia un objeto servidor para la fecha en el puerto 13
ServidorFecha servidorfecha = new ServidorFecha();
// Se instancia un objeto servidor para escuchar el puerto 80
55
ServidorHttp servidorHttp = new ServidorHttp();
}
}

• La primera sentencia instancia un objeto servidor que monitoriza el puerto 7; este


servidor devuelve al cliente TCP/IP la misma cadena que haya enviado al servidor.
• La segunda sentencia instancia un objeto servidor que monitoriza el puerto 80; este
servidor implementa parte del protocolo HTTP y permite la descarga de archivos a
navegadores.
• La tercera sentencia instancia un objeto servidor que monitoriza el puerto 13; este
servidor devuelve al cliente la fecha del servidor que se accede.

Estos tres servidores se implementan en hilos de ejecución diferentes para que puedan funcionar
en paralelo de forma asíncrona. A continuación, se explican estos servidores:

a. Servidor Eco

Siguiendo con la revisión de las partes interesantes del código del programa que se implementa
en el ejemplo java2009.java, lo que se encuentra el lector ahora es la clase que implementa el
servidor del protocolo Echo, que es el más simple de los dos que se muestran en el programa. En
general, los dos servidores funcionan del mismo modo, corriendo cada uno de ellos en su propio
hilo de ejecución, y escuchando el puerto que corresponde a su protocolo, en espera de
conexiones de clientes. Mientras un servidor se encuentra en este estado de espera de
conexiones, estará bloqueado para consumir el mínimo de recursos del sistema.

// Esta es la clase que se utiliza para instanciar un hilo de ejecucion


// para el servidor que se encarga de escuchar el puerto 7, que es el
// definido como estandar para el protocolo de "echo"
class ServidorEco extends Thread {
// Constructor
ServidorEco() {
// Arrancamos el hilo e invocamos al metodo run() para que empiece
// a correr
start();
}

public void run() {


try {
// Se instancia un objeto sobre el puerto 7
ServerSocket socket = new ServerSocket( 7 );
System.out.println( "Servidor escuchando el puerto 7" );
// Entramos en bucle infinito ecuchando el puerto. Cuando se
// produce una llamada, se lanza un hilo de ejecucion de
// ConexionEcho para atenderla
while( true )
// Nos quedamos parados en el accept(), y devolvemos un socket
// cuando se recibe la llamada. Este socket es el que se pasa
// como parametro al nuevo hilo de ejecucion que se crea
new ConexionEcho( socket.accept() );
} catch( IOException e ) {
e.printStackTrace();
}
}
}

56
// Esta clase se utiliza el lanzar un hilo de ejecucion que atienda
// la llamada recibida a traves del puerto 7, el puerto de eco
class ConexionEcho extends Thread {
Socket socket;

// Constructor
ConexionEcho( Socket socket ) {
System.out.println( "Recibida una llamada en el puerto 7" );
this.socket = socket;
// Trabajamos por debajo de la prioridad de los otros puertos
setPriority( NORM_PRIORITY-1 );
// Se arranca el hilo y se pone a correr
start();
}

public void run() {


System.out.println( "Lanzado el hilo de atencion del puerto 7" );
BufferedReader entrada = null;
PrintWriter salida = null;

try {
// Conseguimos un canal de entrada desde el socket
entrada = new BufferedReader( new InputStreamReader(
socket.getInputStream() ) );
// Conseguimos un canal de salida hacia el socket. El canal es
// con liberaci�n autom�tica (autoflush)
salida = new PrintWriter( new OutputStreamWriter(
socket.getOutputStream()),true );

// Recogemos la cadena que llegue al puerto y la devolvemos en


// el mismo instante
String cadena = entrada.readLine();
salida.println( cadena );
System.out.println( "Recibido: "+cadena );
socket.close();
System.out.println( "Socket cerrado" );
} catch( IOException e ) {
e.printStackTrace();
}
}
}

Explicación de la clase

Cuando un cliente requiere una conexión en cualquiera de los dos puertos, el servidor lanza otro
hilo de ejecución para manejar las necesidades del cliente y luego vuelve para seguir escuchando
el puerto en su propio hilo de ejecución. Los hilos de ejecución para dar soporte a los clientes, se
lanzan con prioridad mínima, de forma que estos hilos no interfieran la capacidad asignada a los
servidores de reconocer y responder a cualquier otro cliente que esté solicitando una conexión. Si
hay muchos clientes solicitando conexiones, el servidor que atiende al puerto lanzará un hilo de
ejecución por cada una de esas conexiones (dentro de las capacidades del sistema); de forma que
puede haber muchos hilos ejecutándose simultáneamente, atendiendo cada uno de ellos a las
necesidades de un cliente específico.

La primera sentencia interesante del servidor de eco es el constructor, que simplemente invoca a
su propio método start() que iniciará el arranque de su propio hilo de ejecución.

// Arrancamos el hilo e invocamos al método run() para que empiece


57
// a correr
start();

La clave del funcionamiento de un servidor en Java se encuentra en el método accept() de la


clase ServerSocket. Antes, hay que construir un objeto de esa clase y, como se puede ver en el
siguiente trozo de código, el constructor de ServerSocket tiene solamente un parámetro: el
número del puerto que va a ser monitorizado por el servidor que construye.

// Se instancia un objeto sobre el puerto 7


ServerSocket socket = new ServerSocket( 7 );
System.out.println( "Servidor escuchando el 7" );

Y acto seguido, ya se puede realizar la llamada al método accept() para recoger las conexiones
que establezcan los clientes con el servidor a través del puerto que está monitorizando ese
servidor. Las líneas siguientes muestran cómo se hace esto en el ejemplo.

// Entramos en bucle infinito ecuchando el puerto. Cuando se


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

Cuando se instancia un objeto de tipo ServerSocket y se invoca el método accept() sobre ese
objeto, este método bloquea el servidor y se queda a la espera hasta que se produce una conexión
de un cliente en el puerto que controla. Cuando esto sucede, se instancia automáticamente un
objeto Socket que es devuelto por el método accept().

El lector comprobará que hay muchos conceptos que se están repitiendo, y que si va leyendo con
detenimiento, se vuelve sobre ellos una y otra vez. Esto está hecho deliberadamente, ya que hay
una serie de conceptos y métodos muy simples en los que se basa todo el funcionamiento de las
comunicaciones en red con Java, y que son los que verdaderamente tienen que quedar arraigados
en el conocimiento del lector. Por ello, aún a fuer de resultar un poco ladrillo, es preferible recaer
una vez sobre otra en los conceptos que el autor considera fundamentales, para que una vez
concluida la lectura del capítulo, al menos esos conceptos hayan quedado lo suficientemente
claros y grabados en el conocimiento del lector.

El objeto Socket devuelto por el método accept(), se conecta automáticamente con el objeto
Socket utilizado por el cliente que ha establecido la conexión, y se puede utilizar para establecer
la comunicación con el cliente. Esto es muy importante, así que hay que detenerse un poco en
ello.

No hay ninguna transferencia de datos por el hecho de disponer de un objeto de tipo


ServerSocket. Es en el momento en que el cliente solicita una conexión cuando se instancia
automáticamente un objeto Socket que se conecta con el Socket del cliente que ha pedido la

58
conexión. Este nuevo socket es el devuelto por el método accept() para poder establecer la
comunicación con el socket del cliente.

En el trozo de código anterior, se observa que cuando se recibe una conexión, se lanza un hilo de
ejecución del tipo ConexionEcho que atiende al cliente que ha solicitado esa conexión. Al
constructor de la clase que maneja este nuevo hilo, se le pasa como parámetro el objeto Socket
que el método accept() devuelve, que ya está conectado con el cliente, y a través del cual, el
protocolo de comunicaciones para el servicio, echo en este caso, puede comunicarse con el
cliente.

Observe el lector que el hilo en el cual está corriendo el objeto ServidorEco se encuentra en
medio de un bucle infinito. Luego, una vez que el hilo de ejecución ConexionEcho haya sido
lanzado con éxito, el objeto ServidorEco volverá a su función inicial de seguir esperando a la
conexión de otro cliente a través del puerto de eco.

El siguiente trozo de código en que merece la pena detenerse es ya el constructor de la clase


ConexionEcho. Los objetos de esta clase son creados a raíz de la petición de conexión de algún
cliente al servidor. No obstante, estos objetos no saben nada de clientes y servidores, lo único
que conocen es que hay un socket TCP/IP conectado con otro socket en otra máquina, o con otro
proceso de la misma máquina.

// Constructor
ConexionEcho( Socket socket ) {
System.out.println( "Recibida una llamada en el puerto 7" );
this.socket = socket;
// Trabajamos por debajo de la prioridad de los otros puertos
setPriority( NORM_PRIORITY-1 );
// Se arranca el hilo y se pone a correr
start();
}

Como puede observar el lector, tras guardar el parámetro en una variable de instancia, el
constructor fija la prioridad del hilo de ejecución. Esto es para que los hilos que están
monitorizando los puertos no se vean interrumpidos por los hilos que están atendiendo a las
conexiones con clientes ya establecidas. Una vez ajustada la prioridad, ya solamente resta
invocar al método start() para que el hilo de ejecución se levante y empiece a correr. Como es
habitual, el método start() invocará al método run() de este hilo de ejecución.

El método run() de este hilo de ejecución es similar al código que ya se ha presentado en


ejemplos anteriores al programar la parte cliente. Por referenciar algo, se reproducen las líneas
de código en donde se lee la cadena recibida del cliente y se devuelve a ese cliente, exactamente
igual que se ha recibido. Una vez hecho esto, se cierra el socket de conexión con el cliente y el
hilo de ejecución se muere.

// Recogemos la cadena que llegue al puerto y la devolvemos en


// el mismo instante
String cadena = entrada.readLine();
salida.println( cadena );

59
System.out.println( "Recibido: "+cadena );
socket.close();
System.out.println( "Socket cerrado" );

Con esto se concluye el repaso al código que implementa el servidor Eco en el programa y se
pasa a discutir la clase que atiende a las conexiones con el puerto estándar del protocolo HTTP.
Aunque este es un caso considerablemente más complejo, gran parte del código es idéntico al
que se acaba de presentar para el caso del servidor Echo.

b. Servidor HTTP

El servidor HTTP que se implementa en el ejemplo java2009.java es realmente simple, y


solamente responde al comando GET del protocolo HTTP, cualquier otro comando será
ignorado y el cliente obtendrá como respuesta un mensaje de error.

En el método main() se instancia un objeto de la clase ServidorHttp, que monta un hilo de


ejecución que instancia un objeto de tipo ServerSocket para atender al puerto estándar del
protocolo HTTP, el puerto 80.

// Esta es la clase que se utiliza para instanciar un hilo de ejecucion


// para el servidor que se encarga de escuchar el puerto 80, que es el
// definido como estandar para el protocolo de "http"
class ServidorHttp extends Thread {
// Constructor
ServidorHttp() {
// Arrancamos el hilo e invocamos al metodo run() para que empiece
// a correr
start();
}

public void run(){


try {
// Se instancia un objeto sobre el puerto 80
ServerSocket socket = new ServerSocket( 1080 );
System.out.println( "Servidor escuchando el puerto 80" );
// Entramos en bucle infinito ecuchando el puerto. Cuando se
// produce una llamada, se lanza un hilo de ejecucion de
// ConexionHttp para atenderla
while( true )
// Nos quedamos parados en el accept(), y devolvemos un socket
// cuando se recibe la llamada. Este socket es el que se pasa
// como parametro al nuevo hilo de ejecucion que se crea
new ConexionHttp( socket.accept() );
} catch( IOException e ) {
e.printStackTrace();
}
}
}

// Esta clase se utiliza la lanzar un hilo de ejecucion que atienda


// la llamada recibida a traves del puerto 80, el puerto de http
class ConexionHttp extends Thread {
Socket socket;
BufferedReader entrada = null;
PrintWriter salida = null;
DataOutputStream pagina = null;

60
// Constructor
ConexionHttp( Socket socket ) {
System.out.println( "Recibida una llamada en el puerto 80" );
this.socket = socket;
// Trabajamos por debajo de la prioridad de los otros puertos
setPriority( NORM_PRIORITY-1 );
// Se arranca el hilo y se pone a correr
start();
}

public void run() {


System.out.println( "Lanzado el hilo de atencion al puerto 80" );
try{
// Conseguimos un canal de entrada desde el socket
entrada = new BufferedReader( new InputStreamReader(
socket.getInputStream() ) );

// Conseguimos un canal de salida hacia el socket. El canal es


// con liberacion automatica (autoflush)
salida = new PrintWriter( new OutputStreamWriter(
socket.getOutputStream() ),true );

// Ahora conseguimos un canal de salida para enviar el contenido


// del ficero que haya solicitado el usuario
pagina = new DataOutputStream( socket.getOutputStream() );

// Recogemos la cadena que llegue al puerto


String peticion = entrada.readLine();
System.out.println( "Recibida peticion: " + peticion );
// Analizamos esa peticion e intentamos responder. Solamente se
// contesta a las peticiones GET, cualquier otra no tiene
// respuesta alguna
StringTokenizer st = new StringTokenizer( peticion );
if( ( st.countTokens() >= 2 )
&& st.nextToken().equals("GET") ) {
System.out.println( "Primer tag GET, correcto" );
if( (peticion = st.nextToken()).startsWith("/") ) {
System.out.println(
"Siguiente toque que empieza por /, se le quita" );
peticion = peticion.substring( 1 );
}
if( peticion.endsWith("/") || peticion.equals("") ) {
System.out.println( "Peticion terminada en / o blanco, "+
"se le incorpora: index.html" );
peticion = peticion + "index.html";
System.out.println( "Peticion modificada: " + peticion );
}

System.out.println( "Intento de desacarga de: " + peticion );


FileInputStream archivo = new FileInputStream( peticion );
// Se instancia un array de bytes igual al numero de bytes que
// se pueden leer del canal de entrada sin bloquearlo.
// Y luego se rellena con los datos
byte[] datos = new byte[archivo.available()];
archivo.read( datos );

// Se envia el array de datos al cliente


pagina.write( datos );
pagina.flush();
} else
// La peticion que se ha hecho no es un GET
salida.println( "<HTML><BODY><P>400 Peticion " +
"Error<P></BODY></HTML>" );
socket.close();
System.out.println( "Socket cerrado" );
}catch( FileNotFoundException e ) {
61
salida.println(
"<HTML><BODY><P>404 No Encontrado<P></BODY></HTML>" );
try {
socket.close();
System.out.println( "Socket cerrado" );
}catch( IOException evt ) {
evt.printStackTrace();
}
}catch( SecurityException e ){
e.printStackTrace();
salida.println(
"<HTML><BODY><P>403 Acceso denegado<P></BODY></HTML>" );
salida.println( e );
try {
socket.close();
System.out.println( "Socket cerrado" );
}catch( IOException evt ) {
evt.printStackTrace();
}
}catch( IOException e ) {
e.printStackTrace();
try {
socket.close();
System.out.println( "Socket cerrado" );
}catch( IOException evt ) {
System.out.println(evt);
}
}
}
}

Explicación de la clase

// Se instancia un objeto sobre el puerto 80


ServerSocket socket = new ServerSocket( 80 );
System.out.println( "Servidor escuchando el puerto 80" );

El método accept(), tal como muestran las líneas de código anteriores, se invoca sobre este
objeto dentro de un bucle infinito para bloquea el servidor y monitorizar el puerto 80, esperando
a que lleguen conexiones de clientes. Cuando esto ocurre, el método accept() instancia y
devuelve un objeto Socket, que ya estará conectado con la máquina cliente.

// Entramos en bucle infinito ecuchando el puerto. Cuando se


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

Este objeto Socket, se pasa como parámetro al constructor de un nuevo objeto de tipo
ConexionHttp, que lanzará un hilo de ejecución específico para atender a la conexión que se ha
establecido con el cliente, a través de la versión abreviada del protocolo HTTP que se ha
implementado en esta clase.

El constructor es similar al visto para el servidor de Eco; así que, en haras de la brevedad no se
incluye aquí. Si el lector sigue el código del ejemplo, no encontrará nada nuevo hasta llegar a la
62
transmisión de información al cliente, que en este caso se hace enviando un array de bytes, algo
no visto hasta ahora. El siguiente fragmento de código muestra la creación del canal de salida
que va a permitir este tipo de comunicación.

// Ahora conseguimos un canal de salida para enviar el contenido


// del archivo que haya solicitado el usuario
pagina = new DataOutputStream( socket.getOutputStream() );

Esto se encuentra dentro del método run(), corazón del hilo de ejecución que está atendiendo a la
conexión. A continuación, lo que se espera es la conexión del cliente, para lo que se utiliza el
método readLine() sobre el canal de entrada que lee las peticiones y almacena esa petición en un
objeto String.

// Recogemos la cadena que llegue al puerto


String peticion = entrada.readLine();

Lo siguiente que hay que hacer es el análisis de la petición que ha realizado el cliente para
comprobar si es posible atenderla o no, teniendo en cuenta que solamente se responde al
comando GET. Para realizar este análisis se utiliza un objeto StringTokenizer, que indicará si
hay o no un comando GET en la petición realizada por el cliente.

StringTokenizer st = new StringTokenizer( peticion );


if( ( st.countTokens() >= 2 )
&& st.nextToken().equals("GET") ) {

En caso de que no sea una petición GET, se creará una página dentro del código para devolver el
mensaje de error al cliente. Si la petición sí corresponde a un comando GET, lo que se intenta es
comprobar el nombre del archivo que ha solicitado el cliente y enviárselo; y en caso de que no
solicite ninguno, completar el camino que haya indicado con el archivo index.html, que es el
archivo estándar que utilizan casi todos los navegadores como archivo de defecto en caso de no
especificar una página determinada en el acceso a un sitio Internet. Las siguiente línea de código
elimina la posibilidad de que la petición contenga barras "/" extra.

if( (peticion = st.nextToken()).startsWith("/") ) {

Si la petición termina con una barra "/" o es una cadena vacía, se supone que el cliente quiere
descargar el archivo index.html; así que, en este caso, se añade este nombre de archivo a la
cadena que contiene la petición realizada por el cliente.

if( peticion.endsWith("/") || peticion.equals("") ) {


System.out.println( "Peticion terminada en / o blanco, "+
"se le incorpora: index.html" );
peticion = peticion + "index.html";
System.out.println( "Peticion modificada: "+peticion );
}

Llegados a este punto, ya se sabe el archivo que hay que enviarle al cliente como respuesta a su
petición, así que se intenta abrir un objeto de tipo FileInputStream con ese nombre de archivo y
en el camino que se indique. Si no se puede conseguir, se lanzará una excepción que será

63
procesada en el bloque catch del final del programa. Si el archivo sí se puede leer, se creará un
array de bytes igual al contenido del archivo y se leerá ahí ese contenido.

FileInputStream archivo = new FileInputStream( peticion );


// Se instancia un array de bytes igual al número de bytes que
// se pueden leer del canal de entrada sin bloquearlo.
// Y luego se rellena con los datos
byte[] datos = new byte[archivo.available()];
archivo.read( datos );

Una vez que se ha identificado, localizado y leído el archivo en un array en memoria, el siguiente
paso consiste en el uso del canal de salida que se había creado anteriormente para transmitir el
contenido del array al cliente; concluyendo con la liberación forzada de todo el contenido del
array, tal como se muestra en las siguientes líneas de código.

// Se envía el array de datos al cliente


pagina.write( datos );
pagina.flush();

El siguiente fragmento de código se ejecuta en el caso de que el cliente no envíe una petición
GET, en cuyo caso se crea un documento sobre la marcha conteniendo el mensaje de error que
indica tal circunstancia y se le envía como respuesta al cliente.

} else
// La petición que se ha hecho no es un GET
salida.println( "<HTML><BODY><P>400 Petici&oactute;n "+
"Err&oacute;nea<P></BODY></HTML>" );
socket.close();

Esta última sentencia cierra el socket y permite que el hilo de ejecución termine normalmente,
siempre asumiendo que no se ha lanzado ninguna excepción entre medio; porque en el caso de
que se hubiese generado alguna excepción, hay varios controladores para recoger estas
excepciones, e incluso algunos, como el lector podrá observar en el código del ejemplo, generan
páginas sobre la marcha que envían al cliente, indicando la circunstancia que ha provocado el
lanzamiento de la excepción que controlan.

Quizá merezca la pena detenerse en el controlador de la última excepción, la de tipo


IOExcepction, que se muestra en el siguiente trozo de código.

}catch( IOException e ) {
e.printStackTrace();
try {
socket.close();
System.out.println( "Socket cerrado" );
}catch( IOException evt ) {
System.out.println(evt);
}

Y es especial porque es necesario cerrar el socket dentro del controlador de la excepción. La


verdad es que el autor no sabe a ciencia cierta cómo se puede forzar una excepción de tipo
IOException, dentro del manejador de una excepción de tipo IOException lanzada

64
anteriormente, pero vamos; es de suponer que funciona, aunque el autor no disponga de ningún
método de comprobación de que eso es así en el momento de redactar este párrafo.

c. Servidor Fecha

El servidor Fecha que se implementa en el ejemplo java2009.java es realmente simple, y


solamente responde la clase Date, cualquier otro comando será ignorado y el cliente obtendrá
como respuesta un mensaje de error.

En el método main() se instancia un objeto de la clase ServidorFecha, que monta un hilo de


ejecución que instancia un objeto de tipo ServerSocket para atender al puerto estándar del
protocolo HTTP, el puerto 13.

// Esta es la clase que se utiliza para instanciar un hilo de ejecucion


// para el servidor que se encarga de entregar la fecha en el puerto 13, que es el
// definido como estandar para el protocolo de "fecha"
class ServidorFecha extends Thread {
// Constructor
ServidorFecha() {
// Arrancamos el hilo e invocamos al metodo run() para que empiece
// a correr
start();
}

public void run() {


try {
// Se instancia un objeto sobre el puerto 13
ServerSocket socket = new ServerSocket( 13 );
System.out.println( "Servidor escuchando el puerto 13" );
// Entramos en bucle infinito ecuchando el puerto. Cuando se
// produce una llamada, se lanza un hilo de ejecucion de
// ConexionEcho para atenderla
while( true )
// Nos quedamos parados en el accept(), y devolvemos un socket
// cuando se recibe la llamada. Este socket es el que se pasa
// como parametro al nuevo hilo de ejecucion que se crea
new ConexionFecha( socket.accept() );
} catch( IOException e ) {
e.printStackTrace();
}
}
}

// Esta clase se utiliza el lanzar un hilo de ejecucion que atienda


// la llamada recibida a traves del puerto 13, el puerto de fecha
class ConexionFecha extends Thread {
Socket socket;

// Constructor
ConexionFecha( Socket socket ) {
System.out.println( "Recibida una llamada en el puerto 13" );
this.socket = socket;
// Trabajamos por debajo de la prioridad de los otros puertos
setPriority( NORM_PRIORITY-1 );
// Se arranca el hilo y se pone a correr
start();
}

public void run() {


System.out.println( "Lanzado el hilo de atencion del puerto 13" );
65
PrintWriter salida = null;

try {

// Conseguimos un canal de salida hacia el socket. El canal es


// con liberacion automatica (autoflush)
salida = new PrintWriter( new OutputStreamWriter(
socket.getOutputStream()),true );

// Enviamos la fecha
salida.println( new Date() );

socket.close();
System.out.println( "Socket cerrado" );
} catch( IOException e ) {
e.printStackTrace();
}
}
}

Explicación de la Clase

El servidor Fecha es realmente simple, y solamente responde la clase Date, cualquier otro
comando será ignorado y el cliente obtendrá como respuesta un mensaje de error. En esta clase
ServidorFecha se utiliza para instanciar un hilo de ejecución para el servidor que se encarga de
entregar la fecha en el puerto 13, que es el definido como estándar para el protocolo de "fecha".

5.3. Los Clientes de prueba con Sockets

Los ejemplos java2010.java, java2011.java java20110.java son clientes implementados para


probar los tres servidores que se crean en ejemplo de los servidores. El servidor de Eco se puede
probar utilizando el ejemplo, java2010.java, el otro ejemplo permite probar la parte HTTP, y el
otro ejemplo permite probar el servidor Fecha.

Este ejemplo es igual que el programa java2005, ya presentado, pero se ha cambiado el servidor
de conexión para poder probar el ejemplo del servidor de eco que se ha planteado en el servidor
de ejemplo del Tutorial. Se trata simplemente de conectarse al servidor y enviar una línea de
texto al puerto 7, que es el puerto de ECO ese servidor. El programa declara las variables
necesarias, luego establece la conexión con el puerto 7 del servidor y abre los canales de entrada
y salida a través de los cuales se va a comunicar. Una vez que están listos envía la línea de texto
al servidor, que la devuelve al cliente, y lo que se hace aquí es presentar la respuesta del servidor
en la pantalla.
import java.net.*;
import java.io.*;
import java.util.*;

class java2010 {
public static void main( String[] args ) {
String servidor = "localhost";
int puerto = 7; // puerto echo

try {
// Abrimos un socket conectado al servidor y al
// puerto est�ndar de echo
66
Socket socket = new Socket( servidor,puerto );

// Conseguimos el canal de entrada


BufferedReader entrada = new BufferedReader(
new InputStreamReader( socket.getInputStream() ) );
// Conseguimos el canal de salida
PrintWriter salida = new PrintWriter(
new OutputStreamWriter( socket.getOutputStream() ),true );

// Enviamos una l�nea de texto al servidor


salida.println( "Prueba de Eco" );
// Recogemos la l�nea devuelta por el servidor y la
// presentamos en pantalla
System.out.println( entrada.readLine() );

// Cerramos el socket
socket.close();
} catch( UnknownHostException e ) {
e.printStackTrace();
System.out.println(
"Debes estar conectado para que esto funcione bien." );
} catch( IOException e ) {
System.out.println( e );
}
}
}

El único propósito de este ejemplo es comprobar el controlador de seguridad que se ha


implementado en el ejemplo java2009, siendo igual que el ejemplo java2007, excepto que se
conecta con el localhost e intenta recoger un archivo protegido de ese servidor, intentando subir
en el árbol de directorio, para acceder a otros archivos, que no tuviesen alcance de otra forma.
Recuerde el lector que el control de seguridad implementado no está protegido contra peticiones
de archivos indicando el camino absoluto en que se encuentra ese archivo. Si se desea
implementar este caso se puede utilizar el método isAbsolute() de la clase File. El programa es
pues un cliente http (navegador) implementado con sockets, pero como el acceso a archivos a
ramas superiores a la de entrada, se generará una excepción, que será enviada al cliente.
import java.net.*;
import java.io.*;

class java2011 {
public static void main( String[] args ) {
String servidor = "localhost"; // servidor
int puerto = 1080; // puerto
try {
// Crea un socket, conectado al servidor y al puerto
// que se indica
Socket socket = new Socket(servidor,puerto);
// Crea el canal de entrada desde el socket
BufferedReader inputStream =
new BufferedReader( new InputStreamReader(
socket.getInputStream() ) );
// Crea el canal de salida
PrintWriter outputStream =
new PrintWriter( new OutputStreamWriter(
socket.getOutputStream() ),true );

// Env�a un comando GET al servidor. La siguiente sentencia hara


// que el servidor devuelva la excepcion de violacion de
// seguridad, al intentar recoger el archivo del directorio
// padre
// outputStream.println( "GET ../prueba01.html" );

67
// La siguiente sentencia devuelve el archivo que se
// solicite, si se encuentra ahi.
// El lector deberia protegerse contra ello
outputStream.println( "GET E:/AEjercicios/prueba01.html" );

// En esta cadena se va a ir leyendo el archivo


String line = null;
// Bucle para leer y presentar la líneas hasta que se
// reciba un null
while( (line = inputStream.readLine()) != null )
System.out.println( line );

// Se cierra el socket
socket.close();
} catch( UnknownHostException e ) {
e.printStackTrace();
System.out.println(
"Debes estar conectado para que esto funcione bien." );
} catch( IOException e ) {
e.printStackTrace();
}
}
}

Este ejemplo es igual que el programa java2006, que muestra la fecha y hora del servidor local,
en comparación con la hora actual de la máquina en que se esté ejecutando, en este caso también
la máquina local. La secuencia del programa es semejante al ejemplo anterior, solamente que en
este caso se conecta al puerto 13 del servidor "localhost", que es el puerto estándar para el
servicio de fecha. Una vez que se establece la conexión, no es necesario preguntar nada al
servidor, éste, por el mero hecho de haber establecido la conexión en ese puerto, envía la hora
automáticamente. Finalmente se imprime la fecha y hora de la máquina utilizando la clase Date.

import java.net.*;
import java.io.*;
import java.util.*;

class java20110 {
public static void main( String[] args ) {
String servidor = "localhost";
int puerto = 13; // puerto de daytime

try {
// Abrimos un socket conectado al servidor y al
// puerto estándar de echo
Socket socket = new Socket( servidor,puerto );
System.out.println( "Socket Abierto." );

// Conseguimos el canal de entrada


BufferedReader entrada = new BufferedReader(
new InputStreamReader( socket.getInputStream() ) );

System.out.println( "Hora actual en www.espe.edu.ec:" );


System.out.println( "\t"+entrada.readLine() );
System.out.println( "Hora Actual en Quito, Ecuador:" );
System.out.println( "\t"+new Date() );

// Cerramos el socket
socket.close();
} catch( UnknownHostException e ) {
System.out.println( e );
System.out.println(
"Debes estar conectado para que esto funcione bien." );
} catch( IOException e ) {
68
System.out.println( e );
}
}
}

69
6. Cliente y Servidor con Datagrama
6.1. Clientes Datagrama

La clase DatagramPacket, junto con la clase DatagramSocket, son las que se utilizan para la
implementación del protocolo UDP (User Datagram Protocol).

En este protocolo, a diferencia de lo que ocurría en el protocolo TCP, en el cual si un paquete se


dañaba durante la transmisión se reenviaba ese paquete, para asegurar una comunicación segura
entre cliente y servidor; con UDP no hay garantía alguna de que los paquetes lleguen en el orden
correcto a su destino y, ni tan siquiera hay seguridad de que lleguen todos los paquetes que se
hayan enviado. Sin embargo, los paquetes que consiguen llegan, lo hacen mucho más
rápidamente que con TCP y, en algunos casos, la velocidad de transmisión es mucho más
importante que el que lleguen todos los paquetes; por ejemplo, si lo que se están transmitiendo
son señales de sensores en tiempo real para la presentación en pantalla, la velocidad es más
importante que la integridad, ya que si un paquete no llega o no puede recomponerse, en el
instante siguiente llegará otro.

La programación para uso del protocolo UDP se diferencia de la programación para utilizar el
protocolo TCP en que no existe el concepto de ServerSocket para los datagramas y que es el
programador el que debe construirse sus propios paquetes a enviar por UDP.

Para enviar datos a través de UDP, hay que construir un objeto de tipo DatagramPacket y
enviarlo a través de un objeto DatagramSocket, y al revés para recibirlos, es decir, a través de
un objeto DatagramSocket se recoge el objeto DatagramPacket. Toda la información respecto
a la dirección, puerto y datos, está contenida en el paquete.

• Para enviar un paquete, primero se construye ese paquete con la información que se
desea transmitir, luego se almacena en un objeto DatagramSocket y, finalmente se
invoca el método send() sobre ese objeto.
• Para recibir un paquete, primero se construye un paquete vacío, luego se le presenta a
un objeto DatagramSocket para que almacene allí el resultado de la ejecución del
método receive() sobre ese objeto.

Hay que tener en cuenta que el hilo de ejecución encargado de todo esto estará bloqueado en el
método receive() hasta que un paquete físico de datos se reciba a través de la red; este paquete
físico será el que se utilice para rellenar el paquete vacío que se había creado.

También hay que tener cuidado cuando se pone a escuchar a un objeto DatagramSocket en un
puerto determinado, ya que va a recibir los datagramas enviados por cualquier cliente. Es decir,
que si los mensajes enviados por los clientes están formados por múltiples paquetes; en la

70
recepción pueden llegar paquetes entremezclados de varios clientes y es responsabilidad de la
aplicación el ordenarlos.

a. La clase DatagramPacket

Para la clase DatagramPacket se dispone de dos constructores, uno utilizado cuando se


quieren enviar paquetes y el otro se usa cuando se quieren recibir paquetes. Ambos requieren que
se les proporcione un array de bytes y la longitud que tiene. En el caso de la recepción de datos,
no es necesario nada más, los datos que se reciban se depositarán en el array; aunque, en el caso
de que se reciban más datos físicos de los que soporta el array, el exceso de información se
perderá y se lanzará una excepción de tipo IllegalArgumentException, que a pesar de que no
sea necesaria su captura, siempre es bueno recogerla.

Cuando se construye el paquete a enviar, es necesario colocar los datos en el array antes de
llamar al método send(); además de eso, hay que incluir la longitud de ese array y, también se
debe proporcionar un objeto de tipo InetAddress indicando la dirección de destino del paquete y
el número del puerto de ese destino en el cual estará escuchando el receptor del mensaje. Es
decir, que la dirección de destino y el puerto de escucha deben ir en el paquete, al contrario de lo
que pasaba en el caso de TCP que se indicaban en el momento de construir el objeto Socket.

El tamaño físico máximo de un datagrama es 65535 bytes, y teniendo en cuenta que hay que
incluir datos de cabecera, esa longitud nunca está disponible para datos de usuario, sino que
siempre es algo menor.

La clase DatagramPacket proporciona varios métodos para poder extraer los datos que llegan
en el paquete recibido. La información que se obtiene con cada método coincide con el propio
nombre del método, aunque hay algún caso en que es necesario saber interpretar la información
que proporciona ese mismo método. A contuación se describen los diferentes métodos de esta
clase:

• El método getAddress() devuelve un objeto de tipo InetAddress que contiene la


dirección del host remoto. El saber cuál es el ordenador de origen del envío depende de la
forma en que se haya obtenido el datagrama. Si ese datagrama ha sido recibido a través
de Internet, la dirección representará al ordenador que ha enviado el datagrama (el origen
del datagrama); pero si el datagrama se ha construido localmente, la dirección
representará al ordenador al cual se intenta enviar el datagrama (el destino del
datagrama).
• El método getPort() de igual modo, devuelve el puerto desde el cual ha sido enviado el
datagrama, o el puerto a través del cual se enviará, dependiendo de la forma en que se
haya obtenido el datagrama.
• El método getData() devuelve un array de bytes que contiene la parte de datos del
datagrama, ya eliminada la cabecera con la información de encaminamiento de ese
datagrama. La forma de interpretar ese array depende del tipo de datos que contenga. Los

71
ejemplos que se ven en este Tutorial utilizan exclusivamente datos de tipo String, pero
esto no es un requerimiento, y se pueden utilizar datagramas para intercambiar cualquier
tipo de datos, siempre que se puedan colocar en un array de bytes en un ordenador y
extraerlos de ese array en la parte contraria. Es decir, que la responsabilidad del sistema
se limita al desplazamiento del array de bytes de un ordenador a otro, y es
responsabilidad del programador el asignar significado a esos bytes.
• El método getLength() devuelve el número de bytes que contiene la parte de datos del
datagrama, y
• El método getOffset() devuelve la posición en la cual empieza el array de bytes dentro
del datagrama completo.

b. La clase DatagramSocket

Un objeto de la clase DatagramSocket puede utilizarse tanto para enviar como para recibir un
datagrama.

La clase tiene tres constructores. Uno de ellos se conecta al primer puerto libre de la máquina
local; el otro permite especificar el puerto a través del cual operará el socket; y el tercero permite
especificar un puerto y una dirección para identificar a una máquina concreta.

Independientemente del constructor que se utilice, el puerto desde el cual se envíe el datagrama,
siempre se incluirá en la cabecera del paquete. Normalmente, la parte del servidor utilizará el
constructor que permite indicar el puerto concreto a usar, ya que si no, la parte cliente no tendría
forma de conocer el puerto por el cual le van a llegar los datagramas.

La parte cliente puede utilizar cualquier constructor, pero por flexibilidad, lo mejor es utilizar el
constructor que deja que el sistema seleccione uno de los puertos disponibles. El servidor debería
entonces comprobar cuál es el puerto que se está utilizando para el envío de datagramas y enviar
la respuesta por ese puerto.

A diferencia de esta posibilidad de especificar el puerto, o no hacerlo, no hay ninguna otra


diferencia entre los sockets datagrama utilizados por cliente y servidor. Si el lector se encuentra
un poco perdido, no desespere, porque en los ejemplos todo esto que es muy difícl explicar con
palabras, se ve mucho más claramente al intuir el funcionamiento físico de la comunicación entre
cliente y servidor.

• Para enviar un datagrama, hay que invocar al método send() sobre un socket
datagrama existente, pasándole el objeto paquete como parámetro. Cuando el paquete es
enviado, la dirección y número de puerto del ordenador origen se coloca
automáticamente en la porción de cabecera del paquete, de forma que esa información
pueda ser recuperada en el ordenador destino del paquete.
• Para recibir datagramas, hay que instanciar un objeto de tipo DatagramSocket,
conectarse a un puerto determinado e invocar al método receive() sobre ese socket. Este

72
método bloquea el hilo de ejecución hasta que se recibe un datagrama, por lo que si es
necesario hacer alguna cosa durante la espera, hay que invocar al método receive() en su
propio hilo de ejecución.

Si se trata de un servidor, hay que conectarse con un puerto específico. Si se trata de un


cliente que está esperando respuestas de un servidor, hay que escuchar en el mismo puerto que
fue utilizado para enviar el datagrama inicial. Si se envía un datagrama a un puerto anónimo, se
puede mantener el socket abierto que fue utilizado en el envío del primer datagrama y utilizar ese
mismo socket para esperar la respuesta. También se puede invocar al método getLocalPort()
sobre el socket antes de cerrarlo, de forma que se pueda saber y guardar el número del puerto que
se ha empleado; de este modo se puede cerrar el socket original y abrir otro socket en el mismo
puerto en el momento en que se necesite.

Si el datagrama inicial es enviado a un puerto determinado, se puede utilizar el mismo socket


para recibir la respuesta, o cerrar el socket original y abrir uno nuevo sobre el mismo puerto.
Para responder a un datagrama, hay que obtener la dirección del origen y el número de puerto
a través del cual fue enviado el datagrama, de la cabecera del paquete y luego, colocar esta
información en el nuevo paquete que se construya con la información a enviar como respuesta.
Una vez pasada esta información a la parte de datos del paquete, se invoca al método send()
sobre el objeto DatagramSocket existente, pasándole el objeto paquete como parámetro. La
dirección y número de puerto de la máquina que está enviando será incorporada
automáticamente a la cabecera del paquete en el momento de ser enviado.

Es importante tener en cuenta que los números de puerto TCP y UDP no están relacionados. Se
puede utilizar el mismo número de puerto en dos procesos si uno se comunica a través de
protocolo TCP y el otro lo hace a través de protocolo UDP. Es muy común que los servidores
utilicen el mismo puerto para proporcionar servicios similares a través de los dos protocolos en
algunos servicios estándar, como puede ser el eco.

1. Cliente Eco Datagrama

Esta aplicación no es más que una actualización del ejemplo java2005.java, al que se le incluye
además el protocolo UDP. El ejemplo java2012.java realiza dos pruebas del servicio Echo contra
el mismo servidor, enviando una línea de texto al puerto estándar de este servicio, el puerto 7. En
la primera prueba utiliza el protocolo TCP/IP y en la segunda emplea un datagrama UDP.

Este ejemplo es una mejora del ejemplo java2005, que comprobaba el protocolo de echo. En este
caso también se incluye el echo a través de UDP. El programa realiza dos comprobaciones, la
primera enviando un mensaje al puerto 7 para probar la comunicación TCP/IP, y la segunda es a
través de un datagrama UDP, también a través de ese puerto. La parte TCP es igual al ejemplo
anterior, pero el mensaje que se envía por UDP se convierte a un array de bytes, que estará
contenido en un objeto de tipo DatagramPacket, que además contendrá la dirección del servidor,
que previamente ha obtenido instanciando un objeto de tipo InetAddress, y el número del puerto
de ese servidor. Luego se invoca al método send() del socket, pasándole el paquete como
parámetro, para que sea enviado. Los datos en el paquete se sobrescriben con "x" para confirmar
73
que se reciben datos del paquete, y que no residuo del mensaje anterior. Luego se invoca el
método receive() sobre el objeto DatagramSocket, pasándole el paquete como parámetro, esto
hace que el hilo se bloquee hasta que le llegue el paquete por el mismo puerto por el cual lo ha
enviado. Cuando el paquete original es recibido, los datos en este paquete físico se extraen y
escriben en la parte de datos del objeto que se proporciona como parámetro al método receive().
El hilo se desbloquea y el control del programa sigue con la presentación de los datos que
contenga el paquete en la pantalla que, como era de esperar, coincide con el que se envía
originalmente al puerto de eco del servidor.
import java.net.*;
import java.io.*;
import java.util.*;

class java2012 {
public static void main( String[] args ) {
String servidor = "localhost"; // servidor
int puerto = 7; // puerto eco
String cadTcp = "Prueba de Eco TCP";
String cadUdp = "Prueba de Eco UDP";

// Primero realizamos el test de Eco con TCP/IP


try {
// Abrimos un socket conectado al servidor y al
// puerto estandar de echo
Socket socket = new Socket( servidor,puerto );

// Conseguimos el canal de entrada


BufferedReader entrada = new BufferedReader(
new InputStreamReader( socket.getInputStream() ) );

// Conseguimos el canal de salida, con liberacion automatica


PrintWriter salida = new PrintWriter(
new OutputStreamWriter( socket.getOutputStream() ),true );

// EnvÃa la lÃnea de texto del mensaje al servidor


salida.println( cadTcp );
// Y recoge la respuesta del servidor, presentandola en pantalla
System.out.println( entrada.readLine() );

// se cierra el socket TCP


socket.close();

// Ahora se realiza la prueba con datagramas sobre el mismo


// puerto del mismo servidor
// Convertimos el mensaje en un array de bytes
byte[] mensajeUdp = cadUdp.getBytes();
// Obtenemos la direccion IP del servdor
InetAddress dirIp = InetAddress.getByName( servidor );
// Creamos el paquete que se va a enviar al pueto
DatagramPacket paquete =
new DatagramPacket( mensajeUdp,mensajeUdp.length,dirIp,puerto );
// Abrimos un socket datagrama para enviarle el mensaje
DatagramSocket socketDgrama = new DatagramSocket();
// Y lo enviamos
socketDgrama.send( paquete );

// Sobreescrimos el mensaje en el paquete para confirmar que el


// eco es realmente lo que llega
byte[] arrayDatos = paquete.getData();
for( int cnt=0; cnt < paquete.getLength(); cnt++ )
arrayDatos[cnt] = (byte)'x';
// Escribimos esta version del mensaje
74
System.out.println( new String( paquete.getData() ) );
// Ahora recibimos el eco en ese mismo paquete, de forma que
// sobreescriba las "x" que se habian colocado
socketDgrama.receive( paquete );
// Presentamos en pantalla el eco
System.out.println( new String( paquete.getData() ) );

// Se cierra el socket
socketDgrama.close();
} catch( UnknownHostException e ) {
e.printStackTrace();
System.out.println(
"Debes estar conectado para que esto funcione bien." );
} catch( SocketException e ) {
e.printStackTrace();
} catch( IOException e ) {
e.printStackTrace();
}
}
}

Explicación del programa

Se instancian dos objetos de tipo String, para utilizar uno diferente en cada protocolo. Luego
está la parte correspondiente a la prueba de eco a través de TCP, sobre la que no se insiste más.
Una vez cerrado el socket TCP, comienza la prueba de eco a través de UDP.

En primer lugar, se convierte el mensaje que se ha de enviar por UDP a un array de bytes. Hay
que instanciar un objeto de tipo InetAddress que contenga la dirección del servidor con el que
se va a realizar la conexión y el envío del datagrama con el array de bytes recién creado.

Se crea un objeto de tipo DatagramPacket que contenga el array de bytes de la cadena a enviar,
junto con la dirección del servidor y el puerto al que hay que conectarse. Se crea ahora un objeto
de tipo DatagramSocket que será utilizado para enviar el paquete al servidor. Sin embargo, hay
que recordar que el protocolo UDP no garantiza que el paquete llegue íntegro al servidor, o que
tan siquiera llegue.

Y, ya solamente resta invocar al método send() sobre el objeto DatagramSocket, pasánsole el


objeto DatagramPacket como parámetro, tal como muestra la siguiente línea de código. Esto
hace que la dirección local y el número de puerto se incorporen en el paquete y éste sea enviado
a la dirección y número de puerto que se ha encapsulado en el objeto DatagramPacket en el
momento de su creación.

Los mismos objetos DatagramSocket y DatagramPacket serán los utilizados para recibir el
paquete de respuesta del servidor (siempre que la suerte acompañe). Se usa un bucle para
sobreescribir los datos en el paquete con una letra para poder comprobar que una vez recibido el
paquete de respuesta del servidor de eco, los datos son nuevos y no simplemente el residuo del
mensaje que se había colocado originalmente en el paquete.

Luego se invoca el método receive() sobre el objeto DatagramSocket, pasánsole el objeto


DatagramPacket como parámetro. Esto hace que el hilo de ejecución se bloquee en el mismo
75
puerto por el que se ha enviado el paquete, hasta que llegue una respuesta. En el momento en que
un paquete físico llegue desde el servidor, se estraen los datos y se colocan en el objeto
DatagramPacket que se le ha proporcionado como parámetro. Una vez hecho esto, el hilo de
ejecución se desbloquea y el flujo de control de la aplicación sigue por la sentencia que muestra
el contenido del paquete.

Y ya, finalmente, se cierra el socket y el programa termina.

Como el ejemplo es una simple actualización de otro de los ejemplos del Tutorial, gran parte del
código ya está más que visto, y es exactamente igual al del ejemplo java2005.java. Así que,
solamente se van a repasar a continuación algunas de las líneas de código más interesantes de la
parte que se aporta nueva en este ejemplo, que corresponderá, evidentemente, a la
implementación del intercambio de mensajes con el servidor a través del puerto del servicio Eco,
pero con protocolo UDP.

El primer fragmento en que hay que detenerse es justo en la declaración del método main(), en
donde se declaran e inicializan algunas variables importantes, cuyo nombre es autoexplicativo,
como se puede comprobar.

public static void main( String[] args ) {


String servidor = "www.fie.us.es"; // servidor
int puerto = 7; // puerto eco
String cadTcp = "Prueba de Eco TCP";
String cadUdp = "Prueba de Eco UDP";

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

// Convertimos el mensaje en un array de bytes


byte[] mensajeUdp = cadUdp.getBytes();
// Obtenemos la dirección IP del servdor
InetAddress dirIp = InetAddress.getByName( servidor );

La siguiente línea de código también es importante, ya es es la que instancia un objeto de tipo


DatagramPacket que va a llevar toda la información del mensaje y del servidor, como es el
array de bytes creado antes, su longitud, y la dirección y puerto del servidor.

// Creamos el paquete que se va a enviar al pueto


DatagramPacket paquete =
new DatagramPacket( mensajeUdp,mensajeUdp.length,dirIp,puerto );

A continuación, se instancia un objeto DatagramSocket anónimo. Esto de anónimo puede


resultar confuso al lector. El objeto es anónimo porque el número de puerto no está especificado.
En algún sitio anteriormente se ha indicado que los objetos anónimos son aquellos que no tienen
una variable de referencia asignada; que no es el caso que se produce aquí. Este objeto se usa

76
para enviar el datagrama al servidor invocando al método send() sobre el objeto
DatagramSocket.

// Abrimos un socket datagrama para enviarle el mensaje


DatagramSocket socketDgrama = new DatagramSocket();
// Y lo enviamos
socketDgrama.send( paquete );

Las siguientes líneas de código son las que sobreescriben los datos del mensaje con una letra,
para el proósito que se ha indicado antes de comprobar que el mensaje que se recibe no es en
realidad el resto del que se ha mandado. Este fragmento de código también muestra al lector
cómo puede acceder a la parte de datos de un objeto DatagramPacket, en caso de que lo
necesite para cualquier otro propósito.

// Sobreescrimos el mensaje en el paquete para confirmar que el


// eco es realmente lo que llega
byte[] arrayDatos = paquete.getData();
for( int cnt=0; cnt < paquete.getLength(); cnt++ )
arrayDatos[cnt] = (byte)'x';

A partir de este punto, se asume que habrá una respuesta del servidor, así que se invoca el
método receive() sobre el mismo objeto DatagramSocket que se ha utilizado para enviar el
mensaje original al servidor. Esto bloquea el thread, o hilo de ejecución, quedando a la espera de
respuesta. Aunque no se ha indicado en ningún sitio, es posible utilizar el método setTimeout()
para indicar la cantidad de tiempo que el método receive() estará a la espera y bloqueando, lo
cual permite colocar una protección al programa para que no se quede colgado esperando una
respuesta que no llegue jamás.

// Ahora recibimos el eco en ese mismo paquete, de forma que


// sobreescriba las "x" que se habían colocado
socketDgrama.receive( paquete );

Y el resto del código del ejemplo es la presentación en pantalla del mensaje recibido del
servidor, que debe coincidir con el enviado, y el código de manejo de excepciones.

6.2. Servidores Datagrama

En el programa java2013.java, se utiliza como base el ejemplo java2009.java, para actualizarlo y


hacer que soporte el protocolo UDP. Además, esta aplicación se completa con el ejemplo
java2014.java, que es un programa que permite comprobar su funcionamiento, constituyendo la
parte cliente del servidor que se implementa.

Como el programa es una actualización del ejemplo java2009.java, el lector deberá tener en
cuenta las mismas advertencias que se realizaban al respecto de ese ejemplo, sobre todo en lo
que se refiere al agente de seguridad, en caso de que decida utilizar el ejemplo para sus propios
propósitos. Aquí solamente se muestra como ilustración de lo que se puede hacer, no para que se
haga uso de él para cualquier otro menester.

77
En el ejemplo se implementan tres servidores:

• Uno de ellos es un servidor Eco UDP implementado a través de un hilo de ejecución que
monitoriza un DatagramSocket sobre el puerto 7. Este servidor devuelve el array de
bytes que llegue en cada datagrama que reciba, enviando esos datos de regreso al cliente
que haya originado el mensaje.
• El segundo servidor es un servidor Eco TCP implementado a través de un hilo de
ejecución que monitoriza un ServerSocket sobre el puerto 7. Este servidor también
devuelve los datos que recibe al cliente que haya realizado la conexión.
• El tercer servidor es un servidor HTTP muy simple implementado a través de un hilo de
ejecucicón TCP que monitoriza el puerto 80. Este servidor solamente responde al
comando GET que se envíe desde un navegador, y enviar un archivo como un stream de
bytes.

La inclusión de estos tres tipos de servidores es para mostrar al lector la forma en que se pueden
utilizar los hilos de ejecución para dar servicio a múltiples puertos, haciendo además una mezcla
de protocolos TCP y UDP.

La parte del servidor HTTP se puede comprobar a través de un navegador indicando localhost
como nombre del servidor, y los otros servidores se pueden chequear mediante el ejemplo
java2014.java, que ha sido pensado específicamente para probar los servidores Eco instalados en
la propia máquina en que se ejecuten cliente y servidor. El código completo del ejemplo es el
que se muestra a continuación.

En este ejemplo mejora el ejemplo java2009 añadiéndole soporte para el protocolo echo a través
del puerto 7, pero a través de datagramas UDP. En el ejemplo se implementan tres servidores
diferentes sobre red IP, y solamente se pretende que sea ilustrativo, no para utilizarlo como algo
serio. Si se hace así, el autor declina toda responsabilidad.

• El primer servidor que se implementa es el servidor de echo "UDP" a través de un hilo de


ejecución que monitoriza un socket de tipo datagrama. Este servidor devuelve el array de
bytes que contenga el datagrama que le llegue por ese puerto. El puerto 7 es el estándar
del protocolo echo, tanto para TCP como para UDP.
• Otro de los servidores que implementa, es un servidor de "echo", creado a través de un
hilo de ejecución que monitoriza el puerto 7. Lo que hace es devolver la cadena que
recibe al cliente.
• El otro servidor implementa el protocolo HTTP, a través de un hilo de ejecución que
monitoriza el puerto 80, estándar de este protocolo. Este servidor tiene la capacidad de
responder al comando GET que se solicite desde el navegador y servir el archivo como
un stream de bytes.

El hecho de que se implementen los dos servidores a la vez, sobre distintos puertos, ilustra
también la forma en que se puede utilizar el mecanismo de los hilos de ejecución que
proporciona Java para servir a múltiples puertos, y en este caso, también muestra como en un
mismo programa se pueden mezclar comunicaciones TCP y UDP. Hay un controlador de
seguridad implementado, para prevenir que el servidor envíe algún archivo no deseado.
78
Solamente está autorizado a servir los archivos que se encuentren en el directorio actual y sus
subdirectorios. De otra forma, el servidor estará abierto y cualquiera podrá solicitar cualquier
archivo del disco. Cuidado si se instala en una red y se deja desatendido, porque se pueden
conectar clientes y tomar el control del ordenador, o hacer cosas extrañas. La parte HTTP del
programa se puede comprobar utilizando cualquier navegador indicando que el servidor es
"localhost". También se puede comprobar utilizando el ejemplo Sockets06, que está pensado
para testear específicamente el controlador de seguridad que se ha instalado en este ejemplo. La
parte de "echo" se puede comprobar con el programa Sockets05, pensado específicamente para
ese propósito, realizando test tanto de la porción UDP como de la porción TCP del programa.
import java.net.*;
import java.io.*;
import java.util.*;

public class java2013 {


public static void main( String[] args ) {
// Se instancia un objeto servidor para escuchar el puerto 80
ServidorHttp servidorHttp = new ServidorHttp();
// Se instancia un objeto servidor para escuchar el puerto 7
ServidorEco servidorEcho = new ServidorEco();
// Se instancia un objeto servidor UDP para escuchar el puerto 7
ServidorEcoUdp servidorEchoUdp = new ServidorEcoUdp();
}
}

// Esta es la clase que se utiliza para instanciar un hilo de ejecucion


// para el servidor Udp que se encarga de escuchar el puerto 7, que es
// el definido como estandar para el protocolo de "echo"
class ServidorEcoUdp extends Thread {
// Constructor
ServidorEcoUdp() {
// Arrancamos el hilo e invocamos al metodo run() para que empiece
// a correr
start();
}

public void run() {


try {
// Se instancia un objeto sobre el puerto 7
DatagramSocket socketDgrama = new DatagramSocket( 7 );
System.out.println( "Servidor Udp escuchando el 7" );
// Entramos en bucle infinito ecuchando el puerto. Cuando se
// produce una llamada, se lanza un hilo de ejecucion de
// ConexionEchoUdp para atenderla
while( true ) {
// Limitamos la cadena de eco a 1024 bytes
DatagramPacket paquete = new DatagramPacket( new byte[1024],1024 );
// Nos quedamos parados en el receive(), y devolvemos un socket
// cuando se recibe la llamada. Este socket es el que se pasa
// como parámetro al nuevo hilo de ejecución que se crrea
socketDgrama.receive( paquete );
new ConexionEchoUdp( paquete );
}
} catch( IOException e ) {
e.printStackTrace();
}
}
}

// Esta clase se utiliza la lanzar un hilo de ejecucion que atienda


// la llamada recibida a traves del puerto 7, el puerto de eco
class ConexionEchoUdp extends Thread {
DatagramPacket paquete;
79
// Constructor
ConexionEchoUdp( DatagramPacket paquete ) {
System.out.println( "Recibida una llamada en el puerto 7" );
this.paquete = paquete;
// Trabajamos por debajo de la prioridad de los otros puertos
setPriority( NORM_PRIORITY-1 );
// Se arranca el hilo y se pone a correr
start();
}

public void run() {


System.out.println( "Lanzado el hilo UDP de atencion del puerto 7" );

// Se crea el paquete de eco baandonos en los datos del paquete


// que se ha recibido como parametro
DatagramPacket paqueteEnvio = new DatagramPacket(
paquete.getData(),paquete.getLength(),
paquete.getAddress(),paquete.getPort() );
// Declaramos el socket datagrama
DatagramSocket socketDgrama = null;

try {
// Abrimos un socket datagrama
socketDgrama = new DatagramSocket();
// Se utiliza el nuevo socket datagrama para enviar el mensaje
// y cerrar el socket
socketDgrama.send( paqueteEnvio );
socketDgrama.close();
System.out.println("Socket UDP cerrado." );
}catch( UnknownHostException e ) {
System.out.println("Socket UDP cerrado." );
e.printStackTrace();
}catch( SocketException e ) {
System.out.println("Socket UDP cerrado." );
e.printStackTrace();
}catch( IOException e ) {
System.out.println("Socket UDP cerrado." );
e.printStackTrace();
}
}
}

// Esta es la clase que se utiliza para instanciar un hilo de ejecución


// para el servidor que se encarga de escuchar el puerto 7, que es el
// definido como estandar para el protocolo de "echo"
class ServidorEco extends Thread {
// Constructor
ServidorEco() {
// Arrancamos el hilo e invocamos al metodo run() para que empiece
// a correr
start();
}

public void run() {


try {
// Se instancia un objeto sobre el puerto 7
ServerSocket socket = new ServerSocket( 7 );
System.out.println( "Servidor escuchando el 7" );
// Entramos en bucle infinito ecuchando el puerto. Cuando se
// produce una llamada, se lanza un hilo de ejecución de
// ConexionEcho para atenderla
while( true )
// Nos quedamos parados en el accept(), y devolvemos un socket
// cuando se recibe la llamada. Este socket es el que se pasa
80
// como parametro al nuevo hilo de ejecución que se crea
new ConexionEcho( socket.accept() );
} catch( IOException e ) {
e.printStackTrace();
}
}
}

// Esta clase se utiliza la lanzar un hilo de ejecucion que atienda


// la llamada recibida a traves del puerto 7, el puerto de eco
class ConexionEcho extends Thread {
Socket socket;

// Constructor
ConexionEcho( Socket socket ) {
System.out.println( "Recibida una llamada en el puerto 7" );
this.socket = socket;
// Trabajamos por debajo de la prioridad de los otros puertos
setPriority( NORM_PRIORITY-1 );
// Se arranca el hilo y se pone a correr
start();
}

public void run() {


System.out.println( "Lanzado el hilo de atencion del puerto 7" );
BufferedReader entrada = null;
PrintWriter salida = null;

try {
// Conseguimos un canal de entrada desde el socket
entrada = new BufferedReader( new InputStreamReader(
socket.getInputStream() ) );
// Conseguimos un canal de salida hacia el socket. El canal es
// con liberación automática (autoflush)
salida = new PrintWriter( new OutputStreamWriter(
socket.getOutputStream()),true );

// Recogemos la cadena que llegue al puerto y la devolvemos en


// el mismo instante
String cadena = entrada.readLine();
salida.println( cadena );
System.out.println( "Recibido: "+cadena );
socket.close();
System.out.println( "Socket cerrado" );
} catch( IOException e ) {
e.printStackTrace();
}
}
}

// Esta es la clase que se utiliza para instanciar un hilo de ejecucion


// para el servidor que se encarga de escuchar el puerto 80, que es el
// definido como estandar para el protocolo de "http"
class ServidorHttp extends Thread {
// Constructor
ServidorHttp() {
// Arrancamos el hilo e invocamos al metodo run() para que empiece
// a correr
start();
}

public void run(){


try {
// Se instancia un objeto sobre el puerto 80
ServerSocket socket = new ServerSocket( 1080 );
81
System.out.println( "Servidor escuchando el puerto 80" );
// Entramos en bucle infinito ecuchando el puerto. Cuando se
// produce una llamada, se lanza un hilo de ejecucion de
// ConexionHttp para atenderla
while( true )
// Nos quedamos parados en el accept(), y devolvemos un socket
// cuando se recibe la llamada. Este socket es el que se pasa
// como parametro al nuevo hilo de ejecución que se crea
new ConexionHttp( socket.accept() );
} catch( IOException e ) {
e.printStackTrace();
}
}
}

// Esta clase se utiliza la lanzar un hilo de ejecucion que atienda


// la llamada recibida a traves del puerto 80, el puerto de http
class ConexionHttp extends Thread {
Socket socket;
BufferedReader entrada = null;
PrintWriter salida = null;
DataOutputStream pagina = null;

// Constructor
ConexionHttp( Socket socket ) {
System.out.println( "Recibida una llamada en el puerto 80" );
this.socket = socket;
// Trabajamos por debajo de la prioridad de los otros puertos
setPriority( NORM_PRIORITY-1 );
// Se arranca el hilo y se pone a correr
start();
}

public void run() {


System.out.println( "Lanzado el hilo de atencion al puerto 80" );
try{
// Conseguimos un canal de entrada desde el socket
entrada = new BufferedReader( new InputStreamReader(
socket.getInputStream() ) );

// Conseguimos un canal de salida hacia el socket. El canal es


// con liberacion automatica (autoflush)
salida = new PrintWriter( new OutputStreamWriter(
socket.getOutputStream() ),true );

// Ahora conseguimos un canal de salida para enviar el contenido


// del ficero que haya solicitado el usuario
pagina = new DataOutputStream( socket.getOutputStream() );

// Recogemos la cadena que llegue al puerto


String peticion = entrada.readLine();
System.out.println( "Recibida peticion: "+peticion );
// Analizamos esa peticion e intentamos responder. Solamente se
// contesta a las peticiones GET, cualquier otra no tiene
// respuesta alguna
StringTokenizer st = new StringTokenizer( peticion );
if( ( st.countTokens() >= 2 )
&& st.nextToken().equals("GET") ) {
System.out.println( "Primer tag GET, correcto" );
if( (peticion = st.nextToken()).startsWith("/") ) {
System.out.println(
"Siguiente toque que empieza por /, se le quita" );
peticion = peticion.substring( 1 );
}
if( peticion.endsWith("/") || peticion.equals("") ) {
82
System.out.println( "Peticion terminada en / o blanco, "+
"se le incorpora: index.html" );
peticion = peticion + "index.html";
System.out.println( "Peticion modificada: "+peticion );
}

System.out.println( "Intento de desacarga de: "+peticion );


FileInputStream archivo = new FileInputStream( peticion );
// Se instancia un array de bytes igual al número de bytes que
// se pueden leer del canal de entrada sin bloquearlo.
// Y luego se rellena con los datos
byte[] datos = new byte[archivo.available()];
archivo.read( datos );

// Se envÃa el array de datos al cliente


pagina.write( datos );
pagina.flush();
} else
// La petición que se ha hecho no es un GET
salida.println( "<HTML><BODY><P>400 Petici&oactute;n "+
"Err&oacute;nea<P></BODY></HTML>" );
socket.close();
System.out.println( "Socket cerrado" );
}catch( FileNotFoundException e ) {
salida.println(
"<HTML><BODY><P>404 No Encontrado<P></BODY></HTML>" );
try {
socket.close();
System.out.println( "Socket cerrado" );
}catch( IOException evt ) {
evt.printStackTrace();
}
}catch( SecurityException e ){
e.printStackTrace();
salida.println(
"<HTML><BODY><P>403 Acceso denegado<P></BODY></HTML>" );
salida.println( e );
try {
socket.close();
System.out.println( "Socket cerrado" );
}catch( IOException evt ) {
evt.printStackTrace();
}
}catch( IOException e ) {
e.printStackTrace();
try {
socket.close();
System.out.println( "Socket cerrado" );
}catch( IOException evt ) {
System.out.println(evt);
}
}
}
}

Explicación del programa


// Esta es la clase que se utiliza para instanciar un hilo de ejecución
// para el servidor que se encarga de escuchar el puerto 7, que es el
// definido como estándar para el protocolo de "echo"

... El resto del programa es igual que el ejemplo java2009.java

83
... por lo que se remite al lector a él como referencia

Ahora llega el turno a la revisión de las partes más interesantes del ejemplo, que en este caso se
limitan a las líneas de código que se han incorporado nuevas al ejemplo java2009.java, para
implementar el servidor Eco a través de UDP. Cada uno de los servidores se implementan
mediante hilos de ejecución, así que los tres servidores actúan concurrente y asíncronamente en
diferentes hilos de ejecución. Además, siempre que un objeto servidor necesite proporcionar un
servicio a un cliente, lanzará otro hilo de ejecución a una prioridad más baja, para dar servicio a
ese cliente, siguiendo a la escucha de otros posibles clientes que requieran su atención.

Dejando a un lado la parte ya vista en el ejemplo base, el primer trozo de código en que hay que
detenerse es la implementación del servidor UDP.

// Se instancia un objeto servidor UDP para escuchar el puerto 7


ServidorEcoUdp servidorEchoUdp = new ServidorEcoUdp();

Y, lo siguiente ya es el propio constructor de esa clase que se utiliza para instanciar el objeto que
va a proporcionar los servicios UDP de Eco a através del puerto 7. Este constructor, como se
muestra, se limita a invocar a su propio método start() para levantarse y empezar a correr. El
método start() invoca al método run().

// Constructor
ServidorEcoUdp() {
// Arrancamos el hilo e invocamos al método run() para que empiece
// a correr
start();
}

La siguiente línea de código muesta la instanciación del objeto DatagramSocket sobre el puerto
7, que ya se encuentra dentro de la acción del método run(), es decir, que es el comienzo el hilo
de ejecución.

// Se instancia un objeto sobre el puerto 7


DatagramSocket socketDgrama = new DatagramSocket( 7 );

Y ya, solamente queda el corazón del hilo de ejecución, que está formado por el bucle infinito
que instancia en primer lugar un objeto DatagramPacket vacío, para invocar al método
receive() sobre el DatagramSocket, pasándole el objeto DatagramPacket como parámetro.

Observe el lector que se ha limitado a 1024 byes los datos de entrada, por lo que no va a poder
recibir mensajes de longitud mayor que esa. Si es necesaria una longitud mayor, es suficiente con
adecuar el valor que se le pasa al constructor al que se necesite.

// Entramos en bucle infinito ecuchando el puerto. Cuando se


// produce una llamada, se lanza un hilo de ejecución de
// ConexionEchoUdp para atenderla
while( true )
// Limitamos la cadena de eco a 1024 bytes

84
DatagramPacket paquete = new DatagramPacket( new byte[1024],1024 );
// Nos quedamos parados en el receive(), y devolvemos un socket
// cuando se recibe la llamada. Este socket es el que se pasa
// como parámetro al nuevo hilo de ejecución que se crea
socketDgrama.receive( paquete );
new ConexionEcoUdp( paquete );
}

El método receive() bloquea el hilo de ejecución y se queda a la espera de que llegue un paquete
datagrama. Cuando esto suceda, se rellenará el objeto DatagramPacket vacío, y se instanciará
un nuevo hilo de ejecución de tipo ConexionEcoUdp para atender la petición del cliente,
pasándole también como parámetro el objeto DatagramPacket, pero en este caso ya relleno con
los datos recibidos en el paquete enviado por el cliente.

Una vez satisfecho el requerimiento del cliente, el hilo de ejecución vuelve al inicio del bucle,
instanciando un nuevo objeto DatagramPacket vacío, y bloqueando el hilo de ejecución a la
espera de la llegada del siguiente paquete datagrama.

En código que sigue en el programa, corresponde a la clase ConexionEchoUdp, de la cual se


instancia un objeto para atender al cliente. Toda la información disponible de este cliente se
encuentra en el objeto DatagramPacket que el constructor de este hilo de ejecución recibe como
parámetro.

Ese objeto es guardado por el constructor para usarlo más tarde, a continuación fija la prioridad
por debajo del nivel de los hilos de ejecución que están monitorizando los puertos, de forma que
la actividad del hilo correspondiente a ConexionEchoUdp no interfiera con otros hilos de
ejecución que puedan lanzarse para atender a las peticiones recibidas por esos puertos. Y, por
fin, ya se invoca el método start(), que a su vez invoca al método run() y el hilo de ejecución se
pone en marcha. Todo esto es lo que se hace en el código que se muestra.

ConexionEchoUdp( DatagramPacket paquete ) {


System.out.println( "Recibida una llamada en el puerto 7" );
this.paquete = paquete;
// Trabajamos por debajo de la prioridad de los otros puertos
setPriority( NORM_PRIORITY-1 );
// Se arranca el hilo y se pone a correr
start();
}

La misión encargada a este hilo de ejecución se limita al envío de una copia de los datos que le
llegan en el paquete recibido, de vuelta al cliente que se lo ha enviado. La dirección y puerto de
este cliente están incluidos en el paquete, donde el DatagramSocket del cliente los colocó antes
de enviar ese paquete.

El objeto DatagramPacket es casi directamente enviable de vuelta al cliente, pero para mostrar
el uso de algunos de los métodos de la clase DatagramPacket, se construye un nuevo objeto
para enviar de regreso al cliente, lo cual suele suceder más a menudo en servidores que realicen
tareas más complejas. El código siguiente es el que permite extraer la información necesaria para
generar un nuevo objeto. Como sugerencia al lector, podría intentar incluir la fecha en la parte de
85
datos del objeto, para que la información devuelta al cliente sea la misma que él envió, más la
fecha en que se recibió en el servidor; pero esto queda como ejercicio para el lector.

// Se crea el paquete de eco basándonos en los datos del paquete


// que se ha recibido como parámetro
DatagramPacket paqueteEnvio = new DatagramPacket(
paquete.getData(),paquete.getLength(),
paquete.getAddress(),paquete.getPort() );

El último código en que merece la pena detenerse en este ejemplo es ya la instanciación de un


nuevo objeto DatagramSocket que se va a utilizar para enviar el nuevo objeto DatagramPacket
creado, invocando al método send() del socket, de regreso al cliente. El resto del código ya es el
cierre del socket y el tratamiento de las excepciones.

// Abrimos un socket datagrama


socketDgrama = new DatagramSocket();
// Se utiliza el nuevo socket datagrama para enviar el mensaje
// y cerrar el socket
socketDgrama.send( paqueteEnvio );
socketDgrama.close();

6.3 Clientes de prueba con Datagramas

El ejemplo java2014.java, es un programa que permite comprobar el funcionamiento de los


servidores Datagramas del subtema 6.2., constituyendo la parte cliente del servidor que se
implementa.

La parte del servidor HTTP se puede comprobar a través de un navegador indicando localhost
como nombre del servidor, y los otros servidores se pueden chequear mendiante el ejemplo
java2014.java, que ha sido pensado específicamente para probar los servidores Eco instalados en
la propia máquina en que se ejecuten cliente y servidor. El código completo del ejemplo es el
que se muestra a continuación.

Esta es una versión del ejemplo java2012 que accede al host local, es decir que solamente se ha
cambiado la definición del servidor para que quien responda al eco sea la misma máquina en que
se está ejecutando el programa. El resto del ejemplo es exactamente igual, y el lector puede
referirse a él para ampliar su información.
import java.net.*;
import java.io.*;
import java.util.*;

class java2014 {
public static void main( String[] args ) {
String servidor = "localhost"; // servidor local
int puerto = 7; // puerto eco
String cadTcp = "Prueba de Eco TCP";
String cadUdp = "Prueba de Eco UDP";

// Primero realizamos el test de Eco con TCP/IP


try {
// Abrimos un socket conectado al servidor y al
// puerto estandar de echo
86
Socket socket = new Socket( servidor,puerto );

// Conseguimos el canal de entrada


BufferedReader entrada = new BufferedReader(
new InputStreamReader( socket.getInputStream() ) );

// Conseguimos el canal de salida, con liberación automática


PrintWriter salida = new PrintWriter(
new OutputStreamWriter( socket.getOutputStream() ),true );

// EnvÃa la linea de texto del mensaje al servidor


salida.println( cadTcp );
// Y recoge la respuesta del servidor, presentandola en pantalla
System.out.println( entrada.readLine() );

// se cierra el socket TCP


socket.close();

// Ahora se realiza la prueba con datagramas sobre el mismo


// puerto del mismo servidor
// Convertimos el mensaje en un array de bytes
byte[] mensajeUdp = cadUdp.getBytes();
// Obtenemos la dirección IP del servdor
InetAddress dirIp = InetAddress.getByName( servidor );
// Creamos el paquete que se va a enviar al pueto
DatagramPacket paquete =
new DatagramPacket( mensajeUdp,mensajeUdp.length,dirIp,puerto );
// Abrimos un socket datagrama para enviarle el mensaje
DatagramSocket socketDgrama = new DatagramSocket();
// Y lo enviamos
socketDgrama.send( paquete );

// Sobreescrimos el mensaje en el paquete para confirmar que el


// eco es realmente lo que llega
byte[] arrayDatos = paquete.getData();
for( int cnt=0; cnt < paquete.getLength(); cnt++ )
arrayDatos[cnt] = (byte)'x';
// Escribimos esta version del mensaje
System.out.println( new String( paquete.getData() ) );
// Ahora recibimos el eco en ese mismo paquete, de forma que
// sobreescriba las "x" que se habian colocado
socketDgrama.receive( paquete );
// Presentamos en pantalla el eco
System.out.println( "Hola: "+new String( paquete.getData() ) );

// Se cierra el socket
socketDgrama.close();
} catch( UnknownHostException e ) {
e.printStackTrace();
System.out.println(
"Debes estar conectado para que esto funcione bien." );
} catch( SocketException e ) {
e.printStackTrace();
} catch( IOException e ) {
e.printStackTrace();
}
}
}

87

También podría gustarte