Download Protocolos Ventajas de un Sistema Distribuidos

Document related concepts
no text concepts found
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 protocolprotocolo 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
80
25
110
119
Servicio
Para web con http
Para correo saliente con SMTP
Para correo entrante con POP3
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.
Programa
Cliente
Programa Servidor
2.accept
1. Instanciación
ServerSocket
4. Instanciación
Puerto destino
5. Conexión TCP
(interno)
Socket
Socket
Nodo destino
Puerto destino
3. return del accept
6. Comunicación
Figura 4.1 Comunicación TCP entre un cliente y un servidor.
Puerto
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
seguridad");
}
no
permitida
por
razones
de
por
razones
de
}
}
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
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.
2.
3.
4.
5.
Abrir un socket
Abrir un flujo de entrada y salida para el socket.
Leer desde y escribir al flujo de acuerdo al protocolo del servidor
Cerrar los flujos
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
InputStreamReader(System.in));
String fromServer;
String fromUser;
=
new
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;
BufferedReader(new
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
socket accept()
void bind(SocketAddress a)
Acción
Espera a que se realice una conexión y devuelve un
socket para comunicarse con el cliente.
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()
InetAddress getInetAddress()
int getLocalPort()
int getSoTimeout()
void setSoTimeout(int ms)
Cierra el socket
Devuelve la dirección a la que está conectada el
socket
Devuelve el número de puerto asociado al socket
Devuelve el valor en milisegundos que el socket
espera al establecimiento de comunicación tras la
ejecución de accept
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
void bind(SocketAddress a)
Acción
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 Conecta el socket a la dirección de servidor
a)
establecida
void connect(SocketAddress Conecta el socket a la dirección de servidor
a, int ms)
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
Devuelve el stream de salida asociado al socket
getOutputStream()
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.
Programa servidor
Programa cliente
1. new DatagramSocket
5. new DatagramPacket
2. new DatagramPacket
4. new DatagramSocket
DatagramSocket
DatagramSocket
6.send
3.receive
Puerto destino
Puerto destino
Nodo destino
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,
InetAddress.getByName("localhost"),1400);
Mensaje.length(),
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
InetAddress getAddress()
Acción
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 Establece el mensaje que contiene el
Dezplazamiento, int Longitud)
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
void bind(SocketAddress a)
void close()
void connect(SocketAddress a)
void connect(InetAddress a, int puerto)
Acción
Asigna la dirección establecida al socket
Cierra el socket
Conecta el socket a la dirección remota
establecida
Conecta el socket a la dirección
establecida y el puerto especificado
void disconnect()
InetAddress getInetAddress()
int getLocalPort()
OutputStream getOutputStream()
int getPort()
Int getSoTimeout()
Boolean isBound()
Boolean isClosed()
Boolean isConneted
Void setSoTimeout(int ms)
Desconecta el socket
Devuelve la dirección a la que está
conectada el socket.
Devuelve el número de puerto asociado al
socket
Devuelve el stream de salida asociado al
socket
Devuelve el valor del puerto remoto al
que está conectado
Devuelve el valor en milisegundos que el
socket espera al establecimiento de
comunicación
Indica si el socket está vinculado
Indica si el socket está cerrado
Indica si el socket está conectado
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.