Servidores Concurrentes
Capítulo 5
Qué pasa cuando varios clientes tratan de
conectarse al mismo tiempo a un servidor
• Una forma es ir atendiéndolos de a uno en un ciclo: como en el
programa que atiende pedidos de archivos
– Se acepta una conexión
– Se lee la petición
– Se lee desde el archivo y se escribe en el socket hasta encontrar
una marca de fin de archivo
• A este tipo de servidores se les llama servidores iterativos
• El problema es que todo cliente tiene que esperar su turno para
ser atendido
• Si uno de ellos pide un archivo muy grande los demás tienen
que esperar
• La mayor parte de la espera es debido a operaciones de IO, hay
capacidad de CPU ociosa !
Un servidor secuencial (iterativo)
atendiendo a más de un cliente
A CLIENT
A SERVER
A CLIENT
4444
A CLIENT
Durante la conversación no puede oír
por el puerto 4444
A CLIENT
A SERVER
A CLIENT
4444
A CLIENT
Sólo después de efectuar la transmisión
se pone a escuchar de nuevo por el 4444
A CLIENT
A SERVER
A CLIENT
4444
A CLIENT
Si el servicio consiste en transferir un
archivo, el cliente debe digitar el
nombre
A CLIENT
A SERVER
A CLIENT
4444
A CLIENT
¿Qué sucede si el servidor tiene que
esperar mucho para que un cliente
escriba el nombre de un archivo?
A CLIENT
A SERVER
Timeout
4444
A CLIENT
A CLIENT
ArchServidor2
Un Servidor Concurrente
• Un servidor concurrente atiende a varios clientes al
mismo tiempo.
• Más aún, mientras está atendiendo sigue escuchando
• El problema es que todo cliente tiene que esperar su
turno para ser atendido.
• Si uno de ellos pide un archivo muy grande los
demás tienen que esperar
• La mayor parte de la espera es debido a operaciones
de IO, hay capacidad de CPU ociosa!
• Se trata de crear un nuevo proceso o línea de
ejecución cada vez que un cliente “llega” a pedir un
servicio.
Servidores Comcurrentes: hay procesos
separados para atender el puerto y para
transferir el archivo
A CLIENT
A SERVER
4444
A CLIENT
A CLIENT
Después que el cliente contacta al
servidor, éste crea otro proceso para
para atender al cliente y se queda
escuchando el puerto 4444 por otro
A CLIENT
A SERVER
4444
A CLIENT
A CLIENT
Mientras el nuevo proceso está
atendiendo al primer cliente, el segundo
cliente puede contactar al servidor en el
puerto 4444
A CLIENT
A SERVER
4444
A CLIENT
A CLIENT
Y el servidor crea otro proceso
A CLIENT
A SERVER
4444
A CLIENT
A CLIENT
Ahora un tercer cliente contacta al
servidor
A CLIENT
A SERVER
4444
A CLIENT
A CLIENT
Y un tercer proceso esclavo o
thread es creado
A CLIENT
A SERVER
4444
A CLIENT
A CLIENT
Algoritmo de Servidor Concurrente
Programa principal o “master” del servidor
1. Crear un Socket de servidor
En un ciclo infinito:
2. Aceptar requerimientos de clientes
3. Cuando llega una petición de un cliente crear un nuevo
proceso “esclavo” que atienda paralelamente la petición
(esto no debe bloquear la ejecución del programa master del
servidor)
4. Volver a 2.
Proceso esclavo:
1. Recibir los parámetros de la comunicación (socket o flujos
de entrada y/o salida)
2. Atender al cliente (ej: leer el nombre del archivo, transmitir
el archivo)
3. Retornar (desaparecer !)
Cómo (y por qué) crear procesos
paralelos
• Si existe sólo una CPU, ¿Por qué crear procesos paralelos?
– Porque algunos programas se escriben más fácilmente así. De hecho, la
programación de un servidor es a veces más fácil si se hace de esta
manera.
– Porque sí hay más de un procesador !!!!! (¿dónde?)
• El concepto de procesos paralelos implentados a nivel de S.O.
aparecen con UNIX y C.
• La forma de crearlos es ejecutando una función llamada fork()
• int i = fork() provoca que se cree un proceso exactamente igual al
que se está ejecutando.
• La única diferencia es que en el proceso hijo (el nuevo creado) la
variable i vale cero. Esto se usa para saber quién soy yo.
• En programación de servidores concurrentes, si soy el hijo ejecuto la
parte que corresponde al proceso esclavo.
• Si soy el padre (i tiene un valor distinto de cero y es el id del
proceso hijo creado) sigo recibiendo peticiones
Ejemplo de procesos paralelos en C
(muy simplificado)
main() {
int pid, msock, ssock;
sock = passivesock(port, “tcp”, qlen);
/* ver capítulo 10.4 del libro Internetworking with tcp/ip de Douglas
Commer para ver cómo se implementa */
while(1) {
ssock = accept(msock, &fsin, &alen);
pid = fork();
if (pid == 0) {
atender al cliente;
retornar;
}
}
Problemas con el fork() en UNIX
• La creación del proceso paralelo es costosa en tiempo.
– En algunos textos se sugiere que se podrían crear los procesos
paralelos al levantar el servidor. Cuando llegue un cliente
simplemente se le pasan los datos por medio de un pipe que se
crea entre el proceso padre y el proceso hijo
• El proceso paralelo duplica exactamente todo el ambiente en el
cual estaba corriendo el proceso original, incluso aquellas
variables que no necesita !!!
• No es fácil manejar procesos paralelos, ya que si no se
terminan en forma “normal” pueden quedar consumiendo
recursos indefinidamente.
• La única información que tiene el padre para controlarlos es su
identificación al crearlos.
• Muchas veces se prefiere usar el método select, que lo que
hace es preguntar de una serie de puntos de lectura de datos (en
este caso sockets) cuál está listo para ser leído: este puede ser
uno de los sockets de comunicación con cliente (en un arreglo)
o el socket por donde se escuchan las peticiones (recordar que
el IO es lo más lento en todo esto)
En JAVA se prefiere usar Threads
• Un thread es una secuencia o flujo de de instrucciones que
se ejecutan dentro de un programa. Tiene un comienzo y un
fin. Entonces qué diferencia tiene con un proceso?
• El thread sólo puede ser creado dentro de un proceso. Y un
proceso (programa) puede crear varios threads dentro de él
que se ejecutan en paralelo.
• Entonces, qué diferencia tiene con el fork(). El programa
principal está “conciente” de los threads que existen, hay
variables que los identifican. Pueden ser creados,
inicializados, sustendidos, reactivados o parados por el el
programa que los creó.
• El programa principal puede darles parámetros distintos a
cada thread. Los thread se pueden programar con la
canatidad de variables necesarias para su ejecución (no lo
heredan TODO).
Usando threads para atender multiples
clientes de un Servidor de Sockets
• La forma de implementar servidores que atiendan a varios
clientes paralelamente a la vez es combinando threads con
sockets.
• El servidor abre un ServerSocket desde donde oye
ciualquier intento por conectarse con él de un cliente.
• Una vez establecida la conexión, abre un socket normal e
inicia un thread que atiende a este cliente. El socket abierto
se pasa como parámetro. De esa manera puede seguir
oyendo por el ServerSocket sin estar bloqueado.
• El thread tiene un método run que atiende los pedidos del
cliente.
• El cliente se conecta al servidor sin saber que finalmente
será un socket el que está atendiéndolo.
Implementación de Threads
• Una forma de usar Threads en Java es creando una nueva
clase que extienda la clase Thread y sobreescribir el
método run.
– Los threads son una clase existente. Esta clase se debe extender
para hacer una clase derivada que haga lo que nosotros queramos.
– Lo que un thread en particular hace cuando se echa a correr se
programa en un método llamado run de la clase extendida de
Thread..
– El método run ejecuta cuando a un objeto de esta clase se le aplica
el método start()
• El encabezado de una clase Thread será:
– public class MiThread extends Thread {
• Y en alguna parte deberá aparecer
– public void run() {
//aquí va lo que queremos que se haga en paralelo }
Ejemplo de Threads: escritura de la clase
public class SimpleThread extends Thread {
public SimpleThread(String str) {
super(str);
}
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(i + " " + getName());
try {
this.sleep((int)(Math.random() * 1000));
} catch (InterruptedException e) {}
}
System.out.println("DONE! " + getName());
}
}
• El método this.sleep(milisegundos) debe ir en un bloque try and catch
Ejemplo de Threads:
Uso de la clase nueva
public class TwoThreadsTest {
public static void main (String[] args) {
SimpleThread t1,t2;
t1 = SimpleThread("Jamaica");
t2 = SimpleThread("Fiji");
t1.start(); t2.start()
}
}
• El método start() inicia la ejecucón de un thread.
Esto implica que se empieza a ejecutar el código
escrito en el método run del thread. También
existen otros métodos que se le pueden aplicar a
un thread: suspend(), resume(), stop().
A veces no se puede programar un
servidor como una extensión de un
thread
• Por ejemplo si necesita extender alguna otra clase como
applet o frame
• Se puede usar la interfaz Runnable, lo que significa que la
clase deberá implementar el método run().
• Para iniciar una ejecución paralela se crea un objeto
Thread y se le pasa como parámetro un objeto de la clase
que implementó la interfaz
• Al ejecutar el método start() sonre un objeto thread creado
de esta manera el método run implementado por el
servidor
Ejemplo de interfaz con Runnable
• Veamos y ejecutemos el programa NoSincron.java
• Noten que los threads servidores creados de esta manera
tendrán acceso a todos los recursos del programa que los
creó.
• De la otra forma es más bien una opción, (pasar un
puntero al servidor cuando se crea un objeto thread)
• De cualquier forma, será frecuente el compartir recursos y
por lo tanto la admistración de ellos es importante
Regiones críticas y semáforos
• Java provee básicamente dos métodos para
proveer acceso sincronizado (exclusión mutua) a
regiones críticas
– Se puede declarar todo un método como región crítica,
con lo cual sólo un thread tiene acceso a ella (ver
sincron1)
– Se pueden usar los semáforos de un objeto, cualquiera
(ver sincron2)
Cómo usar threads para hacer
servidores concurrentes
• Hacer una clase Thread que tenga como variables de un objeto
un socket y flujos de entrada y/o salida.
• Programar el constructor de modo que reciba como parámetro
un socket y haga todo lo necesario para dejar inicializado el
ambiente para empezar a atender al cleinte (por ejemplo, abrir
flujos de datos de entrada y/o salida del socket recibido)
• Programar el método run de modo que implemente el
protocolo necesario.
• Programar un método main que en un ciclo infinito se ponga a
escuchar en un port dado la llegada de clientes.
• Con cada cliente nuevo crear un thread nuevo y pasar como
parámetro el socket.
MultiFileServer MultiFileThread
Broadcasting de un texto a varios
clientes
Hello
Hello
Hello
Hello
Hello
Hello
El cliente contacta al servidor
en un port conocido
4444
El cleinte queda registrado
4444
Se crea un socket nuevo, se abre un
canal de salida y se pone en un vector
que representa a los clientes registrados
The server: Broadcasting a message
Message Text
Lo que se ingrese por el teclado en el
lado del servidor será transmitido
a todos los clientes
BraodcastServerNF
BroadcastCliente
Conditions for implementing a chat
system
 Server must be listening to requests of new clients
AND to messages which are sent by already
connected
 Client must be listening to messages from the server
AND to the keyboard for messages the user wants to
transmit.
 We need in the server 2 server sockets (and a
different thread attending each one)
 We need at the client a server socket and a thread to
attend it
Un Chat basado en TCP/IP
• Servidor:
– un thread para oir clientes que se quieren conectar a la sesión y
un thread para recibir mensajes que deben ser repartidos y un
thread para desconectarse
– un único thread para oir todos los mensajes provinientes del
cleinte, al principio del mensaje viene descrito de qué tipo es el
request
• Clientes:
– un thread para mandar mensajes (puede ser el thread que atiende
los eventos de la interfaz) y otro para recibir mensajes mandados
por el servidor
– Se necesitan clientes distintos para los distintos tipos de
servidores (distinto protocolo !)
Un servidor web concurrente
• En principio solo para archivos html y para clases, pero
extendible, por ejemplo, para procesar CGI o servlets
• Para cada cliente que llega se crea un thread
• Según el request se manda a procesar
Httpd (servidor)
HttpProcessor
processRequest()
thread
browser
HttpOutputStream
HttpFile
HttpInputStream
HttpClass
Echo
HttpClassProcessor
HttpException
Transmitiendo Objetos via TCP
• Transmisión: marshaling, delivery & unmarshaling.
• La clave de esto es la serialización de objetos:
representar el objeto en un formato que pueda ser
transmitido por la red (String)
• Todos los objetos nativos de java son serializables.
• Para los objetos de clases definidas por los usuarios
basta declarar que implementan la interfaz Serializable
(esto no incluye variables estáticas o referencias a
cosas locales como archivos o sockets)
• con esto no hay implementar ningún método, esto lo
hace automáticamente java
Transmitiendo Objetos via TCP
• Clases que permiten la transmisión:
– ObjectInputStream readObjetct()
– ObjectOutputStream writeObject()
• El usuaio puede cambiar la forma “standard” del
mecanismo de serialization que provee java
declarando que la clase implementa la interfaz
Externalizable
• Esto obliga a los usuarios a implementar los
siguientes métodos
– Void writeExternal(ObjectOutputStram o)
– Void readExternal(ObjectInputStream i);
Particularidades de TCP
• Coincidencia de datos en los extremos :
• Bloqueo: hay chequeo (ack)
• Fallas : TCP trata de hacer coincidir las velocidades de escritura
y lectura. Si el escribidor es muy rápido, trata de bloquearlo hasta
que el lector haya consumido suficiente.
• Duplicación y orden de mensajes: los paquetes ip contienen
identificadores correlativos que permiten al recibidor detectar
duplicados o cambiados de orden
• Destino de los mensajes: como se abre una conexión virtual entre
ambos extremos, no es necesario especificar a quién va ya que el
socket se abre con un connect
Qué esconde TCP
• Tamaño del mensaje: Las aplicaciones deciden cuánto leer y
cuánto escribir. El sistema subyacente decide cómo transmitirlo.
• Mensajes Perdidos: hay chequeo (ack)
• Control de flujo: TCP trata de hacer coincidir las velocidades de
escritura y lectura. Si el escribidor es muy rápido, trata de
bloquearlo hasta que el lector haya consumido suficiente.
• Duplicación y orden de mensajes: los paquetes ip contienen
identificadores correlativos que permiten al recibidor detectar
duplicados o cambiados de orden
• Destino de los mensajes: como se abre una conexión virtual entre
ambos extremos, no es necesario especificar a quién va ya que el
socket se abre con un connect
Problemas de TCP
• Coincidencia de datos: Lo que se mande por un lado y lo que se
lea (formato) debe coincidir (en especial al mandar objetos).
• Bloqueo: hay que asegurarse que cundo se escribe pocos datos
estos se manden si es necesario contar con ellos pronto o pueden
bloquear la ejecución (buffer)
• La comunicación se establece de punto a punto, así que sólo se
atiende a un cliente a la vez (a menos que se haga concurrente)
• Falla de la conexión: si se demora mucho en hacer el ack
entonces la conexión se declara rota (se tira un IOException). En
este sentido TCP no es más seguro de lo que la red lo es.
• El proceso usando la conexión no puede distinguir si la falla se
debe a la red o a que el proceso par se cayó
• No puede saber después de la caída qué llego efectivamente a
destino y qué no alcanzó a llegar
Descargar

Servidores Concurrentes