protocolos - benemérita universidad autónoma de …mtovar.cs.buap.mx/doc/cap4red.pdfverificación...

23
Protocolos Otros de los aspectos más importantes de los Sistemas Distribuidos son los protocolos de comunicación que se detallan a continuación. Definición: Un protocolo de comunicación es un conjunto de reglas y formatos que se utilizan para la comunicación entre procesos que realizan una determinada tarea. Se requieren dos tipos de especificaciones: o Especificación de la secuencia de mensajes que se han de intercambiar. o Especificación del formato de los datos en los mensajes. La finalidad de los protocolos es permitir que componentes heterogéneos de sistemas distribuidos puedan desarrollarse independientemente, y por medio de las capas que componen el protocolo, exista una comunicación transparente entre ambos componentes. Es conveniente mencionar que estas capas del protocolo deben presentarse tanto en el receptor como en el emisor. Dentro de los protocolos más utilizados en los sistemas distribuidos se encuentran: o IP: Protocolo de Internet.- Protocolo de la capa de Red, que define la unidad básica de transferencia de datos y se encarga del direccionamiento de la información, para que llegue a su destino en la red. o TCP: Protocolo de Control de Transmisión.- Protocolo de la capa de Transporte, que divide y ordena la información a transportar en paquetes de menor tamaño para su envío y recepción. o HTTP: Protocolo de Transferencia de Hipertexto.- Protocolo de la capa de aplicación, que permite el servicio de transferencia de páginas de hipertexto entre el cliente Web y los servidores. o SMTP: Protocolo de Transferencia de Correo Simple.- Protocolo de la capa de aplicación, que procesa el envío de correo electrónico por la red. o POP3: Protocolo de Oficina de Correo.- Protocolo de la capa de aplicación, que gestiona los correos en Internet, es decir, permite a una estación de trabajo recuperar los correos que están almacenados en el servidor. Ventajas de un Sistema Distribuidos Las ventajas de los sistemas distribuidos con respecto de los centralizados son: - Economía. Los microprocesadores ofrecen mejor proporción precio/rendimiento que los mainframes, pues se pueden reunir un gran número de CPU’s baratos en un mismo sistema y dado el avance tecnológico de estos, se puede dar un mejor rendimiento que un sólo mainframe. - Velocidad. Un sistema distribuido puede tener mayor poder de cómputo que un mainframe. - Distribución inherente. Algunas aplicaciones utilizan computadoras que están separadas a cierta distancia. Por ejemplo, trabajo cooperativo apoyado por computadora, juegos cooperativos apoyados por computadora.

Upload: others

Post on 10-Mar-2020

4 views

Category:

Documents


0 download

TRANSCRIPT

Protocolos

Otros de los aspectos más importantes de los Sistemas Distribuidos son los protocolos de

comunicación que se detallan a continuación.

Definición:

Un protocolo de comunicación es un conjunto de reglas y formatos que se utilizan para la

comunicación entre procesos que realizan una determinada tarea. Se requieren dos tipos de

especificaciones:

o Especificación de la secuencia de mensajes que se han de intercambiar.

o Especificación del formato de los datos en los mensajes.

La finalidad de los protocolos es permitir que componentes heterogéneos de sistemas

distribuidos puedan desarrollarse independientemente, y por medio de las capas que

componen el protocolo, exista una comunicación transparente entre ambos componentes.

Es conveniente mencionar que estas capas del protocolo deben presentarse tanto en el

receptor como en el emisor.

Dentro de los protocolos más utilizados en los sistemas distribuidos se encuentran:

o IP: Protocolo de Internet.- Protocolo de la capa de Red, que define la unidad

básica de transferencia de datos y se encarga del direccionamiento de la

información, para que llegue a su destino en la red.

o TCP: Protocolo de Control de Transmisión.- Protocolo de la capa de Transporte,

que divide y ordena la información a transportar en paquetes de menor tamaño para

su envío y recepción.

o HTTP: Protocolo de Transferencia de Hipertexto.- Protocolo de la capa de

aplicación, que permite el servicio de transferencia de páginas de hipertexto entre el

cliente Web y los servidores.

o SMTP: Protocolo de Transferencia de Correo Simple.- Protocolo de la capa de

aplicación, que procesa el envío de correo electrónico por la red.

o POP3: Protocolo de Oficina de Correo.- Protocolo de la capa de aplicación, que

gestiona los correos en Internet, es decir, permite a una estación de trabajo

recuperar los correos que están almacenados en el servidor.

Ventajas de un Sistema Distribuidos

Las ventajas de los sistemas distribuidos con respecto de los centralizados son:

- Economía. Los microprocesadores ofrecen mejor proporción precio/rendimiento que

los mainframes, pues se pueden reunir un gran número de CPU’s baratos en un mismo

sistema y dado el avance tecnológico de estos, se puede dar un mejor rendimiento que

un sólo mainframe.

- Velocidad. Un sistema distribuido puede tener mayor poder de cómputo que un

mainframe.

- Distribución inherente. Algunas aplicaciones utilizan computadoras que están

separadas a cierta distancia. Por ejemplo, trabajo cooperativo apoyado por

computadora, juegos cooperativos apoyados por computadora.

- Confiabilidad. Si una computadora se descompone, el sistema puede sobrevivir como

un todo.

- Crecimiento por incrementos. Se puede añadir poder de cómputo en pequeños

incrementos.

Ventajas de los sistemas distribuidos con respecto a las computadoras personales aisladas

- Datos compartidos. Permiten que varios usuarios tengan acceso a una base de datos

común.

- Dispositivos compartidos. Permiten que varios usuarios compartan periféricos caros

como scanners o impresoras a color.

- Comunicación. Facilita la comunicación de persona a persona; por ejemplo, mediante

correo electrónico, FAX, chats, foros, etc.

- Flexibilidad. Difunde la carga de trabajo entre las computadoras disponibles en la

forma más eficaz en cuanto a costos.

Desventajas de un Sistema Distribuido

- Software. Existe poco software para los sistemas distribuidos en la actualidad.

- Redes. La red se puede saturar o causar otros problemas.

- Seguridad. Un acceso sencillo también se aplica a datos secretos.

La principal dificultad en el desarrollo de un sistema distribuido es el software, dado que el

diseño, la implantación presenta numerosas interrogantes:

Tipos de sistemas operativos y Lenguajes de Programación adecuados para estos

sistemas

Niveles de Transparencia

Responsabilidades del sistema y de los usuarios

Otro problema potencial es la configuración de las redes dado que es necesario considerar:

perdidas de mensajes

saturación en el trafico

extensión de la red

configuración de la topología

Aplicaciones Distribuidas.

- Una red de computadoras con una pila de procesadores

- Una aerolínea

- Fábrica de robots

- Un banco con sucursales

- Internet

- Multimedia y conferencias

Capítulo 4. Comunicación en red La diferencia más importante entre un sistema distribuido y un sistema con un procesador

es la comunicación entre procesos. En un sistema con un procesador se supone la existencia

de una memoria compartida, lo cual no existe en un sistema distribuido. La comunicación

entre procesos debe respetar reglas llamadas protocolos.

4.1 Protocolos con capas

Para que dos procesos logren la comunicación se deben poner de acuerdo en varios

aspectos como, cuántos voltios hay que utilizar para señalar un bit 0 y un bit 1; cómo

detectar el fin de un mensaje, etc.

Para facilitar este trabajo la organización internacional de estándares (internacional

standards organization- ISO) ha desarrollado un modelo de referencia llamado el modelo de

referencia para interconexión de sistemas abiertos, lo cual se abrevia como ISO OSI o el

modelo OSI.

El modelo OSI está diseñado para permitir la comunicación de los sistemas abiertos. Un

sistema abierto es aquel preparado para comunicarse con cualquier otro sistema abierto

mediante estándares que gobiernan el formato, contenido y significado de los mensajes

enviados y recibidos.

Un protocolo es un acuerdo entre las partes acerca de cómo debe desarrollarse la

comunicación.

El modelo OSI maneja dos tipos generales de protocolos (conexiones).

1. Protocolos orientados a la conexión. Antes de intercambiar datos, el emisor y el

receptor deben establecer en forma explícita una conexión y tal vez el protocolo.

2. Protocolos con conexión. No es necesaria una negociación previa.

4.1.1 Modelo OSI

En el modelo OSI, la comunicación se divide en 7 niveles o capas. Cada capa se maneja de

forma independiente y se encarga de un aspecto específico. Cada capa proporciona una

interfaz con la capa por encima de ella. La interfaz es un conjunto de operaciones que

juntas definen el servicio que la capa está preparada para ofrecer a sus usuarios.

La ventaja de un protocolo por capaz es su independencia, ya que una capa puede

modificarse o mejorarse sin afectar a las demás.

En el modelo OSI cuando se va a enviar un mensaje, este pasa por todas las capas

comenzando en la de Aplicación. Cada capa le coloca un encabezado al frente o al final, al

llegar el mensaje al otro lado, cada capa lo va desmenuzando, quitándole el encabezado que

le corresponde; la capa que recibe el mensaje es la capa física.

Capas

Fisica, Enlace de Datos, Red, Transporte, Sesión, Presentación, Aplicación

Capa física

Su propósito es transportar el flujo de bits de una computadora a otra. El protocolo de la

capa física se encarga de la estandarización de las interfaces eléctricas, mecánicas y de

señalización. Maneja elementos como la intensidad de la señal de red, cables, enchufes,

voltajes, distancias entre cables.

Capa de enlace de datos

La tarea principal de esta capa es agrupar a los bits en unidades llamadas marcos o tramas,

y detectar y corregir errores.

Uno de los mecanismos en la detección de errores es asignar números secuenciales a las

tramas y a cada trama colocarle una suma de verificación, sino esta correcta la suma de

verificación a la hora de recibir el marco, entonces el receptor le pide al transmisor que

vuelva a transmitir el marco x.

Capa de red

La tarea principal de la capa de red es elegir la mejor ruta (a esta actividad se le llama

ruteo), la ruta más corta no siempre es la mejor, lo importante es la cantidad de retraso, y

esto complica los algoritmos de ruteo.

La capa de red maneja dos protocolos: X.25 (orientado a la conexión) y el IP (sin

conexión).

Capa de transporte

La tarea de la capa de transporte es proporcionar conexiones confiables y económicas. Las

conexiones confiables (orientadas a la conexión) se pueden construir por arriba de X.25 o

IP. En X.25 los paquetes llegan en orden, en IP no, entonces la capa de transporte es la

encargada de ponerlos en orden.

El protocolo de transporte oficial ISO tiene cinco variantes, TP0, TP1, … , TP4.

Los protocolos más utilizados en esta capa son: TCP (transmisión control protocol-

protocolo para el control de transmisiones), orientado a la conexión y UDP (universal

datagrama protocol- protocolo datagrama universal) que es un protocolo sin conexión.

Capa de sesión

Esta es en esencia una versión mejorada de la capa de transporte. Proporciona el control del

diálogo, facilidades en la sincronización, la posibilidad de establecer conexiones llamadas

sesiones y la posibilidad de transferir datos sobre las sesiones en forma ordenada. En la

práctica rara vez las aplicaciones soportan esta capa.

Capa de presentación

Esta capa trata los problemas relacionados con la representación y el significado de los

datos.

Capa de aplicación

Es una colección de varios protocolos para actividades comunes, como el correo

electrónico, la transferencia de archivos y la conexión entre terminales remotas a las

computadoras en una red. Esta capa contiene los programas de usuario.

Protocolos utilizados: X.400 para correo electrónico, X.500 para el servidor de directorios.

4.2 El modelo cliente servidor

Un sistema distribuido basado en LAN no utiliza de modo alguno los protocolos con capas;

y si lo hacen, solo utilizan un subconjunto de toda una pila de protocolos.

El modelo cliente-servidor se presenta con la idea de estructurar el sistema operativo como

un grupo de procesos en cooperación, llamados servidores, que ofrezcan servicios a los

usuarios, llamados clientes. Las computadoras de los clientes y servidores ejecutan el

mismo micronúcleo (generalmente) y ambos se ejecutan como procesos de usuario.

Este modelo se basa usualmente en un protocolo solicitud/respuesta sencillo y sin conexión.

El cliente es quien solicita al servidor algún servicio, y el servidor es quien atiende la

solicitud y regresa los datos solicitados o un código de error si algo falló.

Ventajas

Sencillez. El cliente envía un mensaje y obtiene respuesta.

Eficiencia. La pila del protocolo es más corta y por tanto más eficiente. No es necesario

implantar capas como la de sesión o red y transporte, ya que este trabajo lo hace el

hardware.

Se reducen los servicios de comunicación. Solo necesitamos las llamadas send (enviar)

y receive (recibir).

4.3 Direccionamiento

Para que un proceso envíe un mensaje a otro proceso que esta en otra computadora, es

necesario que el primer proceso sepa donde esta el otro proceso (su dirección).

El primer acercamiento para resolver el problema de cómo identificar un proceso, es

asociarle a cada computadora una dirección, aunque esto implicaría que cada computadora

sólo puede ejecutar un proceso. Para resolver esta limitante, se podrían dar identificadores a

los procesos y no a las computadoras, pero ahora surge el problema de cómo identificar dos

procesos. Un esquema común consiste en utilizar nombre con dos partes, una para

especificar la computadora y la otra para especificar el proceso.

Sin embargo este tipo de direccionamiento no es bueno, ya que cada usuario debe conocer

la dirección del servidor y esto ya no es transparente. Si el servidor falla y su dirección

cambia, entonces se deben recompilar los programas que utilicen ese servidor.

Una alternativa es que cada proceso elija su identificador de un gran espacio de direcciones

dispersas, como el espacio de enteros de 64 bits. La posibilidad de que dos procesos elijan

el mismo número es muy pequeña y el método puede utilizarse en sistemas extensos.

¿Cómo sabe el núcleo emisor a qué computadora enviar el mensaje? En una LAN que

soporte transmisiones, el emisor puede transmitir un paquete especial de localización con la

dirección del proceso destino. Todas las computadoras de la red lo recibirán y los núcleos

verificarán si la dirección es la suya, en este caso, regresa un mensaje “aquí estoy” con su

dirección en la red. El núcleo emisor utiliza entonces esa dirección y la captura, para evitar

el envío de otra transmisión la próxima vez que necesite al servidor.

Este último esquema tiene un problema: la carga adicional en el sistema. Esto se evita

utilizando una computadora adicional para la asociación a alto nivel de los nombres de los

servicios con las direcciones de las computadoras. Así se utilizan nombres de procesos y no

números. Cada vez que se ejecute un cliente, primero va al servidor de nombres, y pide el

número de la computadora donde se localiza algún servidor. Así las direcciones se ocultan.

Este método tiene la desventaja de utilizar un componente centralizado.

Primitivas con bloqueo vs. Sin bloqueo

La forma de comunicación entre los procesos es el intercambio de mensajes, para lo cual

son necesarias ciertas primitivas como send para enviar y receive para recibir.

Existen dos tipos de primitivas: con bloqueo o sin bloqueo.

Primitivas con bloqueo (síncronas). Al invocarse un send o un receive, el control

del proceso no regresa hasta que se haya enviado el mensaje o hasta que se haya recibido

un mensaje en el caso de receive, mientras tanto el proceso queda bloqueado. En este caso

la desventaja esta en que el CPU esta inactivo durante la transmisión de los mensajes.

Primitivas sin bloqueo (asíncronas). Tanto send como receive no tiene bloqueo. En

el caso de send, esta primitiva coloca el mensaje en el buffer de mensaje y regresa el

control inmediatamente sin que el mensaje haya sido enviado. El problema esta en saber

cuando se puede volver a utilizar el buffer de mensajes.

Existen dos soluciones a este problema. La primera es que el núcleo copie el mensaje a un

buffer interno del núcleo y luego permita el proceso continuar. Así el proceso permanecerá

bloqueado mientras se lleva a cabo la copia. La desventaja es que cada mensaje debe ser

copiado del espacio del usuario al espacio del núcleo. Esto puede reducir el desempeño del

sistema.

La segunda solución es interrumpir al emisor cuando el mensaje ha sido enviado y hacerle

saber que ya puede usar nuevamente el buffer de mensajes. Este método es eficiente, pero

es muy difícil escribir los programas en forma correcta y es casi imposible depurarlos

cuando están incorrectos.

En el caso de un receive sin bloqueo, éste sólo avisa al núcleo de la localización del buffer

y regresa el control inmediatamente. Para hacer que éste espere se utilizaría un wait.

En las primitivas síncronas puede suceder que un send o receive se queden esperando por

siempre, así que para evitarlo se utilizan los llamados tiempos de espera, si en este tiempo

nada llega, la llamada send termina con un estado de error.

Primitivas almacenadas en buffer vs. No almacenadas

Las primitivas anteriores son primitivas no almacenadas, esto es, los mensajes son enviados

a una dirección específica. El proceso receptor se bloquea mientras copia el mensaje a un

buffer. Esto funciona bien mientras el receptor este siempre listo para escuchar un mensaje.

Pero cuando primero se ejecuta send y luego receive hay problemas, puesto que la llamada

a receive es el mecanismo que indica al núcleo del servidor la dirección que utiliza el

servidor y la posición donde colocar el mensaje que está por llegar; entonces si primero

llega un mensaje, el servidor no sabrá de quién es y dónde ponerlo.

Soluciones:

1. Confiar que se va a ejecutar siempre un receive antes de un send. Mala idea.

2. Hacer que el transmisor realice varios intentos para enviar su mensaje, pero si el

servidor esta muy ocupado, otros esperarán un tiempo y luego se rendirán.

3. Que el núcleo receptor mantenga pendientes los mensajes. Si no ocurre un receive

en un determinado tiempo, el mensaje es descartado.

4. Utilizar un buzón donde almacenar los mensajes. Un proceso que va a recibir

mensajes pide al núcleo que le sea creado un buzón, en donde le depositarán los

mensajes y él los irá leyendo. A esta técnica se le conoce como primitiva con

almacenamiento en buffers.

El problema con los buzones es que son finitos y se pueden llenar.

Soluciones:

1. Mantener pendientes los mensajes

2. Descartarlos

3. El cliente debe verificar primero si hay espacio, si existe entonces enviar mensaje

sino bloquearse.

Primitivas confiables vs. No confiables

Cuando el cliente envía un mensaje, a pesar de utilizar primitivas con bloqueo, no existe

garantía de que el mensaje haya sido entregado. El mensaje pudo haberse perdido.

Existen tres enfoques de este problema:

1. Volver a definir la semántica de send para hacerla no confiable, es decir, la

primitiva no se encarga de verificar que su mensaje haya sido entregado y toda la

responsabilidad de comunicación confiable se deja en manos del usuario.

2. El núcleo de la computadora receptora envía un reconocimiento al núcleo de la

computadora emisora. El reconocimiento va de núcleo a núcleo, así ni el cliente ni

el servidor se dan cuenta de esto. El núcleo entonces liberará al proceso cuando

reciba el reconocimiento.

3. Aprovechar el hecho de que en la comunicación cliente-servidor se realiza de la

siguiente manera: el cliente le envía un mensaje de solicitud al servidor y el servidor

le da una respuesta al cliente. Entonces no hay mensaje de reconocimiento por parte

del núcleo del servidor, sino que la respuesta del servidor funciona como

reconocimiento. Así, el emisor (cliente) permanece bloqueado hasta que regresa la

respuesta. Si se tarda demasiado, el núcleo emisor puede enviar la solicitud

nuevamente.

En este método no existe reconocimiento para la respuesta, esta omisión puede ocasionar

problemas si el servicio solicitado por el cliente requiere cómputo extenso, porque el

servidor no se enteraría si el cliente recibió o no la respuesta.

Para solucionar esto se envía reconocimiento de la respuesta en base a un cronómetro, si la

respuesta llegó rápido, no hay reconocimiento, si tardó demasiado si hay reconocimiento.

4.4 Sockets

Ambas formas de comunicación (UDP y TCP) utilizan la abstracción de sockets, que

proporciona los puntos extremos de la comunicación entre procesos. Los sockets

(conectores) están presentes en la mayoría de las versiones de UNIX, incluido Linux y

también Windows NT y Macintosh OS.

Los sockets permiten conectar dos programas en red para que se puedan intercambiar datos.

Los sockets están basados en una arquitectura cliente/servidor. En esta arquitectura uno de

los programas debe estar siempre arrancado y pendiente de que alguien establezca conexión

con él. Este programa se denomina servidor. El otro programa lo arranca el usuario cuando

lo necesita y es el programa que da el primer paso en el establecimiento de la

comunicación. Este programa se llama cliente.

El servidor, está continuamente a la escucha y a la espera de que alguien se quiera conectar

a él. Si hacemos una comparación con un teléfono, un servidor es una empresa 24 horas,

365 días al año, pendiente de recibir llamadas de sus clientes.

El cliente, en un momento dado decide conectarse a un servidor y hacerle alguna petición.

Cuando el cliente no necesita al servidor, cierra la conexión. En la comparativa del

teléfono, el cliente es el que llama por teléfono a la empresa cuando necesita algo de ella.

Por ejemplo, un servidor de páginas web está siempre en marcha y a la escucha. El

navegador es el cliente. Cuando arrancamos el navegador y ponemos la dirección del

servidor web, el navegador establece la comunicación y le pide al servidor la página web

que queremos ver. El servidor la envía y el navegador la muestra.

La comunicación entre procesos consiste en la transmisión de un mensaje entre un conector

de un proceso y un conector de otro proceso.

Para los procesos receptores de mensajes, su conector debe estar asociado a un puerto local

y a una de las direcciones Internet de la computadora donde se ejecuta. Los mensajes

enviados a una dirección de Internet y a un número de puerto concretos, sólo pueden ser

recibidos por el proceso cuyo conector esté asociado con esa dirección y con ese puerto.

Los procesos pueden utilizar un mismo conector tanto para enviar como para recibir

mensajes. Cada computadora permite un gran número (216) de puertos posibles, que

pueden ser usados por los procesos locales para recibir mensajes.

Cada proceso puede utilizar varios puertos para recibir mensajes, pero un proceso no puede

compartir puertos con otros procesos de la misma computadora.

Cualquier cantidad de procesos puede enviar mensajes a un mismo puerto. Cada conector

se asocia con un protocolo concreto, que puede ser UDP o TCP.

4.4.1 Sockets flujos (TCP)

Son un servicio orientado a la 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 flujos es un protocolo orientado a la 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.

El protocolo TCP (Transmission Control Protocol) funciona en el nivel de trasporte,

basándose en el protocolo de red IP (Internet Protocol). IP proporciona comunicaciones no

fiables y no basadas en conexión, muy dependientes de saturación en la red, caídas de

notos, etc. Por el contrario, TCP está orientado a conexión y proporciona comunicaciones

fiables basadas en mecanismos de red que gestionan el control de flujo de paquetes y de

congestión en los nodos.

En Java, las comunicaciones TCP se realizan utilizando la clásica abstracción de socket.

Los sockets nos permiten establecer y programar comunicaciones sin tener que conocer los

niveles inferiores sobre los que se asientan.

Para identificar el destino de los paquetes de datos, los sockets utilizan los conceptos de

dirección y puerto.

Los valores númericos de puestos 1 – 1023 se reservan a servicios de interés general,

montados a menudo sobre protocolos de uso extendido:

Puerto Servicio

80 Para web con http

25 Para correo saliente con SMTP

110 Para correo entrante con POP3

119 Para el servicio de noticias con NNTP

Los valores de puertos entre 1024 y 49151 se usan para servicios específicos de uso no

general, el resto (a partir de 49152) se emplean para designar servicios de uso esporádico.

Establecimiento de comunicaciones

Java proporciona dos clases de abstracción de comunicaciones TCP: una para los procesos

cliente (socket) y otra para los procesos servidor (ServerSocket). Antes de entrar en los

detalles de programación se muestra la Figura 4.1 que es el esquema básico de

establecimiento de comunicaciones TCP.

1. El programa que proporciona el servicio (programa servidor) crea una instancia de la

clase ServerSocket, indicando el puerto asociado al servicio: ServerSocket SocketServidor = new ServerSocket(Puerto)

2 El programa que proporciona el servicio invoca al método accept sobre el socket de tipo

ServerSocket. Este método bloquea el programa hasta que se produce una conexión por

parte de un cliente: …SocketServidor.accept();

2. El método accept devuelve un socket de tipo Socket, con el que se realiza la

comunicación de datos del cliente al servidor: Socket ComunicaConCliente = SocketServidor.accept();

3. El programa cliente crea una instancia de tipo Socket, a la que proporciona la dirección

del nodo destino y el puerto del servicio:

Socket SocketCliente = new Socket(Destino, Puerto);

4. Internamente, el socket del cliente trata de establecer comunicación con el socket de

tipo ServerSocket existente en el servidor; cuando la comunicación se establece es

cuando realmente (físicamente) se produce el paso 3 del diagrama.

5. Con los pasos anteriores completados se puede empezar a comunicar datos entre el

cliente (o cliente) y el servidor.

Figura 4.1 Comunicación TCP entre un cliente y un servidor.

Programa

Cliente

Programa Servidor

Nodo destino

Puerto destino

Socket

Puerto destino

ServerSocket

Socket

Puerto

2.accept

4. Instanciación

1. Instanciación

3. return del accept

5. Conexión TCP

(interno)

6. Comunicación

Transmisión de datos

TCP es un protocolo especialmente útil cuando se desea transmitir un flujo de datos en

lugar de pequeñas cantidades aisladas de información. Debido a está característica, los

sockets de Java están diseñados para transmitir y recibir datos a través de los Streams

definidos en el paquete java.io.

La clase Socket contiene dos métodos importantes que se emplean en el proceso de

transmisión de flujos de datos:

InputStream getInputStream()

OutputStream getOutputStream()

Estas clases son abstractas, por lo que no podemos emplear directamente todos sus

métodos. En general se usan otras clases más especializadas que nos permiten trabajar con

flujos de datos como: DataOutputStream, DataInputStream, FileOutputStream,

FileInputStream, etc.

Ejemplo Hola Mundo

Para esta versión necesitamos:

Un programa que se ejecute en el equipo cliente y envíe el texto “Hola mundo”:

TCPClienteHolaMundo

Un programa que se ejecute en el equipo servidor y reciba e imprima el mensaje:

TCPServidorHolaMundo

Los programas TCPClienteHolaMundo y TCPServidorHolaMundo han sido

implementados siguiendo los esquemas mostrados en los apartados anteriores, por lo que

resultarán muy fáciles de entender. Comencemos con TCPClienteHolaMundo:

TCPClienteHolaMundo.java import java.net.Socket;

import java.io.*;

import java.net.UnknownHostException;

public class TCPClienteHolaMundo{

public static void main(String[] args){

OutputStream FlujoDeSalida;

DataOutputStream Flujo;

try{

Socket SocketCliente = new Socket("localhost",8000);

FlujoDeSalida = SocketCliente.getOutputStream();

Flujo = new DataOutputStream(FlujoDeSalida);

Flujo.writeBytes("Hola Mundo");

SocketCliente.close();

}catch (UnknownHostException e){

System.out.println("Referencia a host no resuelta");

}catch (IOException e){

System.out.println("Error en las comunicaciones");

}catch (SecurityException e){

System.out.println("Comunicacion no permitida por razones de

seguridad");

}

}

}

Ahora escribimos la clase TCPServidorHolaMundo:

import java.net.ServerSocket;

import java.net.Socket;

import java.io.*;

public class TCPServidorHolaMundo{

public static void main(String[] args){

byte[] Mensaje = new byte[80];

InputStream FlujoDeEntrada;

DataInputStream Flujo;

try{

ServerSocket SocketServidor = new ServerSocket(8000);

Socket ComunicaConCliente = SocketServidor.accept();

System.out.println("Comunicacion establecida");

FlujoDeEntrada = ComunicaConCliente.getInputStream();

Flujo = new DataInputStream(FlujoDeEntrada);

int BytesLeidos = Flujo.read(Mensaje);

System.out.println(new String(Mensaje));

ComunicaConCliente.close();

SocketServidor.close();

}catch (IOException e){

System.out.println("Error en las comunicaciones");

System.exit(0);

}catch (SecurityException e){

System.out.println("Comunicacion no permitida por razones de

seguridad");

System.exit(0);

}

}

}

Ejemplos de lectura y escritura de un socket

A continuación se presenta un simple ejemplo que ilustra como un programa puede

establecer la conexión a un programa servidor usando la clase Socket y como el cliente

puede enviar datos y recibir datos del servidor a través del socket.

El ejemplo implementa un cliente, echoClient, que se conecta al echoServer. El echoServer

simplemente recibe datos del cliente y hace echo hacia atrás.

Programa: echoClient.java

import java.io.*;

import java.net.*;

public class echoClient {

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

Socket echoSocket = null;

PrintWriter out = null;

BufferedReader in = null;

try {

echoSocket = new Socket("taranis", 7);

out = new PrintWriter(echoSocket.getOutputStream(), true);

in = new BufferedReader(new InputStreamReader(

echoSocket.getInputStream()));

} catch (UnknownHostException e) {

System.err.println("Don't know about host: taranis.");

System.exit(1);

} catch (IOException e) {

System.err.println("Couldn't get I/O for "

+ "the connection to: taranis.");

System.exit(1);

}

BufferedReader stdIn = new BufferedReader(

new InputStreamReader(System.in));

String userInput;

while ((userInput = stdIn.readLine()) != null) {

out.println(userInput);

System.out.println("echo: " + in.readLine());

}

out.close();

in.close();

stdIn.close();

echoSocket.close();

}

}

El programa cliente debe realizar los siguientes pasos:

1. Abrir un socket

2. Abrir un flujo de entrada y salida para el socket.

3. Leer desde y escribir al flujo de acuerdo al protocolo del servidor

4. Cerrar los flujos

5. Cerrar el socket

Únicamente el paso 3 difiere de cliente a cliente, dependiendo del servidor. Los otros pasos

son los mismos.

Ejercicio: Construya el servidor.

Otros ejemplos

Revisar los programas: KnockKnockProtocol KnockKnockServer

KnockKnockClient

KnockKnockServer.java

import java.net.*;

import java.io.*;

public class KnockKnockServer {

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

ServerSocket serverSocket = null;

try {

serverSocket = new ServerSocket(4444);

} catch (IOException e) {

System.err.println("Could not listen on port: 4444.");

System.exit(1);

}

Socket clientSocket = null;

try {

clientSocket = serverSocket.accept();

} catch (IOException e) {

System.err.println("Accept failed.");

System.exit(1);

}

PrintWriter out =

new PrintWriter(clientSocket.getOutputStream(), true);

BufferedReader in = new BufferedReader(

new InputStreamReader(

clientSocket.getInputStream()));

String inputLine, outputLine;

KnockKnockProtocol kkp = new KnockKnockProtocol();

outputLine = kkp.processInput(null);

out.println(outputLine);

while ((inputLine = in.readLine()) != null) {

outputLine = kkp.processInput(inputLine);

out.println(outputLine);

if (outputLine.equals("Bye."))

break;

}

out.close();

in.close();

clientSocket.close();

serverSocket.close();

}

}

KnockKnockClient.java

import java.io.*;

import java.net.*;

public class KnockKnockClient {

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

Socket kkSocket = null;

PrintWriter out = null;

BufferedReader in = null;

try {

kkSocket = new Socket("taranis", 4444);

out = new PrintWriter(kkSocket.getOutputStream(), true);

in = new BufferedReader(new

InputStreamReader(kkSocket.getInputStream()));

} catch (UnknownHostException e) {

System.err.println("Don't know about host: taranis.");

System.exit(1);

} catch (IOException e) {

System.err.println("Couldn't get I/O for the connection to:

taranis.");

System.exit(1);

}

BufferedReader stdIn = new BufferedReader(new

InputStreamReader(System.in));

String fromServer;

String fromUser;

while ((fromServer = in.readLine()) != null) {

System.out.println("Server: " + fromServer);

if (fromServer.equals("Bye."))

break;

fromUser = stdIn.readLine();

if (fromUser != null) {

System.out.println("Client: " + fromUser);

out.println(fromUser);

}

}

out.close();

in.close();

stdIn.close();

kkSocket.close();

}

}

KnockKnockProtocol.java

import java.net.*;

import java.io.*;

public class KnockKnockProtocol {

private static final int WAITING = 0;

private static final int SENTKNOCKKNOCK = 1;

private static final int SENTCLUE = 2;

private static final int ANOTHER = 3;

private static final int NUMJOKES = 5;

private int state = WAITING;

private int currentJoke = 0;

private String[] clues = { "Turnip", "Little Old Lady", "Atch", "Who", "Who"

};

private String[] answers = { "Turnip the heat, it's cold in here!",

"I didn't know you could yodel!",

"Bless you!",

"Is there an owl in here?",

"Is there an echo in here?" };

public String processInput(String theInput) {

String theOutput = null;

if (state == WAITING) {

theOutput = "Knock! Knock!";

state = SENTKNOCKKNOCK;

} else if (state == SENTKNOCKKNOCK) {

if (theInput.equalsIgnoreCase("Who's there?")) {

theOutput = clues[currentJoke];

state = SENTCLUE;

} else {

theOutput = "You're supposed to say \"Who's there?\"! " +

"Try again. Knock! Knock!";

}

} else if (state == SENTCLUE) {

if (theInput.equalsIgnoreCase(clues[currentJoke] + " who?")) {

theOutput = answers[currentJoke] + " Want another? (y/n)";

state = ANOTHER;

} else {

theOutput = "You're supposed to say \"" +

clues[currentJoke] +

" who?\"" +

"! Try again. Knock! Knock!";

state = SENTKNOCKKNOCK;

}

} else if (state == ANOTHER) {

if (theInput.equalsIgnoreCase("y")) {

theOutput = "Knock! Knock!";

if (currentJoke == (NUMJOKES - 1))

currentJoke = 0;

else

currentJoke++;

state = SENTKNOCKKNOCK;

} else {

theOutput = "Bye.";

state = WAITING;

}

}

return theOutput;

}

}

Configuración de las comunicaciones

Clase ServerSocket

Métodos principales Acción

socket accept() Espera a que se realice una conexión y devuelve un

socket para comunicarse con el cliente.

void bind(SocketAddress a) Asigna la dirección establecida al socket creado con

accept, si no se utiliza este método se asigna

automáticamente una dirección temporal.

void close() Cierra el socket

InetAddress getInetAddress() Devuelve la dirección a la que está conectada el

socket

int getLocalPort() Devuelve el número de puerto asociado al socket

int getSoTimeout() Devuelve el valor en milisegundos que el socket

espera al establecimiento de comunicación tras la

ejecución de accept

void setSoTimeout(int ms) Asigna el número de milisegundos que el socket

espera al establecimiento de comunicación tras la

ejecución de accept

Clase Socket

Métodos principales Acción

void bind(SocketAddress a) Asigna la dirección establecida al socket creado con

accept, si no se utiliza este método se asigna

automáticamente una dirección temporal

void close() Cierra el socket.

void connect(SocketAddress

a)

Conecta el socket a la dirección de servidor

establecida

void connect(SocketAddress

a, int ms)

Conecta el socket a la dirección de servidor

establecida, esperando un máximo de ms

milisegundos.

InetAddress getInetAddress() Devuelve la dirección a la que está conectada el

socket

InputStream getInputStream Devuelve el stream de entrada asociada al socket

Int gelLocalPort() Devuelve el número de puerto asociado al socket

OutputStream

getOutputStream()

Devuelve el stream de salida asociado al socket

int getPort() Devuelve el valor del puerto remoto al que está

conectado

int getSoLinger() Devuelve el número de milisegundos que se espera a

los datos después de cerrar el socket.

4.4.2 Sockets Datagrama (UDP)

Son un servicio de transporte sin conexión. Son 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 comunicación 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, aunque tiene la ventaja de que

se pueden indicar direcciones globales y el mismo mensaje llegará a muchas computadoras

a la vez.

Un datagrama enviado por UDP se transmite desde un proceso emisor a un proceso

receptor sin acuse de recibo ni reintentos. Si algo falla, el mensaje puede no llegar a su

destino. Se transmite un datagrama, entre procesos, cuando uno lo envía, y el otro lo recibe. La comunicación de datagramas UDP utiliza operaciones de envío, no bloqueantes y

recepciones, bloqueantes. La operación send devuelve el control cuando ha dirigido el

mensaje a las capas inferiores UDP e IP, que son las responsables de su entrega en el

destino. A la llegada, el mensaje será colocado en una cola del conector que está enlazado

con el puerto de destino. El mensaje podrá obtenerse de la cola de recepción mediante una

invocación pendiente o futura del método recibe sobre ese conector. Si no existe ningún

proceso ligado al conector destino, los mensajes serán descartados.

El método recibe produce un bloqueo hasta que se reciba un datagrama, a menos que se

haya establecido un tiempo límite (time out) asociado al conector.

Cualquier proceso que necesite enviar o recibir mensajes debe crear, primero, un conector

asociado a una dirección Internet y a un puerto local. Un servidor enlazará su conector a un

puerto de servidor (uno que resulte con los clientes de modo que puedan enviarle

mensajes). Un cliente ligará su conector a cualquier puerto local libre. El método recibe

devolverá, además del mensaje, la dirección Internet y el puerto del emisor, permitiendo al

receptor enviar la correspondiente respuesta.

Para algunas aplicaciones, resulta aceptable utilizar un servicio que sea susceptible de sufrir

fallos de omisión ocasionales. Por ejemplo, el Servicio de Nombres de Dominio en Internet

(Domain Name Service, DNS) está implementado sobre UDP. Los datagramas UDP son, en

algunas ocasiones, una elección atractiva porque no padecen la sobrecarga asociadas a la

entrega de mensajes garantizada. Existen tres fuentes principales para esa sobrecarga:

1. La necesidad de almacenar información de estado en el origen y en el destino.

2. La transmisión de mensajes extra.

3. La latencia para el emisor.

Establecimiento de comunicaciones

La Figura 4.2 muestra gráficamente el esquema básico de establecimiento de

comunicaciones UDP.

Figura 4.2 Comunicación UDP

Los pasos que sigue la Figura 4.2 se explican a continuación.

1 El programa que proporciona el servicio (servidor) crea una instancia de la clase

DatagramSocket, hincando el puerto asociado al servicio:

DatagramSocket MiSocket = new DatagramSocket(4000);

2 El programa servidor crea una instancia de la clase DatagramPacket, donde se

guardarán los datos recibidos:

DatagramPacket(buffer, buffer.length);

3 El programa servidor invoca el método receive sobre el socket de tipo DatagramSocket.

Este método, por defecto, bloquea el programa hasta que llegan los datos:

MiSocket.receive(Paquete);

4 El programa cliente crea una instancia de tipo DatagramSocket; DatagramSocket

MiSocket = new DatagramSocket();

5 El programa cliente crea una instancia de tipo DatagramPacket, proporcionándole los

datos, además de la dirección y puerto destino.

DatagramPacket Paquete = new DatagramPacket(buffer, Mensaje.length(),

InetAddress.getByName(“localhost”),4000)

6 El programa que utiliza el servicio (programa cliente) invoca el método send sobre el

socket de tipo DatagramSocket: MiSocket.send(Paquete);

Ejemplo Hola Mundo

UPDEnvia.java

import java.net.*;

public class UDPEnvia{

public static void main(String args[]){

try{

DatagramSocket MiSocket = new DatagramSocket();

byte[] buffer= new byte[15];

String Mensaje = "Hola Mundo";

buffer = Mensaje.getBytes();

DatagramPacket Paquete = new DatagramPacket(buffer, Mensaje.length(),

InetAddress.getByName("localhost"),1400);

Programa cliente

DatagramSocket DatagramSocket

Puerto destino

2. new DatagramPacket 1. new DatagramSocket

4. new DatagramSocket

5. new DatagramPacket

6.send 3.receive

Puerto destino

Nodo destino

Programa servidor

MiSocket.send(Paquete);

MiSocket.close();

}catch (Exception exc){

System.out.println("Error");

}//try

}

}//UDPEnvia

UDPRecibe.java

import java.net.*;

public class UDPRecibe{

public static void main(String args[]){

try{

DatagramSocket MiSocket = new DatagramSocket(1400);

byte[] buffer= new byte[15];

DatagramPacket Paquete = new DatagramPacket(buffer, buffer.length);

MiSocket.receive(Paquete);

System.out.println(new String(Paquete.getData()));

MiSocket.close();

}catch (Exception e){

System.out.println("Error");

}//try

}//main

}//UDPRecibe

Otros ejemplos de datagramas en Java

El paquete java.net contiene tres clases para el uso de datagramas, es decir, para enviar y

recibir paquetes en la red: DatagramSocket, DatagramPacket y MulticastSocket. Una

aplicación puede enviar y recibir DatagramPackets a través de un DatagramSocket.

También, se puede hacer un broadcast a múltiples recipientes escuchando a un

MulticastSocket.

DatagramPacket: esta clase proporciona un constructor que crea una instancia compuesta

por una cadena de bytes que almacena el mensaje, la longitud del mensaje y la dirección

Internet y el número de puerto local del conector destino, tal y como sigue:

Paquete del datagrama

cadena de bytes conteniendo el mensaje, longitud del mensaje. dirección Internet,

número de puerto.

Las instancias de DatagramPacket podrán ser transmitidas entre procesos cuando uno las

envía, y el otro las recibe .

La clase DatagramSocket proporciona varios métodos que incluyen los siguientes:

- send y receive: estos métodos sirven para transmitir datagramas entre un par de

conectores. El argumento de send es una instancia de DatagramPacket conteniendo el

mensaje y su destino. El argumento de receive es un DatagramPacket vacío en el que

se coloca el mensaje, su longitud y su origen. Tanto el método send como receive

pueden lanzar una excepción IOException.

- setSoTimeout: este método permite establecer un tiempo de espera límite. Cuando se

fija un límite, el método receive se bloquea durante el tiempo fijado y después lanza una

excepción InterruptedIOException.

- connect: este método se utiliza para conectarse a un puerto remoto y a una dirección

Internet concretos, en cuyo caso el conector sólo podrá enviar y recibir mensajes de esa

dirección.

Programa UDP: Un cliente UDP enviando un mensaje a un servidor y recoge su respuesta.

UDPClient.java

import java.net.*;

import java.io.*;

public class UDPClient{

public static void main(String args[]){

// args give message contents and server hostname

DatagramSocket aSocket = null;

try {

aSocket = new DatagramSocket();

byte[] m = args[0].getBytes();

InetAddress aHost = InetAddress.getByName(args[1]);

int serverPort = 6789;

DatagramPacket request = new DatagramPacket(m,

args[0].length(), aHost, serverPort);

aSocket.send(request);

byte[] buffer = new byte[1000];

DatagramPacket reply = new DatagramPacket(buffer,

buffer.length);

aSocket.receive(reply);

System.out.println("Reply: " + new String(reply.getData()));

}catch (SocketException e){

System.out.println("Socket: " + e.getMessage());

}catch (IOException e){

System.out.println("IO: " + e.getMessage());

}finally

{if(aSocket != null) aSocket.close();}

}

}

Programa UDP: Un servidor UDP recibe peticiones y las devuelve al cliente de forma

repetitiva

UDPServer.java

import java.net.*;

import java.io.*;

public class UDPServer{

public static void main(String args[]){

DatagramSocket aSocket = null;

try{

aSocket = new DatagramSocket(6789);

byte[] buffer = new byte[1000];

while(true){

DatagramPacket request = new DatagramPacket(buffer, buffer.length);

aSocket.receive(request);

DatagramPacket reply = new DatagramPacket(request.getData(),

request.getLength(), request.getAddress(), request.getPort());

aSocket.send(reply);

}

}catch (SocketException e){System.out.println("Socket: " + e.getMessage());

}catch (IOException e) {System.out.println("IO: " + e.getMessage());

}finally {if(aSocket != null) aSocket.close();}

}

}

Configuración de las comunicaciones

A continuación se muestran los métodos más utilizados de las clases DatagramPacket y

DatagramSocket.

DatagramPacket

Métodos Acción

InetAddress getAddress() Dirección del nodo remoto en la

comunicación

byte[] getData() Devuelve el mensaje que contiene el

datagrama

int getLength() Devuelve la longitud del mensaje del

datagrama

int getOffset() Devuelve el desplazamiento que indica el

inicio del mensaje (dentro del array de

bytes)

int getPort() Devuelve el valor del puerto remoto

void setAddress(InetAddress d) Establece el nodo remoto en la

comunicación

void setData(byte[] Mensaje) Establece el mensaje que contiene el

datagrama

Void setData(byte[] Mensaje, int

Dezplazamiento, int Longitud)

Establece el mensaje que contiene el

datagrama, indicando su desplazamiento

en el array de bytes y su longitud.

Void setLength(int Longitud) Establece la longitud del mensaje del

datagrama

void setPort() Establece el valor del puerto remoto

Void setSocketAddress(SocketAddress d) Establece la dirección (nodo+ puerto)

remota en la comunicación

DatagramSocket

Metódos Acción

void bind(SocketAddress a) Asigna la dirección establecida al socket

void close() Cierra el socket

void connect(SocketAddress a) Conecta el socket a la dirección remota

establecida

void connect(InetAddress a, int puerto) Conecta el socket a la dirección

establecida y el puerto especificado

void disconnect() Desconecta el socket

InetAddress getInetAddress() Devuelve la dirección a la que está

conectada el socket.

int getLocalPort() Devuelve el número de puerto asociado al

socket

OutputStream getOutputStream() Devuelve el stream de salida asociado al

socket

int getPort() Devuelve el valor del puerto remoto al

que está conectado

Int getSoTimeout() Devuelve el valor en milisegundos que el

socket espera al establecimiento de

comunicación

Boolean isBound() Indica si el socket está vinculado

Boolean isClosed() Indica si el socket está cerrado

Boolean isConneted Indica si el socket está conectado

Void setSoTimeout(int ms) Indica el valor en milisegundos que el

socket espera al establecimiento de

comunicación

2.4.3 Diferencias entre Sockets Stream y Datagrama

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, lo que implica un cierto tiempo empleado en el establecimiento de la

conexión, que no es necesario emplear en UDP.

En UDP hay un límite de tamaño de los datagramas, establecido en 64 kilobytes, que se

pueden enviar a una localización determinada, mientras que TCP no tiene límite; una vez

que se ha establecido la conexión, el par de sockets funciona como los streams: todos los

datos se leen inmediatamente, en el mismo orden en que se van recibiendo.

UDP es un protocolo desordenado, no garantiza que los datagramas que se hayan enviado

sean recibidos en el mismo orden por el socket de recepción. Al contrario, TCP es un

protocolo ordenado, garantiza que todos los paquetes que se envíen serán recibidos en el

socket destino en el mismo orden en que se han enviado.

En resumen, TCP parece más indicado para la implementación de servicios de red como un

control remoto (rlogin, telnet) y transmisión de ficheros (ftp); que necesitan transmitir datos

de longitud indefinida. UDP es menos complejo y tiene una menor sobrecarga sobre la

conexión; esto hace que sea el indicado en la implementación de aplicaciones

cliente/servidor en sistemas distribuidos montados sobre redes de área local.