Professional Documents
Culture Documents
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.
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.
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.
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.
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.
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 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.
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.
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).
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 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.
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.
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
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.
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.
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.
Para identificar el destino de los paquetes de datos, los sockets utilizan los conceptos de
dirección y puerto.
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.
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:
5. Con los pasos anteriores completados se puede empezar a comunicar datos entre el
cliente (o cliente) y el servidor.
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.
TCPClienteHolaMundo.java
import java.net.Socket;
import java.io.*;
import java.net.UnknownHostException;
import java.net.ServerSocket;
import java.net.Socket;
import java.io.*;
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);
}
}
Programa: echoClient.java
import java.io.*;
import java.net.*;
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);
}
out.close();
in.close();
stdIn.close();
echoSocket.close();
}
}
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.
Otros ejemplos
Revisar los programas:
KnockKnockProtocol
KnockKnockServer
KnockKnockClient
KnockKnockServer.java
import java.net.*;
import java.io.*;
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);
KnockKnockClient.java
import java.io.*;
import java.net.*;
public class KnockKnockClient {
public static void main(String[] args) throws IOException {
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);
}
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.*;
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
Clase Socket
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 cliente Programa servidor
1. new DatagramSocket
5. new DatagramPacket 2. new DatagramPacket
4. new DatagramSocket
DatagramSocket DatagramSocket
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);
UPDEnvia.java
import java.net.*;
UDPRecibe.java
import java.net.*;
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:
Las instancias de DatagramPacket podrán ser transmitidas entre procesos cuando uno las
envía, y el otro las recibe .
- 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.
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();}
}
}
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 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 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
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 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.