paper sockets

Upload: rodrigo-jimenez

Post on 06-Jul-2015

284 views

Category:

Documents


0 download

TRANSCRIPT

SOCKETS BLOQUEANTESRodrigo Jimnez [email protected] RESUMEN En el presente artculo vamos a hablar todo lo que se refiere a sockets, [1] especficamente el formato y las funciones utilizadas para establecer un socket como no bloqueante, aunque por defecto los sockets son bloqueantes tenemos funciones que pueden ha que un socket sea no cer bloqueante en estos si no se puede realizar la operacin de lectura, la llamada retorna inmediatamente y devuelve el error, lo veremos ms detalladamente a continuacin. Y finalmente hablaremos de losthreads (hebras) [2], multi-hebras. Y como un servidor[3] atiende a varios clientes al mismo tiempo.1.

INTRODUCCION

De las diferentes formas de IPC (Inter Process Communication), los sockets son de lejos la ms popular. En una plataforma dada, probablemente hay otras formas de IPC ms rpidas, pero para comunicaciones inter-plataforma, los sockets son casi la nica eleccin. Se inventaron en Berkeley como parte de la variante BSD de UNIX. Se extendieron muy rpidamente junto con Internet. Y con razn la combinacin de los sockets con INET hace que la comunicacin entre mquinas cualesquiera sea increblemente sencilla (comparada con otros esquemas). Para entender que es un socket no bloqueante debemos primero tener muy clara la definicin de lo que es un socket. Un socket, o internet socket, o network socket es una va de comunicacin o un canal entre dos computadoras que se comunican sobre una red, es una representacin abstracta de una comunicacin punto a punto. La definicin de socket ha evolucionado para cumplir con especificaciones de diferentes estndares a lo largo de su historia pero en este caso nosotros vamos a tomar la definicin mencionada como global. Es importante notar que una conexin socket completa se compone de

la informacin de ambos extremos de la comunicacin: direccin y puerto de cada uno de los dos equipos involucrados. De esta forma cuando un navegador web se conecta a un servidor, en ambos extremos existe un socket con la misma informacin. Tal como se ve en el ejemplo: Cliente 201.55.68.123 18455 Servidor : 200.49.150.78 : 80

Con sockets no bloqueantes las funciones de entrada y salida que no puede realizar su tarea por falta de datos retornan inmediatamente y devuelven un cdigo de error. Comparativamente la ventaja de utilizar sockets bloqueantes (en conjunto con programacin multitarea (multitasking)) a utilizar no bloqueantes es que los primeros permiten esperar eventos de entrada salida fcilmente sin ocupar tiempo de CPU. Esta diferencia se hace mucho ms evidente desde el punto de vista del servidor. Sin embargo los socket bloqueantes son ms lentos que los no bloqueantes. Por otro lado es importante que un servidor pueda atender mltiples clientes ya que en la actualidad las empresas necesitan servidores as por lo que todo est relacionado ya que para tener un servidor que atienda a mltiples

Pgina 1

clientes necesitamos combinar sockets con multi-hebras. MARCO TEORICO SOCKETS NO BLOQUEANTES Con sockets no bloqueantes, si no se puede realizar la operacin de lectura, la llamada retorna inmediatamente y devuelve el error EWOULDBLOCK es posible que incluso con este tipo de sockets la conexin se pueda establecer inmediatamente y se retorne el valor 0 en lugar del error anterior (por ejemplo, cuando cliente y servidor residen en un mismo host). Con un socket TCP no bloqueante, si no hay espacio en el buffer de envo del socket, retorna inmediatamente con un error EWOULDBLOCK. Si hay espacio el valor de retorno ser el nmero de bytes que el kernel haya podido copiar en el buffer (y que ser menor del requerido porque en caso contrario no se habra producido el bloqueo).

puede volver sin haber hecho nada. Hay, por supuesto, varias alternativas. Puede comprobar el valor de retorno y el cdigo de error por lo que su aplicacin pronto ser grande, pesada y llena de errores, empezaremos primero haciendo un socket como no bloqueante: Funcin fcntl(): Fcntl es un acrnimo de 'file control'. Esta funcin permite llevar a cabo distintas operaciones de control sobre descriptores. El protipo de la funcin es el siguiente: #include intfcntl(intfd, intcmd , /* intarg*/); Cada descriptor tiene un conjunto de flags que se obtiene utilizando la orden (parmetro cmd) F_GETFL y que se obtienen utilizando la orden F_SETFL. Del conjunto de flags que puede manipular fcntl, los dos flags que afectan al comportamiento de los sockets son los siguientes: O_NONBLOCK E/S no bloqueante. O_ASYNC E/S guiada por seales (signaldrive I/O) Si nos centramos en la programacin de aplicaciones en redes la funcin fcntl nos permite hacer lo siguiente: E/S dirigida mediante seales. Si activamos el flag de estado O_ASYNC utilizando F_SETFL conseguimos que se genere la seal SIGIO cuando se modifica el estado de un socket. La orden F_SETOWN permite que establezcamos un propietario para el socket (mediante un identificador de proceso o de grupo) para poder recibir las seales SIGIO y SIGURG (relacionadas con E/S dirigida por seales y con la recepcin de datos fuera de banda). Entrada/Salida no bloqueante: Si activamos el flagO_NONBLOCK (utilizando una vez ms F_SETFL) conseguimos hacer nobloqueante un socket.Nosotros nos vamos

Figura1. Nos muestra como est formada la interface de un socket. En Python, se usa socket setblocking () para hacer un socket no bloqueante. En C, es ms complicado, (necesita elegir entre BSD modo O_NONBLOCK o el casi idntico Posix modo O_NDELAY, que es completamente diferente de TCP_NODELAY), pero es exactamente la misma idea. Eso se hace despus de crear el socket, pero antes de usarlo. La diferencia ms importante es que send(), recv(), connect() y accept()

Pgina 2

un in s l t :Nos permite monitorear un conjunto de descriptores de soc ets y nos avisa cuales tienen datos para leer, cules estn listos para escribir, y cules produjeron e cepciones.

so fd:Descriptor de soc et sobre el cual se va a realizar alguna operacin. C d: Determina el comando que se va a aplicar, para nuestro caso usaremos el comando F_SETFL, el cual establece los flag del descriptor al valor especificado en g.

i out determina cunto tiempo select puede esperar para retornar. Si timeout es N LL, select se bloquear indefinidamente hasta por lo menos un descriptor cumpla con alguno de los criterios especificados. La otra opcin es que timeout apunte a una structuratimeval que especifique el tiempo mximo que select debe esperar antes de retornar. Cuando se produzca un evento el valor de timeval ser alterado. Si timeval se inicializa con {0, 0}, select retornar inmediatamente.

6

#

8

7

(

Cuando creamos un soc et, se establece como bloqueante, al llamar a ciertas funciones como pt fo etc, se bloquea el programa. Para establecer al so t o o no bloqu nt utilizamos la funcin fcntl() de la siguiente manera : intfcntl ( intsockfd, intcmd, long arg);

#include #include #include int select ( int n, fd_set *readfds, fd_set * ritefds, fd_set *e ceptfds, structtimeval *timeout);

P

5% # #

6

&

Llamamos a la funcin para recibir datos, pero el servidor en ese momento no tiene nada para enviarnos, entonces la n o retorna. Justo en ese funcin momento queremos enviar datos hacia el servidor, pero no podemos porque la funcin no retorn y nos bloque el ebemos encontrar alguna programa. forma para que la funcin retorne aunque el servidor no envie nada. Esto se realiza estableciendo al so t o o no bloqu nt .

Supongamos un ejemplo: creamos un soc et y nos conectamos a un servidor, sabemos que el soc et nos provee una comunicacin bidireccional, podemos enviar y recibir datos simultneamente

52 1

1

Figura2 Arquitectura de un soc et no bloqueante

)4 '3 (0 2 1 0

$

1 ) & ' & ) % ( 0

)('

& % #

# !# # #

"

a ce ar e e ltimo uso que hemos come tado de fc tl, esto es como hacer un so t no bloqu nt .

5

"!

A g:Argumentos que necesita el comando, para establecer el soc et comono O_ ONBLOCK.Si se bloqu nt sera produce un error, retorna -1 y se establece errno especificando el error.Ejemplo: #in lud #in lud ... so fd=so t A _IN C _ A 0; f ntl so fd _ L _N NBL C ; Una vez establecido el soc et como no bloqueante, se puede llamar a la funciones bloqueantes como recv() para recibir datos, si no hay datos disponibles recv() devuelve -1 y establece errno=EWOULDBLOCK. Se puede ir consultando el soc et para saber si hay datos disponibles, pero esta no es una solucin muy buena, se consume tiempo de CPU consultando al soc et si tiene datos.

i

3

Estructura timeval: struct timeval{ int tv_sec; // segundos int tv_usec; // micro segundos }; Funcin connect int connect (SOCKET sock, const struct sock addr FAR* name, int namelen); Funcin send int send (SOCKET sock, const char FAR * bu f, int len, int flags); Funcinrecv int recv (SOCKET sock, char FAR* buf, int le n, int flags); Funcin shutdown La funcin shutdown desactiva el envo y/o la recepcin de mensajes en un socket. int shutdown (SOCKET sock, int how) Hay un problema muy feo con select(): Si en alguna de las listas de entrada hay un socket que ha muerto de mala manera, select() fallar. Entonces necesitar recorrer todos los sockets (uno por uno) de las tres listas y hacer select([sock],[],[],0) hasta que encuentre al responsable. El timeout 0 significa que debe retornar inmediatamente. Advertencia de portabilidad: En UNIX, select() funciona tanto con sockets como con ficheros. No intente esto en Windows. En Windows, select() slo funciona con sockets. Tambin debe notar que en C, muchas de las opciones avanzadas con sockets son diferentes en Windows. De hecho, en Windows normalmente se usan hilos (que funcionan muy bien) con sockets. Si quiere rendimiento, su cdigo ser muy diferente en Windows y en UNIX. Rendimiento No hay duda de que el cdigo ms rpido es el que usa sockets no bloqueantes y select() para multiplexarlos. Se puede

enviar una cantidad inmensa de datos que saturen una conexin LAN sin que suponga una carga excesiva para la CPU. El problema es que una aplicacin escrita de este modo no puede hacer mucho ms necesita estar lista para generar bytes en cualquier momento. Asumiendo que se supone que su aplicacin har algo ms que eso, utilizar hilos es la solucin ptima, (y usar sockets no bloqueantes es ms rpido que usar sockets bloqueantes). Desafortunadamente, el soporte de hilos en los UNIX'es varia en API y calidad. As que la solucin habitual en UNIX es crear un subproceso para manejar cada conexinFinalmente, recuerde que a pesar de que los sockets bloqueantes son algo ms lentos que los no bloqueantes, en mucho casos son la solucin "correcta". Despus de todo, si su aplicacin reacciona ante los datos que recibe de un socket, no tiene mucho sentido complicar la lgica slo para que la aplicacin pueda esperar en un select() en lugar de en un recv(). THREADS (MULTI-HEBRAS)

Fi ura3. Hilos y Multi-hilos La programacin con hilos, tambin conocida como multiproceso, tiene la ventaja de poder trabajar de manera asncrona. Esto permite que aquellos procesos que pueden requerir un tiempo ms o menos largo en llevarse a cabo se pongan a trabajar 'paralelamente' al proceso principal, de manera que la aplicacin pueda retomar el control y as el usuario seguir trabajando con ella. Las aplicaciones que trabajan con multihilos son aquellas que dividen su carga de trabajo en diversos Hilos o unidades de ejecucin. Una razn para esto es la simplificacin del diseo. Multi-

9

Pgina 4

hilos puede permitir una fuerte cohesin entre tareas, significando que cada clase de hilo capturar un simple abstraccin o idea. Este diseo produce una estrategia llamada divide-and-conquer (dividir y reducir) en lo cual sistemas grandes y complejos son divididos entre ms unidades manejables, resultando un sistema con menos costo en la construccin y mantenimiento. La introduccin de mltiples hilos tambin produce beneficios tanto como incrementar concurrencia, mejorar la interaccin con el usuario y ms eficiencia en el uso de la CPU.Threading est basada en los pthreads (los hilos POSIX que tan bien implementados estn en Linux) y es fcil comprobar que el paso de trabajar con unos a trabajar con otros es casi inmediato. Reglas para hacer un multi-hilo start(), inicia la funcin y llama automticamente a su funcin run().La funcin run() debe reescribirse sin parmetros ni devolucin de objetos. En la funcin run() el programador inserta las acciones especficas del hilo. sleep() e interrupt() Esta es una primera aproximacin a la sincronizacin de hilos, usando sleep(), que nos sirve para decirle a un hilo que se duerma durante un periodo de tiempo (medido en milisegundos). sleep() exige el manejo de InterruptedException. Pero no es una solucin muy elegante: el sueo debe durar 1 segundo?, o tal vez un minuto?,... Por cierto, nuestra llamada invoca al mtodo de un objeto (this). Pero podemos usar sleep() como mtodo de clase (ya que es static) en la forma: Thread.sleep( 1050 ); Lo que estamos haciendo es dormir al hilo actual, dando as la posibilidad de ms ciclos de procesamiento al resto de hilos.

La excepcin InterruptedException se dispara cuando otro hilo llama a interrupt() del hilo que se quiere interrumpir. No suele usarse interrupt(), ya que los hilos suelen interrumpirse cuando termina, llega al fin de run(). Con interrupt() despertamos a un hilo que se encuentra dormido o bloqueado, por ejemplo por una larga operacin de entrada/salida, por wait() o por sleep(). Hay una excepcin a la regla sobre InterruptedException: si se llama a interrupt() cuando el hilo no est durmiendo o esperando, no se genera InterruptedException. Puede saber si un hilo esta interrumpido por medio de la funcin "booleanisInterrupted()": if ( !isInterrupted() ) Bloqueo de objetos (synchronized) Uno de los problemas centrales de la programacin multihilo es manejar situaciones en las que ms de un hilo tiene acceso a la misma estructura de datos. Por ejemplo, si un hilo estuviera intentando actualizar los elementos de una lista, mientras otro est simultneamente intentando clasificarla, su programa puede bloquearse o producir resultados incorrectos. Para evitar este problema, debe utilizar la sincronizacin de hilos. La forma ms sencilla de evitar que dos objetos accedan a un mtodo de un tercero al mismo tiempo es que el primer hilo lo bloquee. Cuando un hilo mantiene un bloqueo, otro hilo que tambin lo necesita tiene que esperar hasta que el primer hilo libera su bloqueo y para eso declaramos mtodos sincronizados (synchronized), por ejemplo: synchronizedvoidleer_datos() { ... } Una buena metfora es pensar la situacin de bloqueo como una tradicional cabina de telfonos que tiene un cerrojo en su interior, cuando un hilo accede al mtodo cierra la puerta y bloquea la cabina, de tal forma que otro hilo que quiera acceder a la cabina se debe mantener en espera hasta que el primero sale de la cabina.

Pgina 5

Para mantener un mtodo libre de problemas con los hilos (thread-safe), utilice la palabra clave synchronized. El hilo anula el bloqueo cuando sale del ltimo mtodo sincronizado. En nuestro caso hemos hecho que dos mtodos sean sincronizados: synchronized void leer_datos() { try { BufferedReader in = new BufferedReader( new FileReader("xxx") ); sleep( 1000 ); resultado = Double.parseDouble( in.readLine() ); in.close(); } catch (IOException e) { System.out.println( e.getMessage() ); } } synchronizeddoubleobt_resultado() { return resultado; } // Hemos quitado sleep() de aqui Hay un malentendido habitual: creer que lo que se bloquea es el mtodo. Es un error natural, puesto que ponemos la palabra synchronized en un mtodo tendemos a pensar que el bloqueo se produce sobre el mtodo. Lo diremos de forma breve: ES EL OBJETO EL QUE ESTA BLOQUEADO, NO EL METODO. Para la JVM cada objeto tiene un contador de bloqueo, que registra el nmero de mtodos sincronizados que permanecen en ejecucin. Cuando el contador alcanza el valor de cero, se libera el bloqueo. Mtodos wait() y notify() Ya hemos vista como podemos poner secuencialmente los hilos por medio de synchronized. Los mtodos wait() y notify() extienden o potencian esta capacidad. Estos mtodos los hereda toda clase de Java, ya que estn definidos en la clase Object. Slo pueden usarse en mtodos sincronizados.

Con wait() un hilo se queda en espera y libera el cerrojo sobre el objeto que hubiese bloqueado (recuerde aqu la metfora de una cabina de telfono con cerrojo). Importante para evitar esperas "eternas" (y aplicaciones "colgadas") es tener en cuenta que cuando un hilo entra en espera no tiene posibilidad de despertarse slo, sino que depende de otro hilo que lo despierte mediante notity(). Cuando no estes seguro de cual es el hilo que debes despertar lo ms seguro es usar notifyAll(), se inicia el hilo con thre . t rt ; Con hilos mltiples su aplicacin puede continuar la ejecucin en hilos separados mientras un hilo espera por los resultados de un proceso lento.Si la PC que ejecuta el programa tiene procesadores mltiples, usted puede mejorar la ejecucin dividiendo el trabajo en varios hilos y permitindoles que corran simultneamente en procesadores separados. Los sistemas operativos Windows instrumentan arreglos multi-proceso cuando es apoyado por el hardware subyacente. 1.1 COMO UN SERVIDOR ATIENDE A VARIOS CLIENTES

Fi ura4. Muestra como un servidor atiende a mltiples clientes al mismo tiempo Servi r C urre te: Espera por la llegada de un cliente, Inicia un servidor para manejar los requerimientos del cliente. Esto involucra la creacin de un nuevo proceso o hilo. Cuando el cliente se va

H

IHG

C @ B A@ D GF

E

Pgina 6

(termina) el proceso o hilo tambin termina, regresa al primer paso. ste es el caso tpico de servidores TCP (TCPServer.c TCPClient.c) con procesos o hebras a cargo de cada cliente. El paso tres es alcanzado tan pronto como se inicia la atencin del Cliente, por lo tanto mltiples clientes pueden ser atendidos en forma concurrente. Una situacin intermedia es creada cuando un servidor TCP atiende mltiples clientes en forma secuencial por requerimiento y no por conexinms an, mientras est atendiendo si ue escuchando. Es un caso parecido al servidor Iterativo UDP, pero implementado con TCP. En este caso tenemos: Espera por la llegada de un cliente o nuevo requerimiento de un cliente ya conectado, Acepta la conexin de un nuevo cliente o procesa requerimiento de uno ya conectado, Si se atendi un requerimiento, enva la respuesta al cliente que envi el requerimiento, Regresa al primer paso Para poder esperar requerimientos que pueden llegar por mltiples descriptores, se dispone de la funcin sele t(), que es soportado ms ampliamente, y es simple pues en todo momento tenemos un slo hilo (secuencia en ejecucin) Las funciones sele t y p ll proveen un mecanismo para que los programas chequeen un grupo de descriptores y sepan cuando existe entradas, salidas, o alguna condicin de excepcin en alguno de ellos. #include #include int select ( intmaxfd, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, structtimeval * timeout_ptr ); La forma de implementar servidores que atiendan a varios clientes paralelamente a la vez es combinando threads con sockets. Dos servicios de comunicacin pueden utilizarse con sockets y Orientado a conexin.- TCP (stream)

Sin conexin y sin asentimiento.UDP (datagram) En el caso del TCP, para comunicar dos procesos (Cliente) debe hacerse lo siguiente: Cliente: y Crear socket ( socket() ) y Definir direccin destino del Servidor y Conectarse al Servidor ( connect() ) y Enviar y recibir mensajes ( send() y recv() ) y Cerrar conexin ( close() ) Por otra parte, en el Servidor debe hacerse lo siguiente: Servidor: y Crear socket ( socket() ) y Definir direccin destino del Servidor y Asociar al socket con la direccin del Servidor (bind ()) y Habilitar el puerto para recibir conexiones (listen ()) y Aceptar conexiones ( accept() ) y Enviar y recibir mensajes ( send() y recv() ) y Cerrar conexin ( close() )y

Q

S

R

P

Para crear un socket se utiliza la siguiente funcin: #include #include int socket (int domain, int type, int protocol); El servidor abre un ServerSocket desde donde oye cualquier intento por conectarse con l de un cliente,una vez establecida la conexin, abre un socket normal e inicia un thread que atiende a este cliente. El socket abierto se pasa como parmetro. De esa manera puede seguir oyendo por el ServerSocket sin estar bloqueado, el thread tiene un mtodo run que atiende los pedidos del cliente, el cliente se conecta al servidor sin saber que finalmente ser un socket el que est atendindolo. 2. CONCLUSIONES

Pgina 7

La programacin multihilo no es siempre la solucin correcta para todas las aplicaciones e incluso en algunos casos puede relentizar la aplicacin aunque parezca que no es as o cosas an peores como prdida de datos, etc. Los socket no bloqueantes no nos permiten esperar eventos de entrada salida fcilmente y ocupan tiempo de CPU. El cdigo ms rpido es el que usa sockets no bloqueantes y select () para multiplexarlos. Hoy en da sockets es el ms usado en el estndar en Linux, MacOSX y Windows (al menos hasta el .net) Cuando son combinados threads con sockets se implementar servidores que atiendan a varios clientes a la vez. Debido a que los servidores han de atender mltiples clientes debemos implementarlos con multi-hebras.

3.

REFERENCIAS

http://it.aut.uah.es/enrique/docencia/ii/re des/documentos/IOavanzada.htm[2]http:/ /www.python.org/doc/howto. https://arco.esi.uclm.es/svn/public/doc/py thon-sockets/. http://wiki.python.org/moin/HowTo/Socke ts[5]http://msdn2.microsoft.com/enus/library/ms741416. http://web.archive.org/web/20001025162 345/www.sockaddr.com/index.html Como programar en C/C++ H.M. Deitel y P.J. Deitel Andrew S. Tanenbaum, "Redes Ordenadores", De. Prentice Hall. de

Pgina 8