2009-08-18 6 views
16

Estoy trabajando en algún código Python modelado en el servidor prefork MPM de Apache. Soy más un programador de aplicaciones que un programador de redes y han pasado 10 años desde que leí a Stevens, así que estoy tratando de ponerme al día para entender el código.accept() con sockets compartidos entre procesos múltiples (basados ​​en el preforking de Apache)

Encontré una breve descripción de how Apache's prefork code works, by Sander Temme.

El proceso principal, que normalmente se ejecuta como raíz, se une a un socket (normalmente el puerto 80 o 443). Engendra hijos, que heredan el descriptor de archivo abierto para el socket y cambian uid y gid al grupo y usuario sin privilegios . Los niños construyen un conjunto de encuestas de los descriptores de archivos de escucha (si hay más de un oyente) y vigilan la actividad en ellos. Si se encuentra actividad, el hijo llama al accept() en el socket activo y maneja la conexión. Cuando es hecho con eso, vuelve a mirar el conjunto de encuestas (o el archivo de escucha descriptor).

Dado que varios hijos están activos y todos ellos heredaron el mismo descriptor (es) del archivo de socket , estarán viendo el mismo conjunto de encuestas. Un aceptar mutex permite que solo un solo niño vea realmente el conjunto de polls, y una vez que haya encontrado un socket activo, desbloqueará el mutex para que el siguiente niño pueda comenzar a ver el conjunto de polls. Si solo hay un único oyente , acepte que no se utiliza mutex y todos los elementos secundarios se colgarán en accept().

Así es como funciona el código que estoy viendo, pero no entiendo algunas cosas.

1) ¿Cuál es la diferencia entre un "niño" y un "oyente"? Pensé que cada niño es un oyente, lo cual es cierto para el código que estoy viendo, pero en la descripción de Temme puede haber "un solo oyente" y "niños". ¿Cuándo un niño tendrá múltiples oyentes?

2) (Relacionados con 1) ¿Esto es un mutex por proceso o un mutex del sistema? Para el caso, ¿por qué tener un mutex? ¿No acepta (2) hacer su propio mutex en todos los oyentes? Mi investigación dice que necesito un mutex y que el mutex debe estar en todo el sistema. (Rebaño, semáforos, etc.)

Temme continúa diciendo:

niños registro en un área de memoria compartida (el marcador) cuando se agoten sirvió una solicitud. Los hijos inactivos pueden ser asesinados por el proceso principal al satisfacer MaxSpareServers. Si muy pocos niños están inactivos, el padre engendrará hijos para satisfacer MinSpareServers.

3) ¿Existe un buen código de referencia para esta implementación (preferiblemente en Python)? Encontré el Net::Server::Prefork de Perl, que usa tubos en lugar de memoria compartida para el marcador. Encontré un artículo por Randal Schwartz que solo hace los preparativos pero no hace el marcador.

El pre-fork example from the Perl Cookbook no tiene ningún tipo de bloqueo en torno a seleccionar, y Chris Siebenmann's Python example dice que está basado en Apache, pero utiliza sockets emparejados para el marcador, no se comparte la memoria y utilizar las tomas para los controles, incluir el control de un niño dado a 'aceptar. Esto no coincide con la descripción de Apache.

+0

¿Estás utilizando algo como 'mod_wsgi' como interfaz entre Apache y Python? Si es así, debería manejar todo esto por ti. –

+0

Esto es para un servidor WSGI de precodificación de Python puro. Mi cliente quiere una solución liviana para los lugares que no quieren Apache y mod_wsgi, o equivalente. El único servidor WSGI de Python que encontré fue el Spawn, y eso requiere eventlet. ... Aunque ahora descubrí que flup tiene una implementación como la de Siebenmann, que utiliza tuberías para el marcador en lugar de memoria compartida, y con una licencia aceptable para mi cliente. –

Respuesta

15

Con respecto a (1), el oyente es simplemente una referencia a la existencia de un socket en el que aceptar conexiones. Dado que Apache puede aceptar conexiones en múltiples sockets al mismo tiempo, por ejemplo, 80/443, hay múltiples sockets de escucha. Cada proceso secundario necesitaría escuchar en todos estos enchufes cuando llegue el momento. Como accept() solo puede hacerse en un socket a la vez, va precedido de poll/select para que se sepa en qué socket de escucha se debe realizar la aceptación.

Con respecto a (2), es un mutex global o de proceso cruzado. Es decir, un bloqueo de proceso bloqueará otros procesos que intenten adquirir el mismo bloqueo. Aunque accept() técnicamente serializará los procesos, la presencia de múltiples sockets de escucha significa que no puede confiar en eso ya que no sabe de antemano qué socket aceptar. Incluso cuando hay un único socket de escucha, la razón para aceptar mutex es que si hay un gran número de procesos manejando peticiones, entonces podría ser bastante costoso si el sistema operativo despierta todos los procesos para ver cuál tiene aceptado() para él. Dado que Apache en modo prefork puede tener más de 100 procesos, eso podría causar un problema.

Por lo tanto, si solo tiene un único socket de escucha y sabe que solo hay unos pocos procesos que desean hacer la llamada accept(), entonces posiblemente pueda eliminar el proceso cruzado de aceptar mutex.

+0

Gracias Graham! Totalmente no pensé en cómo puede haber múltiples oyentes, y cómo eso afectaría todo. Mi comprensión es mucho más clara ahora, tanto sobre por qué el código que estaba usando funcionó, y por qué parecía que no funcionó. –

Cuestiones relacionadas